portable-agent-layer 0.88.1 → 0.89.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/README.md +8 -5
- package/assets/agents/gemini-researcher.md +13 -0
- package/assets/agents/grok-researcher.md +13 -0
- package/assets/agents/multi-perspective-researcher.md +12 -0
- package/assets/agents/perplexity-researcher.md +13 -0
- package/assets/agents/skill-author.md +17 -0
- package/assets/agents/subagent-author.md +18 -1
- package/assets/skills/create-subagent/SKILL.md +3 -2
- package/assets/skills/create-subagent/authoring-guide.md +16 -0
- package/assets/templates/PAL/MEMORY_SYSTEM.md +1 -1
- package/assets/templates/PAL/README.md +6 -5
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +4 -1
- package/assets/templates/hooks.antigravity.json +42 -0
- package/assets/templates/settings.antigravity.json +44 -0
- package/package.json +1 -1
- package/src/cli/builtin-tools.ts +28 -23
- package/src/cli/doctor/agents.ts +64 -64
- package/src/cli/identity.ts +26 -20
- package/src/cli/index.ts +109 -312
- package/src/cli/knowledge.ts +160 -201
- package/src/cli/ledger-command.ts +69 -0
- package/src/cli/ledger.ts +20 -101
- package/src/cli/personal-context.ts +15 -12
- package/src/cli/rule.ts +29 -29
- package/src/cli/server.ts +33 -52
- package/src/cli/session-agent.ts +7 -9
- package/src/cli/skill.ts +73 -64
- package/src/cli/subagent.ts +51 -58
- package/src/cli/tree.ts +177 -0
- package/src/hooks/InvocationContext.ts +26 -0
- package/src/hooks/LedgerCommit.ts +4 -2
- package/src/hooks/LedgerSnapshot.ts +2 -0
- package/src/hooks/SecurityValidator.ts +2 -0
- package/src/hooks/StopOrchestrator.ts +12 -2
- package/src/hooks/UserPromptOrchestrator.ts +3 -15
- package/src/hooks/handlers/context-digests.ts +21 -21
- package/src/hooks/handlers/readme-sync.ts +6 -2
- package/src/hooks/lib/agent-definition.ts +6 -16
- package/src/hooks/lib/agent-registry.ts +88 -0
- package/src/hooks/lib/agent.ts +47 -23
- package/src/hooks/lib/antigravity-inference.ts +84 -0
- package/src/hooks/lib/antigravity-transcript.ts +129 -0
- package/src/hooks/lib/claim-check.ts +13 -2
- package/src/hooks/lib/claim-log.ts +5 -1
- package/src/hooks/lib/claude-md.ts +21 -1
- package/src/hooks/lib/context.ts +5 -1
- package/src/hooks/lib/export-manifest.ts +23 -0
- package/src/hooks/lib/export.ts +1 -24
- package/src/hooks/lib/hook-turn.ts +25 -2
- package/src/hooks/lib/import-merge.ts +1 -1
- package/src/hooks/lib/inference.ts +77 -1
- package/src/hooks/lib/invocation-context.ts +99 -0
- package/src/hooks/lib/ledger-hook.ts +14 -6
- package/src/hooks/lib/models.ts +34 -17
- package/src/hooks/lib/paths.ts +39 -6
- package/src/hooks/lib/prompt-capture.ts +28 -0
- package/src/hooks/lib/readme-sync.ts +3 -31
- package/src/hooks/lib/security-gate.ts +10 -3
- package/src/hooks/lib/semi-static.ts +13 -3
- package/src/hooks/lib/session-context.ts +3 -1
- package/src/hooks/lib/stop.ts +1 -0
- package/src/hooks/lib/transcript.ts +6 -3
- package/src/targets/agent-render.ts +3 -3
- package/src/targets/antigravity/install.ts +68 -0
- package/src/targets/antigravity/uninstall.ts +66 -0
- package/src/targets/lib.ts +81 -64
- package/src/targets/opencode/install.ts +2 -0
- package/src/targets/opencode/permission.ts +39 -0
- package/src/targets/opencode/uninstall.ts +9 -8
- package/src/tools/agent/algorithm-reflect.ts +70 -62
- package/src/tools/agent/algorithm-synthesize.ts +39 -14
- package/src/tools/agent/analyze.ts +31 -36
- package/src/tools/agent/handoff-note.ts +51 -54
- package/src/tools/agent/interaction.ts +32 -28
- package/src/tools/agent/project.ts +300 -287
- package/src/tools/agent/relationship-note.ts +40 -36
- package/src/tools/agent/relationship-reflect.ts +23 -32
- package/src/tools/agent/synthesize.ts +26 -27
- package/src/tools/agent/thread.ts +42 -51
- package/src/tools/agent/wisdom-frame.ts +45 -38
- package/src/tools/control-room/server.ts +0 -2
- package/src/tools/control-room/ui/dist/assets/{index-BZMDzWFa.js → index-mFpXpiBj.js} +1 -1
- package/src/tools/control-room/ui/dist/index.html +1 -1
- package/src/tools/control-room/ui/screens/log.tsx +10 -1
- package/src/tools/lib/command.ts +275 -0
- package/src/tools/subagent-doctor.ts +74 -9
- package/src/tools/token-cost.ts +18 -17
package/src/cli/subagent.ts
CHANGED
|
@@ -1,75 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* pal cli subagent — manage personal subagents under ~/.pal/agents
|
|
3
|
-
*
|
|
4
|
-
* pal cli subagent link <name> Install ~/.pal/agents/<name>.md into every
|
|
5
|
-
* installed agent (split per platform).
|
|
6
|
-
* pal cli subagent doctor <name> Evaluate ~/.pal/agents/<name>.md against the
|
|
7
|
-
* subagent-authoring best practices.
|
|
8
|
-
* pal cli subagent list List the user-authored subagents.
|
|
9
|
-
* pal cli subagent author-model Print the flagship model configured to author
|
|
10
|
-
* subagents for the active agent (empty if none).
|
|
2
|
+
* pal cli subagent — manage personal subagents under ~/.pal/agents/, each one
|
|
3
|
+
* merged definition split per platform on install.
|
|
11
4
|
*/
|
|
12
5
|
|
|
13
|
-
import { getActiveAgent } from "../hooks/lib/agent";
|
|
14
|
-
import { flagshipAuthorModel } from "../hooks/lib/models";
|
|
15
6
|
import { installPersonalSubagent, listPersonalSubagents, log } from "../targets/lib";
|
|
7
|
+
import { group, leaf } from "../tools/lib/command";
|
|
16
8
|
import {
|
|
17
9
|
formatSubagentReport,
|
|
18
10
|
lintSubagent,
|
|
19
11
|
resolveSubagentFile,
|
|
20
12
|
} from "../tools/subagent-doctor";
|
|
13
|
+
import { printAuthorModel } from "./skill";
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return 0;
|
|
15
|
+
function list(): number {
|
|
16
|
+
const names = listPersonalSubagents();
|
|
17
|
+
if (names.length === 0) {
|
|
18
|
+
log.info("No personal subagents in ~/.pal/agents/");
|
|
19
|
+
} else {
|
|
20
|
+
for (const n of names) console.log(n);
|
|
29
21
|
}
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
function doctor(name: string): number {
|
|
26
|
+
const report = lintSubagent(resolveSubagentFile(name));
|
|
27
|
+
console.log(formatSubagentReport(report));
|
|
28
|
+
return report.errors > 0 ? 1 : 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function link(name: string): number {
|
|
32
|
+
try {
|
|
33
|
+
const installed = installPersonalSubagent(name);
|
|
34
|
+
if (installed.length === 0) {
|
|
35
|
+
log.warn(
|
|
36
|
+
`'${name}' installed into no agents (none installed yet). ` +
|
|
37
|
+
"Run 'pal cli install' first, then re-link."
|
|
38
|
+
);
|
|
35
39
|
} else {
|
|
36
|
-
|
|
40
|
+
log.success(`Installed '${name}' into: ${installed.join(", ")}`);
|
|
37
41
|
}
|
|
38
42
|
return 0;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
log.error(e instanceof Error ? e.message : String(e));
|
|
45
|
+
return 1;
|
|
39
46
|
}
|
|
40
|
-
|
|
41
|
-
if (sub === "doctor") {
|
|
42
|
-
if (!name) {
|
|
43
|
-
log.error("Usage: pal cli subagent doctor <file-or-name>");
|
|
44
|
-
return 1;
|
|
45
|
-
}
|
|
46
|
-
const report = lintSubagent(resolveSubagentFile(name));
|
|
47
|
-
console.log(formatSubagentReport(report));
|
|
48
|
-
return report.errors > 0 ? 1 : 0;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (sub === "link") {
|
|
52
|
-
if (!name) {
|
|
53
|
-
log.error("Usage: pal cli subagent link <name>");
|
|
54
|
-
return 1;
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
const installed = installPersonalSubagent(name);
|
|
58
|
-
if (installed.length === 0) {
|
|
59
|
-
log.warn(
|
|
60
|
-
`'${name}' installed into no agents (none installed yet). ` +
|
|
61
|
-
"Run 'pal cli install' first, then re-link."
|
|
62
|
-
);
|
|
63
|
-
} else {
|
|
64
|
-
log.success(`Installed '${name}' into: ${installed.join(", ")}`);
|
|
65
|
-
}
|
|
66
|
-
return 0;
|
|
67
|
-
} catch (e) {
|
|
68
|
-
log.error(e instanceof Error ? e.message : String(e));
|
|
69
|
-
return 1;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
log.error("Usage: pal cli subagent <link|doctor|list|author-model> [name]");
|
|
74
|
-
return 1;
|
|
75
47
|
}
|
|
48
|
+
|
|
49
|
+
export const subagentCommand = group({
|
|
50
|
+
summary: "Manage personal subagents under ~/.pal/agents/",
|
|
51
|
+
commands: {
|
|
52
|
+
link: leaf({
|
|
53
|
+
summary: "Install ~/.pal/agents/<name>.md into every installed agent",
|
|
54
|
+
args: "<name>",
|
|
55
|
+
run: ({ positionals }) => link(positionals[0]),
|
|
56
|
+
}),
|
|
57
|
+
doctor: leaf({
|
|
58
|
+
summary: "Check a subagent against the subagent-authoring best practices",
|
|
59
|
+
args: "<name>",
|
|
60
|
+
run: ({ positionals }) => doctor(positionals[0]),
|
|
61
|
+
}),
|
|
62
|
+
list: leaf({ summary: "List the user-authored subagents", run: list }),
|
|
63
|
+
"author-model": leaf({
|
|
64
|
+
summary: "Print the flagship model that authors subagents for the active agent",
|
|
65
|
+
run: printAuthorModel,
|
|
66
|
+
}),
|
|
67
|
+
},
|
|
68
|
+
});
|
package/src/cli/tree.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every `pal cli` command, as one tree. The commands whose code lives in
|
|
3
|
+
* index.ts arrive as handlers, because index.ts routes on import and so can
|
|
4
|
+
* never be imported by a test that walks this tree.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AGENT_NAMES, AGENT_REGISTRY } from "../hooks/lib/agent-registry";
|
|
8
|
+
import {
|
|
9
|
+
type Group,
|
|
10
|
+
group,
|
|
11
|
+
type Leaf,
|
|
12
|
+
leaf,
|
|
13
|
+
type OptionSpec,
|
|
14
|
+
type OptionSpecs,
|
|
15
|
+
} from "../tools/lib/command";
|
|
16
|
+
import { usageCommand } from "../tools/token-cost";
|
|
17
|
+
import { builtinTools, builtinToolVerbs } from "./builtin-tools";
|
|
18
|
+
import { identityCommand } from "./identity";
|
|
19
|
+
import { knowledgeCommand } from "./knowledge";
|
|
20
|
+
import { ledgerCommand } from "./ledger-command";
|
|
21
|
+
import { runMigrate } from "./migrate";
|
|
22
|
+
import { telosCommand, timezoneCommand } from "./personal-context";
|
|
23
|
+
import { ruleCommand } from "./rule";
|
|
24
|
+
import { serverCommand } from "./server";
|
|
25
|
+
import { skillCommand } from "./skill";
|
|
26
|
+
import { subagentCommand } from "./subagent";
|
|
27
|
+
|
|
28
|
+
type Handler = Leaf["run"];
|
|
29
|
+
export type DebugState = "on" | "off" | "status";
|
|
30
|
+
|
|
31
|
+
export interface AdminHandlers {
|
|
32
|
+
init: Handler;
|
|
33
|
+
install: Handler;
|
|
34
|
+
uninstall: Handler;
|
|
35
|
+
update: Handler;
|
|
36
|
+
export: Handler;
|
|
37
|
+
import: Handler;
|
|
38
|
+
status: Handler;
|
|
39
|
+
doctor: Handler;
|
|
40
|
+
debug(state: DebugState): void;
|
|
41
|
+
version: Handler;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const ENVIRONMENT = `Environment:
|
|
45
|
+
PAL_HOME Override user state directory (default: ~/.pal or repo root)
|
|
46
|
+
PAL_PKG Override package root
|
|
47
|
+
PAL_CLAUDE_DIR Override Claude config dir (default: ~/.claude)
|
|
48
|
+
PAL_OPENCODE_DIR Override opencode config dir (default: ~/.config/opencode)
|
|
49
|
+
PAL_CURSOR_DIR Override Cursor config dir (default: ~/.cursor)
|
|
50
|
+
PAL_COPILOT_DIR Override Copilot config dir (default: ~/.copilot)
|
|
51
|
+
PAL_CODEX_DIR Override Codex config dir (default: ~/.codex)
|
|
52
|
+
PAL_GEMINI_DIR Override Antigravity's Gemini dir (default: ~/.gemini)
|
|
53
|
+
PAL_AGENTS_DIR Override agents dir (default: ~/.agents)`;
|
|
54
|
+
|
|
55
|
+
function agentOptions(verb: string): OptionSpecs {
|
|
56
|
+
const perAgent = AGENT_NAMES.map((agent): [string, OptionSpec] => [
|
|
57
|
+
agent,
|
|
58
|
+
{ type: "boolean", description: `${verb} ${AGENT_REGISTRY[agent].label}` },
|
|
59
|
+
]);
|
|
60
|
+
return {
|
|
61
|
+
...Object.fromEntries(perAgent),
|
|
62
|
+
all: { type: "boolean", description: `${verb} every supported agent` },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const VERBOSE: OptionSpec = {
|
|
67
|
+
type: "boolean",
|
|
68
|
+
description: "Step-by-step log, and every passing doctor check",
|
|
69
|
+
};
|
|
70
|
+
const DRY_RUN: OptionSpec = { type: "boolean", description: "Show what would happen" };
|
|
71
|
+
|
|
72
|
+
function debugCommand(admin: AdminHandlers): Group {
|
|
73
|
+
const state = (s: DebugState, summary: string) =>
|
|
74
|
+
leaf({ summary, run: () => admin.debug(s) });
|
|
75
|
+
return group({
|
|
76
|
+
summary: "Verbose hook debug logging (persisted)",
|
|
77
|
+
fallback: "status",
|
|
78
|
+
commands: {
|
|
79
|
+
status: state("status", "Show whether it is on, and where it logs"),
|
|
80
|
+
on: state("on", "Turn it on"),
|
|
81
|
+
off: state("off", "Turn it off"),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function adminCommands(admin: AdminHandlers): Record<string, Leaf | Group> {
|
|
87
|
+
return {
|
|
88
|
+
init: leaf({
|
|
89
|
+
summary: "Scaffold PAL home and install (default: every installed agent)",
|
|
90
|
+
options: { ...agentOptions("Install into"), verbose: VERBOSE },
|
|
91
|
+
run: admin.init,
|
|
92
|
+
}),
|
|
93
|
+
install: leaf({
|
|
94
|
+
summary:
|
|
95
|
+
"Register hooks and skills (default: every installed agent), then run the doctor",
|
|
96
|
+
options: { ...agentOptions("Install into"), verbose: VERBOSE },
|
|
97
|
+
run: admin.install,
|
|
98
|
+
}),
|
|
99
|
+
uninstall: leaf({
|
|
100
|
+
summary: "Remove hooks and skills (default: every agent)",
|
|
101
|
+
options: agentOptions("Uninstall from"),
|
|
102
|
+
run: admin.uninstall,
|
|
103
|
+
}),
|
|
104
|
+
update: leaf({ summary: "Update PAL (git pull or bun add -g)", run: admin.update }),
|
|
105
|
+
export: leaf({
|
|
106
|
+
summary: "Export your state to a zip",
|
|
107
|
+
args: "[path]",
|
|
108
|
+
options: { "dry-run": DRY_RUN },
|
|
109
|
+
run: admin.export,
|
|
110
|
+
}),
|
|
111
|
+
import: leaf({
|
|
112
|
+
summary: "Merge your state from a zip (default: the newest export or backup)",
|
|
113
|
+
args: "[path]",
|
|
114
|
+
options: {
|
|
115
|
+
"dry-run": DRY_RUN,
|
|
116
|
+
overwrite: { type: "boolean", description: "Replace instead of merging" },
|
|
117
|
+
},
|
|
118
|
+
run: admin.import,
|
|
119
|
+
}),
|
|
120
|
+
status: leaf({ summary: "Show PAL configuration", run: admin.status }),
|
|
121
|
+
doctor: leaf({
|
|
122
|
+
summary: "Find what is wrong and how to fix it; exits 1 on any failure",
|
|
123
|
+
options: {
|
|
124
|
+
verbose: { type: "boolean", description: "Show passing checks too" },
|
|
125
|
+
json: { type: "boolean", description: "Machine-readable output" },
|
|
126
|
+
"probe-inference": {
|
|
127
|
+
type: "boolean",
|
|
128
|
+
description: "Also fire a real inference call per route (costs tokens)",
|
|
129
|
+
},
|
|
130
|
+
probe: { type: "boolean", description: "Same as --probe-inference" },
|
|
131
|
+
},
|
|
132
|
+
run: admin.doctor,
|
|
133
|
+
}),
|
|
134
|
+
migrate: leaf({
|
|
135
|
+
summary: "Run pending data migrations",
|
|
136
|
+
options: {
|
|
137
|
+
list: { type: "boolean", description: "List pending and done migrations" },
|
|
138
|
+
"dry-run": DRY_RUN,
|
|
139
|
+
},
|
|
140
|
+
run: ({ argv }) => runMigrate(argv),
|
|
141
|
+
}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function cliTree(admin: AdminHandlers): Group {
|
|
146
|
+
return group({
|
|
147
|
+
summary: "Admin commands for the Portable Agent Layer",
|
|
148
|
+
aliases: { "-v": "version", "--version": "version" },
|
|
149
|
+
details: ENVIRONMENT,
|
|
150
|
+
commands: {
|
|
151
|
+
...adminCommands(admin),
|
|
152
|
+
usage: usageCommand,
|
|
153
|
+
actor: identityCommand("actor"),
|
|
154
|
+
machine: identityCommand("machine"),
|
|
155
|
+
telos: telosCommand,
|
|
156
|
+
timezone: timezoneCommand,
|
|
157
|
+
knowledge: knowledgeCommand,
|
|
158
|
+
ledger: ledgerCommand,
|
|
159
|
+
rule: ruleCommand,
|
|
160
|
+
server: serverCommand,
|
|
161
|
+
skill: skillCommand,
|
|
162
|
+
subagent: subagentCommand,
|
|
163
|
+
...builtinTools,
|
|
164
|
+
debug: debugCommand(admin),
|
|
165
|
+
version: leaf({ summary: "Print the installed PAL version", run: admin.version }),
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function inertAdmin(): AdminHandlers {
|
|
171
|
+
return new Proxy({} as AdminHandlers, { get: () => () => undefined });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function adminCommandNames(): string[] {
|
|
175
|
+
const names = Object.keys(cliTree(inertAdmin()).commands);
|
|
176
|
+
return names.filter((name) => !builtinToolVerbs.includes(name));
|
|
177
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook: PreInvocation (Antigravity) — injects the session context once per
|
|
3
|
+
* conversation and the prompt context once per turn.
|
|
4
|
+
*
|
|
5
|
+
* Silent and fail-open: anything that goes wrong prints nothing. What to inject
|
|
6
|
+
* is decided in lib/invocation-context.ts, where a test can import it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { enterHookWorkspace } from "./lib/hook-turn";
|
|
10
|
+
import { type InvocationPayload, invocationContext } from "./lib/invocation-context";
|
|
11
|
+
import { logError } from "./lib/log";
|
|
12
|
+
import { isPalSpawnedInference } from "./lib/spawn-guard";
|
|
13
|
+
import { readStdinJSON } from "./lib/stdin";
|
|
14
|
+
|
|
15
|
+
if (isPalSpawnedInference()) process.exit(0);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const input = await readStdinJSON<InvocationPayload>();
|
|
19
|
+
if (!input) process.exit(0);
|
|
20
|
+
|
|
21
|
+
enterHookWorkspace(input);
|
|
22
|
+
const reply = await invocationContext(input);
|
|
23
|
+
if (reply) process.stdout.write(reply);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
logError("InvocationContext", err);
|
|
26
|
+
}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { duplicatesCursorHooks } from "./lib/cursor-shadow";
|
|
14
|
-
import {
|
|
14
|
+
import { enterHookWorkspace } from "./lib/hook-turn";
|
|
15
|
+
import { commitApplied, landedCalls } from "./lib/ledger-hook";
|
|
15
16
|
import { logDebug } from "./lib/log";
|
|
16
17
|
import { readStdinJSON } from "./lib/stdin";
|
|
17
18
|
|
|
@@ -21,7 +22,8 @@ try {
|
|
|
21
22
|
const input = await readStdinJSON<Record<string, unknown>>();
|
|
22
23
|
if (!input) process.exit(0);
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
enterHookWorkspace(input);
|
|
26
|
+
for (const call of landedCalls(input)) {
|
|
25
27
|
const entry = commitApplied(call);
|
|
26
28
|
if (entry) {
|
|
27
29
|
logDebug("LedgerCommit", `recorded ${entry.id} ${entry.tool} ${entry.target}`);
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { duplicatesCursorHooks } from "./lib/cursor-shadow";
|
|
14
|
+
import { enterHookWorkspace } from "./lib/hook-turn";
|
|
14
15
|
import { ledgeredCalls, snapshotCall } from "./lib/ledger-hook";
|
|
15
16
|
import { logDebug } from "./lib/log";
|
|
16
17
|
import { readStdinJSON } from "./lib/stdin";
|
|
@@ -21,6 +22,7 @@ try {
|
|
|
21
22
|
const input = await readStdinJSON<Record<string, unknown>>();
|
|
22
23
|
if (!input) process.exit(0);
|
|
23
24
|
|
|
25
|
+
enterHookWorkspace(input);
|
|
24
26
|
const calls = ledgeredCalls(input);
|
|
25
27
|
if (calls.length === 0) process.exit(0);
|
|
26
28
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { blockResponse } from "./lib/agent";
|
|
11
11
|
import { duplicatesCursorHooks } from "./lib/cursor-shadow";
|
|
12
|
+
import { enterHookWorkspace } from "./lib/hook-turn";
|
|
12
13
|
import { recordBlocked } from "./lib/ledger";
|
|
13
14
|
import { logError } from "./lib/log";
|
|
14
15
|
import { decideRefusal, type SecurityInput } from "./lib/security-gate";
|
|
@@ -20,6 +21,7 @@ try {
|
|
|
20
21
|
const input = await readStdinJSON<SecurityInput>();
|
|
21
22
|
if (!input) process.exit(0);
|
|
22
23
|
|
|
24
|
+
enterHookWorkspace(input);
|
|
23
25
|
const refusal = decideRefusal(input, process.cwd());
|
|
24
26
|
if (!refusal) process.exit(0);
|
|
25
27
|
|
|
@@ -8,8 +8,14 @@
|
|
|
8
8
|
import { checkReadmeSync } from "./handlers/readme-sync";
|
|
9
9
|
import { watchReplyRules } from "./lib/adaptation-steering";
|
|
10
10
|
import { stopBlockResponse } from "./lib/agent";
|
|
11
|
+
import {
|
|
12
|
+
type AntigravityStopFields,
|
|
13
|
+
isSideStop,
|
|
14
|
+
withTranscriptReply,
|
|
15
|
+
} from "./lib/antigravity-transcript";
|
|
11
16
|
import { watchClaims } from "./lib/claim-log";
|
|
12
17
|
import { duplicatesCursorHooks } from "./lib/cursor-shadow";
|
|
18
|
+
import { enterHookWorkspace } from "./lib/hook-turn";
|
|
13
19
|
import { logError } from "./lib/log";
|
|
14
20
|
import { isPalSpawnedInference } from "./lib/spawn-guard";
|
|
15
21
|
import { readStdinJSON } from "./lib/stdin";
|
|
@@ -20,11 +26,16 @@ import { type StopTurnPayload, stopTurn } from "./lib/stop";
|
|
|
20
26
|
if (isPalSpawnedInference()) process.exit(0);
|
|
21
27
|
if (duplicatesCursorHooks()) process.exit(0);
|
|
22
28
|
|
|
29
|
+
const received = await readStdinJSON<StopTurnPayload & AntigravityStopFields>();
|
|
30
|
+
if (isSideStop(received)) process.exit(0);
|
|
31
|
+
const payload = withTranscriptReply(received);
|
|
32
|
+
enterHookWorkspace(payload);
|
|
33
|
+
|
|
23
34
|
// Check README sync before anything else — may block the session
|
|
24
35
|
try {
|
|
25
36
|
// A block carrying no reason stops the turn without telling the model why, so it
|
|
26
37
|
// is worth less than not blocking at all — require the reason to raise one.
|
|
27
|
-
const decision = checkReadmeSync();
|
|
38
|
+
const decision = await checkReadmeSync();
|
|
28
39
|
if (decision.decision === "block" && decision.reason) {
|
|
29
40
|
process.stdout.write(stopBlockResponse(decision.reason));
|
|
30
41
|
process.exit(0);
|
|
@@ -33,7 +44,6 @@ try {
|
|
|
33
44
|
logError("StopOrchestrator:readme-sync", err);
|
|
34
45
|
}
|
|
35
46
|
|
|
36
|
-
const payload = await readStdinJSON<StopTurnPayload>();
|
|
37
47
|
try {
|
|
38
48
|
const sendBack = watchClaims(payload);
|
|
39
49
|
if (sendBack) {
|
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { injectPromptContext } from "./handlers/inject-retrieval";
|
|
11
|
-
import { captureRating } from "./handlers/rating";
|
|
12
|
-
import { captureSessionName } from "./handlers/session-name";
|
|
13
11
|
import { duplicatesCursorHooks } from "./lib/cursor-shadow";
|
|
14
12
|
import { type HookTurnPayload, hookSessionId } from "./lib/hook-turn";
|
|
15
|
-
import { logDebug,
|
|
13
|
+
import { logDebug, logPromptSnapshot } from "./lib/log";
|
|
14
|
+
import { capturePrompt } from "./lib/prompt-capture";
|
|
16
15
|
import { isPalSpawnedInference } from "./lib/spawn-guard";
|
|
17
16
|
import { readStdinJSON } from "./lib/stdin";
|
|
18
17
|
|
|
@@ -34,15 +33,4 @@ const sessionId = hookSessionId(input);
|
|
|
34
33
|
const injected = await injectPromptContext(input.prompt, sessionId);
|
|
35
34
|
logPromptSnapshot(input.prompt, injected);
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
captureRating(input.prompt, sessionId, sentAt),
|
|
39
|
-
captureSessionName(input.prompt, sessionId ?? ""),
|
|
40
|
-
]);
|
|
41
|
-
|
|
42
|
-
const handlerNames = ["rating", "session-name"];
|
|
43
|
-
for (let i = 0; i < results.length; i++) {
|
|
44
|
-
const r = results[i];
|
|
45
|
-
if (r.status === "rejected") {
|
|
46
|
-
logError(`UserPromptOrchestrator:${handlerNames[i]}`, r.reason);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
36
|
+
await capturePrompt(input.prompt, sessionId, sentAt, "UserPromptOrchestrator");
|
|
@@ -5,42 +5,34 @@
|
|
|
5
5
|
* at the next session start, keeping hook stdout small.
|
|
6
6
|
*
|
|
7
7
|
* Sources are defined in src/hooks/lib/semi-static.ts — add one entry there
|
|
8
|
-
* to extend coverage to all consumers (CLAUDE.md, opencode, Cursor, Copilot).
|
|
8
|
+
* to extend coverage to all consumers (CLAUDE.md, opencode, Cursor, Copilot, Antigravity).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { dirname, resolve } from "node:path";
|
|
13
13
|
import { ensureDir, platform } from "../lib/paths";
|
|
14
14
|
import {
|
|
15
|
+
antigravityFilename,
|
|
16
|
+
antigravityRule,
|
|
15
17
|
copilotFilename,
|
|
16
18
|
cursorFilename,
|
|
17
19
|
getSemiStaticSources,
|
|
18
20
|
} from "../lib/semi-static";
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
const sources = getSemiStaticSources();
|
|
22
|
-
|
|
23
|
-
// Resolve Cursor/Copilot destination dirs once (null if agent not installed)
|
|
24
|
-
let rulesDir: string | null = null;
|
|
25
|
-
let instructionsDir: string | null = null;
|
|
26
|
-
|
|
22
|
+
function subdirIfInstalled(agentHome: () => string, subdir: string): string | null {
|
|
27
23
|
try {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
rulesDir = ensureDir(resolve(cursorDir, "rules"));
|
|
31
|
-
}
|
|
24
|
+
const home = agentHome();
|
|
25
|
+
return existsSync(home) ? ensureDir(resolve(home, subdir)) : null;
|
|
32
26
|
} catch {
|
|
33
|
-
|
|
27
|
+
return null;
|
|
34
28
|
}
|
|
29
|
+
}
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} catch {
|
|
42
|
-
/* non-fatal */
|
|
43
|
-
}
|
|
31
|
+
export function writeContextDigests(): void {
|
|
32
|
+
const sources = getSemiStaticSources();
|
|
33
|
+
const rulesDir = subdirIfInstalled(platform.cursorDir, "rules");
|
|
34
|
+
const instructionsDir = subdirIfInstalled(platform.copilotDir, "instructions");
|
|
35
|
+
const antigravityRulesDir = subdirIfInstalled(platform.antigravityPluginDir, "rules");
|
|
44
36
|
|
|
45
37
|
for (const src of sources) {
|
|
46
38
|
try {
|
|
@@ -67,6 +59,14 @@ export function writeContextDigests(): void {
|
|
|
67
59
|
"utf-8"
|
|
68
60
|
);
|
|
69
61
|
}
|
|
62
|
+
|
|
63
|
+
if (antigravityRulesDir) {
|
|
64
|
+
writeFileSync(
|
|
65
|
+
resolve(antigravityRulesDir, antigravityFilename(src)),
|
|
66
|
+
antigravityRule(src.description, content),
|
|
67
|
+
"utf-8"
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
70
|
} catch {
|
|
71
71
|
/* non-fatal */
|
|
72
72
|
}
|
|
@@ -39,15 +39,19 @@ interface ReadmeSyncDecision {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** Returns a block decision if README is stale, or empty object to allow stop. */
|
|
42
|
-
export function checkReadmeSync(): ReadmeSyncDecision {
|
|
42
|
+
export async function checkReadmeSync(): Promise<ReadmeSyncDecision> {
|
|
43
43
|
if (!hasDocumentableChanges()) {
|
|
44
44
|
logDebug("readme-sync", "No documentable changes detected");
|
|
45
45
|
return {};
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
logDebug("readme-sync", "Documentable files changed — validating README");
|
|
49
|
-
const
|
|
49
|
+
const { adminCommandNames } = await import("../../cli/tree");
|
|
50
|
+
return readmeDecision(adminCommandNames());
|
|
51
|
+
}
|
|
50
52
|
|
|
53
|
+
function readmeDecision(cliCommands: string[]): ReadmeSyncDecision {
|
|
54
|
+
const result = validateReadmeSync(cliCommands);
|
|
51
55
|
if (!result.ok) {
|
|
52
56
|
logDebug("readme-sync", `README out of sync: ${result.issues.join("; ")}`);
|
|
53
57
|
const issueList = result.issues.map((i) => `- ${i}`).join("\n");
|
|
@@ -4,27 +4,17 @@
|
|
|
4
4
|
* and a body that becomes the subagent's system prompt.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { AGENT_NAMES, type AgentName } from "./agent-registry";
|
|
7
8
|
import { splitFrontmatter } from "./frontmatter";
|
|
8
9
|
|
|
9
|
-
export const AGENT_PLATFORMS = [
|
|
10
|
-
"claude",
|
|
11
|
-
"opencode",
|
|
12
|
-
"cursor",
|
|
13
|
-
"copilot",
|
|
14
|
-
"codex",
|
|
15
|
-
] as const;
|
|
16
|
-
export type AgentPlatform = (typeof AGENT_PLATFORMS)[number];
|
|
17
|
-
|
|
18
10
|
export interface AgentDefinition {
|
|
19
11
|
hasFrontmatter: boolean;
|
|
20
12
|
global: string[];
|
|
21
|
-
platforms: Partial<Record<
|
|
13
|
+
platforms: Partial<Record<AgentName, string[]>>;
|
|
22
14
|
body: string;
|
|
23
15
|
}
|
|
24
16
|
|
|
25
|
-
const PLATFORM_BLOCK_HEADER = new RegExp(
|
|
26
|
-
String.raw`^(${AGENT_PLATFORMS.join("|")}):\s*$`
|
|
27
|
-
);
|
|
17
|
+
const PLATFORM_BLOCK_HEADER = new RegExp(String.raw`^(${AGENT_NAMES.join("|")}):\s*$`);
|
|
28
18
|
const BLOCK_INDENT = " ";
|
|
29
19
|
|
|
30
20
|
export function parseAgentDefinition(content: string): AgentDefinition {
|
|
@@ -34,14 +24,14 @@ export function parseAgentDefinition(content: string): AgentDefinition {
|
|
|
34
24
|
}
|
|
35
25
|
|
|
36
26
|
const global: string[] = [];
|
|
37
|
-
const platforms: Partial<Record<
|
|
38
|
-
let current:
|
|
27
|
+
const platforms: Partial<Record<AgentName, string[]>> = {};
|
|
28
|
+
let current: AgentName | null = null;
|
|
39
29
|
|
|
40
30
|
for (const line of split.frontmatter.split("\n")) {
|
|
41
31
|
if (!line.trim()) continue;
|
|
42
32
|
const header = PLATFORM_BLOCK_HEADER.exec(line);
|
|
43
33
|
if (header) {
|
|
44
|
-
current = header[1] as
|
|
34
|
+
current = header[1] as AgentName;
|
|
45
35
|
platforms[current] ??= [];
|
|
46
36
|
continue;
|
|
47
37
|
}
|