@phnx-labs/agents-cli 1.20.56 → 1.20.58

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 (47) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/README.md +34 -3
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/defaults.js +24 -0
  5. package/dist/commands/exec.js +28 -4
  6. package/dist/commands/secrets.d.ts +3 -2
  7. package/dist/commands/secrets.js +35 -25
  8. package/dist/commands/teams.d.ts +20 -1
  9. package/dist/commands/teams.js +105 -2
  10. package/dist/commands/versions.js +11 -3
  11. package/dist/commands/view.js +19 -4
  12. package/dist/lib/agents.d.ts +21 -0
  13. package/dist/lib/agents.js +28 -4
  14. package/dist/lib/daemon.d.ts +5 -5
  15. package/dist/lib/daemon.js +88 -17
  16. package/dist/lib/git.d.ts +9 -0
  17. package/dist/lib/git.js +12 -0
  18. package/dist/lib/hosts/dispatch.d.ts +21 -0
  19. package/dist/lib/hosts/dispatch.js +88 -5
  20. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  21. package/dist/lib/permissions.d.ts +19 -1
  22. package/dist/lib/permissions.js +137 -0
  23. package/dist/lib/project-root.d.ts +65 -0
  24. package/dist/lib/project-root.js +133 -0
  25. package/dist/lib/resources/permissions.js +2 -0
  26. package/dist/lib/resources/types.d.ts +1 -1
  27. package/dist/lib/secrets/agent.d.ts +48 -18
  28. package/dist/lib/secrets/agent.js +288 -165
  29. package/dist/lib/secrets/remote.js +1 -0
  30. package/dist/lib/session/active.d.ts +3 -0
  31. package/dist/lib/session/active.js +1 -0
  32. package/dist/lib/session/parse.js +38 -15
  33. package/dist/lib/session/state.d.ts +4 -1
  34. package/dist/lib/session/state.js +18 -1
  35. package/dist/lib/session/types.d.ts +8 -0
  36. package/dist/lib/staleness/detectors/permissions.js +42 -0
  37. package/dist/lib/staleness/detectors/subagents.js +30 -0
  38. package/dist/lib/staleness/writers/subagents.js +13 -1
  39. package/dist/lib/subagents.d.ts +22 -0
  40. package/dist/lib/subagents.js +146 -0
  41. package/dist/lib/teams/agents.d.ts +30 -0
  42. package/dist/lib/teams/agents.js +271 -42
  43. package/dist/lib/types.d.ts +13 -0
  44. package/dist/lib/versions.d.ts +39 -0
  45. package/dist/lib/versions.js +199 -12
  46. package/package.json +1 -1
  47. package/scripts/postinstall.js +26 -11
@@ -55,6 +55,8 @@ function sanitizeEvent(e) {
55
55
  e.command = sanitizeForTerminal(e.command);
56
56
  if (e.path)
57
57
  e.path = sanitizeForTerminal(e.path);
58
+ if (e.name)
59
+ e.name = sanitizeForTerminal(e.name);
58
60
  if (e.output)
59
61
  e.output = sanitizeForTerminal(e.output);
60
62
  if (e.tool)
@@ -66,6 +68,36 @@ function sanitizeEvent(e) {
66
68
  if (e.args)
67
69
  e.args = sanitizeArgsDeep(e.args);
68
70
  }
71
+ function firstString(...values) {
72
+ for (const value of values) {
73
+ if (typeof value === 'string' && value.trim())
74
+ return value.trim();
75
+ }
76
+ return undefined;
77
+ }
78
+ function attachmentPath(block, source) {
79
+ return firstString(source?.path, source?.file_path, source?.filePath, source?.url, source?.ref, block?.path, block?.file_path, block?.filePath, block?.ref);
80
+ }
81
+ function attachmentName(block, source, filePath) {
82
+ return firstString(block?.name, block?.title, source?.name, source?.filename, source?.file_name, source?.fileName, filePath ? path.basename(filePath) : undefined);
83
+ }
84
+ function normalizedAttachmentEvent(agent, timestamp, block, source, defaultMediaType, sizeBytes) {
85
+ const filePath = attachmentPath(block, source);
86
+ const name = attachmentName(block, source, filePath);
87
+ const explicitSize = typeof source?.sizeBytes === 'number' ? source.sizeBytes :
88
+ typeof source?.size === 'number' ? source.size :
89
+ typeof block?.sizeBytes === 'number' ? block.sizeBytes :
90
+ undefined;
91
+ return {
92
+ type: 'attachment',
93
+ agent,
94
+ timestamp,
95
+ path: filePath,
96
+ name,
97
+ mediaType: firstString(source?.media_type, source?.mediaType, block?.media_type, block?.mediaType) || defaultMediaType,
98
+ sizeBytes: sizeBytes || explicitSize || 0,
99
+ };
100
+ }
69
101
  /**
70
102
  * Read a session file, refusing files above maxBytes. Bounded read protects
71
103
  * against multi-GB session blobs that would OOM the CLI or exceed V8's
@@ -351,24 +383,15 @@ export function parseClaudeContent(content) {
351
383
  const source = block.source || {};
352
384
  if (source.type === 'base64') {
353
385
  const sizeBytes = Math.ceil((source.data?.length || 0) * 0.75);
354
- events.push({
355
- type: 'attachment',
356
- agent: 'claude',
357
- timestamp,
358
- mediaType: source.media_type || 'image/png',
359
- sizeBytes,
360
- });
386
+ events.push(normalizedAttachmentEvent('claude', timestamp, block, source, 'image/png', sizeBytes));
387
+ }
388
+ else {
389
+ events.push(normalizedAttachmentEvent('claude', timestamp, block, source, 'image/png', 0));
361
390
  }
362
391
  }
363
392
  else if (block.type === 'document') {
364
393
  const source = block.source || {};
365
- events.push({
366
- type: 'attachment',
367
- agent: 'claude',
368
- timestamp,
369
- mediaType: source.media_type || 'application/pdf',
370
- sizeBytes: 0,
371
- });
394
+ events.push(normalizedAttachmentEvent('claude', timestamp, block, source, 'application/pdf', 0));
372
395
  }
373
396
  else if (block.type === 'tool_result') {
374
397
  const toolId = block.tool_use_id;
@@ -1554,7 +1577,7 @@ export function parseDroid(filePath) {
1554
1577
  else if (block.type === 'image') {
1555
1578
  const source = block.source || {};
1556
1579
  const sizeBytes = source.type === 'base64' ? Math.ceil((source.data?.length || 0) * 0.75) : 0;
1557
- events.push({ type: 'attachment', agent: 'droid', timestamp, mediaType: source.media_type || 'image/png', sizeBytes });
1580
+ events.push(normalizedAttachmentEvent('droid', timestamp, block, source, 'image/png', sizeBytes));
1558
1581
  }
1559
1582
  }
1560
1583
  }
@@ -14,7 +14,7 @@
14
14
  * markers. Codex has no such tools, so it falls back to last-role + question
15
15
  * shape + mtime — same function, driven off the normalized events.
16
16
  */
17
- import type { SessionEvent } from './types.js';
17
+ import type { SessionAttachment, SessionEvent } from './types.js';
18
18
  export type SessionActivity = 'working' | 'waiting_input' | 'idle';
19
19
  export type AwaitingReason = 'question' | 'plan_review' | 'permission';
20
20
  /** One discrete choice the agent offered the user. */
@@ -98,6 +98,8 @@ export interface SessionState {
98
98
  createdTickets?: string[];
99
99
  /** Team name this session SPAWNED via `agents teams create/add`. */
100
100
  spawnedTeam?: string;
101
+ /** Displayable files/screenshots attached to the session prompt. */
102
+ attachments?: SessionAttachment[];
101
103
  }
102
104
  export interface StateContext {
103
105
  /** Session file mtime; drives running-vs-stale. */
@@ -169,6 +171,7 @@ export declare function detectDurableSignals(events: SessionEvent[]): {
169
171
  ticket?: DetectedTicket;
170
172
  createdTickets?: string[];
171
173
  spawnedTeam?: string;
174
+ attachments?: SessionAttachment[];
172
175
  };
173
176
  /** Full inference: activity + preview + durable signals + worktree/ticket from ctx. */
174
177
  export declare function inferSessionState(events: SessionEvent[], ctx?: StateContext): SessionState;
@@ -352,6 +352,8 @@ export function detectDurableSignals(events) {
352
352
  let sawTicketCreate = false;
353
353
  let spawnedTeam;
354
354
  const createdTickets = new Set();
355
+ const attachments = [];
356
+ const seenAttachments = new Set();
355
357
  for (const e of events) {
356
358
  // Structural PR signal: a real `gh pr create` tool call, then the pull URL
357
359
  // from a following tool_result — never a bare URL mentioned in prose.
@@ -384,18 +386,32 @@ export function detectDurableSignals(events) {
384
386
  if (!ticket && e.type === 'message' && e.role === 'user') {
385
387
  ticket = detectTicket(e.content);
386
388
  }
389
+ if (e.type === 'attachment') {
390
+ const mediaType = e.mediaType || 'application/octet-stream';
391
+ const key = e.path || e.name || `${mediaType}:${e.sizeBytes ?? 0}:${e.timestamp}`;
392
+ if (key && !seenAttachments.has(key)) {
393
+ seenAttachments.add(key);
394
+ attachments.push({
395
+ path: e.path,
396
+ name: e.name,
397
+ mediaType,
398
+ sizeBytes: e.sizeBytes,
399
+ });
400
+ }
401
+ }
387
402
  }
388
403
  return {
389
404
  pr,
390
405
  ticket,
391
406
  createdTickets: createdTickets.size > 0 ? [...createdTickets] : undefined,
392
407
  spawnedTeam,
408
+ attachments: attachments.length > 0 ? attachments : undefined,
393
409
  };
394
410
  }
395
411
  /** Full inference: activity + preview + durable signals + worktree/ticket from ctx. */
396
412
  export function inferSessionState(events, ctx = {}) {
397
413
  const state = inferActivity(events, ctx);
398
- const { pr, ticket, createdTickets, spawnedTeam } = detectDurableSignals(events);
414
+ const { pr, ticket, createdTickets, spawnedTeam, attachments } = detectDurableSignals(events);
399
415
  const worktree = detectWorktree(ctx.cwd, ctx.gitBranch);
400
416
  // Rate-limit: scan the most recent assistant messages + tool errors (tail-first).
401
417
  let rateLimited = false;
@@ -421,6 +437,7 @@ export function inferSessionState(events, ctx = {}) {
421
437
  ticket: ticket ?? detectTicket(undefined, ctx.gitBranch) ?? state.ticket,
422
438
  createdTickets,
423
439
  spawnedTeam,
440
+ attachments,
424
441
  rateLimited: rateLimited || undefined,
425
442
  };
426
443
  }
@@ -30,9 +30,17 @@ export interface SessionEvent {
30
30
  outputTokens?: number;
31
31
  cacheReadTokens?: number;
32
32
  cacheCreationTokens?: number;
33
+ name?: string;
33
34
  mediaType?: string;
34
35
  sizeBytes?: number;
35
36
  }
37
+ /** A displayable file attachment discovered in a session transcript. */
38
+ export interface SessionAttachment {
39
+ path?: string;
40
+ name?: string;
41
+ mediaType: string;
42
+ sizeBytes?: number;
43
+ }
36
44
  /** Metadata attached when a session was spawned by `agents teams`. */
37
45
  export interface TeamOrigin {
38
46
  /** Teammate name if set, otherwise first 8 chars of the agent UUID. */
@@ -13,6 +13,7 @@
13
13
  import * as fs from 'fs';
14
14
  import * as path from 'path';
15
15
  import * as TOML from 'smol-toml';
16
+ import * as yaml from 'yaml';
16
17
  import { capableAgents } from '../../capabilities.js';
17
18
  import { discoverPermissionGroups, buildPermissionsFromGroups, CODEX_RULES_FILENAME, } from '../../permissions.js';
18
19
  import { lazyAgentMap } from '../writers/lazy-map.js';
@@ -181,6 +182,45 @@ function buildKimiDetector() {
181
182
  },
182
183
  };
183
184
  }
185
+ function buildCursorDetector() {
186
+ return {
187
+ kind: 'permissions',
188
+ agent: 'cursor',
189
+ list({ versionHome }) {
190
+ const configPath = path.join(versionHome, '.cursor', 'cli-config.json');
191
+ if (!fs.existsSync(configPath))
192
+ return [];
193
+ try {
194
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
195
+ const allow = config.permissions?.allow?.length ?? 0;
196
+ const deny = config.permissions?.deny?.length ?? 0;
197
+ if (allow + deny > 0)
198
+ return discoverPermissionGroups().map(g => g.name);
199
+ }
200
+ catch { /* parse fail */ }
201
+ return [];
202
+ },
203
+ };
204
+ }
205
+ function buildKiroDetector() {
206
+ return {
207
+ kind: 'permissions',
208
+ agent: 'kiro',
209
+ list({ versionHome }) {
210
+ const permissionsPath = path.join(versionHome, '.kiro', 'settings', 'permissions.yaml');
211
+ if (!fs.existsSync(permissionsPath))
212
+ return [];
213
+ try {
214
+ const config = yaml.parse(fs.readFileSync(permissionsPath, 'utf-8'));
215
+ if (config && Array.isArray(config.rules) && config.rules.length > 0) {
216
+ return discoverPermissionGroups().map(g => g.name);
217
+ }
218
+ }
219
+ catch { /* parse fail */ }
220
+ return [];
221
+ },
222
+ };
223
+ }
184
224
  const handlers = {
185
225
  claude: buildClaudeDetector,
186
226
  codex: buildCodexDetector,
@@ -189,6 +229,8 @@ const handlers = {
189
229
  antigravity: buildAntigravityDetector,
190
230
  grok: buildGrokDetector,
191
231
  kimi: buildKimiDetector,
232
+ cursor: buildCursorDetector,
233
+ kiro: buildKiroDetector,
192
234
  };
193
235
  export const permissionsDetectors = lazyAgentMap(() => {
194
236
  const m = {};
@@ -57,6 +57,20 @@ function buildDroidDetector() {
57
57
  },
58
58
  };
59
59
  }
60
+ function buildCopilotDetector() {
61
+ return {
62
+ kind: 'subagents',
63
+ agent: 'copilot',
64
+ list({ versionHome }) {
65
+ const agentsDir = path.join(versionHome, '.copilot', 'agents');
66
+ if (!fs.existsSync(agentsDir))
67
+ return [];
68
+ return fs.readdirSync(agentsDir)
69
+ .filter(f => f.endsWith('.agent.md'))
70
+ .map(f => f.replace('.agent.md', ''));
71
+ },
72
+ };
73
+ }
60
74
  function buildOpenclawDetector() {
61
75
  return {
62
76
  kind: 'subagents',
@@ -71,6 +85,20 @@ function buildOpenclawDetector() {
71
85
  },
72
86
  };
73
87
  }
88
+ function buildKiroDetector() {
89
+ return {
90
+ kind: 'subagents',
91
+ agent: 'kiro',
92
+ list({ versionHome }) {
93
+ const agentsDir = path.join(versionHome, '.kiro', 'agents');
94
+ if (!fs.existsSync(agentsDir))
95
+ return [];
96
+ return fs.readdirSync(agentsDir)
97
+ .filter(f => f.endsWith('.json'))
98
+ .map(f => f.replace('.json', ''));
99
+ },
100
+ };
101
+ }
74
102
  function buildKimiDetector() {
75
103
  return {
76
104
  kind: 'subagents',
@@ -102,12 +130,14 @@ function buildOpenCodeDetector() {
102
130
  }
103
131
  const handlers = {
104
132
  claude: buildClaudeDetector,
133
+ copilot: buildCopilotDetector,
105
134
  grok: buildGrokDetector,
106
135
  codex: buildCodexDetector,
107
136
  kimi: buildKimiDetector,
108
137
  opencode: buildOpenCodeDetector,
109
138
  droid: buildDroidDetector,
110
139
  openclaw: buildOpenclawDetector,
140
+ kiro: buildKiroDetector,
111
141
  };
112
142
  export const subagentsDetectors = lazyAgentMap(() => {
113
143
  const m = {};
@@ -13,7 +13,7 @@
13
13
  import * as fs from 'fs';
14
14
  import * as path from 'path';
15
15
  import { capableAgents } from '../../capabilities.js';
16
- import { listInstalledSubagents, transformSubagentForClaude, transformSubagentForCodex, writeKimiSubagentFiles, buildKimiSubagentsParentYaml, KIMI_SUBAGENTS_PARENT_FILE, transformSubagentForOpenCode, transformSubagentForDroid, syncSubagentToOpenclaw, parseSubagentFrontmatter, } from '../../subagents.js';
16
+ import { listInstalledSubagents, transformSubagentForClaude, transformSubagentForCodex, transformSubagentForCopilot, writeKimiSubagentFiles, buildKimiSubagentsParentYaml, KIMI_SUBAGENTS_PARENT_FILE, transformSubagentForOpenCode, transformSubagentForDroid, transformSubagentForKiro, syncSubagentToOpenclaw, parseSubagentFrontmatter, } from '../../subagents.js';
17
17
  import { safeJoin } from '../../paths.js';
18
18
  import { lazyAgentMap } from './lazy-map.js';
19
19
  function buildSubagentsWriter(agent) {
@@ -57,12 +57,24 @@ function buildSubagentsWriter(agent) {
57
57
  fs.writeFileSync(safeJoin(droidsDir, `${sub.name}.md`), transformSubagentForDroid(sub.path));
58
58
  synced.push(sub.name);
59
59
  }
60
+ else if (agent === 'copilot') {
61
+ const agentsDir = path.join(versionHome, '.copilot', 'agents');
62
+ fs.mkdirSync(agentsDir, { recursive: true });
63
+ fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.agent.md`), transformSubagentForCopilot(sub.path));
64
+ synced.push(sub.name);
65
+ }
60
66
  else if (agent === 'openclaw') {
61
67
  const target = safeJoin(path.join(versionHome, '.openclaw'), sub.name);
62
68
  const r = syncSubagentToOpenclaw(sub.path, target);
63
69
  if (r.success)
64
70
  synced.push(sub.name);
65
71
  }
72
+ else if (agent === 'kiro') {
73
+ const agentsDir = path.join(versionHome, '.kiro', 'agents');
74
+ fs.mkdirSync(agentsDir, { recursive: true });
75
+ fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.json`), transformSubagentForKiro(sub.path));
76
+ synced.push(sub.name);
77
+ }
66
78
  }
67
79
  catch { /* per-item sync failure: skip */ }
68
80
  }
@@ -56,6 +56,18 @@ 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 a GitHub Copilot CLI custom agent `.agent.md` file.
61
+ *
62
+ * Copilot custom agents are Markdown profiles with YAML frontmatter stored in
63
+ * `~/.copilot/agents/` (user) or `.github/agents/` (project). The file name
64
+ * ends in `.agent.md` and the frontmatter carries `name`, `description`, and
65
+ * optionally `model` and `tools`. The emitted body is identical to Factory
66
+ * Droid's custom-droid format (flatten frontmatter + body + appended .md
67
+ * sections, `color` dropped), so this is an alias of transformSubagentForDroid.
68
+ * See GitHub docs for custom agents.
69
+ */
70
+ export declare const transformSubagentForCopilot: typeof transformSubagentForDroid;
59
71
  /**
60
72
  * Transform a subagent into an OpenCode agent markdown file.
61
73
  *
@@ -107,6 +119,16 @@ export declare function writeKimiSubagentFiles(agentsDir: string, subagentDir: s
107
119
  * https://developers.openai.com/codex/subagents (custom agents section)
108
120
  */
109
121
  export declare function transformSubagentForCodex(subagentDir: string): string;
122
+ /**
123
+ * Transform a subagent into a Kiro CLI custom-agent JSON file.
124
+ *
125
+ * Kiro custom agents live in `~/.kiro/agents/<name>.json` (or `.kiro/agents/`
126
+ * workspace-local) and declare name, description, prompt, tools, and optional
127
+ * model. We flatten the AGENT.md frontmatter + body plus any sibling .md files
128
+ * as sections into a single `prompt`, and expose the standard built-in tool
129
+ * set so the subagent can actually run.
130
+ */
131
+ export declare function transformSubagentForKiro(subagentDir: string): string;
110
132
  /**
111
133
  * Sync a subagent to an OpenClaw workspace
112
134
  * Copies full directory, renames AGENT.md to AGENTS.md
@@ -262,6 +262,18 @@ export function transformSubagentForDroid(subagentDir) {
262
262
  }
263
263
  return result;
264
264
  }
265
+ /**
266
+ * Transform a subagent into a GitHub Copilot CLI custom agent `.agent.md` file.
267
+ *
268
+ * Copilot custom agents are Markdown profiles with YAML frontmatter stored in
269
+ * `~/.copilot/agents/` (user) or `.github/agents/` (project). The file name
270
+ * ends in `.agent.md` and the frontmatter carries `name`, `description`, and
271
+ * optionally `model` and `tools`. The emitted body is identical to Factory
272
+ * Droid's custom-droid format (flatten frontmatter + body + appended .md
273
+ * sections, `color` dropped), so this is an alias of transformSubagentForDroid.
274
+ * See GitHub docs for custom agents.
275
+ */
276
+ export const transformSubagentForCopilot = transformSubagentForDroid;
265
277
  /**
266
278
  * Transform a subagent into an OpenCode agent markdown file.
267
279
  *
@@ -409,6 +421,43 @@ export function transformSubagentForCodex(subagentDir) {
409
421
  toml += `developer_instructions = """\n${safeInstructions}\n"""\n`;
410
422
  return toml;
411
423
  }
424
+ /**
425
+ * Transform a subagent into a Kiro CLI custom-agent JSON file.
426
+ *
427
+ * Kiro custom agents live in `~/.kiro/agents/<name>.json` (or `.kiro/agents/`
428
+ * workspace-local) and declare name, description, prompt, tools, and optional
429
+ * model. We flatten the AGENT.md frontmatter + body plus any sibling .md files
430
+ * as sections into a single `prompt`, and expose the standard built-in tool
431
+ * set so the subagent can actually run.
432
+ */
433
+ export function transformSubagentForKiro(subagentDir) {
434
+ const agentMd = path.join(subagentDir, 'AGENT.md');
435
+ const frontmatter = parseSubagentFrontmatter(agentMd);
436
+ const body = getSubagentBody(agentMd);
437
+ if (!frontmatter) {
438
+ throw new Error(`Invalid AGENT.md in ${subagentDir}`);
439
+ }
440
+ const files = fs.readdirSync(subagentDir)
441
+ .filter(f => f.endsWith('.md') && f !== 'AGENT.md')
442
+ .sort();
443
+ let prompt = body;
444
+ for (const file of files) {
445
+ const content = fs.readFileSync(path.join(subagentDir, file), 'utf-8').trim();
446
+ const sectionName = file.replace('.md', '');
447
+ const title = sectionName.charAt(0).toUpperCase() + sectionName.slice(1).toLowerCase();
448
+ prompt += `\n\n## ${title}\n\n${content}`;
449
+ }
450
+ const config = {
451
+ name: frontmatter.name,
452
+ description: frontmatter.description,
453
+ prompt,
454
+ tools: ['read', 'write', 'shell', 'web_search', 'web_fetch'],
455
+ };
456
+ if (frontmatter.model) {
457
+ config.model = frontmatter.model;
458
+ }
459
+ return JSON.stringify(config, null, 2);
460
+ }
412
461
  /**
413
462
  * Sync a subagent to an OpenClaw workspace
414
463
  * Copies full directory, renames AGENT.md to AGENTS.md
@@ -497,6 +546,21 @@ export function installSubagentToAgent(subagentDir, subagentName, agent, agentHo
497
546
  const targetDir = safeJoin(path.join(agentHome, '.openclaw'), subagentName);
498
547
  return syncSubagentToOpenclaw(subagentDir, targetDir);
499
548
  }
549
+ else if (agent === 'kiro') {
550
+ // Kiro: JSON custom-agent file under ~/.kiro/agents/
551
+ const agentsDir = path.join(agentHome, '.kiro', 'agents');
552
+ if (!fs.existsSync(agentsDir)) {
553
+ fs.mkdirSync(agentsDir, { recursive: true });
554
+ }
555
+ try {
556
+ const transformed = transformSubagentForKiro(subagentDir);
557
+ fs.writeFileSync(safeJoin(agentsDir, `${subagentName}.json`), transformed);
558
+ return { success: true };
559
+ }
560
+ catch (err) {
561
+ return { success: false, error: String(err) };
562
+ }
563
+ }
500
564
  else {
501
565
  // Other agents don't support subagents yet
502
566
  return { success: false, error: `Agent '${agent}' does not support subagents` };
@@ -545,6 +609,13 @@ export function removeSubagentFromAgent(subagentName, agent, agentHome) {
545
609
  }
546
610
  return { success: true };
547
611
  }
612
+ else if (agent === 'kiro') {
613
+ const targetPath = safeJoin(path.join(agentHome, '.kiro', 'agents'), `${subagentName}.json`);
614
+ if (fs.existsSync(targetPath)) {
615
+ fs.unlinkSync(targetPath);
616
+ }
617
+ return { success: true };
618
+ }
548
619
  else {
549
620
  return { success: true }; // No-op for unsupported agents
550
621
  }
@@ -656,6 +727,22 @@ export function listSubagentsForAgent(agentId, home) {
656
727
  subagents.push({ name, path: filePath, files: [file], frontmatter });
657
728
  }
658
729
  }
730
+ else if (agentId === 'copilot') {
731
+ // Copilot: flat `<name>.agent.md` files under ~/.copilot/agents/
732
+ const agentsDir = path.join(home, '.copilot', 'agents');
733
+ if (!fs.existsSync(agentsDir))
734
+ return subagents;
735
+ for (const file of fs.readdirSync(agentsDir)) {
736
+ if (!file.endsWith('.agent.md'))
737
+ continue;
738
+ const filePath = path.join(agentsDir, file);
739
+ if (!fs.statSync(filePath).isFile())
740
+ continue;
741
+ const name = file.replace(/\.agent\.md$/, '');
742
+ const frontmatter = parseSubagentFrontmatter(filePath) ?? { name, description: '' };
743
+ subagents.push({ name, path: filePath, files: [file], frontmatter });
744
+ }
745
+ }
659
746
  else if (agentId === 'openclaw') {
660
747
  // OpenClaw: directories with AGENTS.md
661
748
  const openclawDir = path.join(home, '.openclaw');
@@ -697,6 +784,38 @@ export function listSubagentsForAgent(agentId, home) {
697
784
  });
698
785
  }
699
786
  }
787
+ else if (agentId === 'kiro') {
788
+ // Kiro: JSON files under ~/.kiro/agents/
789
+ const agentsDir = path.join(home, '.kiro', 'agents');
790
+ if (!fs.existsSync(agentsDir))
791
+ return subagents;
792
+ for (const file of fs.readdirSync(agentsDir)) {
793
+ if (!file.endsWith('.json'))
794
+ continue;
795
+ const filePath = path.join(agentsDir, file);
796
+ if (!fs.statSync(filePath).isFile())
797
+ continue;
798
+ let frontmatter;
799
+ try {
800
+ const config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
801
+ frontmatter = {
802
+ name: config.name || file.replace('.json', ''),
803
+ description: config.description || '',
804
+ model: config.model,
805
+ };
806
+ }
807
+ catch {
808
+ continue;
809
+ }
810
+ const name = file.replace('.json', '');
811
+ subagents.push({
812
+ name,
813
+ path: filePath,
814
+ files: [file],
815
+ frontmatter,
816
+ });
817
+ }
818
+ }
700
819
  return subagents;
701
820
  }
702
821
  /**
@@ -768,6 +887,19 @@ export function diffVersionSubagents(agent, version) {
768
887
  }
769
888
  }
770
889
  }
890
+ else if (agent === 'kiro') {
891
+ const agentsDir = path.join(versionHome, '.kiro', 'agents');
892
+ if (fs.existsSync(agentsDir)) {
893
+ for (const file of fs.readdirSync(agentsDir)) {
894
+ if (!file.endsWith('.json'))
895
+ continue;
896
+ const name = path.basename(file, '.json');
897
+ if (!discovered.has(name)) {
898
+ orphans.push(name);
899
+ }
900
+ }
901
+ }
902
+ }
771
903
  return { agent, version, orphans: orphans.sort() };
772
904
  }
773
905
  /**
@@ -826,6 +958,13 @@ export function removeSubagentFromVersion(agent, version, subagentName) {
826
958
  fs.renameSync(targetPath, path.join(trashDir, `${subagentName}.md.${stamp}`));
827
959
  }
828
960
  }
961
+ else if (agent === 'copilot') {
962
+ const targetPath = path.join(versionHome, '.copilot', 'agents', `${subagentName}.agent.md`);
963
+ if (fs.existsSync(targetPath)) {
964
+ fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
965
+ fs.renameSync(targetPath, path.join(trashDir, `${subagentName}.agent.md.${stamp}`));
966
+ }
967
+ }
829
968
  else if (agent === 'openclaw') {
830
969
  const targetDir = path.join(versionHome, '.openclaw', subagentName);
831
970
  if (fs.existsSync(targetDir)) {
@@ -834,6 +973,13 @@ export function removeSubagentFromVersion(agent, version, subagentName) {
834
973
  fs.renameSync(targetDir, trashDest);
835
974
  }
836
975
  }
976
+ else if (agent === 'kiro') {
977
+ const targetPath = path.join(versionHome, '.kiro', 'agents', `${subagentName}.json`);
978
+ if (fs.existsSync(targetPath)) {
979
+ fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
980
+ fs.renameSync(targetPath, path.join(trashDir, `${subagentName}.json.${stamp}`));
981
+ }
982
+ }
837
983
  return { success: true };
838
984
  }
839
985
  catch (err) {
@@ -213,6 +213,10 @@ export declare class AgentProcess {
213
213
  * round-trips so a bare `teams status`/`teams logs` is still correct.
214
214
  */
215
215
  private syncRemoteMirror;
216
+ /** Reset the local stdout cursor for a newly truncated resume log. */
217
+ resetLogReadPosition(): number;
218
+ /** Restore the cursor when a resume transaction puts the prior log back. */
219
+ restoreLogReadPosition(position: number): void;
216
220
  readNewEvents(): Promise<void>;
217
221
  /**
218
222
  * Truncate the local mirror to its trailing REMOTE_MIRROR_MAX_BYTES and reset
@@ -268,6 +272,16 @@ export declare class AgentProcess {
268
272
  export type CloudDispatchFn = (agent: AgentProcess) => Promise<{
269
273
  cloudSessionId: string;
270
274
  }>;
275
+ interface ResumeLogTransaction {
276
+ agent: AgentProcess;
277
+ stdoutPath: string;
278
+ backupPath: string;
279
+ hadOriginal: boolean;
280
+ previousReadPos: number;
281
+ }
282
+ export declare function beginResumeLogTransaction(agent: AgentProcess): Promise<ResumeLogTransaction>;
283
+ export declare function commitResumeLogTransaction(transaction: ResumeLogTransaction): Promise<void>;
284
+ export declare function terminateSpawnedProcess(pid: number): Promise<void>;
271
285
  export declare class AgentManager {
272
286
  private agents;
273
287
  private maxAgents;
@@ -301,6 +315,22 @@ export declare class AgentManager {
301
315
  rescanFromDisk(): Promise<number>;
302
316
  private loadExistingAgents;
303
317
  spawn(taskName: string, agentType: AgentType, prompt: string, cwd?: string | null, mode?: Mode | null, effort?: EffortLevel, parentSessionId?: string | null, workspaceDir?: string | null, version?: string | null, name?: string | null, after?: string[], model?: string | null, envOverrides?: Record<string, string> | null, taskType?: TaskType | null, cloudProvider?: string | null, cloudSessionId?: string | null, cloudRepo?: string | null, cloudBranch?: string | null, worktreeName?: string | null, worktreePath?: string | null, profileName?: string | null, hostName?: string | null, hostTarget?: string | null, repoPath?: string | null): Promise<AgentProcess>;
318
+ /**
319
+ * Resume a STOPPED teammate (completed / failed / stopped) by re-entering its
320
+ * own session with `message` as the next user turn. Re-launches through the
321
+ * SAME backend the teammate first used (local process or remote host), reusing
322
+ * its stored cwd / worktree / host / version / model / effort, and flips it
323
+ * back to RUNNING so the team tracks it live again.
324
+ *
325
+ * The resume target is the teammate's underlying agent session id: for Claude
326
+ * that IS its agent_id (unified identity, pinned via --session-id at first
327
+ * launch); other harnesses only expose their session/thread id after their
328
+ * first stream event, captured as `remoteSessionId`.
329
+ *
330
+ * Callers branch on status first — a RUNNING teammate is steered via its
331
+ * mailbox, never re-launched — so this method assumes a non-running teammate.
332
+ */
333
+ resumeTeammate(agentId: string, message: string): Promise<AgentProcess>;
304
334
  /**
305
335
  * Actually spawn the OS process for a teammate. Extracted from spawn() so
306
336
  * staged teammates can be launched later by startReady().