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,674 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Council manifest helpers: file-based multi-agent coordination primitives.
|
|
3
|
+
*
|
|
4
|
+
* Lives under .harnery/councils/ alongside heartbeats + scratchpads. Council
|
|
5
|
+
* lifecycle commands serialize manifest mutations through a shared flock;
|
|
6
|
+
* round contribution files are per-member and don't need shared locking.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
9
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
10
|
+
import { basename, resolve } from "node:path";
|
|
11
|
+
import { monorepoRoot } from "../../core/agents/index.js";
|
|
12
|
+
import { resolveBinName } from "../../core/config.js";
|
|
13
|
+
import { ensureIdentity, lookupById as lookupIdentityById } from "../identities/index.js";
|
|
14
|
+
export const COUNCIL_SCHEMA_VERSION = 2;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the effective steward: explicit `steward` field if set, otherwise
|
|
17
|
+
* fall back to `created_by`. Always returns a normalized `agent-Foo` name.
|
|
18
|
+
*/
|
|
19
|
+
export function effectiveSteward(manifest) {
|
|
20
|
+
return normalizeAgentName(manifest.steward || manifest.created_by);
|
|
21
|
+
}
|
|
22
|
+
/** Resolve `.harnery/councils/` (creates the dir lazily on first write). */
|
|
23
|
+
export function councilsDir() {
|
|
24
|
+
const root = monorepoRoot();
|
|
25
|
+
if (!root)
|
|
26
|
+
return null;
|
|
27
|
+
return resolve(root, ".harnery", "councils");
|
|
28
|
+
}
|
|
29
|
+
/** Resolve `.harnery/councils/archive/`. */
|
|
30
|
+
export function councilsArchiveDir() {
|
|
31
|
+
const cd = councilsDir();
|
|
32
|
+
if (!cd)
|
|
33
|
+
return null;
|
|
34
|
+
return resolve(cd, "archive");
|
|
35
|
+
}
|
|
36
|
+
/** Manifest file path: `.harnery/councils/<id>.json`. */
|
|
37
|
+
export function manifestPath(councilId) {
|
|
38
|
+
const cd = councilsDir();
|
|
39
|
+
if (!cd)
|
|
40
|
+
return null;
|
|
41
|
+
return resolve(cd, `${councilId}.json`);
|
|
42
|
+
}
|
|
43
|
+
/** Council body dir: `.harnery/councils/<id>/` (holds invite.md + round-N/...). */
|
|
44
|
+
export function councilBodyDir(councilId) {
|
|
45
|
+
const cd = councilsDir();
|
|
46
|
+
if (!cd)
|
|
47
|
+
return null;
|
|
48
|
+
return resolve(cd, councilId);
|
|
49
|
+
}
|
|
50
|
+
/** Normalize an `agent-Foo`/`Foo` reference to canonical `agent-Foo` form. */
|
|
51
|
+
export function normalizeAgentName(raw) {
|
|
52
|
+
const trimmed = raw.trim();
|
|
53
|
+
if (!trimmed)
|
|
54
|
+
return "";
|
|
55
|
+
return trimmed.startsWith("agent-") ? trimmed : `agent-${trimmed}`;
|
|
56
|
+
}
|
|
57
|
+
/** Strip the `agent-` prefix for output to lookups that expect bare name. */
|
|
58
|
+
export function bareAgentName(raw) {
|
|
59
|
+
return raw.startsWith("agent-") ? raw.slice("agent-".length) : raw;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Derive a kebab-case slug from objective text. Keeps the first 5 words after
|
|
63
|
+
* lowercasing and stripping non-alphanumeric chars.
|
|
64
|
+
*/
|
|
65
|
+
export function deriveSlug(objective) {
|
|
66
|
+
const cleaned = objective
|
|
67
|
+
.toLowerCase()
|
|
68
|
+
.replace(/[^a-z0-9\s-]+/g, " ")
|
|
69
|
+
.split(/\s+/)
|
|
70
|
+
.filter(Boolean)
|
|
71
|
+
.slice(0, 5)
|
|
72
|
+
.join("-")
|
|
73
|
+
.replace(/-+/g, "-")
|
|
74
|
+
.replace(/^-|-$/g, "");
|
|
75
|
+
return cleaned || "council";
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build a council_id from objective + today's UTC date.
|
|
79
|
+
*
|
|
80
|
+
* Format: `<slug>-<YYYY-MM-DD>-<4hex>`. The 4-hex suffix is sourced from a
|
|
81
|
+
* crypto-strong random byte (not from a hash of the objective) to avoid
|
|
82
|
+
* collisions when two councils share the same slug + date.
|
|
83
|
+
*/
|
|
84
|
+
export function buildCouncilId(objective, now = new Date()) {
|
|
85
|
+
const slug = deriveSlug(objective);
|
|
86
|
+
const date = now.toISOString().slice(0, 10);
|
|
87
|
+
const hash = randomBytes(2).toString("hex");
|
|
88
|
+
return `${slug}-${date}-${hash}`;
|
|
89
|
+
}
|
|
90
|
+
/** Hash an objective deterministically, used by tests for stable IDs. */
|
|
91
|
+
export function deterministicCouncilId(objective, now = new Date()) {
|
|
92
|
+
const slug = deriveSlug(objective);
|
|
93
|
+
const date = now.toISOString().slice(0, 10);
|
|
94
|
+
const hash = createHash("sha256").update(`${objective}|${date}`).digest("hex").slice(0, 4);
|
|
95
|
+
return `${slug}-${date}-${hash}`;
|
|
96
|
+
}
|
|
97
|
+
/** Atomically write a manifest (write tmp → rename). */
|
|
98
|
+
export function writeManifest(manifest) {
|
|
99
|
+
const mp = manifestPath(manifest.council_id);
|
|
100
|
+
if (!mp)
|
|
101
|
+
throw new Error("not in an agent session; no monorepo root");
|
|
102
|
+
const cd = councilsDir();
|
|
103
|
+
if (cd && !existsSync(cd))
|
|
104
|
+
mkdirSync(cd, { recursive: true });
|
|
105
|
+
const tmp = `${mp}.tmp.${process.pid}`;
|
|
106
|
+
writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
107
|
+
renameSync(tmp, mp);
|
|
108
|
+
}
|
|
109
|
+
/** Read a manifest by id. Returns null when the file is missing. */
|
|
110
|
+
export function readManifest(councilId) {
|
|
111
|
+
const mp = manifestPath(councilId);
|
|
112
|
+
if (!mp || !existsSync(mp))
|
|
113
|
+
return null;
|
|
114
|
+
try {
|
|
115
|
+
const parsed = JSON.parse(readFileSync(mp, "utf8"));
|
|
116
|
+
if (parsed.schema_version !== COUNCIL_SCHEMA_VERSION) {
|
|
117
|
+
throw new Error(`council ${councilId}: unsupported schema_version=${parsed.schema_version} (expected ${COUNCIL_SCHEMA_VERSION})`);
|
|
118
|
+
}
|
|
119
|
+
return parsed;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
throw new Error(`failed to read council manifest ${councilId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Read an archived manifest by id from `.harnery/councils/archive/<id>.json`.
|
|
126
|
+
* Symmetric to readManifest() but scoped to the archive dir, used by
|
|
127
|
+
* `agents council unarchive` to load an archived council that is
|
|
128
|
+
* (by definition) not in the active councils dir. */
|
|
129
|
+
export function readArchivedManifest(councilId) {
|
|
130
|
+
const archive = councilsArchiveDir();
|
|
131
|
+
if (!archive)
|
|
132
|
+
return null;
|
|
133
|
+
const mp = resolve(archive, `${councilId}.json`);
|
|
134
|
+
if (!existsSync(mp))
|
|
135
|
+
return null;
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(readFileSync(mp, "utf8"));
|
|
138
|
+
if (parsed.schema_version !== COUNCIL_SCHEMA_VERSION) {
|
|
139
|
+
throw new Error(`council ${councilId}: unsupported schema_version=${parsed.schema_version} (expected ${COUNCIL_SCHEMA_VERSION})`);
|
|
140
|
+
}
|
|
141
|
+
return parsed;
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
throw new Error(`failed to read archived council manifest ${councilId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Reassign the steward on an active or closed council. Atomic via
|
|
149
|
+
* writeManifest's tmp+rename. Refuses to mutate archived councils
|
|
150
|
+
* (those are read-only by convention). Pass `null` to clear the field
|
|
151
|
+
* and revert to the default (the convener, via effectiveSteward).
|
|
152
|
+
*/
|
|
153
|
+
export function setCouncilSteward(councilId, steward) {
|
|
154
|
+
const manifest = readManifest(councilId);
|
|
155
|
+
if (!manifest) {
|
|
156
|
+
throw new Error(`no council matching '${councilId}' in .harnery/councils/`);
|
|
157
|
+
}
|
|
158
|
+
if (manifest.status === "archived") {
|
|
159
|
+
throw new Error(`council ${manifest.council_id} is archived (read-only); cannot reassign steward`);
|
|
160
|
+
}
|
|
161
|
+
let next;
|
|
162
|
+
if (steward === null) {
|
|
163
|
+
const { steward: _ds, steward_id: _di, ...rest } = manifest;
|
|
164
|
+
void _ds;
|
|
165
|
+
void _di;
|
|
166
|
+
next = rest;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const identity = ensureIdentity(steward);
|
|
170
|
+
next = { ...manifest, steward, steward_id: identity.agent_id };
|
|
171
|
+
}
|
|
172
|
+
writeManifest(next);
|
|
173
|
+
return next;
|
|
174
|
+
}
|
|
175
|
+
/** Default lookback for "recently stale" agents, kept in sync with the
|
|
176
|
+
* next-app's same-named constant. */
|
|
177
|
+
const KNOWN_AGENT_STALE_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;
|
|
178
|
+
/**
|
|
179
|
+
* Active heartbeats + recently-archived scratchpads, deduped by name.
|
|
180
|
+
* Used by `agents council set-steward` to refuse arbitrary names;
|
|
181
|
+
* pass `--allow-unknown` to bypass when bootstrapping a new agent.
|
|
182
|
+
*/
|
|
183
|
+
export function listKnownAgents() {
|
|
184
|
+
const root = monorepoRoot();
|
|
185
|
+
if (!root)
|
|
186
|
+
return [];
|
|
187
|
+
const activeDir = resolve(root, ".harnery", "active");
|
|
188
|
+
const archiveDir = resolve(root, ".harnery", "scratch", "archived");
|
|
189
|
+
const byName = new Map();
|
|
190
|
+
if (existsSync(activeDir)) {
|
|
191
|
+
for (const f of readdirSync(activeDir)) {
|
|
192
|
+
if (!f.endsWith(".json"))
|
|
193
|
+
continue;
|
|
194
|
+
try {
|
|
195
|
+
const hb = JSON.parse(readFileSync(resolve(activeDir, f), "utf8"));
|
|
196
|
+
if (!hb.name)
|
|
197
|
+
continue;
|
|
198
|
+
const name = hb.name.startsWith("agent-") ? hb.name : `agent-${hb.name}`;
|
|
199
|
+
const last_seen = hb.last_heartbeat ?? new Date().toISOString();
|
|
200
|
+
const existing = byName.get(name);
|
|
201
|
+
if (existing?.state !== "active") {
|
|
202
|
+
byName.set(name, { name, state: "active", last_seen });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
/* skip unreadable */
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const cutoff = Date.now() - KNOWN_AGENT_STALE_WINDOW_MS;
|
|
211
|
+
if (existsSync(archiveDir)) {
|
|
212
|
+
const fileTimestampRe = /-(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z\.md$/;
|
|
213
|
+
for (const f of readdirSync(archiveDir)) {
|
|
214
|
+
const m = f.match(fileTimestampRe);
|
|
215
|
+
if (!m)
|
|
216
|
+
continue;
|
|
217
|
+
const iso = `${m[1]}T${m[2]}:${m[3]}:${m[4]}.${m[5]}Z`;
|
|
218
|
+
const ts = Date.parse(iso);
|
|
219
|
+
if (Number.isNaN(ts) || ts < cutoff)
|
|
220
|
+
continue;
|
|
221
|
+
try {
|
|
222
|
+
const head = readFileSync(resolve(archiveDir, f), "utf8").slice(0, 200);
|
|
223
|
+
const nameMatch = head.match(/^#\s+Scratchpad:\s+(agent-[A-Za-z][A-Za-z0-9_-]*)/m);
|
|
224
|
+
if (!nameMatch)
|
|
225
|
+
continue;
|
|
226
|
+
const name = nameMatch[1];
|
|
227
|
+
const existing = byName.get(name);
|
|
228
|
+
if (existing) {
|
|
229
|
+
if (existing.state === "stale" && iso > existing.last_seen) {
|
|
230
|
+
existing.last_seen = iso;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
byName.set(name, { name, state: "stale", last_seen: iso });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
/* skip */
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return Array.from(byName.values()).sort((a, b) => {
|
|
243
|
+
if (a.state !== b.state)
|
|
244
|
+
return a.state === "active" ? -1 : 1;
|
|
245
|
+
return b.last_seen.localeCompare(a.last_seen);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
/** List all council manifests in the active dir (skips archive/). */
|
|
249
|
+
export function listManifests() {
|
|
250
|
+
const cd = councilsDir();
|
|
251
|
+
if (!cd || !existsSync(cd))
|
|
252
|
+
return [];
|
|
253
|
+
const out = [];
|
|
254
|
+
for (const f of readdirSync(cd)) {
|
|
255
|
+
if (!f.endsWith(".json") || f === "archive")
|
|
256
|
+
continue;
|
|
257
|
+
const id = f.slice(0, -5);
|
|
258
|
+
try {
|
|
259
|
+
const m = readManifest(id);
|
|
260
|
+
if (m)
|
|
261
|
+
out.push(m);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
/* skip malformed manifests; surfaced separately if needed */
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Move a council's manifest + body dir into the archive subdir. Idempotent:
|
|
271
|
+
* archiving an already-archived council is a no-op (the source paths won't
|
|
272
|
+
* exist). Used by `council archive` and by `council close --archive`.
|
|
273
|
+
*/
|
|
274
|
+
export function moveToArchive(councilId) {
|
|
275
|
+
const cd = councilsDir();
|
|
276
|
+
const archive = councilsArchiveDir();
|
|
277
|
+
if (!cd || !archive) {
|
|
278
|
+
throw new Error("not in an agent session; no monorepo root");
|
|
279
|
+
}
|
|
280
|
+
if (!existsSync(archive))
|
|
281
|
+
mkdirSync(archive, { recursive: true });
|
|
282
|
+
const srcManifest = resolve(cd, `${councilId}.json`);
|
|
283
|
+
const dstManifest = resolve(archive, `${councilId}.json`);
|
|
284
|
+
if (existsSync(srcManifest)) {
|
|
285
|
+
renameSync(srcManifest, dstManifest);
|
|
286
|
+
}
|
|
287
|
+
const srcDir = resolve(cd, councilId);
|
|
288
|
+
const dstDir = resolve(archive, councilId);
|
|
289
|
+
if (existsSync(srcDir)) {
|
|
290
|
+
if (existsSync(dstDir)) {
|
|
291
|
+
// already archived, keep the original archive, drop the duplicate
|
|
292
|
+
rmSync(srcDir, { recursive: true, force: true });
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
renameSync(srcDir, dstDir);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Reverse of moveToArchive: move a council's manifest + body dir back from
|
|
301
|
+
* archive/ to the active councils dir. Idempotent: a missing archive path is
|
|
302
|
+
* a no-op; an existing active path is left untouched and the archived copy
|
|
303
|
+
* is dropped (mirrors moveToArchive's clobber-avoidance rule). Used by
|
|
304
|
+
* `agents council unarchive` for testing the archive flow and as an undo
|
|
305
|
+
* escape hatch.
|
|
306
|
+
*/
|
|
307
|
+
export function moveFromArchive(councilId) {
|
|
308
|
+
const cd = councilsDir();
|
|
309
|
+
const archive = councilsArchiveDir();
|
|
310
|
+
if (!cd || !archive) {
|
|
311
|
+
throw new Error("not in an agent session; no monorepo root");
|
|
312
|
+
}
|
|
313
|
+
const srcManifest = resolve(archive, `${councilId}.json`);
|
|
314
|
+
const dstManifest = resolve(cd, `${councilId}.json`);
|
|
315
|
+
if (existsSync(srcManifest)) {
|
|
316
|
+
if (existsSync(dstManifest)) {
|
|
317
|
+
// already-active manifest wins; drop the archived copy
|
|
318
|
+
rmSync(srcManifest, { force: true });
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
renameSync(srcManifest, dstManifest);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const srcDir = resolve(archive, councilId);
|
|
325
|
+
const dstDir = resolve(cd, councilId);
|
|
326
|
+
if (existsSync(srcDir)) {
|
|
327
|
+
if (existsSync(dstDir)) {
|
|
328
|
+
rmSync(srcDir, { recursive: true, force: true });
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
renameSync(srcDir, dstDir);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Permanently remove an archived council: manifest + body dir under
|
|
337
|
+
* .harnery/councils/archive/<id>. Refuses to touch a council that's still
|
|
338
|
+
* in the active dir (caller must archive first; the trash-can pattern).
|
|
339
|
+
* Idempotent: missing paths are a no-op. Returns true when something was
|
|
340
|
+
* actually deleted, false when both targets were already absent.
|
|
341
|
+
*
|
|
342
|
+
* NB: does NOT touch the council's target_doc (separate authored artifact),
|
|
343
|
+
* close_handoff_path (separate authored artifact), or the canonical event
|
|
344
|
+
* stream (immutable activity log). The delete is scoped to the manifest +
|
|
345
|
+
* per-round member contributions.
|
|
346
|
+
*/
|
|
347
|
+
export function deleteArchivedCouncil(councilId) {
|
|
348
|
+
const cd = councilsDir();
|
|
349
|
+
const archive = councilsArchiveDir();
|
|
350
|
+
if (!cd || !archive) {
|
|
351
|
+
throw new Error("not in an agent session; no monorepo root");
|
|
352
|
+
}
|
|
353
|
+
const activeManifest = resolve(cd, `${councilId}.json`);
|
|
354
|
+
if (existsSync(activeManifest)) {
|
|
355
|
+
throw new Error(`council ${councilId} is not archived; archive it before delete`);
|
|
356
|
+
}
|
|
357
|
+
const archivedManifest = resolve(archive, `${councilId}.json`);
|
|
358
|
+
const archivedBody = resolve(archive, councilId);
|
|
359
|
+
let removed = false;
|
|
360
|
+
if (existsSync(archivedManifest)) {
|
|
361
|
+
rmSync(archivedManifest, { force: true });
|
|
362
|
+
removed = true;
|
|
363
|
+
}
|
|
364
|
+
if (existsSync(archivedBody)) {
|
|
365
|
+
rmSync(archivedBody, { recursive: true, force: true });
|
|
366
|
+
removed = true;
|
|
367
|
+
}
|
|
368
|
+
return removed;
|
|
369
|
+
}
|
|
370
|
+
/** Resolve a member name (or partial id) to a council manifest from the active dir. */
|
|
371
|
+
export function findManifestByPartialId(partial) {
|
|
372
|
+
const cd = councilsDir();
|
|
373
|
+
if (!cd || !existsSync(cd))
|
|
374
|
+
return null;
|
|
375
|
+
for (const f of readdirSync(cd)) {
|
|
376
|
+
if (!f.endsWith(".json"))
|
|
377
|
+
continue;
|
|
378
|
+
const id = f.slice(0, -5);
|
|
379
|
+
if (id === partial || id.includes(partial) || basename(f, ".json").startsWith(partial)) {
|
|
380
|
+
try {
|
|
381
|
+
return readManifest(id);
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
/** Build the invite.md body that ships alongside the manifest. */
|
|
391
|
+
export function buildInviteMarkdown(manifest) {
|
|
392
|
+
const lines = [];
|
|
393
|
+
lines.push(`# Council invitation: ${manifest.council_id}`);
|
|
394
|
+
lines.push("");
|
|
395
|
+
lines.push(`**Convened by:** ${manifest.created_by}`);
|
|
396
|
+
lines.push(`**Created:** ${manifest.created_at}`);
|
|
397
|
+
lines.push(`**Members:** ${manifest.members.join(", ")}`);
|
|
398
|
+
if (manifest.target_doc) {
|
|
399
|
+
lines.push(`**Target doc:** \`${manifest.target_doc}\``);
|
|
400
|
+
}
|
|
401
|
+
lines.push(`**Auto-advance:** ${manifest.auto_advance ? "yes" : "no (convener advances each round manually)"}`);
|
|
402
|
+
lines.push(`**Round visibility:** ${manifest.round_visibility} (peer contributions surface at round N+1 by default)`);
|
|
403
|
+
lines.push("");
|
|
404
|
+
lines.push("## Objective");
|
|
405
|
+
lines.push("");
|
|
406
|
+
lines.push(manifest.objective);
|
|
407
|
+
lines.push("");
|
|
408
|
+
lines.push("## How to participate");
|
|
409
|
+
lines.push("");
|
|
410
|
+
const bin = resolveBinName();
|
|
411
|
+
lines.push(`1. Read the objective + target doc (if any).\n2. Run \`${bin} agents council show ${manifest.council_id}\` for full state.\n3. Contribute your round-${manifest.current_round} take with:\n\n ${bin} agents council contribute ${manifest.council_id} \\\n --message "<your take>"\n # or --file /path/to/written/feedback.md\n\n4. After all members contribute, ${manifest.created_by} (or auto-advance) opens round ${manifest.current_round + 1}.`);
|
|
412
|
+
lines.push("");
|
|
413
|
+
return lines.join("\n");
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Path to the contribution file for a member in a specific round.
|
|
417
|
+
* Filename uses the agent's durable persona uuid (`<agent_id>.md`) so
|
|
418
|
+
* a future rename doesn't break the link between manifest and on-disk
|
|
419
|
+
* contribution. Resolves the name through the identity registry; mints a
|
|
420
|
+
* new identity when the member name isn't registered yet (rare post-
|
|
421
|
+
* migration; only happens for a brand-new persona that's never run).
|
|
422
|
+
*/
|
|
423
|
+
export function contributionPath(councilId, round, memberName) {
|
|
424
|
+
const body = councilBodyDir(councilId);
|
|
425
|
+
if (!body)
|
|
426
|
+
return null;
|
|
427
|
+
const id = ensureIdentity(memberName).agent_id;
|
|
428
|
+
return resolve(body, `round-${round}`, `${id}.md`);
|
|
429
|
+
}
|
|
430
|
+
/** Path to a round's directory: `.harnery/councils/<id>/round-<N>/`. */
|
|
431
|
+
export function roundDir(councilId, round) {
|
|
432
|
+
const body = councilBodyDir(councilId);
|
|
433
|
+
if (!body)
|
|
434
|
+
return null;
|
|
435
|
+
return resolve(body, `round-${round}`);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Read the set of agent-Names that have contributed to a given round.
|
|
439
|
+
* Returns display names ("agent-Maya"), not raw uuids; filenames on disk
|
|
440
|
+
* are now `<agent_id>.md`, so we resolve each one through the registry
|
|
441
|
+
* before returning. An identity that's been pruned (or never registered)
|
|
442
|
+
* surfaces as `agent-<8-char-prefix>` so the value remains scannable.
|
|
443
|
+
*
|
|
444
|
+
* For UUID-keyed callers, use `contributorIdsInRound`.
|
|
445
|
+
* Empty array when the round directory doesn't exist yet.
|
|
446
|
+
*/
|
|
447
|
+
export function contributorsInRound(councilId, round) {
|
|
448
|
+
return contributorIdsInRound(councilId, round)
|
|
449
|
+
.map((id) => {
|
|
450
|
+
const identity = lookupIdentityById(id);
|
|
451
|
+
return identity ? `agent-${identity.name}` : `agent-${id.slice(0, 8)}`;
|
|
452
|
+
})
|
|
453
|
+
.sort();
|
|
454
|
+
}
|
|
455
|
+
/** Like contributorsInRound but returns the raw agent_id uuids: the
|
|
456
|
+
* filenames on disk without the .md extension. */
|
|
457
|
+
export function contributorIdsInRound(councilId, round) {
|
|
458
|
+
const rd = roundDir(councilId, round);
|
|
459
|
+
if (!rd || !existsSync(rd))
|
|
460
|
+
return [];
|
|
461
|
+
return readdirSync(rd)
|
|
462
|
+
.filter((f) => f.endsWith(".md"))
|
|
463
|
+
.map((f) => f.slice(0, -3));
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Return the IDs of active councils where this agent is a member AND has not
|
|
467
|
+
* yet contributed to the current open round. Used by `agents status` to
|
|
468
|
+
* surface a `council N pending` line in the status box, and by SessionStart
|
|
469
|
+
* adapters to inject system reminders about pending invites.
|
|
470
|
+
*/
|
|
471
|
+
export function pendingCouncilsForMember(memberName) {
|
|
472
|
+
const normalized = normalizeAgentName(memberName);
|
|
473
|
+
if (!normalized)
|
|
474
|
+
return [];
|
|
475
|
+
const out = [];
|
|
476
|
+
for (const m of listManifests()) {
|
|
477
|
+
if (m.status !== "active")
|
|
478
|
+
continue;
|
|
479
|
+
if (!m.members.includes(normalized))
|
|
480
|
+
continue;
|
|
481
|
+
if (m.round_status === "collected")
|
|
482
|
+
continue;
|
|
483
|
+
const contributors = contributorsInRound(m.council_id, m.current_round);
|
|
484
|
+
if (contributors.includes(normalized))
|
|
485
|
+
continue;
|
|
486
|
+
out.push(m.council_id);
|
|
487
|
+
}
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
/** Write a contribution file (atomic). Creates the round directory lazily. */
|
|
491
|
+
export function writeContribution(councilId, round, memberName, body) {
|
|
492
|
+
const filePath = contributionPath(councilId, round, memberName);
|
|
493
|
+
if (!filePath)
|
|
494
|
+
throw new Error("not in an agent session; no monorepo root");
|
|
495
|
+
const rd = roundDir(councilId, round);
|
|
496
|
+
if (rd && !existsSync(rd))
|
|
497
|
+
mkdirSync(rd, { recursive: true });
|
|
498
|
+
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
499
|
+
writeFileSync(tmp, body, "utf8");
|
|
500
|
+
renameSync(tmp, filePath);
|
|
501
|
+
return filePath;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Path to the round-N prompts directory: `.harnery/councils/<id>/round-N/prompts/`.
|
|
505
|
+
* Sibling to the contribution files. Holds one `<member>.md` per non-self
|
|
506
|
+
* council member, drafted by the steward, read by the operator (copy-paste
|
|
507
|
+
* into each agent harness) and the web UI (per-member panel).
|
|
508
|
+
*/
|
|
509
|
+
export function promptsDir(councilId, round) {
|
|
510
|
+
const rd = roundDir(councilId, round);
|
|
511
|
+
if (!rd)
|
|
512
|
+
return null;
|
|
513
|
+
return resolve(rd, "prompts");
|
|
514
|
+
}
|
|
515
|
+
/** Path to a single member's prompt file in a given round. Like
|
|
516
|
+
* contributionPath, filename uses the agent's durable persona uuid. */
|
|
517
|
+
export function promptPath(councilId, round, memberName) {
|
|
518
|
+
const pd = promptsDir(councilId, round);
|
|
519
|
+
if (!pd)
|
|
520
|
+
return null;
|
|
521
|
+
const id = ensureIdentity(memberName).agent_id;
|
|
522
|
+
return resolve(pd, `${id}.md`);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Build the routing header prepended to every steward-drafted prompt. The
|
|
526
|
+
* contributor skill scans inbound messages for this comment block; if the
|
|
527
|
+
* `member:` line does not match the receiving agent's whoami, the agent
|
|
528
|
+
* refuses to contribute (catches operator misrouting). HTML-comment so it
|
|
529
|
+
* renders invisibly in markdown previews.
|
|
530
|
+
*/
|
|
531
|
+
export function buildRouteHeader(councilId, round, memberName) {
|
|
532
|
+
const m = normalizeAgentName(memberName);
|
|
533
|
+
return [
|
|
534
|
+
"<!-- council-route",
|
|
535
|
+
`council-id: ${councilId}`,
|
|
536
|
+
`council-round: ${round}`,
|
|
537
|
+
`member: ${m}`,
|
|
538
|
+
"-->",
|
|
539
|
+
"",
|
|
540
|
+
].join("\n");
|
|
541
|
+
}
|
|
542
|
+
/** Strip a leading route header from a prompt body, if present. */
|
|
543
|
+
export function stripRouteHeader(body) {
|
|
544
|
+
return body.replace(/^<!--\s*council-route[\s\S]*?-->\n?/, "");
|
|
545
|
+
}
|
|
546
|
+
/** Sentinel marking the start of the auto-appended submit footer. */
|
|
547
|
+
const SUBMIT_FOOTER_MARKER = "<!-- council-submit-footer -->";
|
|
548
|
+
/**
|
|
549
|
+
* Build the submit footer appended to every steward-drafted prompt. This is the
|
|
550
|
+
* load-bearing instruction that a contribution composed in chat is NOT recorded;
|
|
551
|
+
* the agent must run the command below. It rides on the prompt (the one
|
|
552
|
+
* artifact the operator always pastes) so it reaches every harness regardless of
|
|
553
|
+
* whether the convene-time invitation was delivered or a `/council` skill is
|
|
554
|
+
* available. Without it, agents (esp. non-Claude harnesses with no skill) write
|
|
555
|
+
* their take as a reply and end the turn, leaving the council showing them as
|
|
556
|
+
* still-pending. Visible markdown (not an HTML comment) so the agent reads it.
|
|
557
|
+
*/
|
|
558
|
+
export function buildSubmitFooter(councilId) {
|
|
559
|
+
const bin = resolveBinName();
|
|
560
|
+
return [
|
|
561
|
+
SUBMIT_FOOTER_MARKER,
|
|
562
|
+
"---",
|
|
563
|
+
"**⚠ To record your contribution you MUST run the command below; a reply in chat is NOT counted:**",
|
|
564
|
+
"",
|
|
565
|
+
"```bash",
|
|
566
|
+
`${bin} agents council contribute ${councilId} --message "<your take, end with the status tag>"`,
|
|
567
|
+
`# longer write-up? ${bin} agents council contribute ${councilId} --file <path>`,
|
|
568
|
+
"```",
|
|
569
|
+
"",
|
|
570
|
+
'_(Or invoke the `council` skill in your harness: `/council contribute` in Claude Code, `$council` / "use the council skill" in Codex/Cursor, for the same flow with routing guards.)_',
|
|
571
|
+
].join("\n");
|
|
572
|
+
}
|
|
573
|
+
/** Strip an appended submit footer from a prompt body, if present. */
|
|
574
|
+
export function stripSubmitFooter(body) {
|
|
575
|
+
return body.replace(new RegExp(`\\n*${SUBMIT_FOOTER_MARKER}[\\s\\S]*$`), "");
|
|
576
|
+
}
|
|
577
|
+
/** Parse a route header from a string (the inbound user message). Returns
|
|
578
|
+
* null when the comment is absent or malformed. Used by the /council
|
|
579
|
+
* contribute skill to detect operator misrouting before contributing. */
|
|
580
|
+
export function parseRouteHeader(text) {
|
|
581
|
+
const m = text.match(/<!--\s*council-route\s*([\s\S]*?)-->/);
|
|
582
|
+
if (!m)
|
|
583
|
+
return null;
|
|
584
|
+
const lines = m[1].split("\n");
|
|
585
|
+
const get = (key) => {
|
|
586
|
+
for (const line of lines) {
|
|
587
|
+
const mm = line.match(new RegExp(`^\\s*${key}:\\s*(.+)$`));
|
|
588
|
+
if (mm)
|
|
589
|
+
return mm[1].trim();
|
|
590
|
+
}
|
|
591
|
+
return null;
|
|
592
|
+
};
|
|
593
|
+
const councilId = get("council-id");
|
|
594
|
+
const roundStr = get("council-round");
|
|
595
|
+
const member = get("member");
|
|
596
|
+
if (!councilId || !roundStr || !member)
|
|
597
|
+
return null;
|
|
598
|
+
const round = Number.parseInt(roundStr, 10);
|
|
599
|
+
if (!Number.isFinite(round))
|
|
600
|
+
return null;
|
|
601
|
+
return { council_id: councilId, council_round: round, member };
|
|
602
|
+
}
|
|
603
|
+
/** Write a prompt file (atomic). Creates the prompts dir lazily. The body
|
|
604
|
+
* is automatically prepended with a route header (see `buildRouteHeader`) so
|
|
605
|
+
* the contributor skill can verify the operator routed the prompt to the
|
|
606
|
+
* right agent. */
|
|
607
|
+
export function writePrompt(councilId, round, memberName, body) {
|
|
608
|
+
const filePath = promptPath(councilId, round, memberName);
|
|
609
|
+
if (!filePath)
|
|
610
|
+
throw new Error("not in an agent session; no monorepo root");
|
|
611
|
+
const pd = promptsDir(councilId, round);
|
|
612
|
+
if (pd && !existsSync(pd))
|
|
613
|
+
mkdirSync(pd, { recursive: true });
|
|
614
|
+
// Strip any existing route header + submit footer from `body` first so
|
|
615
|
+
// re-writes don't stack them when a steward updates a prompt.
|
|
616
|
+
const header = buildRouteHeader(councilId, round, memberName);
|
|
617
|
+
const footer = buildSubmitFooter(councilId);
|
|
618
|
+
const cleaned = stripSubmitFooter(stripRouteHeader(body)).trimEnd();
|
|
619
|
+
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
620
|
+
writeFileSync(tmp, `${header}${cleaned}\n\n${footer}\n`, "utf8");
|
|
621
|
+
renameSync(tmp, filePath);
|
|
622
|
+
return filePath;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Read a member's prompt for a given round. Returns null when the file
|
|
626
|
+
* doesn't exist (steward hasn't drafted one yet). Includes a `completed`
|
|
627
|
+
* boolean so the UI can mark the prompt deactivated once the contribution
|
|
628
|
+
* has landed.
|
|
629
|
+
*/
|
|
630
|
+
export function readPrompt(councilId, round, memberName) {
|
|
631
|
+
const filePath = promptPath(councilId, round, memberName);
|
|
632
|
+
if (!filePath || !existsSync(filePath))
|
|
633
|
+
return null;
|
|
634
|
+
const body = readFileSync(filePath, "utf8");
|
|
635
|
+
const contributors = contributorsInRound(councilId, round);
|
|
636
|
+
const completed = contributors.includes(normalizeAgentName(memberName));
|
|
637
|
+
return { body, completed };
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Read every member's prompt for a round, in `manifest.members` order
|
|
641
|
+
* (which is the agreed round-robin sequence; alphabetical is wrong because
|
|
642
|
+
* stewards typically build councils with a deliberate first-to-last order).
|
|
643
|
+
*
|
|
644
|
+
* Each entry carries `order` (1-indexed position within manifest.members,
|
|
645
|
+
* skipping members whose prompts don't exist) + `state` (contributed /
|
|
646
|
+
* active / queued) so the UI can render the three-state pattern without
|
|
647
|
+
* duplicating the active-determination logic.
|
|
648
|
+
*/
|
|
649
|
+
export function readRoundPrompts(manifest, round) {
|
|
650
|
+
const contributors = contributorsInRound(manifest.council_id, round);
|
|
651
|
+
const out = [];
|
|
652
|
+
for (const member of manifest.members) {
|
|
653
|
+
const filePath = promptPath(manifest.council_id, round, member);
|
|
654
|
+
if (!filePath || !existsSync(filePath))
|
|
655
|
+
continue;
|
|
656
|
+
const body = readFileSync(filePath, "utf8");
|
|
657
|
+
const completed = contributors.includes(normalizeAgentName(member));
|
|
658
|
+
out.push({
|
|
659
|
+
member,
|
|
660
|
+
body,
|
|
661
|
+
completed,
|
|
662
|
+
order: out.length + 1,
|
|
663
|
+
state: completed ? "contributed" : "queued", // placeholder; promoted below
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
// Second pass: promote the first not-contributed entry to `active`.
|
|
667
|
+
for (const row of out) {
|
|
668
|
+
if (row.state !== "contributed") {
|
|
669
|
+
row.state = "active";
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return out;
|
|
674
|
+
}
|