@phnx-labs/agents-cli 1.20.51 → 1.20.53

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 (142) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +12 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/browser.js +215 -7
  5. package/dist/commands/cloud.d.ts +3 -0
  6. package/dist/commands/cloud.js +8 -1
  7. package/dist/commands/events.d.ts +1 -1
  8. package/dist/commands/events.js +2 -3
  9. package/dist/commands/exec.js +82 -3
  10. package/dist/commands/factory.js +8 -0
  11. package/dist/commands/feed.d.ts +38 -0
  12. package/dist/commands/feed.js +274 -0
  13. package/dist/commands/logs.d.ts +5 -1
  14. package/dist/commands/logs.js +248 -3
  15. package/dist/commands/mcp.js +7 -0
  16. package/dist/commands/memory.d.ts +9 -0
  17. package/dist/commands/memory.js +164 -0
  18. package/dist/commands/message.d.ts +11 -6
  19. package/dist/commands/message.js +140 -5
  20. package/dist/commands/routines.js +12 -0
  21. package/dist/commands/secrets-migrate.d.ts +2 -1
  22. package/dist/commands/secrets-migrate.js +88 -13
  23. package/dist/commands/secrets.d.ts +22 -0
  24. package/dist/commands/secrets.js +176 -42
  25. package/dist/commands/sessions.js +10 -1
  26. package/dist/commands/teams.js +4 -0
  27. package/dist/commands/worktree.js +4 -2
  28. package/dist/index.js +21 -22
  29. package/dist/lib/agents.js +249 -17
  30. package/dist/lib/answer-router.d.ts +75 -0
  31. package/dist/lib/answer-router.js +149 -0
  32. package/dist/lib/ask-classifier.d.ts +71 -0
  33. package/dist/lib/ask-classifier.js +197 -0
  34. package/dist/lib/browser/login-detection.d.ts +94 -0
  35. package/dist/lib/browser/login-detection.js +274 -0
  36. package/dist/lib/browser/profiles.d.ts +17 -8
  37. package/dist/lib/browser/profiles.js +27 -8
  38. package/dist/lib/browser/secret-ref.d.ts +10 -0
  39. package/dist/lib/browser/secret-ref.js +14 -0
  40. package/dist/lib/browser/service.js +14 -12
  41. package/dist/lib/cloud/antigravity.d.ts +0 -2
  42. package/dist/lib/cloud/antigravity.js +2 -17
  43. package/dist/lib/cloud/codex.js +3 -18
  44. package/dist/lib/cloud/rush.d.ts +15 -0
  45. package/dist/lib/cloud/rush.js +10 -16
  46. package/dist/lib/cloud/stream.js +2 -0
  47. package/dist/lib/cloud/types.d.ts +21 -0
  48. package/dist/lib/cloud/types.js +81 -0
  49. package/dist/lib/crabbox/cli.d.ts +1 -1
  50. package/dist/lib/crabbox/cli.js +12 -2
  51. package/dist/lib/crabbox/lease.d.ts +19 -0
  52. package/dist/lib/crabbox/lease.js +22 -11
  53. package/dist/lib/crabbox/progress.d.ts +62 -0
  54. package/dist/lib/crabbox/progress.js +129 -0
  55. package/dist/lib/crabbox/runtimes.d.ts +38 -1
  56. package/dist/lib/crabbox/runtimes.js +98 -5
  57. package/dist/lib/daemon.d.ts +12 -9
  58. package/dist/lib/daemon.js +32 -17
  59. package/dist/lib/events.d.ts +31 -5
  60. package/dist/lib/events.js +291 -101
  61. package/dist/lib/exec.js +20 -1
  62. package/dist/lib/feed-outcome.d.ts +101 -0
  63. package/dist/lib/feed-outcome.js +244 -0
  64. package/dist/lib/feed-policy.d.ts +30 -0
  65. package/dist/lib/feed-policy.js +133 -0
  66. package/dist/lib/feed.d.ts +180 -0
  67. package/dist/lib/feed.js +627 -0
  68. package/dist/lib/git.d.ts +17 -1
  69. package/dist/lib/git.js +20 -1
  70. package/dist/lib/hooks.js +529 -14
  71. package/dist/lib/hosts/passthrough.d.ts +3 -3
  72. package/dist/lib/hosts/passthrough.js +3 -3
  73. package/dist/lib/mailbox-gc.d.ts +22 -0
  74. package/dist/lib/mailbox-gc.js +161 -0
  75. package/dist/lib/mailbox.d.ts +26 -2
  76. package/dist/lib/mailbox.js +80 -5
  77. package/dist/lib/mcp.js +82 -0
  78. package/dist/lib/memory.d.ts +55 -0
  79. package/dist/lib/memory.js +274 -0
  80. package/dist/lib/notify.d.ts +16 -0
  81. package/dist/lib/notify.js +61 -0
  82. package/dist/lib/operator.d.ts +26 -0
  83. package/dist/lib/operator.js +107 -0
  84. package/dist/lib/plugins.d.ts +35 -0
  85. package/dist/lib/plugins.js +217 -0
  86. package/dist/lib/remote-agents-json.d.ts +14 -0
  87. package/dist/lib/remote-agents-json.js +94 -0
  88. package/dist/lib/resources/mcp.js +44 -0
  89. package/dist/lib/resources/memory.d.ts +15 -0
  90. package/dist/lib/resources/memory.js +46 -0
  91. package/dist/lib/resources/types.d.ts +2 -2
  92. package/dist/lib/rotate.js +2 -0
  93. package/dist/lib/runner.d.ts +43 -0
  94. package/dist/lib/runner.js +323 -74
  95. package/dist/lib/sandbox.js +6 -0
  96. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  97. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  98. package/dist/lib/secrets/agent.d.ts +21 -0
  99. package/dist/lib/secrets/agent.js +63 -1
  100. package/dist/lib/secrets/bundles.d.ts +33 -1
  101. package/dist/lib/secrets/bundles.js +76 -21
  102. package/dist/lib/secrets/icloud-import.d.ts +79 -0
  103. package/dist/lib/secrets/icloud-import.js +203 -0
  104. package/dist/lib/secrets/index.d.ts +142 -2
  105. package/dist/lib/secrets/index.js +695 -30
  106. package/dist/lib/secrets/remote.js +1 -1
  107. package/dist/lib/secrets/sync.js +1 -1
  108. package/dist/lib/session/active.d.ts +18 -0
  109. package/dist/lib/session/active.js +47 -17
  110. package/dist/lib/session/db.d.ts +9 -1
  111. package/dist/lib/session/db.js +18 -3
  112. package/dist/lib/session/discover.d.ts +13 -0
  113. package/dist/lib/session/discover.js +32 -2
  114. package/dist/lib/session/parse.d.ts +8 -0
  115. package/dist/lib/session/parse.js +42 -21
  116. package/dist/lib/session/remote-active.js +8 -89
  117. package/dist/lib/session/state.d.ts +11 -0
  118. package/dist/lib/session/state.js +50 -1
  119. package/dist/lib/session/tail.d.ts +23 -4
  120. package/dist/lib/session/tail.js +34 -16
  121. package/dist/lib/session/throughput.d.ts +30 -0
  122. package/dist/lib/session/throughput.js +86 -0
  123. package/dist/lib/shim-heal.d.ts +12 -3
  124. package/dist/lib/shim-heal.js +12 -6
  125. package/dist/lib/staleness/detectors/subagents.js +57 -3
  126. package/dist/lib/staleness/writers/hooks.js +7 -3
  127. package/dist/lib/staleness/writers/subagents.js +37 -6
  128. package/dist/lib/startup/command-registry.d.ts +2 -0
  129. package/dist/lib/startup/command-registry.js +4 -0
  130. package/dist/lib/state.d.ts +2 -0
  131. package/dist/lib/state.js +25 -8
  132. package/dist/lib/subagents.d.ts +52 -0
  133. package/dist/lib/subagents.js +315 -12
  134. package/dist/lib/teams/agents.js +6 -3
  135. package/dist/lib/teams/worktree.d.ts +8 -0
  136. package/dist/lib/teams/worktree.js +8 -0
  137. package/dist/lib/types.d.ts +20 -2
  138. package/dist/lib/versions.js +38 -48
  139. package/dist/lib/whats-new.d.ts +5 -3
  140. package/dist/lib/whats-new.js +25 -5
  141. package/package.json +4 -3
  142. package/scripts/postinstall.js +61 -1
package/dist/lib/state.js CHANGED
@@ -89,6 +89,7 @@ const TEAMS_AGENTS_DIR = path.join(HISTORY_DIR, 'teams', 'agents');
89
89
  const BACKUPS_DIR = path.join(HISTORY_DIR, 'backups');
90
90
  const TRASH_DIR = path.join(HISTORY_DIR, 'trash');
91
91
  const MAILBOX_DIR = path.join(HISTORY_DIR, 'mailbox');
92
+ const FEED_DIR = path.join(HISTORY_DIR, 'feed');
92
93
  // Cache bucket (regenerable).
93
94
  const SHIMS_DIR = path.join(CACHE_DIR, 'shims');
94
95
  const HOOK_SHIMS_DIR = path.join(SHIMS_DIR, 'hooks');
@@ -313,6 +314,8 @@ export function getProjectRoutinesDir(cwd = process.cwd()) {
313
314
  export function getRunsDir() { return RUNS_DIR; }
314
315
  /** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
315
316
  export function getMailboxRootDir() { return MAILBOX_DIR; }
317
+ /** Root for open-block feed records (~/.agents/.history/feed/). */
318
+ export function getFeedDir() { return FEED_DIR; }
316
319
  /** Path to installed agent CLI binaries (~/.agents/.history/versions/). */
317
320
  export function getVersionsDir() { return VERSIONS_DIR; }
318
321
  /** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
@@ -569,25 +572,35 @@ function writeIfChanged(filePath, content) {
569
572
  /**
570
573
  * Partition the in-memory Meta across three files by sync-domain:
571
574
  * - central `~/.agents/agents.yaml` — portable, everything else
572
- * - device `~/.agents/devices/<machine>/agents.yaml` — `agents:` pins (per-device)
575
+ * - device `~/.agents/devices/<machine>/agents.yaml` — `agents:` pins +
576
+ * `defaultBrowserProfile:` (both per-device)
573
577
  * - history `~/.agents/.history/version-resources.json` — `versions:` (machine-local)
574
578
  * All callers funnel through writeMeta → here, so nothing else changes. Empty
575
579
  * `agents:` / `versions:` are not written (no empty committed files).
576
580
  */
577
581
  function writeMetaUnlocked(meta) {
578
- const { agents, versions, ...central } = meta;
582
+ const { agents, versions, defaultBrowserProfile, ...central } = meta;
579
583
  // Write the machine-local files FIRST, then strip central — so a crash mid-write
580
584
  // never removes pins/versions from central before they're persisted elsewhere.
581
585
  const devicePath = getDeviceMetaPath();
582
- if (agents && Object.keys(agents).length > 0) {
586
+ const hasAgents = !!agents && Object.keys(agents).length > 0;
587
+ const hasDefaultBrowser = !!defaultBrowserProfile;
588
+ if (hasAgents || hasDefaultBrowser) {
589
+ // Device-local doc carries `agents:` pins and `defaultBrowserProfile:` — both
590
+ // are per-machine and must never land in central agents.yaml (which syncs).
591
+ const deviceDoc = {};
592
+ if (hasAgents)
593
+ deviceDoc.agents = agents;
594
+ if (hasDefaultBrowser)
595
+ deviceDoc.defaultBrowserProfile = defaultBrowserProfile;
583
596
  fs.mkdirSync(path.dirname(devicePath), { recursive: true });
584
- writeIfChanged(devicePath, META_HEADER + yaml.stringify({ agents }));
597
+ writeIfChanged(devicePath, META_HEADER + yaml.stringify(deviceDoc));
585
598
  }
586
599
  else if (fs.existsSync(devicePath)) {
587
- // Every pin was cleared. Persist the emptied map instead of skipping the
588
- // write — otherwise the stale device file survives and overlayMachineLocal
589
- // re-applies the removed pin on the next read, leaving a dangling default
590
- // (e.g. a launcher pointing at a version that was just uninstalled).
600
+ // Every device-local value was cleared. Persist the emptied doc instead of
601
+ // skipping the write — otherwise the stale device file survives and
602
+ // overlayMachineLocal re-applies the removed pin/default on the next read,
603
+ // leaving a dangling value (e.g. a default pointing at a deleted profile).
591
604
  writeIfChanged(devicePath, META_HEADER + yaml.stringify({ agents: {} }));
592
605
  }
593
606
  if (versions && Object.keys(versions).length > 0) {
@@ -602,6 +615,8 @@ function writeMetaUnlocked(meta) {
602
615
  * Overlay this machine's local state onto a central-portable Meta:
603
616
  * - `agents:` from the device file (device wins; the union both preserves the
604
617
  * one-level merge and self-heals a pre-migration central that still has pins)
618
+ * - `defaultBrowserProfile:` from the device file (device is the sole source;
619
+ * the field is stripped from central on write, so nothing to merge against)
605
620
  * - `versions:` from the history JSON (wholesale replace; falls back to
606
621
  * whatever central carried when the history file doesn't exist yet)
607
622
  */
@@ -612,6 +627,8 @@ function overlayMachineLocal(meta) {
612
627
  const dm = yaml.parse(fs.readFileSync(devicePath, 'utf-8'));
613
628
  if (dm?.agents)
614
629
  meta.agents = { ...meta.agents, ...dm.agents };
630
+ if (dm?.defaultBrowserProfile)
631
+ meta.defaultBrowserProfile = dm.defaultBrowserProfile;
615
632
  }
616
633
  catch { /* ignore malformed device file */ }
617
634
  }
@@ -56,6 +56,57 @@ export declare function transformSubagentForClaude(subagentDir: string): string;
56
56
  * See https://docs.factory.ai/cli/configuration/custom-droids.
57
57
  */
58
58
  export declare function transformSubagentForDroid(subagentDir: string): string;
59
+ /**
60
+ * Transform a subagent into an OpenCode agent markdown file.
61
+ *
62
+ * OpenCode loads agents from ~/.config/opencode/agents/*.md (global) with
63
+ * YAML frontmatter (description required; mode: subagent for invocable
64
+ * subagents) and a prompt body. File stem becomes the agent name.
65
+ * https://opencode.ai/docs/agents/
66
+ */
67
+ export declare function transformSubagentForOpenCode(subagentDir: string): string;
68
+ /** Managed parent agent file for Kimi (underscore prefix avoids clobbering a user subagent named agents-cli). */
69
+ export declare const KIMI_SUBAGENTS_PARENT_FILE = "_agents-cli.yaml";
70
+ export interface KimiSubagentFiles {
71
+ /** Agent YAML content (uses system_prompt_path, not inline system_prompt). */
72
+ yaml: string;
73
+ /** Markdown body written next to the YAML and referenced by system_prompt_path. */
74
+ systemPrompt: string;
75
+ /** Sibling prompt filename, e.g. `reviewer.system.md`. */
76
+ systemPromptFileName: string;
77
+ }
78
+ /**
79
+ * Transform a subagent into Kimi Code agent files.
80
+ *
81
+ * Kimi's schema (agentspec) only accepts `system_prompt_path` (path relative to
82
+ * the agent YAML) — there is no inline `system_prompt` key. We write:
83
+ * ~/.kimi-code/agents/<name>.yaml — agent YAML
84
+ * ~/.kimi-code/agents/<name>.system.md — system prompt body
85
+ * plus a managed parent `_agents-cli.yaml` that lists them under agent.subagents
86
+ * for `kimi --agent-file …/_agents-cli.yaml`.
87
+ * https://moonshotai.github.io/kimi-cli/en/customization/agents.html
88
+ */
89
+ export declare function transformSubagentForKimi(subagentDir: string, fileBaseName: string): KimiSubagentFiles;
90
+ /**
91
+ * Build the managed parent agent YAML that declares all installed subagents
92
+ * so `kimi --agent-file ~/.kimi-code/agents/_agents-cli.yaml` can launch them.
93
+ */
94
+ export declare function buildKimiSubagentsParentYaml(entries: Array<{
95
+ name: string;
96
+ description: string;
97
+ relativePath: string;
98
+ }>): string;
99
+ /** Write a Kimi subagent YAML + sibling system-prompt md into agentsDir. */
100
+ export declare function writeKimiSubagentFiles(agentsDir: string, subagentDir: string, name: string): void;
101
+ /**
102
+ * Transform a subagent into a Codex custom-agent TOML file.
103
+ *
104
+ * Codex loads standalone TOML under ~/.codex/agents/*.toml. Required fields:
105
+ * name, description, developer_instructions. Optional model maps from our
106
+ * frontmatter when present.
107
+ * https://developers.openai.com/codex/subagents (custom agents section)
108
+ */
109
+ export declare function transformSubagentForCodex(subagentDir: string): string;
59
110
  /**
60
111
  * Sync a subagent to an OpenClaw workspace
61
112
  * Copies full directory, renames AGENT.md to AGENTS.md
@@ -85,6 +136,7 @@ export declare function subagentContentMatches(installedDir: string, sourceDir:
85
136
  /**
86
137
  * List subagents installed to a specific agent's home
87
138
  * Claude: scans ~/.claude/agents/{name}.md
139
+ * Kimi: scans ~/.kimi-code/agents/{name}.yaml (+ sibling .system.md)
88
140
  * OpenClaw: scans ~/.openclaw/{name}/AGENTS.md
89
141
  */
90
142
  export declare function listSubagentsForAgent(agentId: AgentId, home: string): InstalledSubagent[];
@@ -262,6 +262,153 @@ export function transformSubagentForDroid(subagentDir) {
262
262
  }
263
263
  return result;
264
264
  }
265
+ /**
266
+ * Transform a subagent into an OpenCode agent markdown file.
267
+ *
268
+ * OpenCode loads agents from ~/.config/opencode/agents/*.md (global) with
269
+ * YAML frontmatter (description required; mode: subagent for invocable
270
+ * subagents) and a prompt body. File stem becomes the agent name.
271
+ * https://opencode.ai/docs/agents/
272
+ */
273
+ export function transformSubagentForOpenCode(subagentDir) {
274
+ const agentMd = path.join(subagentDir, 'AGENT.md');
275
+ const frontmatter = parseSubagentFrontmatter(agentMd);
276
+ const body = getSubagentBody(agentMd);
277
+ if (!frontmatter) {
278
+ throw new Error(`Invalid AGENT.md in ${subagentDir}`);
279
+ }
280
+ const fm = {
281
+ description: frontmatter.description,
282
+ mode: 'subagent',
283
+ };
284
+ if (frontmatter.model)
285
+ fm.model = frontmatter.model;
286
+ let systemPrompt = body.trim();
287
+ const files = fs.readdirSync(subagentDir)
288
+ .filter(f => f.endsWith('.md') && f !== 'AGENT.md')
289
+ .sort();
290
+ for (const file of files) {
291
+ const content = fs.readFileSync(path.join(subagentDir, file), 'utf-8').trim();
292
+ const sectionName = file.replace('.md', '');
293
+ const title = sectionName.charAt(0).toUpperCase() + sectionName.slice(1).toLowerCase();
294
+ systemPrompt += `\n\n## ${title}\n\n${content}`;
295
+ }
296
+ return `---\n${yaml.stringify(fm).trim()}\n---\n\n${systemPrompt}\n`;
297
+ }
298
+ /** Managed parent agent file for Kimi (underscore prefix avoids clobbering a user subagent named agents-cli). */
299
+ export const KIMI_SUBAGENTS_PARENT_FILE = '_agents-cli.yaml';
300
+ /**
301
+ * Transform a subagent into Kimi Code agent files.
302
+ *
303
+ * Kimi's schema (agentspec) only accepts `system_prompt_path` (path relative to
304
+ * the agent YAML) — there is no inline `system_prompt` key. We write:
305
+ * ~/.kimi-code/agents/<name>.yaml — agent YAML
306
+ * ~/.kimi-code/agents/<name>.system.md — system prompt body
307
+ * plus a managed parent `_agents-cli.yaml` that lists them under agent.subagents
308
+ * for `kimi --agent-file …/_agents-cli.yaml`.
309
+ * https://moonshotai.github.io/kimi-cli/en/customization/agents.html
310
+ */
311
+ export function transformSubagentForKimi(subagentDir, fileBaseName) {
312
+ const agentMd = path.join(subagentDir, 'AGENT.md');
313
+ const frontmatter = parseSubagentFrontmatter(agentMd);
314
+ const body = getSubagentBody(agentMd);
315
+ if (!frontmatter) {
316
+ throw new Error(`Invalid AGENT.md in ${subagentDir}`);
317
+ }
318
+ let systemPrompt = body.trim();
319
+ const files = fs.readdirSync(subagentDir)
320
+ .filter(f => f.endsWith('.md') && f !== 'AGENT.md')
321
+ .sort();
322
+ for (const file of files) {
323
+ const content = fs.readFileSync(path.join(subagentDir, file), 'utf-8').trim();
324
+ const sectionName = file.replace('.md', '');
325
+ const title = sectionName.charAt(0).toUpperCase() + sectionName.slice(1).toLowerCase();
326
+ systemPrompt += `\n\n## ${title}\n\n${content}`;
327
+ }
328
+ const systemPromptFileName = `${fileBaseName}.system.md`;
329
+ const doc = {
330
+ version: 1,
331
+ agent: {
332
+ extend: 'default',
333
+ name: frontmatter.name,
334
+ description: frontmatter.description,
335
+ system_prompt_path: `./${systemPromptFileName}`,
336
+ },
337
+ };
338
+ if (frontmatter.model) {
339
+ doc.agent.model = frontmatter.model;
340
+ }
341
+ return {
342
+ yaml: yaml.stringify(doc),
343
+ systemPrompt,
344
+ systemPromptFileName,
345
+ };
346
+ }
347
+ /**
348
+ * Build the managed parent agent YAML that declares all installed subagents
349
+ * so `kimi --agent-file ~/.kimi-code/agents/_agents-cli.yaml` can launch them.
350
+ */
351
+ export function buildKimiSubagentsParentYaml(entries) {
352
+ const subagents = {};
353
+ for (const e of entries) {
354
+ subagents[e.name] = { path: e.relativePath, description: e.description };
355
+ }
356
+ return yaml.stringify({
357
+ version: 1,
358
+ agent: {
359
+ extend: 'default',
360
+ name: 'agents-cli',
361
+ description: 'Managed parent agent listing agents-cli synced subagents',
362
+ subagents,
363
+ },
364
+ });
365
+ }
366
+ /** Write a Kimi subagent YAML + sibling system-prompt md into agentsDir. */
367
+ export function writeKimiSubagentFiles(agentsDir, subagentDir, name) {
368
+ const { yaml: yml, systemPrompt, systemPromptFileName } = transformSubagentForKimi(subagentDir, name);
369
+ fs.mkdirSync(agentsDir, { recursive: true });
370
+ fs.writeFileSync(safeJoin(agentsDir, `${name}.yaml`), yml);
371
+ fs.writeFileSync(safeJoin(agentsDir, systemPromptFileName), systemPrompt);
372
+ }
373
+ /**
374
+ * Transform a subagent into a Codex custom-agent TOML file.
375
+ *
376
+ * Codex loads standalone TOML under ~/.codex/agents/*.toml. Required fields:
377
+ * name, description, developer_instructions. Optional model maps from our
378
+ * frontmatter when present.
379
+ * https://developers.openai.com/codex/subagents (custom agents section)
380
+ */
381
+ export function transformSubagentForCodex(subagentDir) {
382
+ const agentMd = path.join(subagentDir, 'AGENT.md');
383
+ const frontmatter = parseSubagentFrontmatter(agentMd);
384
+ const body = getSubagentBody(agentMd);
385
+ if (!frontmatter) {
386
+ throw new Error(`Invalid AGENT.md in ${subagentDir}`);
387
+ }
388
+ // Append other .md files into the developer_instructions body.
389
+ let instructions = body.trim();
390
+ const files = fs.readdirSync(subagentDir)
391
+ .filter(f => f.endsWith('.md') && f !== 'AGENT.md')
392
+ .sort();
393
+ for (const file of files) {
394
+ const content = fs.readFileSync(path.join(subagentDir, file), 'utf-8').trim();
395
+ const sectionName = file.replace('.md', '');
396
+ const title = sectionName.charAt(0).toUpperCase() + sectionName.slice(1).toLowerCase();
397
+ instructions += `\n\n## ${title}\n\n${content}`;
398
+ }
399
+ // Escape TOML multi-line string (""") content — only """ needs escaping.
400
+ const safeInstructions = instructions.replace(/"""/g, '\\"""');
401
+ const safeName = frontmatter.name.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
402
+ const safeDesc = frontmatter.description.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
403
+ let toml = `name = "${safeName}"\n`;
404
+ toml += `description = "${safeDesc}"\n`;
405
+ if (frontmatter.model) {
406
+ const safeModel = String(frontmatter.model).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
407
+ toml += `model = "${safeModel}"\n`;
408
+ }
409
+ toml += `developer_instructions = """\n${safeInstructions}\n"""\n`;
410
+ return toml;
411
+ }
265
412
  /**
266
413
  * Sync a subagent to an OpenClaw workspace
267
414
  * Copies full directory, renames AGENT.md to AGENTS.md
@@ -291,9 +438,10 @@ export function syncSubagentToOpenclaw(subagentDir, targetDir) {
291
438
  * Install a subagent to a specific agent's home
292
439
  */
293
440
  export function installSubagentToAgent(subagentDir, subagentName, agent, agentHome) {
294
- if (agent === 'claude') {
295
- // Claude: flatten to single .md file
296
- const agentsDir = path.join(agentHome, '.claude', 'agents');
441
+ if (agent === 'claude' || agent === 'grok') {
442
+ // Claude / Grok: flatten to single .md under ~/.claude/agents or ~/.grok/agents
443
+ // Grok discovers user agent defs from ~/.grok/agents/*.md (same shape as Claude).
444
+ const agentsDir = path.join(agentHome, agent === 'grok' ? '.grok' : '.claude', 'agents');
297
445
  if (!fs.existsSync(agentsDir)) {
298
446
  fs.mkdirSync(agentsDir, { recursive: true });
299
447
  }
@@ -306,6 +454,44 @@ export function installSubagentToAgent(subagentDir, subagentName, agent, agentHo
306
454
  return { success: false, error: String(err) };
307
455
  }
308
456
  }
457
+ else if (agent === 'codex') {
458
+ // Codex: standalone TOML under ~/.codex/agents/<name>.toml
459
+ const agentsDir = path.join(agentHome, '.codex', 'agents');
460
+ if (!fs.existsSync(agentsDir)) {
461
+ fs.mkdirSync(agentsDir, { recursive: true });
462
+ }
463
+ try {
464
+ const toml = transformSubagentForCodex(subagentDir);
465
+ fs.writeFileSync(safeJoin(agentsDir, `${subagentName}.toml`), toml);
466
+ return { success: true };
467
+ }
468
+ catch (err) {
469
+ return { success: false, error: String(err) };
470
+ }
471
+ }
472
+ else if (agent === 'kimi') {
473
+ // Kimi: YAML + sibling system-prompt md under ~/.kimi-code/agents/
474
+ try {
475
+ writeKimiSubagentFiles(path.join(agentHome, '.kimi-code', 'agents'), subagentDir, subagentName);
476
+ return { success: true };
477
+ }
478
+ catch (err) {
479
+ return { success: false, error: String(err) };
480
+ }
481
+ }
482
+ else if (agent === 'opencode') {
483
+ // OpenCode: markdown agents under ~/.config/opencode/agents/
484
+ const agentsDir = path.join(agentHome, '.config', 'opencode', 'agents');
485
+ if (!fs.existsSync(agentsDir))
486
+ fs.mkdirSync(agentsDir, { recursive: true });
487
+ try {
488
+ fs.writeFileSync(safeJoin(agentsDir, `${subagentName}.md`), transformSubagentForOpenCode(subagentDir));
489
+ return { success: true };
490
+ }
491
+ catch (err) {
492
+ return { success: false, error: String(err) };
493
+ }
494
+ }
309
495
  else if (agent === 'openclaw') {
310
496
  // OpenClaw: copy full directory
311
497
  const targetDir = safeJoin(path.join(agentHome, '.openclaw'), subagentName);
@@ -321,13 +507,37 @@ export function installSubagentToAgent(subagentDir, subagentName, agent, agentHo
321
507
  */
322
508
  export function removeSubagentFromAgent(subagentName, agent, agentHome) {
323
509
  try {
324
- if (agent === 'claude') {
325
- const targetPath = safeJoin(path.join(agentHome, '.claude', 'agents'), `${subagentName}.md`);
510
+ if (agent === 'claude' || agent === 'grok') {
511
+ const agentsRoot = agent === 'grok' ? '.grok' : '.claude';
512
+ const targetPath = safeJoin(path.join(agentHome, agentsRoot, 'agents'), `${subagentName}.md`);
513
+ if (fs.existsSync(targetPath)) {
514
+ fs.unlinkSync(targetPath);
515
+ }
516
+ return { success: true };
517
+ }
518
+ else if (agent === 'codex') {
519
+ const targetPath = safeJoin(path.join(agentHome, '.codex', 'agents'), `${subagentName}.toml`);
326
520
  if (fs.existsSync(targetPath)) {
327
521
  fs.unlinkSync(targetPath);
328
522
  }
329
523
  return { success: true };
330
524
  }
525
+ else if (agent === 'kimi') {
526
+ const agentsDir = path.join(agentHome, '.kimi-code', 'agents');
527
+ const targetPath = safeJoin(agentsDir, `${subagentName}.yaml`);
528
+ const promptPath = safeJoin(agentsDir, `${subagentName}.system.md`);
529
+ if (fs.existsSync(targetPath))
530
+ fs.unlinkSync(targetPath);
531
+ if (fs.existsSync(promptPath))
532
+ fs.unlinkSync(promptPath);
533
+ return { success: true };
534
+ }
535
+ else if (agent === 'opencode') {
536
+ const targetPath = safeJoin(path.join(agentHome, '.config', 'opencode', 'agents'), `${subagentName}.md`);
537
+ if (fs.existsSync(targetPath))
538
+ fs.unlinkSync(targetPath);
539
+ return { success: true };
540
+ }
331
541
  else if (agent === 'openclaw') {
332
542
  const targetDir = safeJoin(path.join(agentHome, '.openclaw'), subagentName);
333
543
  if (fs.existsSync(targetDir)) {
@@ -373,13 +583,14 @@ export function subagentContentMatches(installedDir, sourceDir) {
373
583
  /**
374
584
  * List subagents installed to a specific agent's home
375
585
  * Claude: scans ~/.claude/agents/{name}.md
586
+ * Kimi: scans ~/.kimi-code/agents/{name}.yaml (+ sibling .system.md)
376
587
  * OpenClaw: scans ~/.openclaw/{name}/AGENTS.md
377
588
  */
378
589
  export function listSubagentsForAgent(agentId, home) {
379
590
  const subagents = [];
380
- if (agentId === 'claude') {
381
- // Claude: flat .md files in agents/
382
- const agentsDir = path.join(home, '.claude', 'agents');
591
+ if (agentId === 'claude' || agentId === 'grok') {
592
+ // Claude / Grok: flat .md files in agents/
593
+ const agentsDir = path.join(home, agentId === 'grok' ? '.grok' : '.claude', 'agents');
383
594
  if (!fs.existsSync(agentsDir))
384
595
  return subagents;
385
596
  for (const file of fs.readdirSync(agentsDir)) {
@@ -400,6 +611,51 @@ export function listSubagentsForAgent(agentId, home) {
400
611
  });
401
612
  }
402
613
  }
614
+ else if (agentId === 'kimi') {
615
+ // Kimi: flat YAML under .kimi-code/agents/ (exclude managed parent _*.yaml)
616
+ const agentsDir = path.join(home, '.kimi-code', 'agents');
617
+ if (!fs.existsSync(agentsDir))
618
+ return subagents;
619
+ for (const file of fs.readdirSync(agentsDir)) {
620
+ if (!file.endsWith('.yaml') || file.startsWith('_'))
621
+ continue;
622
+ const filePath = path.join(agentsDir, file);
623
+ if (!fs.statSync(filePath).isFile())
624
+ continue;
625
+ const name = file.replace(/\.yaml$/, '');
626
+ let description = '';
627
+ try {
628
+ const parsed = yaml.parse(fs.readFileSync(filePath, 'utf-8'));
629
+ description = parsed?.agent?.description ?? '';
630
+ }
631
+ catch { /* leave description empty */ }
632
+ const files = [file];
633
+ const promptFile = `${name}.system.md`;
634
+ if (fs.existsSync(path.join(agentsDir, promptFile)))
635
+ files.push(promptFile);
636
+ subagents.push({
637
+ name,
638
+ path: filePath,
639
+ files,
640
+ frontmatter: { name, description },
641
+ });
642
+ }
643
+ }
644
+ else if (agentId === 'opencode') {
645
+ const agentsDir = path.join(home, '.config', 'opencode', 'agents');
646
+ if (!fs.existsSync(agentsDir))
647
+ return subagents;
648
+ for (const file of fs.readdirSync(agentsDir)) {
649
+ if (!file.endsWith('.md'))
650
+ continue;
651
+ const filePath = path.join(agentsDir, file);
652
+ if (!fs.statSync(filePath).isFile())
653
+ continue;
654
+ const name = file.replace(/\.md$/, '');
655
+ const frontmatter = parseSubagentFrontmatter(filePath) ?? { name, description: '' };
656
+ subagents.push({ name, path: filePath, files: [file], frontmatter });
657
+ }
658
+ }
403
659
  else if (agentId === 'openclaw') {
404
660
  // OpenClaw: directories with AGENTS.md
405
661
  const openclawDir = path.join(home, '.openclaw');
@@ -462,8 +718,8 @@ export function diffVersionSubagents(agent, version) {
462
718
  }
463
719
  }
464
720
  // Check what's installed
465
- if (agent === 'claude') {
466
- const agentsDir = path.join(versionHome, '.claude', 'agents');
721
+ if (agent === 'claude' || agent === 'grok') {
722
+ const agentsDir = path.join(versionHome, agent === 'grok' ? '.grok' : '.claude', 'agents');
467
723
  if (fs.existsSync(agentsDir)) {
468
724
  for (const file of fs.readdirSync(agentsDir)) {
469
725
  if (!file.endsWith('.md'))
@@ -475,6 +731,31 @@ export function diffVersionSubagents(agent, version) {
475
731
  }
476
732
  }
477
733
  }
734
+ else if (agent === 'kimi') {
735
+ const agentsDir = path.join(versionHome, '.kimi-code', 'agents');
736
+ if (fs.existsSync(agentsDir)) {
737
+ for (const file of fs.readdirSync(agentsDir)) {
738
+ if (!file.endsWith('.yaml') || file.startsWith('_'))
739
+ continue;
740
+ const name = path.basename(file, '.yaml');
741
+ if (!discovered.has(name)) {
742
+ orphans.push(name);
743
+ }
744
+ }
745
+ }
746
+ }
747
+ else if (agent === 'opencode') {
748
+ const agentsDir = path.join(versionHome, '.config', 'opencode', 'agents');
749
+ if (fs.existsSync(agentsDir)) {
750
+ for (const file of fs.readdirSync(agentsDir)) {
751
+ if (!file.endsWith('.md'))
752
+ continue;
753
+ const name = path.basename(file, '.md');
754
+ if (!discovered.has(name))
755
+ orphans.push(name);
756
+ }
757
+ }
758
+ }
478
759
  else if (agent === 'openclaw') {
479
760
  const openclawDir = path.join(versionHome, '.openclaw');
480
761
  if (fs.existsSync(openclawDir)) {
@@ -516,8 +797,30 @@ export function removeSubagentFromVersion(agent, version, subagentName) {
516
797
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
517
798
  const trashDir = path.join(getTrashSubagentsDir(), agent, version, subagentName);
518
799
  try {
519
- if (agent === 'claude') {
520
- const targetPath = path.join(versionHome, '.claude', 'agents', `${subagentName}.md`);
800
+ if (agent === 'claude' || agent === 'grok') {
801
+ const agentsRoot = agent === 'grok' ? '.grok' : '.claude';
802
+ const targetPath = path.join(versionHome, agentsRoot, 'agents', `${subagentName}.md`);
803
+ if (fs.existsSync(targetPath)) {
804
+ fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
805
+ fs.renameSync(targetPath, path.join(trashDir, `${subagentName}.md.${stamp}`));
806
+ }
807
+ }
808
+ else if (agent === 'kimi') {
809
+ const agentsDir = path.join(versionHome, '.kimi-code', 'agents');
810
+ const yamlPath = path.join(agentsDir, `${subagentName}.yaml`);
811
+ const promptPath = path.join(agentsDir, `${subagentName}.system.md`);
812
+ if (fs.existsSync(yamlPath) || fs.existsSync(promptPath)) {
813
+ fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
814
+ if (fs.existsSync(yamlPath)) {
815
+ fs.renameSync(yamlPath, path.join(trashDir, `${subagentName}.yaml.${stamp}`));
816
+ }
817
+ if (fs.existsSync(promptPath)) {
818
+ fs.renameSync(promptPath, path.join(trashDir, `${subagentName}.system.md.${stamp}`));
819
+ }
820
+ }
821
+ }
822
+ else if (agent === 'opencode') {
823
+ const targetPath = path.join(versionHome, '.config', 'opencode', 'agents', `${subagentName}.md`);
521
824
  if (fs.existsSync(targetPath)) {
522
825
  fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
523
826
  fs.renameSync(targetPath, path.join(trashDir, `${subagentName}.md.${stamp}`));
@@ -1693,6 +1693,7 @@ export class AgentManager {
1693
1693
  ];
1694
1694
  if (model)
1695
1695
  args.push('--model', model);
1696
+ args.push('--env', 'AGENTS_RUNTIME=teams');
1696
1697
  return args;
1697
1698
  }
1698
1699
  buildCommand(agentType, prompt, mode, model, cwd = null, sessionId = null, effort = 'medium', version = null, profileName = null) {
@@ -1704,9 +1705,11 @@ export class AgentManager {
1704
1705
  ];
1705
1706
  if (cwd)
1706
1707
  cmd.push('--cwd', cwd);
1707
- // Pin Claude's session UUID to our agent_id so its session file lands at
1708
- // ~/.claude/projects/.../<agent_id>.jsonl unified identity for status polling.
1709
- if (agentType === 'claude' && sessionId) {
1708
+ // Pin the session UUID to our agent_id so buildExecEnv keys
1709
+ // AGENTS_MAILBOX_DIR by the same id mailboxIdForActiveSession returns.
1710
+ // Claude also forwards --session-id to its CLI (unified identity);
1711
+ // other agents ignore the flag but still get the correct mailbox dir.
1712
+ if (sessionId) {
1710
1713
  cmd.push('--session-id', sessionId);
1711
1714
  }
1712
1715
  // Claude: grant access to the teammate's working directory.
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Check if `dir` is inside a git repository (**async, worktree-correct**).
3
+ *
4
+ * Shells out to `git rev-parse --git-dir`, so it returns true from any
5
+ * subdirectory and for linked worktrees. Distinct from the synchronous,
6
+ * root-only `isGitRepo` in `lib/git.ts` (a `.git`-existence check): different
7
+ * semantics, so the two are intentionally **not** merged.
8
+ */
1
9
  export declare function isGitRepo(dir: string): Promise<boolean>;
2
10
  export declare function getGitRoot(dir: string): Promise<string>;
3
11
  /**
@@ -11,6 +11,14 @@ import * as path from 'path';
11
11
  import { safeJoin } from '../paths.js';
12
12
  const execFileAsync = promisify(execFile);
13
13
  const WORKTREE_NAME_RE = /^[A-Za-z0-9_-]+$/;
14
+ /**
15
+ * Check if `dir` is inside a git repository (**async, worktree-correct**).
16
+ *
17
+ * Shells out to `git rev-parse --git-dir`, so it returns true from any
18
+ * subdirectory and for linked worktrees. Distinct from the synchronous,
19
+ * root-only `isGitRepo` in `lib/git.ts` (a `.git`-existence check): different
20
+ * semantics, so the two are intentionally **not** merged.
21
+ */
14
22
  export async function isGitRepo(dir) {
15
23
  try {
16
24
  await execFileAsync('git', ['rev-parse', '--git-dir'], { cwd: dir });
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type { CloudProviderId } from './cloud/types.js';
9
9
  /** Unique identifier for a supported AI coding agent. */
10
- export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid';
10
+ export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid' | 'hermes' | 'forge';
11
11
  /** How `agents run <agent>` chooses an installed version when none is pinned. */
12
12
  export type RunStrategy = 'pinned' | 'available' | 'balanced';
13
13
  /** Per-agent run strategy config. */
@@ -148,6 +148,12 @@ export interface AgentConfig {
148
148
  subagents: Capability;
149
149
  rules: RulesCapability;
150
150
  workflows: Capability;
151
+ /**
152
+ * Portable knowledge-store memory (`agents memory` / ~/.agents/memory/).
153
+ * Distinct from `rules` (instructions). When true, sync fans facts into
154
+ * the agent version home (see memoryTargetDir).
155
+ */
156
+ memory: Capability;
151
157
  /**
152
158
  * Permission modes this agent natively supports. Modes outside this set
153
159
  * are gated by buildExecCommand: `auto` silently degrades to `edit`,
@@ -177,7 +183,7 @@ export type RulesCapability = false | {
177
183
  file: string;
178
184
  };
179
185
  /** Names of every gateable capability on AgentConfig. */
180
- export type CapabilityName = 'hooks' | 'mcp' | 'mcpHttp' | 'mcpHeaders' | 'allowlist' | 'skills' | 'commands' | 'plugins' | 'subagents' | 'rules' | 'workflows';
186
+ export type CapabilityName = 'hooks' | 'mcp' | 'mcpHttp' | 'mcpHeaders' | 'allowlist' | 'skills' | 'commands' | 'plugins' | 'subagents' | 'rules' | 'workflows' | 'memory';
181
187
  /**
182
188
  * Permission modes controlling agent autonomy.
183
189
  * plan read-only investigation; no writes, no shell side-effects
@@ -516,6 +522,8 @@ export interface DiscoveredPlugin {
516
522
  commands: string[];
517
523
  /** Subagent .md files in the plugin's agents/ directory (names without extension). */
518
524
  agentDefs: string[];
525
+ /** Memory fact basenames from the plugin's memory/ directory (without .md). */
526
+ memory: string[];
519
527
  /** Executable files in the plugin's bin/ directory. */
520
528
  bin: string[];
521
529
  /** MCP server names parsed from .mcp.json. */
@@ -652,6 +660,16 @@ export interface Meta {
652
660
  * lives separately in ~/.agents/.cache/browser/<profile>/.
653
661
  */
654
662
  browser?: Record<string, BrowserProfileConfig>;
663
+ /**
664
+ * Device-local pointer: the browser profile `agents browser start` resolves to
665
+ * when no `--profile` is passed (and what an explicit `--profile default`
666
+ * re-points to). Stored per-machine in `~/.agents/devices/<machine>/agents.yaml`,
667
+ * NOT central — the target profile may carry machine-local logins, so the choice
668
+ * must not ride `agents repo push/pull` to other machines. Unset = auto-detect an
669
+ * installed Chromium-family browser (legacy behavior). Set via
670
+ * `agents browser profiles set-default <name>`.
671
+ */
672
+ defaultBrowserProfile?: string;
655
673
  /**
656
674
  * Agent-host registry keyed by host name (`agents hosts`). Portable user
657
675
  * config synced with `agents repo push/pull`. For `ssh-config` hosts this is