portable-agent-layer 0.88.0 → 0.89.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.
Files changed (88) hide show
  1. package/README.md +8 -5
  2. package/assets/agents/gemini-researcher.md +13 -0
  3. package/assets/agents/grok-researcher.md +13 -0
  4. package/assets/agents/multi-perspective-researcher.md +12 -0
  5. package/assets/agents/perplexity-researcher.md +13 -0
  6. package/assets/agents/skill-author.md +17 -0
  7. package/assets/agents/subagent-author.md +18 -1
  8. package/assets/skills/create-subagent/SKILL.md +3 -2
  9. package/assets/skills/create-subagent/authoring-guide.md +16 -0
  10. package/assets/templates/PAL/MEMORY_SYSTEM.md +1 -1
  11. package/assets/templates/PAL/README.md +6 -5
  12. package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +4 -1
  13. package/assets/templates/hooks.antigravity.json +42 -0
  14. package/assets/templates/settings.antigravity.json +44 -0
  15. package/package.json +1 -1
  16. package/src/cli/builtin-tools.ts +28 -23
  17. package/src/cli/doctor/agents.ts +64 -64
  18. package/src/cli/identity.ts +26 -20
  19. package/src/cli/index.ts +109 -312
  20. package/src/cli/knowledge.ts +160 -201
  21. package/src/cli/ledger-command.ts +69 -0
  22. package/src/cli/ledger.ts +20 -101
  23. package/src/cli/personal-context.ts +15 -12
  24. package/src/cli/rule.ts +29 -29
  25. package/src/cli/server.ts +33 -52
  26. package/src/cli/session-agent.ts +7 -9
  27. package/src/cli/skill.ts +73 -64
  28. package/src/cli/subagent.ts +51 -58
  29. package/src/cli/tree.ts +177 -0
  30. package/src/hooks/InvocationContext.ts +26 -0
  31. package/src/hooks/LedgerCommit.ts +4 -2
  32. package/src/hooks/LedgerSnapshot.ts +2 -0
  33. package/src/hooks/SecurityValidator.ts +2 -0
  34. package/src/hooks/StopOrchestrator.ts +12 -2
  35. package/src/hooks/UserPromptOrchestrator.ts +3 -15
  36. package/src/hooks/handlers/context-digests.ts +21 -21
  37. package/src/hooks/handlers/inject-retrieval.ts +7 -5
  38. package/src/hooks/handlers/readme-sync.ts +6 -2
  39. package/src/hooks/handlers/session-name.ts +3 -12
  40. package/src/hooks/lib/agent-definition.ts +6 -16
  41. package/src/hooks/lib/agent-registry.ts +88 -0
  42. package/src/hooks/lib/agent.ts +47 -23
  43. package/src/hooks/lib/antigravity-inference.ts +84 -0
  44. package/src/hooks/lib/antigravity-transcript.ts +129 -0
  45. package/src/hooks/lib/claim-check.ts +13 -2
  46. package/src/hooks/lib/claim-log.ts +5 -1
  47. package/src/hooks/lib/claude-md.ts +21 -1
  48. package/src/hooks/lib/context.ts +5 -1
  49. package/src/hooks/lib/hook-turn.ts +25 -2
  50. package/src/hooks/lib/inference.ts +77 -1
  51. package/src/hooks/lib/invocation-context.ts +99 -0
  52. package/src/hooks/lib/ledger-hook.ts +14 -6
  53. package/src/hooks/lib/models.ts +34 -17
  54. package/src/hooks/lib/paths.ts +39 -6
  55. package/src/hooks/lib/prompt-capture.ts +28 -0
  56. package/src/hooks/lib/prompt-text.ts +1 -0
  57. package/src/hooks/lib/readme-sync.ts +3 -31
  58. package/src/hooks/lib/security-gate.ts +10 -3
  59. package/src/hooks/lib/semi-static.ts +13 -3
  60. package/src/hooks/lib/session-context.ts +3 -1
  61. package/src/hooks/lib/session-names.ts +20 -4
  62. package/src/hooks/lib/stop.ts +1 -0
  63. package/src/hooks/lib/transcript.ts +6 -3
  64. package/src/targets/agent-render.ts +3 -3
  65. package/src/targets/antigravity/install.ts +68 -0
  66. package/src/targets/antigravity/uninstall.ts +66 -0
  67. package/src/targets/lib.ts +81 -64
  68. package/src/targets/opencode/install.ts +2 -0
  69. package/src/targets/opencode/permission.ts +39 -0
  70. package/src/targets/opencode/uninstall.ts +9 -8
  71. package/src/tools/agent/algorithm-reflect.ts +70 -62
  72. package/src/tools/agent/algorithm-synthesize.ts +39 -14
  73. package/src/tools/agent/analyze.ts +31 -36
  74. package/src/tools/agent/handoff-note.ts +51 -54
  75. package/src/tools/agent/interaction.ts +32 -28
  76. package/src/tools/agent/project.ts +300 -287
  77. package/src/tools/agent/relationship-note.ts +40 -36
  78. package/src/tools/agent/relationship-reflect.ts +23 -32
  79. package/src/tools/agent/synthesize.ts +26 -27
  80. package/src/tools/agent/thread.ts +42 -51
  81. package/src/tools/agent/wisdom-frame.ts +45 -38
  82. package/src/tools/control-room/server.ts +0 -2
  83. package/src/tools/control-room/ui/dist/assets/{index-BZMDzWFa.js → index-mFpXpiBj.js} +1 -1
  84. package/src/tools/control-room/ui/dist/index.html +1 -1
  85. package/src/tools/control-room/ui/screens/log.tsx +10 -1
  86. package/src/tools/lib/command.ts +275 -0
  87. package/src/tools/subagent-doctor.ts +74 -9
  88. package/src/tools/token-cost.ts +18 -17
@@ -1,15 +1,20 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { isAbsolute, resolve } from "node:path";
4
- import type { AgentPlatform } from "../../hooks/lib/agent-definition";
4
+ import {
5
+ AGENT_REGISTRY,
6
+ type AgentName,
7
+ AGENT_NAMES as REGISTERED_AGENTS,
8
+ skillsDirOf,
9
+ } from "../../hooks/lib/agent-registry";
5
10
  import { palPkg, platform } from "../../hooks/lib/paths";
6
11
  import { nativeAgentsDir, staleShippedAgents } from "../../targets/lib";
7
12
  import { NO_SESSION_AGENT_MESSAGE } from "../session-agent";
8
13
  import { type Finding, type Fix, failing, optional, passed, warning } from "./finding";
9
14
 
10
- export type AgentName = "claude" | "opencode" | "cursor" | "copilot" | "codex";
15
+ export type { AgentName };
11
16
 
12
- const AGENT_NAMES: AgentName[] = ["claude", "codex", "copilot", "cursor", "opencode"];
17
+ const AGENT_NAMES: AgentName[] = [...REGISTERED_AGENTS].sort();
13
18
 
14
19
  export interface ToolCheck {
15
20
  name: string;
@@ -17,16 +22,11 @@ export interface ToolCheck {
17
22
  version?: string;
18
23
  }
19
24
 
20
- export interface DoctorResult {
25
+ export type DoctorResult = Record<AgentName, ToolCheck> & {
21
26
  bun: ToolCheck;
22
- claude: ToolCheck;
23
- opencode: ToolCheck;
24
- cursor: ToolCheck;
25
- copilot: ToolCheck;
26
- codex: ToolCheck;
27
27
  rtk: ToolCheck;
28
28
  hasAgent: boolean;
29
- }
29
+ };
30
30
 
31
31
  function checkTool(cmd: string, versionArgs: string[] = ["--version"]): ToolCheck {
32
32
  try {
@@ -69,33 +69,39 @@ function checkCursor(): ToolCheck {
69
69
  return checkTool("cursor");
70
70
  }
71
71
 
72
+ const AGENT_PROBES: Record<AgentName, () => ToolCheck> = {
73
+ claude: () => checkTool("claude"),
74
+ opencode: () => checkTool("opencode"),
75
+ cursor: checkCursor,
76
+ copilot: checkCopilot,
77
+ codex: () => checkTool("codex"),
78
+ antigravity: () => checkTool("agy"),
79
+ };
80
+
81
+ function agentChecks(
82
+ probe: (agent: AgentName) => ToolCheck
83
+ ): Record<AgentName, ToolCheck> {
84
+ return Object.fromEntries(
85
+ REGISTERED_AGENTS.map((agent) => [agent, probe(agent)])
86
+ ) as Record<AgentName, ToolCheck>;
87
+ }
88
+
72
89
  export function detectAgents(): DoctorResult {
90
+ const bun = { name: "bun", available: true, version: Bun.version };
73
91
  if (process.env.PAL_SKIP_DOCTOR === "1") {
74
92
  return {
75
- bun: { name: "bun", available: true, version: Bun.version },
76
- claude: { name: "claude", available: true },
77
- opencode: { name: "opencode", available: true },
78
- cursor: { name: "cursor", available: true },
79
- copilot: { name: "copilot", available: true },
80
- codex: { name: "codex", available: true },
93
+ ...agentChecks((agent) => ({ name: agent, available: true })),
94
+ bun,
81
95
  rtk: { name: "rtk", available: true },
82
96
  hasAgent: true,
83
97
  };
84
98
  }
85
- const claude = checkTool("claude");
86
- const opencode = checkTool("opencode");
87
- const cursor = checkCursor();
88
- const copilot = checkCopilot();
89
- const codex = checkTool("codex");
99
+ const agents = agentChecks((agent) => AGENT_PROBES[agent]());
90
100
  return {
91
- bun: { name: "bun", available: true, version: Bun.version },
92
- claude,
93
- opencode,
94
- cursor,
95
- copilot,
96
- codex,
101
+ ...agents,
102
+ bun,
97
103
  rtk: checkTool("rtk"),
98
- hasAgent: [claude, opencode, cursor, copilot, codex].some((tool) => tool.available),
104
+ hasAgent: Object.values(agents).some((tool) => tool.available),
99
105
  };
100
106
  }
101
107
 
@@ -103,46 +109,39 @@ export function installedAgents(result: DoctorResult): AgentName[] {
103
109
  return AGENT_NAMES.filter((name) => result[name].available);
104
110
  }
105
111
 
106
- interface AgentLayout {
107
- label: string;
108
- skillsDir: () => string;
112
+ interface AgentWiring {
109
113
  hookFile?: () => string;
114
+ /** The hook whose absence means PAL's context never arrives; LoadContext unless set. */
115
+ contextHook?: string;
110
116
  instructions?: { file: () => string; name: string };
111
117
  }
112
118
 
113
- const LAYOUT: Record<AgentName, AgentLayout> = {
119
+ const WIRING: Record<AgentName, AgentWiring> = {
114
120
  claude: {
115
- label: "Claude Code",
116
- skillsDir: () => resolve(platform.claudeDir(), "skills"),
117
121
  hookFile: () => resolve(platform.claudeDir(), "settings.json"),
118
122
  instructions: {
119
123
  file: () => resolve(platform.claudeDir(), "CLAUDE.md"),
120
124
  name: "CLAUDE.md",
121
125
  },
122
126
  },
123
- codex: {
124
- label: "Codex",
125
- skillsDir: () => resolve(platform.codexDir(), "skills"),
126
- hookFile: () => resolve(platform.codexDir(), "hooks.json"),
127
- },
128
- copilot: {
129
- label: "Copilot",
130
- skillsDir: () => resolve(platform.copilotDir(), "skills"),
131
- hookFile: () => resolve(platform.copilotDir(), "hooks", "pal-hooks.json"),
132
- },
133
- cursor: {
134
- label: "Cursor",
135
- skillsDir: () => resolve(platform.cursorDir(), "skills"),
136
- hookFile: () => resolve(platform.cursorDir(), "hooks.json"),
137
- },
138
- opencode: {
139
- label: "opencode",
140
- skillsDir: () => resolve(platform.agentsDir(), "skills"),
127
+ codex: { hookFile: () => resolve(platform.codexDir(), "hooks.json") },
128
+ copilot: { hookFile: () => resolve(platform.copilotDir(), "hooks", "pal-hooks.json") },
129
+ cursor: { hookFile: () => resolve(platform.cursorDir(), "hooks.json") },
130
+ opencode: {},
131
+ antigravity: {
132
+ hookFile: () => resolve(platform.antigravityPluginDir(), "hooks.json"),
133
+ contextHook: "InvocationContext",
134
+ instructions: {
135
+ file: () => resolve(platform.antigravityPluginDir(), "rules", "pal.md"),
136
+ name: "PAL plugin rule",
137
+ },
141
138
  },
142
139
  };
143
140
 
141
+ const labelOf = (agent: AgentName) => AGENT_REGISTRY[agent].label;
142
+
144
143
  const reinstall = (agent: AgentName): Fix => ({
145
- say: `Reinstall PAL for ${LAYOUT[agent].label}`,
144
+ say: `Reinstall PAL for ${labelOf(agent)}`,
146
145
  command: `pal cli install --${agent}`,
147
146
  external: false,
148
147
  });
@@ -207,8 +206,8 @@ function readHookCommands(file: string): string[] | "missing" | "unreadable" {
207
206
  }
208
207
  }
209
208
 
210
- function hookFindings(agent: AgentName, file: string): Finding[] {
211
- const label = LAYOUT[agent].label;
209
+ function hookFindings(agent: AgentName, file: string, contextHook: string): Finding[] {
210
+ const label = labelOf(agent);
212
211
  const commands = readHookCommands(file);
213
212
  if (commands === "unreadable")
214
213
  return [
@@ -223,7 +222,7 @@ function hookFindings(agent: AgentName, file: string): Finding[] {
223
222
  ),
224
223
  ];
225
224
  const pal = commands === "missing" ? [] : commands.filter((c) => isPalHook(c, agent));
226
- if (!pal.some((c) => c.includes("LoadContext")))
225
+ if (!pal.some((c) => c.includes(contextHook)))
227
226
  return [
228
227
  failing(
229
228
  `${agent}.hooks.missing`,
@@ -276,8 +275,8 @@ function opencodePluginFindings(): Finding[] {
276
275
  return [passed("opencode.hooks", "opencode plugin installed and current")];
277
276
  }
278
277
 
279
- function subagentFinding(agent: AgentPlatform): Finding {
280
- const label = LAYOUT[agent].label;
278
+ function subagentFinding(agent: AgentName): Finding {
279
+ const label = labelOf(agent);
281
280
  const stale = staleShippedAgents(nativeAgentsDir(agent), agent);
282
281
  if (stale.length === 0)
283
282
  return passed(`${agent}.subagents`, `${label}: subagents match this PAL version`);
@@ -294,16 +293,17 @@ function countSkills(dir: string): number {
294
293
  }
295
294
 
296
295
  function oneAgentFindings(agent: AgentName): Finding[] {
297
- const layout = LAYOUT[agent];
298
- const skills = countSkills(layout.skillsDir());
296
+ const layout = WIRING[agent];
297
+ const label = labelOf(agent);
298
+ const skills = countSkills(skillsDirOf(agent));
299
299
  const findings = [
300
300
  skills > 0
301
- ? passed(`${agent}.skills`, `${layout.label}: ${skills} skills`)
302
- : warning(`${agent}.skills`, `${layout.label} has no PAL skills`, reinstall(agent)),
301
+ ? passed(`${agent}.skills`, `${label}: ${skills} skills`)
302
+ : warning(`${agent}.skills`, `${label} has no PAL skills`, reinstall(agent)),
303
303
  ];
304
304
  findings.push(
305
305
  ...(layout.hookFile
306
- ? hookFindings(agent, layout.hookFile())
306
+ ? hookFindings(agent, layout.hookFile(), layout.contextHook ?? "LoadContext")
307
307
  : opencodePluginFindings()),
308
308
  subagentFinding(agent)
309
309
  );
@@ -345,7 +345,7 @@ export function rosterFindings(agents: AgentName[]): Finding[] {
345
345
  return [
346
346
  optional(
347
347
  "agents.others",
348
- `Other agents PAL supports — ${others.map((name) => LAYOUT[name].label).join(", ")}`,
348
+ `Other agents PAL supports — ${others.map(labelOf).join(", ")}`,
349
349
  {
350
350
  say: `install it, then run pal cli install --${others.length === 1 ? others[0] : "<agent>"}`,
351
351
  }
@@ -1,11 +1,6 @@
1
1
  /**
2
2
  * pal cli actor / pal cli machine — read and rename the two identities PAL keeps.
3
3
  *
4
- * pal cli actor Show this actor's label and id
5
- * pal cli actor label <name> Rename the actor — who caused a record
6
- * pal cli machine Show this install's label and id
7
- * pal cli machine label <name> Rename the machine — where a record was written
8
- *
9
4
  * Renaming touches no stored record: both subjects resolve a label on read, so
10
5
  * an id already written into a thread or a reflection reads under the new name
11
6
  * immediately. The registry entry is refreshed here so the change also travels
@@ -26,6 +21,7 @@ import {
26
21
  setLabel,
27
22
  } from "../hooks/lib/machine";
28
23
  import { log } from "../targets/lib";
24
+ import { type Group, group, leaf, runCommand, UsageError } from "../tools/lib/command";
29
25
 
30
26
  export type IdentitySubject = "actor" | "machine";
31
27
 
@@ -84,21 +80,31 @@ function rename(subject: IdentitySubject, name: string): number {
84
80
  return 0;
85
81
  }
86
82
 
87
- export function runIdentity(subject: IdentitySubject, args: string[]): number {
88
- const [action, ...rest] = args;
89
-
90
- if (!action) return show(subject);
83
+ function renameTo(subject: IdentitySubject, words: string[]): number {
84
+ const name = words.join(" ").trim();
85
+ if (!name) throw new UsageError("the name is empty");
86
+ return rename(subject, name);
87
+ }
91
88
 
92
- if (action === "label") {
93
- const name = rest.join(" ").trim();
94
- if (!name) {
95
- log.error(`Usage: pal cli ${subject} label <name>`);
96
- return 1;
97
- }
98
- return rename(subject, name);
99
- }
89
+ export function identityCommand(subject: IdentitySubject): Group {
90
+ const { noun } = SUBJECTS[subject];
91
+ return group({
92
+ summary: `Show or rename this ${subject} — ${noun}`,
93
+ fallback: "show",
94
+ commands: {
95
+ show: leaf({
96
+ summary: `Show this ${subject}'s label and id`,
97
+ run: () => show(subject),
98
+ }),
99
+ label: leaf({
100
+ summary: `Rename the ${subject}; stored records read under the new name at once`,
101
+ args: "<name...>",
102
+ run: ({ positionals }) => renameTo(subject, positionals),
103
+ }),
104
+ },
105
+ });
106
+ }
100
107
 
101
- log.error(`Unknown ${subject} action: ${action}`);
102
- log.info(`Usage: pal cli ${subject} [label <name>]`);
103
- return 1;
108
+ export function runIdentity(subject: IdentitySubject, args: string[]): Promise<number> {
109
+ return runCommand(identityCommand(subject), args, ["pal", "cli", subject]);
104
110
  }