Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/hermes-image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ on:
hermes_agent_ref:
description: Hermes Agent git ref
required: false
default: 2ffa1c97c09317c1d066aa5708b8ad961a4ca589
default: 9de9c25f620ff7f1ce0fd5457d596052d5159596
hermes_lcm_ref:
description: Hermes LCM git ref
required: false
default: 34043036d12f69d0089c4630eb9acfb2d9424b4c
tlon_apps_ref:
description: tlon-apps git ref
required: false
default: 33112008b1f3e83816dee61020dc5d4c57770c15
default: 15fc26bd9d8a13345d54f4c7b3e00601a2e0996f
push:
description: Push image to Docker Hub
required: false
Expand Down Expand Up @@ -68,10 +68,10 @@ permissions:

env:
DEFAULT_IMAGE: nativeplanet/hermes-tlon
HERMES_IMAGE_TAG: 0.14.0-0.14.0
HERMES_AGENT_REF: 2ffa1c97c09317c1d066aa5708b8ad961a4ca589
HERMES_IMAGE_TAG: 0.18.2-0.5.0
HERMES_AGENT_REF: 9de9c25f620ff7f1ce0fd5457d596052d5159596
HERMES_LCM_REF: 34043036d12f69d0089c4630eb9acfb2d9424b4c
TLON_APPS_REF: 33112008b1f3e83816dee61020dc5d4c57770c15
TLON_APPS_REF: 15fc26bd9d8a13345d54f4c7b3e00601a2e0996f
VERSION_AUTH: ${{ secrets.VERSION_AUTH }}

jobs:
Expand Down
21 changes: 12 additions & 9 deletions containers/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ ARG NODE_VERSION=22.22.2
FROM node:${NODE_VERSION}-bookworm-slim

ARG HERMES_AGENT_REPO=https://github.com/NousResearch/hermes-agent.git
ARG HERMES_AGENT_REF=2ffa1c97c09317c1d066aa5708b8ad961a4ca589
ARG HERMES_AGENT_REF=9de9c25f620ff7f1ce0fd5457d596052d5159596
ARG HERMES_LCM_REPO=https://github.com/stephenschoettler/hermes-lcm.git
ARG HERMES_LCM_REF=34043036d12f69d0089c4630eb9acfb2d9424b4c
ARG TLON_APPS_REPO=https://github.com/tloncorp/tlon-apps.git
ARG TLON_APPS_REF=33112008b1f3e83816dee61020dc5d4c57770c15
ARG PNPM_VERSION=9.15.9
ARG TLON_APPS_REF=15fc26bd9d8a13345d54f4c7b3e00601a2e0996f
ARG PNPM_VERSION=11.9.0

LABEL org.opencontainers.image.title="NativePlanet Hermes Tlon"
LABEL org.opencontainers.image.description="Hermes Agent with the Tlon platform adapter and tlon CLI"
Expand Down Expand Up @@ -64,9 +64,10 @@ RUN apt-get update \

RUN npm install -g "pnpm@${PNPM_VERSION}"

RUN git clone --filter=blob:none "${HERMES_AGENT_REPO}" "${HERMES_AGENT_DIR}" \
RUN git init "${HERMES_AGENT_DIR}" \
&& cd "${HERMES_AGENT_DIR}" \
&& git checkout "${HERMES_AGENT_REF}"
&& git fetch --depth=1 "${HERMES_AGENT_REPO}" "${HERMES_AGENT_REF}" \
&& git checkout --detach FETCH_HEAD

COPY patches /tmp/hermes-patches
RUN cd "${HERMES_AGENT_DIR}" \
Expand All @@ -89,17 +90,19 @@ RUN python3 -m venv "${HERMES_VENV}" \
&& "${HERMES_VENV}/bin/python" -m pip install -e "${HERMES_AGENT_DIR}[cron,cli,pty,mcp,acp,web]" \
&& "${HERMES_VENV}/bin/python" -m pip install "aiohttp>=3.9"

RUN git clone --filter=blob:none "${HERMES_LCM_REPO}" /tmp/hermes-lcm \
RUN git init /tmp/hermes-lcm \
&& cd /tmp/hermes-lcm \
&& git checkout "${HERMES_LCM_REF}" \
&& git fetch --depth=1 "${HERMES_LCM_REPO}" "${HERMES_LCM_REF}" \
&& git checkout --detach FETCH_HEAD \
&& mkdir -p "${HERMES_AGENT_DIR}/plugins/context_engine/lcm" \
&& cp -a . "${HERMES_AGENT_DIR}/plugins/context_engine/lcm/" \
&& ln -sfn "${HERMES_AGENT_DIR}/plugins/context_engine/lcm" "${HERMES_AGENT_DIR}/hermes_lcm" \
&& rm -rf "${HERMES_AGENT_DIR}/plugins/context_engine/lcm/.git" /tmp/hermes-lcm

RUN git clone --filter=blob:none "${TLON_APPS_REPO}" "${TLON_APPS_DIR}" \
RUN git init "${TLON_APPS_DIR}" \
&& cd "${TLON_APPS_DIR}" \
&& git checkout "${TLON_APPS_REF}"
&& git fetch --depth=1 "${TLON_APPS_REPO}" "${TLON_APPS_REF}" \
&& git checkout --detach FETCH_HEAD

RUN cd "${TLON_APPS_DIR}" \
&& pnpm install --filter @tloncorp/tlon-skill... --frozen-lockfile --ignore-scripts \
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
--- a/agent/file_safety.py
+++ b/agent/file_safety.py
@@ -91,8 +91,15 @@
@@ -100,8 +100,15 @@
home = os.path.realpath(os.path.expanduser("~"))
resolved = os.path.realpath(os.path.expanduser(str(path)))

Expand All @@ -18,72 +18,3 @@
for prefix in build_write_denied_prefixes(home):
if resolved.startswith(prefix):
return True
@@ -116,6 +123,8 @@

for base_real in hermes_dirs:
for name in control_file_names:
+ if allow_config_write and name == "config.yaml":
+ continue
try:
if resolved == os.path.realpath(os.path.join(base_real, name)):
return True
--- a/hermes_cli/web_server.py
+++ b/hermes_cli/web_server.py
@@ -4319,12 +4319,18 @@

def _validate_plugin_name(name: str) -> str:
"""Reject path-traversal attempts in plugin name URL parameters."""
- if not name or "/" in name or "\\" in name or ".." in name:
+ name = (name or "").strip().strip("/")
+ parts = [part for part in name.split("/") if part]
+ if (
+ not parts
+ or "\\" in name
+ or any(part in {".", ".."} for part in parts)
+ ):
raise HTTPException(status_code=400, detail="Invalid plugin name.")
- return name
+ return "/".join(parts)


-@app.post("/api/dashboard/agent-plugins/{name}/enable")
+@app.post("/api/dashboard/agent-plugins/{name:path}/enable")
async def post_agent_plugin_enable(request: Request, name: str):
_require_token(request)
name = _validate_plugin_name(name)
@@ -4336,7 +4342,7 @@
return result


-@app.post("/api/dashboard/agent-plugins/{name}/disable")
+@app.post("/api/dashboard/agent-plugins/{name:path}/disable")
async def post_agent_plugin_disable(request: Request, name: str):
_require_token(request)
name = _validate_plugin_name(name)
@@ -4348,7 +4354,7 @@
return result


-@app.post("/api/dashboard/agent-plugins/{name}/update")
+@app.post("/api/dashboard/agent-plugins/{name:path}/update")
async def post_agent_plugin_update(request: Request, name: str):
_require_token(request)
name = _validate_plugin_name(name)
@@ -4361,7 +4367,7 @@
return result


-@app.delete("/api/dashboard/agent-plugins/{name}")
+@app.delete("/api/dashboard/agent-plugins/{name:path}")
async def delete_agent_plugin(request: Request, name: str):
_require_token(request)
name = _validate_plugin_name(name)
@@ -4399,7 +4405,7 @@
hidden: bool


-@app.post("/api/dashboard/plugins/{name}/visibility")
+@app.post("/api/dashboard/plugins/{name:path}/visibility")
async def post_plugin_visibility(request: Request, name: str, body: _PluginVisibilityBody):
"""Toggle a plugin's sidebar visibility (persists to config.yaml dashboard.hidden_plugins)."""
_require_token(request)
72 changes: 17 additions & 55 deletions goseg/click/hark.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,27 @@ import (
"fmt"
)

/*
=/ m (strand ,vase)
;< =bowl:rand
bind:m
get-our
;< ~
bind:m
=+ id=(end 7 (shas %startram-notification eny.bowl))
=/ con=(list content:h) ~[text text]
=+ rope=[[~ our.bowl %nativeplanet] [~ %diary our.bowl %changelog] %groups /]
=/ wer=path /groups/'~nattyv'/nativeplanet/channels/diary/'~nattyv'/changelog/note/'170141184506582040503264511680103579648'
=+ but=~
(poke [our.bowl %hark] %hark-action !>([%add-yarn & & id rope now.bowl con wer but])
(pure:m !>('success'))
*/
func sendStartramReminder(patp string, daysLeft int) error {
file := "startram-hark"
// construct poke
text := fmt.Sprintf("'Your startram code is expiring in %v days. Click for more information.'", daysLeft)
con := fmt.Sprintf("~[%s %s]", text, text)
//id := "0v14e.5p95b.d5mk5.vrqe0.oeu0m.3ghcb"
id := "(end 7 (shas %startram-notification eny.bowl))"
rope := "[[~ our.bowl %nativeplanet] [~ %diary our.bowl %documentation] %groups /]" // temp location
wer := "/groups/'~nattyv'/nativeplanet/channels/diary/'~nattyv'/documentation/note/'170141184506683847949839018055058849792'" //temp location
func harkNotificationHoon(category, text string) string {
id := "(end 7 (shas %nativeplanet-notification eny.bowl))"
rope := fmt.Sprintf("[~ ~ %%nativeplanet /nativeplanet/%s]", category)
content := fmt.Sprintf("~[%s]", text)

but := "~"
hoon := joinGap([]string{
return joinGap([]string{
"=/", "m", "(strand ,vase)",
";<", "=bowl:rand", "bind:m", "get-bowl",
"=/", "id", id,
"=/", "rope", rope,
"=/", "content", content,
";<", "~", "bind:m",
fmt.Sprintf("(poke [our.bowl %%hark] %%hark-action !>([%%add-yarn & & [%s %s now.bowl %s %s %s]]))", id, rope, con, wer, but),
"(poke [our.bowl %hark] %hark-action !>([%add-yarn & & id rope now.bowl content / ~]))",
"(pure:m !>('success'))",
})
}

func sendStartramReminder(patp string, daysLeft int) error {
file := "startram-hark"
text := fmt.Sprintf("'Your startram code is expiring in %v days. Click for more information.'", daysLeft)
hoon := harkNotificationHoon("startram", text)

// create hoon file
if err := createHoon(patp, file, hoon); err != nil {
Expand All @@ -61,21 +49,8 @@ func sendStartramReminder(patp string, daysLeft int) error {

func sendDiskSpaceWarning(patp, diskName string, diskUsage float64) error {
file := "diskspace-hark"
// construct poke
text := fmt.Sprintf("'Your drive %s is %v%% full. Manage your disk to prevent issues!'", diskName, diskUsage)
con := fmt.Sprintf("~[%s]", text)
id := "(end 7 (shas %diskusage eny.bowl))"
rope := "[[~ our.bowl %nativeplanet] [~ %diary our.bowl %documentation] %groups /]"
wer := "/groups/'~nattyv'/nativeplanet/channels/diary/'~nattyv'/documentation/note/'170141184506683847949839018055058849792'"

but := "~"
hoon := joinGap([]string{
"=/", "m", "(strand ,vase)",
";<", "=bowl:rand", "bind:m", "get-bowl",
";<", "~", "bind:m",
fmt.Sprintf("(poke [our.bowl %%hark] %%hark-action !>([%%add-yarn & & [%s %s now.bowl %s %s %s]]))", id, rope, con, wer, but),
"(pure:m !>('success'))",
})
hoon := harkNotificationHoon("disk-space", text)

// create hoon file
if err := createHoon(patp, file, hoon); err != nil {
Expand All @@ -100,21 +75,8 @@ func sendDiskSpaceWarning(patp, diskName string, diskUsage float64) error {

func sendSmartWarning(patp, diskName string) error {
file := "smart-fail-hark"
// construct poke
text := fmt.Sprintf("'Your drive %s failed a health check. Replace your hard drive to prevent data loss!'", diskName)
con := fmt.Sprintf("~[%s]", text)
id := "(end 7 (shas %smartfail eny.bowl))"
rope := "[[~ our.bowl %nativeplanet] [~ %diary our.bowl %documentation] %groups /]"
wer := "/groups/'~nattyv'/nativeplanet/channels/diary/'~nattyv'/documentation/note/'170141184506683847949839018055058849792'"

but := "~"
hoon := joinGap([]string{
"=/", "m", "(strand ,vase)",
";<", "=bowl:rand", "bind:m", "get-bowl",
";<", "~", "bind:m",
fmt.Sprintf("(poke [our.bowl %%hark] %%hark-action !>([%%add-yarn & & [%s %s now.bowl %s %s %s]]))", id, rope, con, wer, but),
"(pure:m !>('success'))",
})
hoon := harkNotificationHoon("disk-health", text)

// create hoon file
if err := createHoon(patp, file, hoon); err != nil {
Expand Down
27 changes: 27 additions & 0 deletions goseg/click/hark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package click

import (
"strings"
"testing"
)

func TestHarkNotificationHoonUsesCurrentAction(t *testing.T) {
hoon := harkNotificationHoon("disk-space", "'Drive warning'")

for _, want := range []string{
"(poke [our.bowl %hark] %hark-action",
"[%add-yarn & & id rope now.bowl content / ~]",
"[~ ~ %nativeplanet /nativeplanet/disk-space]",
"~['Drive warning']",
} {
if !strings.Contains(hoon, want) {
t.Fatalf("generated Hoon does not contain %q:\n%s", want, hoon)
}
}

for _, retired := range []string{"%hark-store", "%add-note"} {
if strings.Contains(hoon, retired) {
t.Fatalf("generated Hoon still contains retired action %q:\n%s", retired, hoon)
}
}
}
10 changes: 5 additions & 5 deletions goseg/defaults/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ var (
Ship: "",
Owner: "",
Port: 19119,
Image: "registry.hub.docker.com/nativeplanet/hermes-tlon:0.14.0-0.14.0",
HermesVersion: "0.14.0",
HermesAgentRef: "2ffa1c97c09317c1d066aa5708b8ad961a4ca589",
TlonAdapterVersion: "0.14.0",
TlonAdapterRef: "33112008b1f3e83816dee61020dc5d4c57770c15",
Image: "registry.hub.docker.com/nativeplanet/hermes-tlon:0.18.2-0.5.0",
HermesVersion: "0.18.2",
HermesAgentRef: "9de9c25f620ff7f1ce0fd5457d596052d5159596",
TlonAdapterVersion: "0.5.0",
TlonAdapterRef: "15fc26bd9d8a13345d54f4c7b3e00601a2e0996f",
ModelProvider: "openrouter",
Model: "deepseek/deepseek-v4-flash",
ProviderAPIKey: "",
Expand Down
3 changes: 3 additions & 0 deletions goseg/defaults/scripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,9 @@ var (
fi
fi

urbit -Lx $ttyflag --loom $loom $dirname || true
urbit roll --loom $loom $dirname || true
urbit -Lx $ttyflag --loom $loom $dirname || true
urbit roll --loom $loom $dirname || true
urbit chop --loom $loom $dirname`

Expand Down
25 changes: 25 additions & 0 deletions goseg/defaults/scripts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package defaults

import (
"strings"
"testing"
)

func TestRollChopScriptCommandSequence(t *testing.T) {
commands := []string{
"urbit -Lx $ttyflag --loom $loom $dirname || true",
"urbit roll --loom $loom $dirname || true",
"urbit -Lx $ttyflag --loom $loom $dirname || true",
"urbit roll --loom $loom $dirname || true",
"urbit chop --loom $loom $dirname",
}

remaining := RollChopScript
for _, command := range commands {
index := strings.Index(remaining, command)
if index == -1 {
t.Fatalf("roll/chop script is missing command %q in the expected order", command)
}
remaining = remaining[index+len(command):]
}
}
10 changes: 5 additions & 5 deletions goseg/docker/hermes.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ const (
HermesTlonSkillDir = "/opt/data/tlon-skill"
hermesConfigVersionLabel = "nativeplanet.groundseg.hermes.config-version"
hermesConfigVersion = "2026-06-28-hermes-tmux-searxng"
DefaultHermesImage = "registry.hub.docker.com/nativeplanet/hermes-tlon:0.14.0-0.14.0"
DefaultHermesImage = "registry.hub.docker.com/nativeplanet/hermes-tlon:0.18.2-0.5.0"
DefaultHermesModelProvider = "openrouter"
DefaultHermesModel = "deepseek/deepseek-v4-flash"
DefaultHermesVersion = "0.14.0"
DefaultHermesAgentRef = "2ffa1c97c09317c1d066aa5708b8ad961a4ca589"
DefaultHermesTlonAdapterVersion = "0.14.0"
DefaultHermesTlonAdapterRef = "33112008b1f3e83816dee61020dc5d4c57770c15"
DefaultHermesVersion = "0.18.2"
DefaultHermesAgentRef = "9de9c25f620ff7f1ce0fd5457d596052d5159596"
DefaultHermesTlonAdapterVersion = "0.5.0"
DefaultHermesTlonAdapterRef = "15fc26bd9d8a13345d54f4c7b3e00601a2e0996f"
DefaultHermesDashboardHostPort = 19119
HermesDashboardContainerPort = 9119
)
Expand Down
Loading
Loading