@hybridlabor-api/aos 4.13.2 → 4.14.1
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/.agents/AGENTS.md +8 -0
- package/.agents/nodes.json +5 -2
- package/.claude/hooks/conventional-commits.mjs +14 -15
- package/.claude/hooks/env-file-protection.mjs +14 -15
- package/.claude/hooks/go-gate.mjs +152 -10
- package/.claude/hooks/go-token.mjs +55 -0
- package/.claude/hooks/memb-inject.mjs +75 -62
- package/.claude/hooks/trail-autostart.mjs +27 -0
- package/.claude/hooks/trail-relay.mjs +1 -0
- package/.claude/settings.json +22 -4
- package/.claude/workflows/startcycle-dispatch.mjs +11 -4
- package/.opencode/plugins/bdb-aos.js +98 -121
- package/.opencode/plugins/lib/trail-autostart.js +38 -0
- package/CLAUDE.md +1 -1
- package/README.de.md +6 -6
- package/README.md +6 -6
- package/README.pt.md +6 -6
- package/THIRD_PARTY_NOTICES.md +19 -3
- package/assets/header-v5.png +0 -0
- package/bin/aos-acp.mjs +211 -0
- package/bin/aos-doctor.mjs +1 -1
- package/bin/aos-uninstall.mjs +2 -2
- package/docs/master-session-acp.md +51 -0
- package/installer.js +314 -42
- package/mcps/mcsc/packages/mcp/server.js +6 -7
- package/package.json +4 -3
- package/scripts/validate-skills.mjs +76 -0
- package/skills/basic/bdbmediastorm/SKILL.md +1 -1
- package/skills/basic/godmode-shipping/SKILL.md +3 -0
- package/skills/basic/master-session/SKILL.md +89 -0
- package/skills/basic/startcycle/SKILL.md +1 -1
- package/skills/basic/startcycle-graph/SKILL.md +2 -2
- package/skills/basic/startcycle-graph-user/SKILL.md +1 -1
- package/skills/basic/teamwork-preview/SKILL.md +1 -1
- package/skills/bdbrainstorm/SKILL.md +7 -1
- package/skills/global_config/agentic-harness-patterns/SKILL.md +257 -0
- package/skills/global_config/agentic-harness-patterns/metadata.json +10 -0
- package/skills/global_config/agentic-harness-patterns/references/agent-orchestration-pattern.md +97 -0
- package/skills/global_config/agentic-harness-patterns/references/bootstrap-sequence-pattern.md +106 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/compress-pattern.md +78 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/isolate-pattern.md +82 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/select-pattern.md +86 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering-pattern.md +29 -0
- package/skills/global_config/agentic-harness-patterns/references/hook-lifecycle-pattern.md +111 -0
- package/skills/global_config/agentic-harness-patterns/references/memory-persistence-pattern.md +109 -0
- package/skills/global_config/agentic-harness-patterns/references/permission-gate-pattern.md +111 -0
- package/skills/global_config/agentic-harness-patterns/references/skill-runtime-pattern.md +104 -0
- package/skills/global_config/agentic-harness-patterns/references/task-decomposition-pattern.md +92 -0
- package/skills/global_config/agentic-harness-patterns/references/tool-registry-pattern.md +101 -0
- package/skills/global_config/agenttrail/SKILL.md +8 -0
- package/skills/global_config/agenttrail/bin/agenttrail.mjs +14 -0
- package/skills/global_config/agenttrail/bin/ensure.mjs +118 -0
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
- package/skills/global_config/bdb-visual-edit/SKILL.md +51 -0
- package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +59 -0
- package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +27 -0
- package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +123 -0
- package/skills/global_config/factory-collect/SKILL.md +74 -0
- package/skills/global_config/factory-human-digest/SKILL.md +92 -0
- package/skills/global_config/factory-lookback/SKILL.md +95 -0
- package/skills/global_config/factory-review-prs/SKILL.md +63 -0
- package/skills/global_config/git-pr-review/SKILL.md +3 -0
- package/skills/global_config/grilling/SKILL.md +2 -0
- package/skills/global_config/mcsc/SKILL.md +1 -1
- package/skills/global_config/plan-arbiter/SKILL.md +125 -0
- package/skills/global_config/plan-canvas/SKILL.md +62 -5
- package/skills/global_config/plan-canvas/scripts/lib/loopback-guard.js +19 -3
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +285 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/agent-trail.js +129 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/board-client.js +124 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/canvas.mdx +19 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/plan.mdx +18 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/recap-demo/plan.mdx +72 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/README.md +29 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/architecture.json +30 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/00_architecture.html +14950 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/canvas.mdx +511 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/plan.mdx +208 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/recap/plan.mdx +102 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/standard/plan.md +136 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/canvas.mdx +124 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/plan.mdx +37 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/index.js +188 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/kit.js +123 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/mdx.js +411 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +1291 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/plan.mdx +195 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/standard.md +95 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/plan.mdx +105 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/standard.md +76 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/canvas.mdx +81 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/plan.mdx +145 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/standard.md +76 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/plan.mdx +172 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/standard.md +100 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/plan.mdx +67 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/standard.md +49 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/canvas.mdx +63 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/plan.mdx +49 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/standard.md +39 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/plan.mdx +118 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/standard.md +57 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/plan.mdx +173 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/standard.md +96 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/plan.mdx +91 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/standard.md +54 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/canvas.mdx +53 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/plan.mdx +225 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/standard.md +111 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/theme.css +472 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/trail.js +216 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/markdown.js +1 -1
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +37 -4
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +125 -29
- package/skills/global_config/plan-canvas/scripts/plan-canvas.js +196 -8
- package/skills/global_config/pr-recap/SKILL.md +47 -0
- package/skills/global_config/pr-recap/scripts/pr-recap.mjs +200 -0
- package/skills/global_config/quick-recap/SKILL.md +55 -0
- package/skills/global_config/stay-within-limits/SKILL.md +85 -0
- package/skills/global_config/triage/SKILL.md +3 -0
- package/skills/global_config/visual-edit/README.md +96 -0
- package/skills/global_config/visual-edit/SKILL.md +615 -0
- package/skills/global_config/visual-plan/README.md +93 -0
- package/skills/global_config/visual-plan/SKILL.md +544 -0
- package/skills/global_config/visual-plan/references/canvas.md +139 -0
- package/skills/global_config/visual-plan/references/connection.md +51 -0
- package/skills/global_config/visual-plan/references/document-quality.md +186 -0
- package/skills/global_config/visual-plan/references/exemplar.md +62 -0
- package/skills/global_config/visual-plan/references/local-files.md +99 -0
- package/skills/global_config/visual-plan/references/wireframe.md +319 -0
- package/skills/global_config/visual-recap/README.md +103 -0
- package/skills/global_config/visual-recap/SKILL.md +560 -0
- package/skills/global_config/visual-recap/references/connection.md +51 -0
- package/skills/global_config/visual-recap/references/local-files.md +99 -0
- package/skills/global_config/visual-recap/references/wireframe.md +319 -0
- package/skills/playbooks/pb-ci-fix/SKILL.md +49 -0
- package/skills/playbooks/pb-event-tracker/SKILL.md +45 -0
- package/skills/playbooks/pb-meeting-actions/SKILL.md +42 -0
- package/skills/playbooks/pb-project-new/SKILL.md +48 -0
- package/skills/playbooks/pb-week-plan/SKILL.md +45 -0
- package/assets/header-v4.jpg +0 -0
package/.agents/AGENTS.md
CHANGED
|
@@ -19,6 +19,7 @@ next. This file defines *what each agent is*, not *what calls what*.
|
|
|
19
19
|
- `bdbrainstorm`
|
|
20
20
|
- `planning-with-files`
|
|
21
21
|
- `concise-planning`
|
|
22
|
+
- `plan-arbiter`
|
|
22
23
|
- **MCP Servers**:
|
|
23
24
|
- `openwiki-skill`
|
|
24
25
|
- `memb_mcp`
|
|
@@ -39,6 +40,7 @@ next. This file defines *what each agent is*, not *what calls what*.
|
|
|
39
40
|
- `startcycle-graph`
|
|
40
41
|
- `agent-pipeline`
|
|
41
42
|
- `subagent-driven-development`
|
|
43
|
+
- `plan-arbiter`
|
|
42
44
|
- **MCP Servers**:
|
|
43
45
|
- `memb_mcp`
|
|
44
46
|
- **Output Artifact**: capability-map approval recorded in `state.json` (no separate markdown file — this is a gate, not a deliverable)
|
|
@@ -59,6 +61,8 @@ next. This file defines *what each agent is*, not *what calls what*.
|
|
|
59
61
|
- `react-best-practices`
|
|
60
62
|
- `ui-component`
|
|
61
63
|
- `ui-tokens`
|
|
64
|
+
- `bdbdesignpro`
|
|
65
|
+
- `editable-design`
|
|
62
66
|
- **MCP Servers**:
|
|
63
67
|
- `open_design_mcp`
|
|
64
68
|
- `chrome-devtools`
|
|
@@ -79,6 +83,7 @@ next. This file defines *what each agent is*, not *what calls what*.
|
|
|
79
83
|
- `postgres-best-practices`
|
|
80
84
|
- `typescript-pro`
|
|
81
85
|
- `python-pro`
|
|
86
|
+
- `bdbresilience`
|
|
82
87
|
- **MCP Servers**:
|
|
83
88
|
- `github`
|
|
84
89
|
- `memb_mcp`
|
|
@@ -138,6 +143,9 @@ next. This file defines *what each agent is*, not *what calls what*.
|
|
|
138
143
|
- `wcag-audit-patterns`
|
|
139
144
|
- `github-repo`
|
|
140
145
|
- `clean-code`
|
|
146
|
+
- `brag`
|
|
147
|
+
- `bdbresilience`
|
|
148
|
+
- `bdb-security-audit`
|
|
141
149
|
- **MCP Servers**:
|
|
142
150
|
- `github`
|
|
143
151
|
- `chrome-devtools`
|
package/.agents/nodes.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"description": "Declarative node registry for /startcycle-graph. The dispatcher reads this instead of hardcoding BUILD_NODES and NODE_ENUM. Adding a build domain = adding an entry here, not editing the dispatcher.",
|
|
4
|
+
"common": ["read-the-damn-docs", "agenttrail", "archify", "plan-canvas", "stay-within-limits", "quick-recap", "verification-before-completion"],
|
|
4
5
|
"nodes": {
|
|
5
6
|
"architect": {
|
|
6
7
|
"label": "Architect",
|
|
@@ -16,7 +17,8 @@
|
|
|
16
17
|
"bdbrainstorm",
|
|
17
18
|
"planning-with-files",
|
|
18
19
|
"concise-planning",
|
|
19
|
-
"archify"
|
|
20
|
+
"archify",
|
|
21
|
+
"plan-arbiter"
|
|
20
22
|
],
|
|
21
23
|
"instructions": null
|
|
22
24
|
},
|
|
@@ -33,7 +35,8 @@
|
|
|
33
35
|
"read-the-damn-docs",
|
|
34
36
|
"startcycle-graph",
|
|
35
37
|
"agent-pipeline",
|
|
36
|
-
"subagent-driven-development"
|
|
38
|
+
"subagent-driven-development",
|
|
39
|
+
"plan-arbiter"
|
|
37
40
|
],
|
|
38
41
|
"instructions": null
|
|
39
42
|
},
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
// failure here, because the consequence (history AOS does not recognise) is
|
|
20
20
|
// recoverable with a rebase, and a wedged commit flow is not.
|
|
21
21
|
|
|
22
|
-
import { readFileSync } from "node:fs";
|
|
22
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
23
24
|
|
|
24
25
|
const TYPES = ["feat", "fix", "docs", "style", "refactor", "perf", "test", "chore", "ci", "build", "revert"];
|
|
25
26
|
|
|
@@ -35,7 +36,7 @@ function readStdin() {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
function respond(isAgy, allowed, reason = ""
|
|
39
|
+
function respond(isAgy, allowed, reason = "") {
|
|
39
40
|
if (isAgy) {
|
|
40
41
|
if (allowed) {
|
|
41
42
|
console.log(JSON.stringify({ decision: "allow" }));
|
|
@@ -87,6 +88,14 @@ function reasonFor(message) {
|
|
|
87
88
|
].join("\n");
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
export function checkConventionalCommit(command) {
|
|
92
|
+
if (typeof command !== "string" || !COMMIT.test(command)) return null;
|
|
93
|
+
const message = extractMessage(command);
|
|
94
|
+
return message === null || CONVENTIONAL.test(message) ? null : reasonFor(message);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const isMain = () => { try { return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } };
|
|
98
|
+
|
|
90
99
|
function main() {
|
|
91
100
|
const rawInput = readStdin();
|
|
92
101
|
let input = {};
|
|
@@ -108,18 +117,8 @@ function main() {
|
|
|
108
117
|
input?.command ||
|
|
109
118
|
"";
|
|
110
119
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const message = extractMessage(command);
|
|
117
|
-
if (message === null || CONVENTIONAL.test(message)) {
|
|
118
|
-
respond(isAgy, true);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
respond(isAgy, false, reasonFor(message), message);
|
|
120
|
+
const reason = checkConventionalCommit(command);
|
|
121
|
+
respond(isAgy, !reason, reason || "");
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
main();
|
|
124
|
+
if (isMain()) main();
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
// every write in the session; missing a protection is recoverable, freezing
|
|
19
19
|
// the agent is not.
|
|
20
20
|
|
|
21
|
-
import { readFileSync } from "node:fs";
|
|
21
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
22
23
|
import { basename } from "node:path";
|
|
23
24
|
|
|
24
25
|
// Templates are committed on purpose and hold no live secret. Blocking them
|
|
@@ -67,6 +68,15 @@ function reasonFor(filePath) {
|
|
|
67
68
|
].join("\n");
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
export function envFileReason(filePath) {
|
|
72
|
+
if (typeof filePath !== "string" || !filePath.trim()) return null;
|
|
73
|
+
// basename(), so a path like /srv/app/.env matches but /srv/app/env-notes.md does not.
|
|
74
|
+
const name = basename(filePath);
|
|
75
|
+
return !ENV_FILE.test(name) || TEMPLATE_FILE.test(name) ? null : reasonFor(filePath);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const isMain = () => { try { return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } };
|
|
79
|
+
|
|
70
80
|
function main() {
|
|
71
81
|
const rawInput = readStdin();
|
|
72
82
|
let input = {};
|
|
@@ -87,19 +97,8 @@ function main() {
|
|
|
87
97
|
input?.file_path ||
|
|
88
98
|
"";
|
|
89
99
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// basename(), so a path like /srv/app/.env matches but /srv/app/env-notes.md does not.
|
|
96
|
-
const name = basename(filePath);
|
|
97
|
-
if (!ENV_FILE.test(name) || TEMPLATE_FILE.test(name)) {
|
|
98
|
-
respond(isAgy, true);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
respond(isAgy, false, reasonFor(filePath));
|
|
100
|
+
const reason = envFileReason(filePath);
|
|
101
|
+
respond(isAgy, !reason, reason || "");
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
main();
|
|
104
|
+
if (isMain()) main();
|
|
@@ -15,10 +15,34 @@
|
|
|
15
15
|
//
|
|
16
16
|
// Gate validity: open ONLY if the LAST human-typed user message in the transcript
|
|
17
17
|
// is the literal word "GO" (case-insensitive, trimmed). Any other message closes it.
|
|
18
|
+
//
|
|
19
|
+
// Token path (master-session): a `GO <session>` typed by the human in a master
|
|
20
|
+
// session is turned into ~/.aos/go/<session>.token by go-token.mjs. This gate
|
|
21
|
+
// honours that token once, if it is <10 min old, names this session, and the
|
|
22
|
+
// master transcript it points at still ends with that very `GO <session>`.
|
|
23
|
+
// Own session name: latest agent-name/custom-title entry in this transcript
|
|
24
|
+
// (what --name / ListAgents show), else env AOS_SESSION_NAME. If neither is
|
|
25
|
+
// available the token path stays closed.
|
|
26
|
+
//
|
|
27
|
+
// A token is single-use per master message: the master's GO entry uuid (Claude)
|
|
28
|
+
// or message id (OpenCode) goes into ~/.aos/go/.consumed, so a re-minted token
|
|
29
|
+
// on an unchanged master cannot be replayed.
|
|
30
|
+
//
|
|
31
|
+
// Threat model: this stops accidental or replayed GOs, not an agent that can
|
|
32
|
+
// write arbitrary files; it can forge a master transcript. Nothing in-process fixes that.
|
|
33
|
+
//
|
|
34
|
+
// OpenCode-issued tokens (issuer "opencode") are verified offline against
|
|
35
|
+
// opencode.db, read-only. Limits: the schema is internal and undocumented, only
|
|
36
|
+
// the default XDG / ~/.local/share data dir is read, and bun:sqlite is an
|
|
37
|
+
// unverified fallback. Any miss fails closed; the human types a literal GO.
|
|
18
38
|
|
|
19
|
-
import { readFileSync } from "node:fs";
|
|
39
|
+
import { readFileSync, writeFileSync, existsSync, unlinkSync, realpathSync } from "node:fs";
|
|
40
|
+
import { createRequire } from "node:module";
|
|
41
|
+
import { homedir } from "node:os";
|
|
42
|
+
import { join } from "node:path";
|
|
43
|
+
import { fileURLToPath } from "node:url";
|
|
20
44
|
|
|
21
|
-
const GUARDED_PATTERNS = [
|
|
45
|
+
export const GUARDED_PATTERNS = [
|
|
22
46
|
/(?:^|[;&|]\s*)git\s+push\b/i,
|
|
23
47
|
/(?:^|[;&|]\s*)npm\s+publish\b/i,
|
|
24
48
|
/(?:^|[;&|]\s*)npm\s+version\b/i,
|
|
@@ -83,12 +107,125 @@ function extractAgyText(rawContent) {
|
|
|
83
107
|
return text.trim();
|
|
84
108
|
}
|
|
85
109
|
|
|
110
|
+
export const slug = (s) => s.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
111
|
+
const TOKEN_TTL_MS = 10 * 60 * 1000;
|
|
112
|
+
|
|
113
|
+
function ownSessionName(transcriptPath) {
|
|
114
|
+
let name = "";
|
|
115
|
+
try {
|
|
116
|
+
for (const line of readFileSync(transcriptPath, "utf8").split("\n")) {
|
|
117
|
+
if (!line.includes('"agent-name"') && !line.includes('"custom-title"')) continue;
|
|
118
|
+
try {
|
|
119
|
+
const e = JSON.parse(line);
|
|
120
|
+
if (e.type === "agent-name" && e.agentName) name = e.agentName;
|
|
121
|
+
else if (e.type === "custom-title" && e.customTitle && !name) name = e.customTitle;
|
|
122
|
+
} catch { /* partial line */ }
|
|
123
|
+
}
|
|
124
|
+
} catch { /* unreadable */ }
|
|
125
|
+
return name || process.env.AOS_SESSION_NAME || "";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const CONSUMED_KEEP = 200;
|
|
129
|
+
const consumedFile = () => join(homedir(), ".aos", "go", ".consumed");
|
|
130
|
+
const readConsumed = () => {
|
|
131
|
+
try { return readFileSync(consumedFile(), "utf8").split("\n").filter(Boolean); } catch (e) { return e.code === "ENOENT" ? [] : null; }
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
function opencodeLastUser(sessionId) {
|
|
135
|
+
let db;
|
|
136
|
+
try {
|
|
137
|
+
const dbPath = join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), "opencode", "opencode.db");
|
|
138
|
+
if (typeof sessionId !== "string" || !existsSync(dbPath)) return null;
|
|
139
|
+
const require = createRequire(import.meta.url);
|
|
140
|
+
try {
|
|
141
|
+
db = new (require("node:sqlite").DatabaseSync)(dbPath, { readOnly: true });
|
|
142
|
+
} catch {
|
|
143
|
+
db = new (require("bun:sqlite").Database)(dbPath, { readonly: true });
|
|
144
|
+
}
|
|
145
|
+
const ses = db.prepare("SELECT parent_id FROM session WHERE id = ?").get(sessionId);
|
|
146
|
+
if (!ses || ses.parent_id != null) return null; // subagent sessions are agent-written, never a human GO
|
|
147
|
+
const msg = db.prepare(
|
|
148
|
+
"SELECT id FROM message WHERE session_id = ? AND json_extract(data, '$.role') = 'user' ORDER BY time_created DESC, id DESC LIMIT 1"
|
|
149
|
+
).get(sessionId);
|
|
150
|
+
if (!msg) return null;
|
|
151
|
+
const text = db.prepare("SELECT data FROM part WHERE message_id = ? ORDER BY id").all(msg.id)
|
|
152
|
+
.map((r) => JSON.parse(r.data))
|
|
153
|
+
.filter((p) => p?.type === "text" && !p.synthetic && typeof p.text === "string")
|
|
154
|
+
.map((p) => p.text)
|
|
155
|
+
.join("\n")
|
|
156
|
+
.trim();
|
|
157
|
+
return { id: msg.id, text };
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
} finally {
|
|
161
|
+
try { db?.close(); } catch { /* already closed */ }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Shared with bin/aos-acp.mjs (ACP permission requests). `consume: false`
|
|
166
|
+
// verifies without deleting the token, for a caller whose inner gate owns it.
|
|
167
|
+
export function tokenGrantsGo(own, { consume = true } = {}) {
|
|
168
|
+
if (typeof own !== "string" || !slug(own)) return { ok: false, reason: "no session name derivable for token path" };
|
|
169
|
+
const file = join(homedir(), ".aos", "go", `${slug(own)}.token`);
|
|
170
|
+
if (!existsSync(file)) return { ok: false, reason: "no GO token for this session" };
|
|
171
|
+
let tok;
|
|
172
|
+
try {
|
|
173
|
+
tok = JSON.parse(readFileSync(file, "utf8"));
|
|
174
|
+
} catch {
|
|
175
|
+
return { ok: false, reason: "GO token unreadable" };
|
|
176
|
+
}
|
|
177
|
+
const age = Date.now() - Date.parse(tok?.issued_at);
|
|
178
|
+
if (!(age >= 0 && age < TOKEN_TTL_MS)) {
|
|
179
|
+
try { unlinkSync(file); } catch { /* already gone */ }
|
|
180
|
+
return { ok: false, reason: "GO token expired" };
|
|
181
|
+
}
|
|
182
|
+
if (typeof tok.target !== "string" || slug(tok.target) !== slug(own)) {
|
|
183
|
+
return { ok: false, reason: "GO token names another session" };
|
|
184
|
+
}
|
|
185
|
+
const want = `go ${own.trim().toLowerCase()}`;
|
|
186
|
+
let key;
|
|
187
|
+
if (tok.issuer === "opencode") {
|
|
188
|
+
const last = opencodeLastUser(tok.master_session_id);
|
|
189
|
+
if (!last || last.id !== tok.master_message_id || last.text.toLowerCase() !== want) {
|
|
190
|
+
return { ok: false, reason: "master session no longer ends with this GO" };
|
|
191
|
+
}
|
|
192
|
+
key = last.id;
|
|
193
|
+
} else if (typeof tok.master_transcript === "string") {
|
|
194
|
+
const last = lastUserText(tok.master_transcript);
|
|
195
|
+
if (!last.text || last.text.trim().toLowerCase() !== want) {
|
|
196
|
+
return { ok: false, reason: "master transcript no longer ends with this GO" };
|
|
197
|
+
}
|
|
198
|
+
if (!last.uuid) return { ok: false, reason: "master GO entry has no uuid" };
|
|
199
|
+
key = last.uuid;
|
|
200
|
+
} else {
|
|
201
|
+
return { ok: false, reason: "GO token has no master transcript" };
|
|
202
|
+
}
|
|
203
|
+
const consumed = readConsumed();
|
|
204
|
+
if (!consumed) return { ok: false, reason: "GO consumed list unreadable" };
|
|
205
|
+
if (consumed.includes(key)) return { ok: false, reason: "GO already used" };
|
|
206
|
+
if (consume) {
|
|
207
|
+
try {
|
|
208
|
+
// ponytail: read-modify-write, concurrent consumers in the same ms can drop a key; switch to append+trim if that ever matters.
|
|
209
|
+
writeFileSync(consumedFile(), [...consumed, key].slice(-CONSUMED_KEEP).join("\n") + "\n");
|
|
210
|
+
unlinkSync(file);
|
|
211
|
+
} catch { return { ok: false, reason: "could not consume GO token" }; }
|
|
212
|
+
}
|
|
213
|
+
return { ok: true };
|
|
214
|
+
}
|
|
215
|
+
|
|
86
216
|
function lastUserMessageIsGo(transcriptPath) {
|
|
217
|
+
const last = lastUserText(transcriptPath);
|
|
218
|
+
if (last.error) return { ok: false, reason: last.error };
|
|
219
|
+
if (last.text === null) return { ok: false, reason: "no user message found in transcript" };
|
|
220
|
+
return { ok: /^GO$/i.test(last.text.trim()), reason: `last user message was: ${JSON.stringify(last.text)}` };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function lastUserText(transcriptPath) {
|
|
87
224
|
let raw;
|
|
88
225
|
try {
|
|
89
226
|
raw = readFileSync(transcriptPath, "utf8");
|
|
90
227
|
} catch (e) {
|
|
91
|
-
return {
|
|
228
|
+
return { text: null, error: `could not read transcript (${e.message})` };
|
|
92
229
|
}
|
|
93
230
|
|
|
94
231
|
const lines = raw.split("\n").filter(Boolean);
|
|
@@ -104,8 +241,7 @@ function lastUserMessageIsGo(transcriptPath) {
|
|
|
104
241
|
if (entry.type === "USER_INPUT" || entry.source === "USER_EXPLICIT") {
|
|
105
242
|
const text = extractAgyText(entry.content);
|
|
106
243
|
if (!text) continue;
|
|
107
|
-
|
|
108
|
-
return { ok: isGo, reason: `last user message was: ${JSON.stringify(text)}` };
|
|
244
|
+
return { text };
|
|
109
245
|
}
|
|
110
246
|
|
|
111
247
|
// 2. Claude Code transcript entry format
|
|
@@ -114,12 +250,11 @@ function lastUserMessageIsGo(transcriptPath) {
|
|
|
114
250
|
const content = entry.message?.content ?? entry.content;
|
|
115
251
|
const text = extractClaudeText(content).trim();
|
|
116
252
|
if (!text) continue;
|
|
117
|
-
|
|
118
|
-
return { ok: isGo, reason: `last user message was: ${JSON.stringify(text)}` };
|
|
253
|
+
return { text, uuid: entry.uuid ?? null };
|
|
119
254
|
}
|
|
120
255
|
}
|
|
121
256
|
|
|
122
|
-
return {
|
|
257
|
+
return { text: null };
|
|
123
258
|
}
|
|
124
259
|
|
|
125
260
|
function main() {
|
|
@@ -164,7 +299,12 @@ function main() {
|
|
|
164
299
|
return;
|
|
165
300
|
}
|
|
166
301
|
|
|
167
|
-
|
|
302
|
+
let result = lastUserMessageIsGo(transcriptPath);
|
|
303
|
+
if (!result.ok) {
|
|
304
|
+
const token = tokenGrantsGo(ownSessionName(transcriptPath));
|
|
305
|
+
if (token.ok) result = token;
|
|
306
|
+
else result.reason += `; ${token.reason}`;
|
|
307
|
+
}
|
|
168
308
|
if (result.ok) {
|
|
169
309
|
respond(isAgy, true, "", command);
|
|
170
310
|
} else {
|
|
@@ -172,4 +312,6 @@ function main() {
|
|
|
172
312
|
}
|
|
173
313
|
}
|
|
174
314
|
|
|
175
|
-
|
|
315
|
+
// Run as a hook (node go-gate.mjs); importing it (bin/aos-acp.mjs) must not.
|
|
316
|
+
const invokedAs = (() => { try { return realpathSync(process.argv[1] || ""); } catch { return ""; } })();
|
|
317
|
+
if (invokedAs === fileURLToPath(import.meta.url)) main();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// UserPromptSubmit hook: when the human types exactly `GO <session-name>` in a
|
|
3
|
+
// master session, record a single-use token that go-gate.mjs in <session-name>
|
|
4
|
+
// accepts. Writes nothing else and never blocks the prompt.
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { slug } from "./go-gate.mjs";
|
|
11
|
+
|
|
12
|
+
function sessionName(transcriptPath) {
|
|
13
|
+
let name = "";
|
|
14
|
+
try {
|
|
15
|
+
for (const line of readFileSync(transcriptPath, "utf8").split("\n")) {
|
|
16
|
+
if (!line.includes('"agent-name"') && !line.includes('"custom-title"')) continue;
|
|
17
|
+
try {
|
|
18
|
+
const e = JSON.parse(line);
|
|
19
|
+
if (e.type === "agent-name" && e.agentName) name = e.agentName;
|
|
20
|
+
else if (e.type === "custom-title" && e.customTitle && !name) name = e.customTitle;
|
|
21
|
+
} catch { /* partial line */ }
|
|
22
|
+
}
|
|
23
|
+
} catch { /* unreadable transcript */ }
|
|
24
|
+
return name || process.env.AOS_SESSION_NAME || "";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Returns the token path, or null. Never throws. An aos-acp worker is a worker
|
|
28
|
+
// by definition and must not mint a token for itself.
|
|
29
|
+
export function issueGoToken(prompt, { transcript_path, session_id, message_id } = {}) {
|
|
30
|
+
try {
|
|
31
|
+
const m = /^GO\s+(\S(?:.*\S)?)$/i.exec(String(prompt ?? "").trim());
|
|
32
|
+
if (!m || process.env.AOS_ACP_CLIENT) return null;
|
|
33
|
+
const target = slug(m[1]);
|
|
34
|
+
const base = { target: m[1].trim(), issued_at: new Date().toISOString() };
|
|
35
|
+
let tok;
|
|
36
|
+
if (transcript_path) tok = { ...base, master_transcript: transcript_path, master_session: sessionName(transcript_path) };
|
|
37
|
+
else if (session_id && message_id) tok = { ...base, issuer: "opencode", master_session_id: session_id, master_message_id: message_id };
|
|
38
|
+
if (!target || !tok) return null;
|
|
39
|
+
const dir = join(homedir(), ".aos", "go");
|
|
40
|
+
mkdirSync(dir, { recursive: true });
|
|
41
|
+
const file = join(dir, `${target}.token`);
|
|
42
|
+
writeFileSync(file, JSON.stringify(tok));
|
|
43
|
+
return file;
|
|
44
|
+
} catch { return null; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isMain = () => { try { return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } };
|
|
48
|
+
|
|
49
|
+
if (isMain()) {
|
|
50
|
+
try {
|
|
51
|
+
const input = JSON.parse(readFileSync(0, "utf8"));
|
|
52
|
+
issueGoToken(input.prompt, input);
|
|
53
|
+
} catch { /* a failing hook must never block the prompt */ }
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// aos-hook-version:
|
|
2
|
+
// aos-hook-version: 7
|
|
3
3
|
/**
|
|
4
4
|
* memB ambient memory hook for Claude Code, Google Antigravity, and OpenAI Codex.
|
|
5
5
|
*
|
|
@@ -137,74 +137,60 @@ function resolveProjectIdentifiers(projectRoot, homeDir) {
|
|
|
137
137
|
return Array.from(ids).filter(Boolean);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
async function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
try { inputRaw = readFileSync(0, 'utf8'); } catch { failOpen(); }
|
|
146
|
-
if (!inputRaw || !inputRaw.trim()) failOpen();
|
|
140
|
+
// Shared with the OpenCode plugin: everything between "which event" and the
|
|
141
|
+
// finished text block. Returns null when there is nothing to inject; never exits.
|
|
142
|
+
export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId } = {}) {
|
|
143
|
+
prompt = String(prompt).trim();
|
|
144
|
+
const home = os.homedir();
|
|
147
145
|
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
// Which harness event is this? SessionStart carries the one-shot identity
|
|
147
|
+
// injection; UserPromptSubmit is recall-only; anything else — including a
|
|
148
|
+
// missing event name from harnesses that never adopted them — keeps the
|
|
149
|
+
// full legacy behaviour plus identity (hookless-start harnesses).
|
|
150
|
+
const rawEvent = event;
|
|
151
|
+
const sessionStart = rawEvent === 'SessionStart';
|
|
152
|
+
const promptSubmit = rawEvent === 'UserPromptSubmit';
|
|
150
153
|
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
const rawCandidateDir =
|
|
155
|
+
cwd ||
|
|
156
|
+
process.env.CLAUDE_PROJECT_DIR ||
|
|
157
|
+
process.env.CODEX_PROJECT_DIR ||
|
|
158
|
+
process.cwd();
|
|
153
159
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// missing event name from harnesses that never adopted them — keeps the
|
|
157
|
-
// full legacy behaviour plus identity (hookless-start harnesses).
|
|
158
|
-
const rawEvent = eventData.hook_event_name || '';
|
|
159
|
-
const sessionStart = rawEvent === 'SessionStart';
|
|
160
|
-
const promptSubmit = rawEvent === 'UserPromptSubmit';
|
|
160
|
+
const projectRoot = findProjectRoot(rawCandidateDir, home);
|
|
161
|
+
const candidateProjectIds = resolveProjectIdentifiers(projectRoot, home);
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
process.env.CODEX_PROJECT_DIR ||
|
|
171
|
-
process.cwd();
|
|
163
|
+
// User identification: reconcile active user and bdb_developer baseline
|
|
164
|
+
const activeUser =
|
|
165
|
+
process.env.MEMB_USER_ID ||
|
|
166
|
+
userId ||
|
|
167
|
+
process.env.USER ||
|
|
168
|
+
process.env.LOGNAME ||
|
|
169
|
+
'bdb_developer';
|
|
170
|
+
const candidateUsers = Array.from(new Set([activeUser, 'bdb_developer'])).filter(Boolean);
|
|
172
171
|
|
|
173
|
-
|
|
174
|
-
const candidateProjectIds = resolveProjectIdentifiers(projectRoot, home);
|
|
172
|
+
const contextItems = [];
|
|
175
173
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// 1. Standing facts, if the user keeps any. Skipped on UserPromptSubmit:
|
|
189
|
-
// they were already injected at SessionStart.
|
|
190
|
-
if (!promptSubmit) {
|
|
191
|
-
const personaFile = path.join(home, '.MemBDB', 'ambient-persona.txt');
|
|
192
|
-
if (existsSync(personaFile)) {
|
|
193
|
-
try {
|
|
194
|
-
for (const line of readFileSync(personaFile, 'utf8').split('\n')) {
|
|
195
|
-
const t = line.trim();
|
|
196
|
-
if (t && !t.startsWith('#')) contextItems.push(`- ${t}`);
|
|
197
|
-
}
|
|
198
|
-
} catch {}
|
|
199
|
-
}
|
|
174
|
+
// 1. Standing facts, if the user keeps any. Skipped on UserPromptSubmit:
|
|
175
|
+
// they were already injected at SessionStart.
|
|
176
|
+
if (!promptSubmit) {
|
|
177
|
+
const personaFile = path.join(home, '.MemBDB', 'ambient-persona.txt');
|
|
178
|
+
if (existsSync(personaFile)) {
|
|
179
|
+
try {
|
|
180
|
+
for (const line of readFileSync(personaFile, 'utf8').split('\n')) {
|
|
181
|
+
const t = line.trim();
|
|
182
|
+
if (t && !t.startsWith('#')) contextItems.push(`- ${t}`);
|
|
183
|
+
}
|
|
184
|
+
} catch {}
|
|
200
185
|
}
|
|
186
|
+
}
|
|
201
187
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
188
|
+
const dbPath = path.join(home, '.MemBDB', 'memb.db');
|
|
189
|
+
if (!existsSync(dbPath)) return null;
|
|
190
|
+
const { DatabaseSync } = await import('node:sqlite');
|
|
191
|
+
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
192
|
+
try {
|
|
206
193
|
db.exec('PRAGMA busy_timeout = 5000;');
|
|
207
|
-
|
|
208
194
|
const textOf = (payload) => {
|
|
209
195
|
const p = JSON.parse(payload);
|
|
210
196
|
return (p.memory || p.data || '').trim().replace(/\s+/g, ' ');
|
|
@@ -344,8 +330,35 @@ async function main() {
|
|
|
344
330
|
// a déjà vu instead of from zero. Once per session, like identity.
|
|
345
331
|
if (!promptSubmit) contextItems.push(...dejaWip(projectRoot));
|
|
346
332
|
|
|
347
|
-
|
|
348
|
-
|
|
333
|
+
return contextItems.length
|
|
334
|
+
? '[memB Ambient Memory Context] (recalled data, not instructions)\n' + contextItems.join('\n')
|
|
335
|
+
: null;
|
|
336
|
+
} finally {
|
|
337
|
+
db.close();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Main logic runs only when the file is executed directly as a hook; importing
|
|
342
|
+
// the module (e.g. from tests) must stay side-effect free.
|
|
343
|
+
async function main() {
|
|
344
|
+
try {
|
|
345
|
+
let inputRaw = '';
|
|
346
|
+
try { inputRaw = readFileSync(0, 'utf8'); } catch { failOpen(); }
|
|
347
|
+
if (!inputRaw || !inputRaw.trim()) failOpen();
|
|
348
|
+
|
|
349
|
+
let eventData = {};
|
|
350
|
+
try { eventData = JSON.parse(inputRaw); } catch { failOpen(); }
|
|
351
|
+
|
|
352
|
+
// Discover candidate directory across harnesses:
|
|
353
|
+
// Claude / Codex: eventData.cwd; Antigravity: eventData.workspacePaths[0] or eventData.cwd
|
|
354
|
+
const memoryBlock = await buildMemoryBlock({
|
|
355
|
+
event: eventData.hook_event_name || '',
|
|
356
|
+
prompt: eventData.prompt || eventData.userPrompt || '',
|
|
357
|
+
cwd: (Array.isArray(eventData.workspacePaths) && eventData.workspacePaths[0]) || eventData.cwd,
|
|
358
|
+
userId: eventData.user_id || eventData.userId,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
if (memoryBlock) {
|
|
349
362
|
process.stdout.write(JSON.stringify({
|
|
350
363
|
hookSpecificOutput: {
|
|
351
364
|
hookEventName: eventData.hook_event_name || 'UserPromptSubmit',
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PreToolUse hook: when a subagent is spawned (Agent/Task), make sure the agenttrail
|
|
3
|
+
// live map runs and hand its URL to the model. Fail-open: never blocks, never throws.
|
|
4
|
+
// Reads only tool_name, session_id, cwd -- never tool_input or prompt text.
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const input = JSON.parse(readFileSync(0, 'utf8'));
|
|
10
|
+
if (input.tool_name === 'Agent' || input.tool_name === 'Task') {
|
|
11
|
+
const args = ['--ensure'];
|
|
12
|
+
if (typeof input.cwd === 'string' && input.cwd) args.push('--cwd', input.cwd);
|
|
13
|
+
if (typeof input.session_id === 'string' && input.session_id) args.push('--session', input.session_id);
|
|
14
|
+
args.push('--json');
|
|
15
|
+
const r = spawnSync(process.env.AOS_TRAIL_BIN || 'aos-trail', args, {
|
|
16
|
+
encoding: 'utf8', timeout: 1800, killSignal: 'SIGKILL', stdio: ['ignore', 'pipe', 'ignore'],
|
|
17
|
+
});
|
|
18
|
+
const out = r.error || r.status !== 0 ? null : JSON.parse(r.stdout);
|
|
19
|
+
const text = out && (out.url ? `agenttrail live map: ${out.url}` : out.hint);
|
|
20
|
+
if (typeof text === 'string' && text) {
|
|
21
|
+
process.stdout.write(JSON.stringify({
|
|
22
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: text },
|
|
23
|
+
}) + '\n');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} catch { /* fail open */ }
|
|
27
|
+
process.exit(0);
|
|
@@ -8,6 +8,7 @@ let raw = '';
|
|
|
8
8
|
try { for await (const c of process.stdin) raw += c; } catch {}
|
|
9
9
|
let ev;
|
|
10
10
|
try { ev = JSON.parse(raw); } catch { process.exit(0); }
|
|
11
|
+
if (!ev || typeof ev !== 'object') process.exit(0);
|
|
11
12
|
|
|
12
13
|
ev.agent = arg('--agent') || ev.agent || 'claude';
|
|
13
14
|
ev.hook_event_name ||= arg('--event');
|