harnery 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +84 -2
- package/bin/agent-coord +42 -0
- package/bin/agent-hook +44 -0
- package/bin/harn +40 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +18 -0
- package/dist/commander.d.ts +128 -0
- package/dist/commander.d.ts.map +1 -0
- package/dist/commander.js +126 -0
- package/dist/commands/agents.d.ts +18 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +3946 -0
- package/dist/commands/backup.d.ts +22 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +262 -0
- package/dist/commands/browse-ai.d.ts +4 -0
- package/dist/commands/browse-ai.d.ts.map +1 -0
- package/dist/commands/browse-ai.js +156 -0
- package/dist/commands/browse.d.ts +4 -0
- package/dist/commands/browse.d.ts.map +1 -0
- package/dist/commands/browse.js +590 -0
- package/dist/commands/callers.d.ts +4 -0
- package/dist/commands/callers.d.ts.map +1 -0
- package/dist/commands/callers.js +276 -0
- package/dist/commands/completion.d.ts +17 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +158 -0
- package/dist/commands/config-get.d.ts +4 -0
- package/dist/commands/config-get.d.ts.map +1 -0
- package/dist/commands/config-get.js +131 -0
- package/dist/commands/context.d.ts +11 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +185 -0
- package/dist/commands/cookies.d.ts +4 -0
- package/dist/commands/cookies.d.ts.map +1 -0
- package/dist/commands/cookies.js +140 -0
- package/dist/commands/docs.d.ts +4 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +137 -0
- package/dist/commands/doctor.d.ts +25 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +200 -0
- package/dist/commands/edit-batch.d.ts +18 -0
- package/dist/commands/edit-batch.d.ts.map +1 -0
- package/dist/commands/edit-batch.js +172 -0
- package/dist/commands/eml.d.ts +4 -0
- package/dist/commands/eml.d.ts.map +1 -0
- package/dist/commands/eml.js +428 -0
- package/dist/commands/env.d.ts +4 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +201 -0
- package/dist/commands/fetch.d.ts +4 -0
- package/dist/commands/fetch.d.ts.map +1 -0
- package/dist/commands/fetch.js +99 -0
- package/dist/commands/file-history.d.ts +4 -0
- package/dist/commands/file-history.d.ts.map +1 -0
- package/dist/commands/file-history.js +152 -0
- package/dist/commands/grep.d.ts +4 -0
- package/dist/commands/grep.d.ts.map +1 -0
- package/dist/commands/grep.js +317 -0
- package/dist/commands/init.d.ts +82 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +288 -0
- package/dist/commands/outline.d.ts +4 -0
- package/dist/commands/outline.d.ts.map +1 -0
- package/dist/commands/outline.js +494 -0
- package/dist/commands/presence.d.ts +12 -0
- package/dist/commands/presence.d.ts.map +1 -0
- package/dist/commands/presence.js +123 -0
- package/dist/commands/read.d.ts +7 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +46 -0
- package/dist/commands/scratch.d.ts +4 -0
- package/dist/commands/scratch.d.ts.map +1 -0
- package/dist/commands/scratch.js +426 -0
- package/dist/commands/session.d.ts +4 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +162 -0
- package/dist/commands/sync.d.ts +24 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +275 -0
- package/dist/commands/toc.d.ts +5 -0
- package/dist/commands/toc.d.ts.map +1 -0
- package/dist/commands/toc.js +153 -0
- package/dist/commands/tokens.d.ts +4 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +48 -0
- package/dist/commands/tunnel.d.ts +4 -0
- package/dist/commands/tunnel.d.ts.map +1 -0
- package/dist/commands/tunnel.js +513 -0
- package/dist/commands/uninstall.d.ts +22 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +126 -0
- package/dist/commands/web.d.ts +4 -0
- package/dist/commands/web.d.ts.map +1 -0
- package/dist/commands/web.js +165 -0
- package/dist/core/agents/canonical-emit.d.ts +27 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -0
- package/dist/core/agents/canonical-emit.js +72 -0
- package/dist/core/agents/cli-emit.d.ts +27 -0
- package/dist/core/agents/cli-emit.d.ts.map +1 -0
- package/dist/core/agents/cli-emit.js +57 -0
- package/dist/core/agents/cli.d.ts +10 -0
- package/dist/core/agents/cli.d.ts.map +1 -0
- package/dist/core/agents/cli.js +757 -0
- package/dist/core/agents/codex-replay.d.ts +29 -0
- package/dist/core/agents/codex-replay.d.ts.map +1 -0
- package/dist/core/agents/codex-replay.js +138 -0
- package/dist/core/agents/coord-client.d.ts +98 -0
- package/dist/core/agents/coord-client.d.ts.map +1 -0
- package/dist/core/agents/coord-client.js +212 -0
- package/dist/core/agents/events/consume.d.ts +59 -0
- package/dist/core/agents/events/consume.d.ts.map +1 -0
- package/dist/core/agents/events/consume.js +147 -0
- package/dist/core/agents/events/emit.d.ts +42 -0
- package/dist/core/agents/events/emit.d.ts.map +1 -0
- package/dist/core/agents/events/emit.js +70 -0
- package/dist/core/agents/events/ulid.d.ts +11 -0
- package/dist/core/agents/events/ulid.d.ts.map +1 -0
- package/dist/core/agents/events/ulid.js +47 -0
- package/dist/core/agents/index.d.ts +14 -0
- package/dist/core/agents/index.d.ts.map +1 -0
- package/dist/core/agents/index.js +13 -0
- package/dist/core/agents/paths.d.ts +6 -0
- package/dist/core/agents/paths.d.ts.map +1 -0
- package/dist/core/agents/paths.js +17 -0
- package/dist/core/agents/render/prompt-context.d.ts +43 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -0
- package/dist/core/agents/render/prompt-context.js +335 -0
- package/dist/core/agents/render/session-context.d.ts +39 -0
- package/dist/core/agents/render/session-context.d.ts.map +1 -0
- package/dist/core/agents/render/session-context.js +283 -0
- package/dist/core/agents/rules/claim-conflict.d.ts +35 -0
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -0
- package/dist/core/agents/rules/claim-conflict.js +244 -0
- package/dist/core/agents/rules/commit-conflict.d.ts +59 -0
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -0
- package/dist/core/agents/rules/commit-conflict.js +244 -0
- package/dist/core/agents/rules/stop-hook.d.ts +44 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -0
- package/dist/core/agents/rules/stop-hook.js +161 -0
- package/dist/core/agents/session-events.d.ts +41 -0
- package/dist/core/agents/session-events.d.ts.map +1 -0
- package/dist/core/agents/session-events.js +205 -0
- package/dist/core/agents/state/activity-log.d.ts +18 -0
- package/dist/core/agents/state/activity-log.d.ts.map +1 -0
- package/dist/core/agents/state/activity-log.js +34 -0
- package/dist/core/agents/state/council.d.ts +39 -0
- package/dist/core/agents/state/council.d.ts.map +1 -0
- package/dist/core/agents/state/council.js +216 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts +59 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -0
- package/dist/core/agents/state/heartbeat-projector.js +436 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts +64 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -0
- package/dist/core/agents/state/heartbeat-writer.js +271 -0
- package/dist/core/agents/state/names.d.ts +35 -0
- package/dist/core/agents/state/names.d.ts.map +1 -0
- package/dist/core/agents/state/names.js +376 -0
- package/dist/core/agents/state/pidmap.d.ts +11 -0
- package/dist/core/agents/state/pidmap.d.ts.map +1 -0
- package/dist/core/agents/state/pidmap.js +32 -0
- package/dist/core/agents/state/scratch.d.ts +27 -0
- package/dist/core/agents/state/scratch.d.ts.map +1 -0
- package/dist/core/agents/state/scratch.js +90 -0
- package/dist/core/agents/state/shell-mutation.d.ts +17 -0
- package/dist/core/agents/state/shell-mutation.d.ts.map +1 -0
- package/dist/core/agents/state/shell-mutation.js +41 -0
- package/dist/core/agents/state/stale-sweep.d.ts +16 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -0
- package/dist/core/agents/state/stale-sweep.js +166 -0
- package/dist/core/config.d.ts +29 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +108 -0
- package/dist/core/hooks/cli.d.ts +21 -0
- package/dist/core/hooks/cli.d.ts.map +1 -0
- package/dist/core/hooks/cli.js +1123 -0
- package/dist/core/hooks/effects/image-capture.d.ts +43 -0
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -0
- package/dist/core/hooks/effects/image-capture.js +288 -0
- package/dist/core/hooks/effects/index.d.ts +64 -0
- package/dist/core/hooks/effects/index.d.ts.map +1 -0
- package/dist/core/hooks/effects/index.js +197 -0
- package/dist/core/hooks/events/emit.d.ts +31 -0
- package/dist/core/hooks/events/emit.d.ts.map +1 -0
- package/dist/core/hooks/events/emit.js +89 -0
- package/dist/core/hooks/events/schema.d.ts +235 -0
- package/dist/core/hooks/events/schema.d.ts.map +1 -0
- package/dist/core/hooks/events/schema.js +12 -0
- package/dist/core/hooks/events/ulid.d.ts +10 -0
- package/dist/core/hooks/events/ulid.d.ts.map +1 -0
- package/dist/core/hooks/events/ulid.js +47 -0
- package/dist/core/hooks/harness/detect.d.ts +9 -0
- package/dist/core/hooks/harness/detect.d.ts.map +1 -0
- package/dist/core/hooks/harness/detect.js +29 -0
- package/dist/core/hooks/harness/events.d.ts +45 -0
- package/dist/core/hooks/harness/events.d.ts.map +1 -0
- package/dist/core/hooks/harness/events.js +71 -0
- package/dist/core/hooks/harness/output.d.ts +46 -0
- package/dist/core/hooks/harness/output.d.ts.map +1 -0
- package/dist/core/hooks/harness/output.js +87 -0
- package/dist/core/hooks/harness/parse.d.ts +67 -0
- package/dist/core/hooks/harness/parse.d.ts.map +1 -0
- package/dist/core/hooks/harness/parse.js +132 -0
- package/dist/core/hooks/index.d.ts +8 -0
- package/dist/core/hooks/index.d.ts.map +1 -0
- package/dist/core/hooks/index.js +7 -0
- package/dist/core/hooks/resolve/anchor.d.ts +37 -0
- package/dist/core/hooks/resolve/anchor.d.ts.map +1 -0
- package/dist/core/hooks/resolve/anchor.js +48 -0
- package/dist/core/hooks/resolve/coord-root.d.ts +6 -0
- package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -0
- package/dist/core/hooks/resolve/coord-root.js +27 -0
- package/dist/core/hooks/resolve/intent.d.ts +33 -0
- package/dist/core/hooks/resolve/intent.d.ts.map +1 -0
- package/dist/core/hooks/resolve/intent.js +79 -0
- package/dist/core/hooks/resolve/owner.d.ts +42 -0
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -0
- package/dist/core/hooks/resolve/owner.js +140 -0
- package/dist/core/hooks/resolve/transcript.d.ts +26 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -0
- package/dist/core/hooks/resolve/transcript.js +73 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/lib/agent-browser/client.d.ts +99 -0
- package/dist/lib/agent-browser/client.d.ts.map +1 -0
- package/dist/lib/agent-browser/client.js +177 -0
- package/dist/lib/agent-browser/index.d.ts +2 -0
- package/dist/lib/agent-browser/index.d.ts.map +1 -0
- package/dist/lib/agent-browser/index.js +1 -0
- package/dist/lib/browser/client.d.ts +193 -0
- package/dist/lib/browser/client.d.ts.map +1 -0
- package/dist/lib/browser/client.js +325 -0
- package/dist/lib/browser/dev-overlay.d.ts +23 -0
- package/dist/lib/browser/dev-overlay.d.ts.map +1 -0
- package/dist/lib/browser/dev-overlay.js +153 -0
- package/dist/lib/browser/index.d.ts +5 -0
- package/dist/lib/browser/index.d.ts.map +1 -0
- package/dist/lib/browser/index.js +2 -0
- package/dist/lib/browser/layout.d.ts +79 -0
- package/dist/lib/browser/layout.d.ts.map +1 -0
- package/dist/lib/browser/layout.js +220 -0
- package/dist/lib/browser/visibility.d.ts +86 -0
- package/dist/lib/browser/visibility.d.ts.map +1 -0
- package/dist/lib/browser/visibility.js +333 -0
- package/dist/lib/browser/visual-diff.d.ts +38 -0
- package/dist/lib/browser/visual-diff.d.ts.map +1 -0
- package/dist/lib/browser/visual-diff.js +107 -0
- package/dist/lib/completion/bash.d.ts +25 -0
- package/dist/lib/completion/bash.d.ts.map +1 -0
- package/dist/lib/completion/bash.js +284 -0
- package/dist/lib/completion/fish.d.ts +16 -0
- package/dist/lib/completion/fish.d.ts.map +1 -0
- package/dist/lib/completion/fish.js +118 -0
- package/dist/lib/completion/index.d.ts +5 -0
- package/dist/lib/completion/index.d.ts.map +1 -0
- package/dist/lib/completion/index.js +4 -0
- package/dist/lib/completion/walk.d.ts +68 -0
- package/dist/lib/completion/walk.d.ts.map +1 -0
- package/dist/lib/completion/walk.js +102 -0
- package/dist/lib/completion/zsh.d.ts +13 -0
- package/dist/lib/completion/zsh.d.ts.map +1 -0
- package/dist/lib/completion/zsh.js +249 -0
- package/dist/lib/context/index.d.ts +107 -0
- package/dist/lib/context/index.d.ts.map +1 -0
- package/dist/lib/context/index.js +275 -0
- package/dist/lib/cookies/client.d.ts +131 -0
- package/dist/lib/cookies/client.d.ts.map +1 -0
- package/dist/lib/cookies/client.js +239 -0
- package/dist/lib/cookies/index.d.ts +2 -0
- package/dist/lib/cookies/index.d.ts.map +1 -0
- package/dist/lib/cookies/index.js +1 -0
- package/dist/lib/council/index.d.ts +266 -0
- package/dist/lib/council/index.d.ts.map +1 -0
- package/dist/lib/council/index.js +674 -0
- package/dist/lib/docs-index.d.ts +28 -0
- package/dist/lib/docs-index.d.ts.map +1 -0
- package/dist/lib/docs-index.js +169 -0
- package/dist/lib/docs-lint.d.ts +26 -0
- package/dist/lib/docs-lint.d.ts.map +1 -0
- package/dist/lib/docs-lint.js +378 -0
- package/dist/lib/docs-sweep.d.ts +34 -0
- package/dist/lib/docs-sweep.d.ts.map +1 -0
- package/dist/lib/docs-sweep.js +304 -0
- package/dist/lib/docs.d.ts +27 -0
- package/dist/lib/docs.d.ts.map +1 -0
- package/dist/lib/docs.js +142 -0
- package/dist/lib/env.d.ts +11 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +12 -0
- package/dist/lib/exec.d.ts +32 -0
- package/dist/lib/exec.d.ts.map +1 -0
- package/dist/lib/exec.js +54 -0
- package/dist/lib/format.d.ts +29 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +139 -0
- package/dist/lib/http/client.d.ts +56 -0
- package/dist/lib/http/client.d.ts.map +1 -0
- package/dist/lib/http/client.js +160 -0
- package/dist/lib/http/index.d.ts +2 -0
- package/dist/lib/http/index.d.ts.map +1 -0
- package/dist/lib/http/index.js +1 -0
- package/dist/lib/identities/index.d.ts +77 -0
- package/dist/lib/identities/index.d.ts.map +1 -0
- package/dist/lib/identities/index.js +190 -0
- package/dist/lib/machine.d.ts +19 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/machine.js +61 -0
- package/dist/lib/presence.d.ts +48 -0
- package/dist/lib/presence.d.ts.map +1 -0
- package/dist/lib/presence.js +123 -0
- package/dist/lib/readability/client.d.ts +32 -0
- package/dist/lib/readability/client.d.ts.map +1 -0
- package/dist/lib/readability/client.js +119 -0
- package/dist/lib/readability/index.d.ts +2 -0
- package/dist/lib/readability/index.d.ts.map +1 -0
- package/dist/lib/readability/index.js +1 -0
- package/dist/lib/scratch/index.d.ts +74 -0
- package/dist/lib/scratch/index.d.ts.map +1 -0
- package/dist/lib/scratch/index.js +393 -0
- package/dist/lib/tunnel/gate.d.ts +12 -0
- package/dist/lib/tunnel/gate.d.ts.map +1 -0
- package/dist/lib/tunnel/gate.js +101 -0
- package/dist/lib/tunnel/state.d.ts +34 -0
- package/dist/lib/tunnel/state.d.ts.map +1 -0
- package/dist/lib/tunnel/state.js +132 -0
- package/package.json +160 -8
- package/schemas/.gitkeep +0 -0
- package/schemas/config.schema.json +109 -0
- package/src/cli.ts +22 -0
- package/src/commander.ts +242 -0
- package/src/commands/.gitkeep +0 -0
- package/src/commands/agents.ts +4567 -0
- package/src/commands/backup.ts +305 -0
- package/src/commands/browse-ai.ts +198 -0
- package/src/commands/browse.ts +849 -0
- package/src/commands/callers.ts +363 -0
- package/src/commands/completion.ts +193 -0
- package/src/commands/config-get.ts +161 -0
- package/src/commands/context.ts +209 -0
- package/src/commands/cookies.ts +198 -0
- package/src/commands/docs.ts +174 -0
- package/src/commands/doctor.ts +231 -0
- package/src/commands/edit-batch.ts +233 -0
- package/src/commands/eml.ts +519 -0
- package/src/commands/env.ts +254 -0
- package/src/commands/fetch.ts +136 -0
- package/src/commands/file-history.ts +202 -0
- package/src/commands/grep.ts +371 -0
- package/src/commands/init.ts +335 -0
- package/src/commands/outline.ts +564 -0
- package/src/commands/presence.ts +152 -0
- package/src/commands/read.ts +64 -0
- package/src/commands/scratch.ts +445 -0
- package/src/commands/session.ts +187 -0
- package/src/commands/sync.ts +306 -0
- package/src/commands/toc.ts +218 -0
- package/src/commands/tokens.ts +79 -0
- package/src/commands/tunnel.ts +633 -0
- package/src/commands/uninstall.ts +144 -0
- package/src/commands/web.ts +193 -0
- package/src/core/agents/canonical-emit.ts +77 -0
- package/src/core/agents/cli-emit.ts +64 -0
- package/src/core/agents/cli.ts +838 -0
- package/src/core/agents/codex-replay.ts +163 -0
- package/src/core/agents/coord-client.ts +249 -0
- package/src/core/agents/events/consume.ts +196 -0
- package/src/core/agents/events/emit.ts +108 -0
- package/src/core/agents/events/ulid.ts +51 -0
- package/src/core/agents/index.ts +14 -0
- package/src/core/agents/paths.ts +16 -0
- package/src/core/agents/render/prompt-context.ts +401 -0
- package/src/core/agents/render/session-context.ts +341 -0
- package/src/core/agents/rules/claim-conflict.ts +282 -0
- package/src/core/agents/rules/commit-conflict.ts +303 -0
- package/src/core/agents/rules/stop-hook.ts +229 -0
- package/src/core/agents/session-events.ts +228 -0
- package/src/core/agents/state/activity-log.ts +33 -0
- package/src/core/agents/state/council.ts +265 -0
- package/src/core/agents/state/heartbeat-projector.ts +488 -0
- package/src/core/agents/state/heartbeat-writer.ts +333 -0
- package/src/core/agents/state/names.ts +399 -0
- package/src/core/agents/state/pidmap.ts +38 -0
- package/src/core/agents/state/scratch.ts +121 -0
- package/src/core/agents/state/shell-mutation.ts +44 -0
- package/src/core/agents/state/stale-sweep.ts +190 -0
- package/src/core/config.ts +111 -0
- package/src/core/hooks/cli.ts +1247 -0
- package/src/core/hooks/effects/image-capture.ts +330 -0
- package/src/core/hooks/effects/index.ts +210 -0
- package/src/core/hooks/events/emit.ts +120 -0
- package/src/core/hooks/events/schema.ts +430 -0
- package/src/core/hooks/events/ulid.ts +51 -0
- package/src/core/hooks/harness/detect.ts +30 -0
- package/src/core/hooks/harness/events.ts +102 -0
- package/src/core/hooks/harness/output.ts +100 -0
- package/src/core/hooks/harness/parse.ts +180 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/resolve/anchor.ts +51 -0
- package/src/core/hooks/resolve/coord-root.ts +25 -0
- package/src/core/hooks/resolve/intent.ts +89 -0
- package/src/core/hooks/resolve/owner.ts +140 -0
- package/src/core/hooks/resolve/transcript.ts +72 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/index.ts +15 -0
- package/src/lib/agent-browser/client.ts +239 -0
- package/src/lib/agent-browser/index.ts +1 -0
- package/src/lib/browser/client.ts +449 -0
- package/src/lib/browser/dev-overlay.ts +207 -0
- package/src/lib/browser/index.ts +24 -0
- package/src/lib/browser/layout.ts +288 -0
- package/src/lib/browser/visibility.ts +419 -0
- package/src/lib/browser/visual-diff.ts +150 -0
- package/src/lib/completion/bash.ts +291 -0
- package/src/lib/completion/fish.ts +134 -0
- package/src/lib/completion/index.ts +10 -0
- package/src/lib/completion/walk.ts +184 -0
- package/src/lib/completion/zsh.ts +262 -0
- package/src/lib/context/index.ts +386 -0
- package/src/lib/cookies/client.ts +301 -0
- package/src/lib/cookies/index.ts +13 -0
- package/src/lib/council/index.ts +803 -0
- package/src/lib/docs-index.ts +216 -0
- package/src/lib/docs-lint.ts +413 -0
- package/src/lib/docs-sweep.ts +348 -0
- package/src/lib/docs.ts +199 -0
- package/src/lib/env.ts +12 -0
- package/src/lib/exec.ts +74 -0
- package/src/lib/format.ts +147 -0
- package/src/lib/http/client.ts +211 -0
- package/src/lib/http/index.ts +1 -0
- package/src/lib/identities/index.ts +210 -0
- package/src/lib/machine.ts +61 -0
- package/src/lib/presence.ts +154 -0
- package/src/lib/readability/client.ts +156 -0
- package/src/lib/readability/index.ts +5 -0
- package/src/lib/readability/turndown-plugin-gfm.d.ts +10 -0
- package/src/lib/scratch/index.ts +470 -0
- package/src/lib/tunnel/gate.ts +113 -0
- package/src/lib/tunnel/state.ts +167 -0
- package/src/web/.gitkeep +0 -0
- package/index.js +0 -1
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
statSync,
|
|
8
|
+
unlinkSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { dirname, resolve } from "node:path";
|
|
12
|
+
import { monorepoRoot, readHeartbeat, resolveOwner } from "../../core/agents/index.ts";
|
|
13
|
+
import { resolveMachineLabel } from "../machine.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Agent scratchpad: per-agent markdown journal at `.harnery/scratch/<instance_id>.md`.
|
|
17
|
+
*
|
|
18
|
+
* Rules:
|
|
19
|
+
* - Single writer (the agent itself, via this lib). Peers read but never write.
|
|
20
|
+
* - Append-only, newest entry first. Atomic temp-file + rename on each write.
|
|
21
|
+
* - Strict entry-header format: `## <Chicago datetime> · <category>`. The
|
|
22
|
+
* linter enforces this so format violations are impossible at the write path.
|
|
23
|
+
* - 50 KB hard cap; auto-prunes oldest entries when exceeded.
|
|
24
|
+
* - SessionEnd hook archives to `.harnery/scratch/archived/<instance_id>-<ts>.md`;
|
|
25
|
+
* SessionStart janitor deletes archives older than 7 days + surfaces the
|
|
26
|
+
* most-recent archive as a recovery cue.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export const SCRATCH_CATEGORIES = [
|
|
30
|
+
"note",
|
|
31
|
+
"plan",
|
|
32
|
+
"decision",
|
|
33
|
+
"blocker",
|
|
34
|
+
"question",
|
|
35
|
+
"done",
|
|
36
|
+
"handoff",
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
export type ScratchCategory = (typeof SCRATCH_CATEGORIES)[number];
|
|
40
|
+
|
|
41
|
+
export const MAX_SCRATCH_BYTES = 50 * 1024;
|
|
42
|
+
export const WARN_SCRATCH_BYTES = 40 * 1024;
|
|
43
|
+
export const ARCHIVE_RETENTION_DAYS = 7;
|
|
44
|
+
|
|
45
|
+
/** Entry-header regex: `## 2026-05-15 1:48 AM CDT · note` */
|
|
46
|
+
export const ENTRY_HEADER_RE =
|
|
47
|
+
/^## (\d{4}-\d{2}-\d{2}) (\d{1,2}):(\d{2}) (AM|PM) (CDT|CST) · (note|plan|decision|blocker|question|done|handoff)$/;
|
|
48
|
+
|
|
49
|
+
/** First line of every scratchpad file: this prefix followed by the agent name. */
|
|
50
|
+
export const SCRATCHPAD_HEADER_PREFIX = "# Scratchpad: agent-";
|
|
51
|
+
|
|
52
|
+
export interface ScratchEntry {
|
|
53
|
+
ts_iso: string; // canonical ISO (computed from Chicago wall time at write)
|
|
54
|
+
ts_display: string; // "2026-05-15 1:48 AM CDT", what's in the file
|
|
55
|
+
category: ScratchCategory;
|
|
56
|
+
body: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ScratchHeader {
|
|
60
|
+
name: string;
|
|
61
|
+
session_id: string;
|
|
62
|
+
machine: string;
|
|
63
|
+
started: string;
|
|
64
|
+
last_updated: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ScratchDoc {
|
|
68
|
+
path: string;
|
|
69
|
+
header: ScratchHeader;
|
|
70
|
+
entries: ScratchEntry[];
|
|
71
|
+
bytes: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── Paths ────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
export function scratchDir(): string {
|
|
77
|
+
const root = monorepoRoot();
|
|
78
|
+
if (!root) throw new Error("Not in a coord-aware repo (coord_root() returned null).");
|
|
79
|
+
const dir = resolve(root, ".harnery", "scratch");
|
|
80
|
+
mkdirSync(dir, { recursive: true });
|
|
81
|
+
return dir;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function archiveDir(): string {
|
|
85
|
+
const dir = resolve(scratchDir(), "archived");
|
|
86
|
+
mkdirSync(dir, { recursive: true });
|
|
87
|
+
return dir;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function scratchPath(instanceId: string): string {
|
|
91
|
+
return resolve(scratchDir(), `${instanceId}.md`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Chicago time formatting ──────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
const CHICAGO_FORMATTER = new Intl.DateTimeFormat("en-US", {
|
|
97
|
+
year: "numeric",
|
|
98
|
+
month: "2-digit",
|
|
99
|
+
day: "2-digit",
|
|
100
|
+
hour: "numeric",
|
|
101
|
+
minute: "2-digit",
|
|
102
|
+
timeZoneName: "short",
|
|
103
|
+
hour12: true,
|
|
104
|
+
timeZone: "America/Chicago",
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/** Format `Date` as `2026-05-15 1:48 AM CDT`, what we write into the file. */
|
|
108
|
+
export function formatChicago(d: Date = new Date()): string {
|
|
109
|
+
const parts = CHICAGO_FORMATTER.formatToParts(d);
|
|
110
|
+
const get = (t: string) => parts.find((p) => p.type === t)?.value ?? "";
|
|
111
|
+
const year = get("year");
|
|
112
|
+
const month = get("month");
|
|
113
|
+
const day = get("day");
|
|
114
|
+
const hour = get("hour");
|
|
115
|
+
const minute = get("minute");
|
|
116
|
+
const dayPeriod = get("dayPeriod");
|
|
117
|
+
const tz = get("timeZoneName");
|
|
118
|
+
return `${year}-${month}-${day} ${hour}:${minute} ${dayPeriod} ${tz}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ─── Parse / serialize ────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
/** Parse a scratchpad file (or empty content) into a structured doc. */
|
|
124
|
+
export function parseScratch(path: string, content: string): ScratchDoc {
|
|
125
|
+
const lines = content.split("\n");
|
|
126
|
+
const header: ScratchHeader = {
|
|
127
|
+
name: "unknown",
|
|
128
|
+
session_id: "",
|
|
129
|
+
machine: "",
|
|
130
|
+
started: "",
|
|
131
|
+
last_updated: "",
|
|
132
|
+
};
|
|
133
|
+
const entries: ScratchEntry[] = [];
|
|
134
|
+
|
|
135
|
+
let i = 0;
|
|
136
|
+
// ── header ──
|
|
137
|
+
if (lines[i]?.startsWith(SCRATCHPAD_HEADER_PREFIX)) {
|
|
138
|
+
header.name = lines[i].replace(SCRATCHPAD_HEADER_PREFIX, "").trim();
|
|
139
|
+
i++;
|
|
140
|
+
}
|
|
141
|
+
for (; i < lines.length; i++) {
|
|
142
|
+
const line = lines[i];
|
|
143
|
+
if (line === "---") {
|
|
144
|
+
i++;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
const m = line.match(/^(session_id|machine|started|last_updated):\s*(.+?)\s*$/);
|
|
148
|
+
if (m) {
|
|
149
|
+
(header as unknown as Record<string, string>)[m[1]] = m[2].trim();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── entries ──
|
|
154
|
+
let currentHeader: string | null = null;
|
|
155
|
+
let bodyLines: string[] = [];
|
|
156
|
+
const flush = () => {
|
|
157
|
+
if (!currentHeader) return;
|
|
158
|
+
const m = currentHeader.match(ENTRY_HEADER_RE);
|
|
159
|
+
if (!m) {
|
|
160
|
+
currentHeader = null;
|
|
161
|
+
bodyLines = [];
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const [, date, hour, minute, period, tz, category] = m;
|
|
165
|
+
entries.push({
|
|
166
|
+
ts_iso: chicagoToIso(date, hour, minute, period as "AM" | "PM", tz as "CDT" | "CST"),
|
|
167
|
+
ts_display: `${date} ${hour}:${minute} ${period} ${tz}`,
|
|
168
|
+
category: category as ScratchCategory,
|
|
169
|
+
body: bodyLines.join("\n").trim(),
|
|
170
|
+
});
|
|
171
|
+
currentHeader = null;
|
|
172
|
+
bodyLines = [];
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
for (; i < lines.length; i++) {
|
|
176
|
+
const line = lines[i];
|
|
177
|
+
if (line.startsWith("## ")) {
|
|
178
|
+
flush();
|
|
179
|
+
currentHeader = line;
|
|
180
|
+
} else if (currentHeader) {
|
|
181
|
+
bodyLines.push(line);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
flush();
|
|
185
|
+
|
|
186
|
+
return { path, header, entries, bytes: Buffer.byteLength(content, "utf8") };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Serialize a parsed doc back to markdown. */
|
|
190
|
+
export function serializeScratch(doc: ScratchDoc): string {
|
|
191
|
+
const lines: string[] = [];
|
|
192
|
+
lines.push(`${SCRATCHPAD_HEADER_PREFIX}${doc.header.name}`);
|
|
193
|
+
if (doc.header.session_id) lines.push(`session_id: ${doc.header.session_id}`);
|
|
194
|
+
if (doc.header.machine) lines.push(`machine: ${doc.header.machine}`);
|
|
195
|
+
if (doc.header.started) lines.push(`started: ${doc.header.started}`);
|
|
196
|
+
if (doc.header.last_updated) lines.push(`last_updated: ${doc.header.last_updated}`);
|
|
197
|
+
lines.push("");
|
|
198
|
+
lines.push("---");
|
|
199
|
+
lines.push("");
|
|
200
|
+
for (const entry of doc.entries) {
|
|
201
|
+
lines.push(`## ${entry.ts_display} · ${entry.category}`);
|
|
202
|
+
if (entry.body.length > 0) {
|
|
203
|
+
lines.push(entry.body);
|
|
204
|
+
}
|
|
205
|
+
lines.push("");
|
|
206
|
+
}
|
|
207
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function chicagoToIso(
|
|
211
|
+
date: string,
|
|
212
|
+
hourStr: string,
|
|
213
|
+
minute: string,
|
|
214
|
+
period: "AM" | "PM",
|
|
215
|
+
tz: "CDT" | "CST",
|
|
216
|
+
): string {
|
|
217
|
+
let hour = Number.parseInt(hourStr, 10);
|
|
218
|
+
if (period === "PM" && hour !== 12) hour += 12;
|
|
219
|
+
if (period === "AM" && hour === 12) hour = 0;
|
|
220
|
+
const offset = tz === "CDT" ? "-05:00" : "-06:00";
|
|
221
|
+
const hh = String(hour).padStart(2, "0");
|
|
222
|
+
return `${date}T${hh}:${minute}:00${offset}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── Lint ─────────────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
export interface LintIssue {
|
|
228
|
+
line: number;
|
|
229
|
+
message: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function lintScratch(content: string, byteCap = MAX_SCRATCH_BYTES): LintIssue[] {
|
|
233
|
+
const issues: LintIssue[] = [];
|
|
234
|
+
const lines = content.split("\n");
|
|
235
|
+
|
|
236
|
+
// Frontmatter checks
|
|
237
|
+
if (!lines[0]?.startsWith(SCRATCHPAD_HEADER_PREFIX)) {
|
|
238
|
+
issues.push({ line: 1, message: `missing '${SCRATCHPAD_HEADER_PREFIX}<name>' header` });
|
|
239
|
+
}
|
|
240
|
+
for (const required of ["session_id:", "machine:", "started:"]) {
|
|
241
|
+
if (!lines.slice(0, 10).some((l) => l.startsWith(required))) {
|
|
242
|
+
issues.push({ line: 1, message: `missing frontmatter field '${required}'` });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Entry header + chronological order checks
|
|
247
|
+
const entryTimes: number[] = [];
|
|
248
|
+
for (let i = 0; i < lines.length; i++) {
|
|
249
|
+
const line = lines[i];
|
|
250
|
+
if (!line.startsWith("## ")) continue;
|
|
251
|
+
if (!ENTRY_HEADER_RE.test(line)) {
|
|
252
|
+
issues.push({ line: i + 1, message: `malformed entry header: ${line}` });
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const m = line.match(ENTRY_HEADER_RE)!;
|
|
256
|
+
const iso = chicagoToIso(m[1], m[2], m[3], m[4] as "AM" | "PM", m[5] as "CDT" | "CST");
|
|
257
|
+
const ts = Date.parse(iso);
|
|
258
|
+
if (Number.isFinite(ts)) entryTimes.push(ts);
|
|
259
|
+
}
|
|
260
|
+
for (let i = 1; i < entryTimes.length; i++) {
|
|
261
|
+
if (entryTimes[i] > entryTimes[i - 1]) {
|
|
262
|
+
issues.push({
|
|
263
|
+
line: 0,
|
|
264
|
+
message: "entries are not in chronological-descending order (newest first)",
|
|
265
|
+
});
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// CRLF + trailing whitespace
|
|
271
|
+
if (content.includes("\r\n")) {
|
|
272
|
+
issues.push({ line: 0, message: "CRLF line endings detected; LF required" });
|
|
273
|
+
}
|
|
274
|
+
for (let i = 0; i < lines.length; i++) {
|
|
275
|
+
if (/\s$/.test(lines[i]) && lines[i].length > 0) {
|
|
276
|
+
issues.push({ line: i + 1, message: "trailing whitespace" });
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Size cap
|
|
282
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
283
|
+
if (bytes > byteCap) {
|
|
284
|
+
issues.push({
|
|
285
|
+
line: 0,
|
|
286
|
+
message: `file size ${bytes} bytes exceeds ${byteCap}-byte cap`,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return issues;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ─── Append + prune ───────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
export function appendEntry(
|
|
296
|
+
instanceId: string,
|
|
297
|
+
category: ScratchCategory,
|
|
298
|
+
body: string,
|
|
299
|
+
): ScratchDoc {
|
|
300
|
+
const hb = readHeartbeat(instanceId);
|
|
301
|
+
const path = scratchPath(instanceId);
|
|
302
|
+
let doc: ScratchDoc;
|
|
303
|
+
if (existsSync(path)) {
|
|
304
|
+
doc = parseScratch(path, readFileSync(path, "utf8"));
|
|
305
|
+
} else {
|
|
306
|
+
// Seed `started` from the heartbeat's session start so cross-agent writes
|
|
307
|
+
// (`harn agents ping`) don't stamp the peer's scratchpad with our wall-clock.
|
|
308
|
+
const startedSeed = hb?.started_at ? formatChicago(new Date(hb.started_at)) : formatChicago();
|
|
309
|
+
doc = {
|
|
310
|
+
path,
|
|
311
|
+
header: {
|
|
312
|
+
name: hb?.name ?? "unknown",
|
|
313
|
+
session_id: hb?.session_id ?? instanceId,
|
|
314
|
+
machine: resolveMachineLabel(),
|
|
315
|
+
started: startedSeed,
|
|
316
|
+
last_updated: "",
|
|
317
|
+
},
|
|
318
|
+
entries: [],
|
|
319
|
+
bytes: 0,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const now = new Date();
|
|
324
|
+
const entry: ScratchEntry = {
|
|
325
|
+
ts_iso: now.toISOString(),
|
|
326
|
+
ts_display: formatChicago(now),
|
|
327
|
+
category,
|
|
328
|
+
body: body.trim(),
|
|
329
|
+
};
|
|
330
|
+
doc.entries.unshift(entry);
|
|
331
|
+
doc.header.last_updated = entry.ts_display;
|
|
332
|
+
if (!doc.header.name || doc.header.name === "unknown") {
|
|
333
|
+
doc.header.name = hb?.name ?? doc.header.name;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Prune from the tail until under cap.
|
|
337
|
+
let serialized = serializeScratch(doc);
|
|
338
|
+
let pruned = 0;
|
|
339
|
+
while (Buffer.byteLength(serialized, "utf8") > MAX_SCRATCH_BYTES && doc.entries.length > 1) {
|
|
340
|
+
doc.entries.pop();
|
|
341
|
+
pruned++;
|
|
342
|
+
serialized = serializeScratch(doc);
|
|
343
|
+
}
|
|
344
|
+
if (pruned > 0) {
|
|
345
|
+
const pruneEntry: ScratchEntry = {
|
|
346
|
+
ts_iso: new Date().toISOString(),
|
|
347
|
+
ts_display: formatChicago(),
|
|
348
|
+
category: "note",
|
|
349
|
+
body: `(auto-pruned ${pruned} oldest entries to stay under ${MAX_SCRATCH_BYTES}-byte cap)`,
|
|
350
|
+
};
|
|
351
|
+
doc.entries.unshift(pruneEntry);
|
|
352
|
+
serialized = serializeScratch(doc);
|
|
353
|
+
}
|
|
354
|
+
doc.bytes = Buffer.byteLength(serialized, "utf8");
|
|
355
|
+
|
|
356
|
+
// Atomic write
|
|
357
|
+
const tmpPath = `${path}.tmp.${process.pid}`;
|
|
358
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
359
|
+
writeFileSync(tmpPath, serialized, "utf8");
|
|
360
|
+
renameSync(tmpPath, path);
|
|
361
|
+
|
|
362
|
+
return doc;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ─── Archive lifecycle ────────────────────────────────────────────────────
|
|
366
|
+
|
|
367
|
+
/** Move `<owner>.md` → `archived/<owner>-<ts>.md`. No-op if missing. Returns archive path (or null). */
|
|
368
|
+
export function archiveScratch(instanceId: string): string | null {
|
|
369
|
+
const src = scratchPath(instanceId);
|
|
370
|
+
if (!existsSync(src)) return null;
|
|
371
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
372
|
+
const dest = resolve(archiveDir(), `${instanceId}-${ts}.md`);
|
|
373
|
+
renameSync(src, dest);
|
|
374
|
+
return dest;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Delete archives older than `days`. Returns number deleted. */
|
|
378
|
+
export function pruneArchives(days = ARCHIVE_RETENTION_DAYS): number {
|
|
379
|
+
const cutoff = Date.now() - days * 24 * 3600 * 1000;
|
|
380
|
+
const dir = archiveDir();
|
|
381
|
+
let deleted = 0;
|
|
382
|
+
for (const f of readdirSync(dir)) {
|
|
383
|
+
if (!f.endsWith(".md")) continue;
|
|
384
|
+
const fp = resolve(dir, f);
|
|
385
|
+
try {
|
|
386
|
+
const s = statSync(fp);
|
|
387
|
+
if (s.mtimeMs < cutoff) {
|
|
388
|
+
unlinkSync(fp);
|
|
389
|
+
deleted++;
|
|
390
|
+
}
|
|
391
|
+
} catch {
|
|
392
|
+
// skip
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return deleted;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Sweep `.harnery/scratch/<owner>.md` files whose corresponding heartbeat is gone. */
|
|
399
|
+
export function sweepOrphanScratchpads(): string[] {
|
|
400
|
+
const dir = scratchDir();
|
|
401
|
+
const archived: string[] = [];
|
|
402
|
+
for (const f of readdirSync(dir)) {
|
|
403
|
+
if (!f.endsWith(".md")) continue;
|
|
404
|
+
const instanceId = f.replace(/\.md$/, "");
|
|
405
|
+
const hb = readHeartbeat(instanceId);
|
|
406
|
+
if (!hb) {
|
|
407
|
+
const dest = archiveScratch(instanceId);
|
|
408
|
+
if (dest) archived.push(dest);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return archived;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** List archive files sorted by mtime descending. */
|
|
415
|
+
export function listArchives(): {
|
|
416
|
+
path: string;
|
|
417
|
+
basename: string;
|
|
418
|
+
mtimeMs: number;
|
|
419
|
+
bytes: number;
|
|
420
|
+
}[] {
|
|
421
|
+
const dir = archiveDir();
|
|
422
|
+
if (!existsSync(dir)) return [];
|
|
423
|
+
return readdirSync(dir)
|
|
424
|
+
.filter((f) => f.endsWith(".md"))
|
|
425
|
+
.map((f) => {
|
|
426
|
+
const fp = resolve(dir, f);
|
|
427
|
+
const s = statSync(fp);
|
|
428
|
+
return { path: fp, basename: f, mtimeMs: s.mtimeMs, bytes: s.size };
|
|
429
|
+
})
|
|
430
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
434
|
+
|
|
435
|
+
export function currentOwnerOrThrow(): string {
|
|
436
|
+
const owner = resolveOwner();
|
|
437
|
+
if (!owner) {
|
|
438
|
+
throw new Error("Not in an agent session; ppid walk found no pid-map entry.");
|
|
439
|
+
}
|
|
440
|
+
return owner;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function loadScratch(instanceId: string): ScratchDoc | null {
|
|
444
|
+
const path = scratchPath(instanceId);
|
|
445
|
+
if (!existsSync(path)) return null;
|
|
446
|
+
return parseScratch(path, readFileSync(path, "utf8"));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Resolve an agent name → instance_id by walking active heartbeats (case-insensitive). */
|
|
450
|
+
export function resolveOwnerByName(name: string): string | null {
|
|
451
|
+
const root = monorepoRoot();
|
|
452
|
+
if (!root) return null;
|
|
453
|
+
const activeDir = resolve(root, ".harnery", "active");
|
|
454
|
+
if (!existsSync(activeDir)) return null;
|
|
455
|
+
for (const f of readdirSync(activeDir)) {
|
|
456
|
+
if (!f.endsWith(".json")) continue;
|
|
457
|
+
try {
|
|
458
|
+
const hb = JSON.parse(readFileSync(resolve(activeDir, f), "utf8")) as {
|
|
459
|
+
instance_id?: string;
|
|
460
|
+
name?: string;
|
|
461
|
+
};
|
|
462
|
+
if ((hb.name ?? "").toLowerCase() === name.toLowerCase()) {
|
|
463
|
+
return hb.instance_id ?? null;
|
|
464
|
+
}
|
|
465
|
+
} catch {
|
|
466
|
+
// skip
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Reverse-proxy worker spawned by `tunnel up`. Listens on 127.0.0.1:<port>,
|
|
2
|
+
// checks CF-Connecting-IP against an allowlist passed via env, and forwards
|
|
3
|
+
// HTTP + WebSocket requests to an upstream with a Host header rewrite and
|
|
4
|
+
// Content-Encoding stripped (Bun's fetch auto-decompresses the body but
|
|
5
|
+
// retains the encoding header, which breaks browser decoding downstream).
|
|
6
|
+
//
|
|
7
|
+
// Runs detached, outside the CLI command framework; no command context is
|
|
8
|
+
// available; stdout/stderr is captured into .cache/tunnel/gate.log by the
|
|
9
|
+
// spawner.
|
|
10
|
+
|
|
11
|
+
// `--port`/`--name` are also passed on argv (not just env) so the gate's port
|
|
12
|
+
// and instance name show up in its process command line. That's what lets
|
|
13
|
+
// `tunnel down` scope its stray-process sweep to a single instance via
|
|
14
|
+
// `pgrep -f`: every instance runs the same `bun run gate.ts`, so the port in
|
|
15
|
+
// the command line is the only thing that distinguishes them. Env stays the
|
|
16
|
+
// source of truth; argv is read only as a fallback/marker.
|
|
17
|
+
function argvFlag(flag: string): string | undefined {
|
|
18
|
+
const i = process.argv.indexOf(flag);
|
|
19
|
+
return i !== -1 ? process.argv[i + 1] : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ALLOW = new Set(
|
|
23
|
+
(process.env.HARNERY_TUNNEL_ALLOW ?? "")
|
|
24
|
+
.split(",")
|
|
25
|
+
.map((s) => s.trim())
|
|
26
|
+
.filter(Boolean),
|
|
27
|
+
);
|
|
28
|
+
const TARGET = process.env.HARNERY_TUNNEL_TARGET ?? "127.0.0.1:8001";
|
|
29
|
+
const VHOST = process.env.HARNERY_TUNNEL_VHOST ?? "localhost";
|
|
30
|
+
const PORT = Number(process.env.HARNERY_TUNNEL_PORT ?? argvFlag("--port") ?? "9001");
|
|
31
|
+
|
|
32
|
+
const UPSTREAM_HTTP = `http://${TARGET}`;
|
|
33
|
+
const UPSTREAM_WS = `ws://${TARGET}`;
|
|
34
|
+
|
|
35
|
+
interface WsData {
|
|
36
|
+
path: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const server = Bun.serve<WsData, never>({
|
|
40
|
+
port: PORT,
|
|
41
|
+
hostname: "127.0.0.1",
|
|
42
|
+
// Bun.serve defaults idleTimeout to 10s. LLM SSE streams commonly take
|
|
43
|
+
// longer than that between events (first-token latency + tool-call
|
|
44
|
+
// pauses), so 10s would kill in-flight chats over the tunnel. 255s is
|
|
45
|
+
// Bun's max, and beyond any realistic single-turn delay.
|
|
46
|
+
idleTimeout: 255,
|
|
47
|
+
async fetch(req, server) {
|
|
48
|
+
const ip = req.headers.get("cf-connecting-ip") ?? "";
|
|
49
|
+
if (!ALLOW.has(ip)) {
|
|
50
|
+
return new Response("403 Forbidden\n", { status: 403 });
|
|
51
|
+
}
|
|
52
|
+
const url = new URL(req.url);
|
|
53
|
+
|
|
54
|
+
if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
|
|
55
|
+
if (server.upgrade(req, { data: { path: url.pathname + url.search } })) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return new Response("WS upgrade failed\n", { status: 500 });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const headers = new Headers(req.headers);
|
|
62
|
+
headers.set("host", VHOST);
|
|
63
|
+
headers.set("accept-encoding", "identity");
|
|
64
|
+
headers.delete("connection");
|
|
65
|
+
|
|
66
|
+
const init: RequestInit = {
|
|
67
|
+
method: req.method,
|
|
68
|
+
headers,
|
|
69
|
+
redirect: "manual",
|
|
70
|
+
};
|
|
71
|
+
if (!["GET", "HEAD"].includes(req.method)) {
|
|
72
|
+
init.body = await req.arrayBuffer();
|
|
73
|
+
}
|
|
74
|
+
const resp = await fetch(UPSTREAM_HTTP + url.pathname + url.search, init);
|
|
75
|
+
const respHeaders = new Headers(resp.headers);
|
|
76
|
+
respHeaders.delete("content-encoding");
|
|
77
|
+
respHeaders.delete("content-length");
|
|
78
|
+
return new Response(resp.body, {
|
|
79
|
+
status: resp.status,
|
|
80
|
+
statusText: resp.statusText,
|
|
81
|
+
headers: respHeaders,
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
websocket: {
|
|
85
|
+
open(ws) {
|
|
86
|
+
const { path } = ws.data;
|
|
87
|
+
// Bun's WebSocket constructor accepts a `headers` option at runtime
|
|
88
|
+
// even though the standard lib.dom.d.ts WebSocket type doesn't.
|
|
89
|
+
const upstream = new WebSocket(UPSTREAM_WS + path, {
|
|
90
|
+
headers: { host: VHOST },
|
|
91
|
+
} as unknown as undefined);
|
|
92
|
+
upstream.binaryType = "arraybuffer";
|
|
93
|
+
(ws as unknown as { upstream?: WebSocket }).upstream = upstream;
|
|
94
|
+
upstream.onmessage = (ev) => ws.send(ev.data);
|
|
95
|
+
upstream.onerror = () => ws.close();
|
|
96
|
+
upstream.onclose = () => ws.close();
|
|
97
|
+
},
|
|
98
|
+
message(ws, msg) {
|
|
99
|
+
const u = (ws as unknown as { upstream?: WebSocket }).upstream;
|
|
100
|
+
if (u && u.readyState === 1) u.send(msg);
|
|
101
|
+
},
|
|
102
|
+
close(ws) {
|
|
103
|
+
const u = (ws as unknown as { upstream?: WebSocket }).upstream;
|
|
104
|
+
u?.close();
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// This worker runs detached via `bun run gate.ts`, outside the CLI command
|
|
110
|
+
// framework, so no AsyncLocalStorage context is available. stdout/stderr is
|
|
111
|
+
// captured into .cache/tunnel/gate.log by the spawning command.
|
|
112
|
+
console.log(`bp-tunnel-gate :${server.port} -> ${UPSTREAM_HTTP} (Host: ${VHOST})`); // lint-ok-emission: detached worker, see file note above
|
|
113
|
+
console.log(`allow: ${[...ALLOW].join(", ") || "(empty, denies all)"}`); // lint-ok-emission: detached worker, see file note above
|