oh-my-opencode 4.16.2 → 4.16.3

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 (133) hide show
  1. package/.agents/skills/pre-publish-review/SKILL.md +3 -0
  2. package/README.md +2 -2
  3. package/dist/agents/hephaestus/agent.d.ts +1 -1
  4. package/dist/agents/hephaestus/gpt-5-6.d.ts +2 -0
  5. package/dist/agents/momus-gpt-5-6.d.ts +1 -0
  6. package/dist/agents/types.d.ts +2 -0
  7. package/dist/cli/index.js +800 -536
  8. package/dist/cli-node/index.js +800 -536
  9. package/dist/features/background-agent/error-classifier.d.ts +5 -0
  10. package/dist/features/builtin-commands/templates/refactor-sections/intro-and-analysis.d.ts +1 -1
  11. package/dist/features/builtin-commands/templates/remove-ai-slops.d.ts +1 -1
  12. package/dist/index.js +416 -37
  13. package/dist/shared/live-server-route.d.ts +1 -1
  14. package/dist/skills/review-work/SKILL.md +1 -1
  15. package/dist/skills/ulw-plan/references/full-workflow.md +1 -1
  16. package/dist/skills/ulw-research/SKILL.md +2 -2
  17. package/dist/skills/visual-qa/SKILL.md +1 -1
  18. package/dist/tui.js +125 -13
  19. package/package.json +13 -13
  20. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  21. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +398 -246
  22. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  23. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  25. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  26. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  27. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  28. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  29. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  30. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  31. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  32. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  33. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +93 -0
  34. package/packages/omo-codex/plugin/components/rules/dist/cli.js +43 -24
  35. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  36. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/rules/src/codex-hook.ts +2 -1
  38. package/packages/omo-codex/plugin/components/rules/src/dynamic-target-fingerprints.ts +15 -6
  39. package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +7 -2
  40. package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +2 -2
  41. package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +32 -0
  42. package/packages/omo-codex/plugin/components/rules/test/hephaestus-model-variant.test.ts +106 -0
  43. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  44. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  45. package/packages/omo-codex/plugin/components/teammode/AGENTS.md +12 -9
  46. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  47. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  48. package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +136 -76
  49. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-guide.mjs +94 -23
  50. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +94 -14
  51. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-transport.mjs +55 -0
  52. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +72 -18
  53. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +23 -0
  54. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  55. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  56. package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +24 -41
  57. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +1 -1
  58. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +1 -1
  59. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-executor.toml +2 -2
  60. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
  61. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-qa-executor.toml +1 -1
  62. package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +21 -49
  63. package/packages/omo-codex/plugin/components/ultrawork/agents/metis.toml +17 -29
  64. package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +20 -50
  65. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +18 -39
  66. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  67. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  68. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +1 -1
  69. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
  70. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  71. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -1
  72. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  73. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  85. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  87. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  88. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  89. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  90. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  91. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  92. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  93. package/packages/omo-codex/plugin/package-lock.json +13 -13
  94. package/packages/omo-codex/plugin/package.json +1 -1
  95. package/packages/omo-codex/plugin/scripts/auto-update.mjs +9 -2
  96. package/packages/omo-codex/plugin/scripts/entry-guard.mjs +26 -0
  97. package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +3 -1
  98. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +39 -4
  99. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +24 -4
  100. package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +3 -3
  101. package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +2 -2
  102. package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +2 -2
  103. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -4
  104. package/packages/omo-codex/plugin/scripts/sync-version.mjs +4 -2
  105. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
  106. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +1 -1
  107. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
  108. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +1 -1
  109. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +1 -1
  110. package/packages/omo-codex/plugin/skills/teammode/SKILL.md +136 -76
  111. package/packages/omo-codex/plugin/skills/teammode/scripts/team-guide.mjs +94 -23
  112. package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +94 -14
  113. package/packages/omo-codex/plugin/skills/teammode/scripts/team-transport.mjs +55 -0
  114. package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +72 -18
  115. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
  116. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +1 -1
  117. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +2 -2
  118. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +2 -2
  119. package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +47 -1
  120. package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +4 -2
  121. package/packages/omo-codex/plugin/test/auto-update-restart-notice.test.mjs +33 -0
  122. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +146 -9
  123. package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +71 -0
  124. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +32 -0
  125. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +9 -2
  126. package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +345 -0
  127. package/packages/omo-codex/scripts/install-dist/install-local.mjs +436 -284
  128. package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +111 -1
  129. package/packages/shared-skills/skills/review-work/SKILL.md +1 -1
  130. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +1 -1
  131. package/packages/shared-skills/skills/ulw-research/SKILL.md +2 -2
  132. package/packages/shared-skills/skills/visual-qa/SKILL.md +1 -1
  133. /package/packages/omo-codex/plugin/components/rules/bundled-rules/{hephaestus.md → hephaestus/gpt-5.5.md} +0 -0
@@ -1,23 +1,57 @@
1
1
  // team-guide.mjs - render the member-facing field manual (guide.md) and the short
2
- // bootstrap trigger a new thread reads on startup. Pure string builders, no I/O.
2
+ // bootstrap trigger a new member reads on startup. Pure string builders, no I/O.
3
3
  //
4
4
  // The manual is the durable, two-channel replacement for per-prompt guidance: the leader
5
- // writes it once at team creation, and every member thread is told to READ it instead of
6
- // receiving the rules inline each turn.
5
+ // writes it once at team creation, and every member is told to READ it instead of
6
+ // receiving the rules inline each turn. Every rendered surface branches on the team's
7
+ // transport: multi_agent_v2 members are native agents addressed by agent path; codex_app
8
+ // members are app threads addressed by thread id.
9
+
10
+ import { isMultiAgentV2, LEADER_AGENT_PATH } from "./team-transport.mjs";
7
11
 
8
12
  export function codexThreadLink(threadId) {
9
13
  return `codex://threads/${threadId}`;
10
14
  }
11
15
 
12
- function memberLine(member) {
16
+ function transportLabel(team) {
17
+ return isMultiAgentV2(team)
18
+ ? "MultiAgentV2 (native agents: flat `spawn_agent` / `send_message` / `followup_task`)"
19
+ : "Codex App threads (`codex_app.*` tools)";
20
+ }
21
+
22
+ function memberEndpoint(member, v2) {
23
+ if (v2) {
24
+ return member.agentPath ? ` | task \`${member.taskName}\` | agent \`${member.agentPath}\`` : " | agent not spawned yet";
25
+ }
13
26
  const thread = member.threadId ? ` | thread ${member.threadId} (${codexThreadLink(member.threadId)})` : " | thread not created yet";
27
+ const title = member.threadTitle ? ` | thread title \`${member.threadTitle}\`` : "";
28
+ return `${title}${thread}`;
29
+ }
30
+
31
+ function memberLine(member, v2) {
14
32
  const wt = member.worktree?.path ? ` | worktree ${member.worktree.path}` : "";
15
33
  const name = member.name ? ` "${member.name}"` : "";
16
- const title = member.threadTitle ? ` | thread title \`${member.threadTitle}\`` : "";
17
- return `- **${member.id}**${name} (${member.lens}) - ${member.focus}${member.deliverable ? ` -> ${member.deliverable}` : ""}${title}${thread}${wt} [${member.status}]`;
34
+ return `- **${member.id}**${name} (${member.lens}) - ${member.focus}${member.deliverable ? ` -> ${member.deliverable}` : ""}${memberEndpoint(member, v2)}${wt} [${member.status}]`;
35
+ }
36
+
37
+ function worktreeMemberEndpoint(member, v2) {
38
+ if (v2) return member.agentPath ? `; agent \`${member.agentPath}\`` : "";
39
+ return member.threadId ? `; thread ${codexThreadLink(member.threadId)}` : "";
40
+ }
41
+
42
+ function worktreeReadinessNote(v2) {
43
+ if (v2) {
44
+ return `Your worktree is created by the leader BEFORE you spawn; the exact path is in your row
45
+ above and in your bootstrap. If the leader enables isolation mid-run, the new path arrives
46
+ as a \`followup_task\` - switch to it before your next edit.`;
47
+ }
48
+ return `If Codex returns only \`pendingWorktreeId\` while the leader is creating a worktree-backed member
49
+ thread, the thread is not ready yet. The leader must wait until Codex surfaces a real thread id,
50
+ then bind that real id and send the bootstrap.`;
18
51
  }
19
52
 
20
53
  function worktreeSection(team) {
54
+ const v2 = isMultiAgentV2(team);
21
55
  if (!team.worktree?.enabled) {
22
56
  return `## Worktrees
23
57
 
@@ -28,8 +62,8 @@ that is a coordination signal: mark yourself \`blocked\` and tell the leader at
28
62
  const lines = team.members
29
63
  .filter((m) => m.worktree?.path || m.worktree?.branch)
30
64
  .map((m) => {
31
- const thread = m.threadId ? `; thread ${codexThreadLink(m.threadId)}` : "";
32
- return `- **${m.id}**: worktree \`${m.worktree.path ?? "(assign with bind-thread --cwd)"}\` on branch \`${m.worktree.branch ?? team.worktree.baseBranch}\`${thread}`;
65
+ const bindHint = v2 ? "(assign with bind-agent --cwd)" : "(assign with bind-thread --cwd)";
66
+ return `- **${m.id}**: worktree \`${m.worktree.path ?? bindHint}\` on branch \`${m.worktree.branch ?? team.worktree.baseBranch}\`${worktreeMemberEndpoint(m, v2)}`;
33
67
  });
34
68
  return `## Worktrees (ISOLATION IS ON - this is not optional for you)
35
69
 
@@ -41,25 +75,48 @@ Before editing, verify that your assigned worktree exists and contains repo file
41
75
  missing, empty, or does not look like a git worktree/repository yet, send \`BLOCKED: worktree not ready\`
42
76
  to the leader and wait instead of editing any parent checkout or empty directory.
43
77
 
44
- If Codex returns only \`pendingWorktreeId\` while the leader is creating a worktree-backed member
45
- thread, the thread is not ready yet. The leader must wait until Codex surfaces a real thread id,
46
- then bind that real id and send the bootstrap.
78
+ ${worktreeReadinessNote(v2)}
47
79
 
48
- ${lines.length ? lines.join("\n") : "- (worktree paths are assigned as threads are bound; read team.json for yours)"}`;
80
+ ${lines.length ? lines.join("\n") : "- (worktree paths are assigned as members are bound; read team.json for yours)"}`;
81
+ }
82
+
83
+ function addressBookRules(team) {
84
+ if (isMultiAgentV2(team)) {
85
+ return `- **Reach the leader and your peers with \`send_message\` - not by narrating in your own
86
+ transcript, where no one reads it.** The address book is in team.json: the leader's target
87
+ is \`${LEADER_AGENT_PATH}\`, and each peer's target is its \`members[].agentPath\` (built from its
88
+ \`members[].taskName\`).
89
+ - **Use \`send_message\` for updates and \`followup_task\` only when handing a peer a NEW task**
90
+ that should wake it. Never invent an agent path - read it from team.json.`;
91
+ }
92
+ return `- **Reach the leader and your peers with \`codex_app.send_message_to_thread\` - not by narrating in
93
+ your own thread, where no one reads it.** The address book is in team.json: the leader thread id
94
+ is \`leader.sessionId\`, and each peer thread id is its \`members[].threadId\`.
95
+ - **When you mention a Codex thread, include its app link too:** \`codex://threads/<threadId>\`.
96
+ These links are the fastest way for the leader to reopen worktree-backed member threads.`;
97
+ }
98
+
99
+ function identityHeaderLine(team) {
100
+ if (isMultiAgentV2(team)) {
101
+ return `- Member identity convention: each member is the agent path \`${LEADER_AGENT_PATH}/<task name>\` - your row below shows yours; two members never share a task name`;
102
+ }
103
+ return `- Thread title convention: \`${team.threadTitleConvention}\` - each member's thread takes its OWN title from its name (your row below shows yours); two members never share a title`;
49
104
  }
50
105
 
51
106
  export function buildGuide(team) {
107
+ const v2 = isMultiAgentV2(team);
52
108
  const artifacts = team.paths?.artifacts ?? ".omo/teams/<session_id>/artifacts";
53
109
  const teamJson = team.paths?.team ?? ".omo/teams/<session_id>/team.json";
54
- const roster = team.members.length ? team.members.map(memberLine).join("\n") : "- (no members yet)";
110
+ const roster = team.members.length ? team.members.map((member) => memberLine(member, v2)).join("\n") : "- (no members yet)";
55
111
  return `# Team ${team.teamName} - Member Field Manual
56
112
 
57
113
  > Auto-generated by the teammode script. This file is the single source of truth for how
58
114
  > this team works. Re-read it whenever you are unsure - do not improvise the protocol.
59
115
 
60
116
  - Team: **${team.teamName}** (id \`${team.teamId}\`)
117
+ - Transport: **${transportLabel(team)}**
61
118
  - Team state: \`${teamJson}\`
62
- - Thread title convention: \`${team.threadTitleConvention}\` - each member's thread takes its OWN title from its name (your row below shows yours); two members never share a title
119
+ ${identityHeaderLine(team)}
63
120
 
64
121
  ## Who you are (your identity is fixed)
65
122
 
@@ -82,11 +139,7 @@ coordinate with each other, but the leader owns the final decision and integrati
82
139
  is mandatory regardless of the language the task was given in.
83
140
  - **Exception: when the END USER addresses you directly, reply in the user's own language.**
84
141
  Team-internal traffic stays English; user-facing replies match the user.
85
- - **Reach the leader and your peers with \`codex_app.send_message_to_thread\` - not by narrating in
86
- your own thread, where no one reads it.** The address book is in team.json: the leader thread id
87
- is \`leader.sessionId\`, and each peer thread id is its \`members[].threadId\`.
88
- - **When you mention a Codex thread, include its app link too:** \`codex://threads/<threadId>\`.
89
- These links are the fastest way for the leader to reopen worktree-backed member threads.
142
+ ${addressBookRules(team)}
90
143
  - **Push a short message at each of these moments - never batch them into one report at the end:**
91
144
  - to the **leader**: every finding or decision, every file or sub-task you finish, a
92
145
  \`WORKING: <focus> - <phase>\` heartbeat every few tool calls so you never look stale, a
@@ -116,6 +169,25 @@ Your slice is done only when (1) the deliverable exists, (2) you have evidence i
116
169
  (3) you have reported it to the leader. Until all three are true, keep going.`;
117
170
  }
118
171
 
172
+ function memberIdentityLine(team, member) {
173
+ if (isMultiAgentV2(team)) {
174
+ return `Your task name is \`${member.taskName}\` (agent path \`${member.agentPath}\`).`;
175
+ }
176
+ return `Your thread title is \`${member.threadTitle}\`.`;
177
+ }
178
+
179
+ function memberEndpointNote(team, member) {
180
+ if (isMultiAgentV2(team)) return "";
181
+ return member.threadId ? `\nYour Codex thread link is ${codexThreadLink(member.threadId)}. Include it when reporting handoffs or worktree status.` : "";
182
+ }
183
+
184
+ function pushInstruction(team) {
185
+ if (isMultiAgentV2(team)) {
186
+ return `Push updates to the leader (target \`${LEADER_AGENT_PATH}\`) and relevant peers (their team.json \`members[].agentPath\`) with \`send_message\` as you work`;
187
+ }
188
+ return "Push updates to the leader and relevant peers with `codex_app.send_message_to_thread` as you work";
189
+ }
190
+
119
191
  export function buildMemberPrompt(team, id) {
120
192
  const member = team.members.find((m) => m.id === id);
121
193
  if (!member) throw new Error(`no member with id "${id}"`);
@@ -126,14 +198,13 @@ export function buildMemberPrompt(team, id) {
126
198
  : team.worktree?.enabled
127
199
  ? "Wait for the leader to bind your worktree cwd before editing."
128
200
  : "Work from the repository root unless your manual assigns a worktree.";
129
- const threadLink = member.threadId ? `\nYour Codex thread link is ${codexThreadLink(member.threadId)}. Include it when reporting handoffs or worktree status.` : "";
130
201
  const readiness =
131
202
  team.worktree?.enabled
132
203
  ? "\nBefore editing, verify that your assigned worktree exists and contains repo files. If this prompt arrived before your Codex worktree checkout is ready, or the path is missing, empty, or not a git worktree/repository yet, report `BLOCKED: worktree not ready` to the leader and wait."
133
204
  : "";
134
- return `You are member ${member.id}${member.name ? ` (${member.name})` : ""} of team ${team.teamName} - owner of: ${member.focus}. Your thread title is \`${member.threadTitle}\`.
205
+ return `You are member ${member.id}${member.name ? ` (${member.name})` : ""} of team ${team.teamName} - owner of: ${member.focus}. ${memberIdentityLine(team, member)}
135
206
  FIRST read your field manual at \`${guide}\`, then the team state at \`${teamJson}\`; they define your scope, deliverable, the leader, the artifacts directory, and the communication rules.
136
- ${where}${threadLink}${readiness}
207
+ ${where}${memberEndpointNote(team, member)}${readiness}
137
208
  Communicate with the team and leader in English; reply to the end user in the user's own language.
138
- Start now. Push updates to the leader and relevant peers with \`codex_app.send_message_to_thread\` as you work - findings, \`WORKING:\` heartbeats, and \`BLOCKED:\` the instant you stall - never just one report at the end.`;
209
+ Start now. ${pushInstruction(team)} - findings, \`WORKING:\` heartbeats, and \`BLOCKED:\` the instant you stall - never just one report at the end.`;
139
210
  }
@@ -13,6 +13,16 @@ import { lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
13
13
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
14
14
  import { setTimeout as delay } from "node:timers/promises";
15
15
 
16
+ import {
17
+ agentPathForTaskName,
18
+ assertTransport,
19
+ isMultiAgentV2,
20
+ LEADER_AGENT_PATH,
21
+ parseTaskName,
22
+ parseTeamTransport,
23
+ TEAM_SCHEMA_VERSION,
24
+ } from "./team-transport.mjs";
25
+
16
26
  export const LENSES = ["area", "ownership", "perspective"];
17
27
  export const MEMBER_STATUSES = ["pending", "active", "reported", "blocked", "archived"];
18
28
  // A team is never a single member: two or more distinct slices is the minimum. One isolated
@@ -32,22 +42,25 @@ function isoNow(now) {
32
42
  return now ?? new Date().toISOString();
33
43
  }
34
44
 
35
- export function buildTeam({ teamName, sessionName, sessionId = null, dir = null, worktreeEnabled = false, baseBranch = "dev", now }) {
45
+ export function buildTeam({ teamName, sessionName, sessionId = null, dir = null, worktreeEnabled = false, baseBranch = "dev", transport, now }) {
36
46
  if (!teamName?.trim()) throw new Error("team name is required");
37
47
  if (!sessionName?.trim()) throw new Error("session name is required");
48
+ const teamTransport = parseTeamTransport(transport);
49
+ const v2 = teamTransport === "multi_agent_v2";
38
50
  const ts = isoNow(now);
39
51
  return {
40
- schemaVersion: 2,
52
+ schemaVersion: TEAM_SCHEMA_VERSION,
53
+ transport: teamTransport,
41
54
  teamId: randomUUID(),
42
55
  teamName: teamName.trim(),
43
56
  sessionName: sessionName.trim(),
44
57
  sessionId,
45
- threadTitleConvention: `[${teamName.trim()}] <member name>`,
58
+ threadTitleConvention: v2 ? null : `[${teamName.trim()}] <member name>`,
46
59
  status: "active",
47
60
  createdAt: ts,
48
61
  updatedAt: ts,
49
62
  archivedAt: null,
50
- leader: { kind: "main-session", sessionId },
63
+ leader: { kind: "main-session", sessionId: v2 ? null : sessionId, agentPath: v2 ? LEADER_AGENT_PATH : null },
51
64
  communication: { memberLanguage: "english", replyToUserInUserLanguage: true },
52
65
  worktree: { enabled: Boolean(worktreeEnabled), baseBranch, root: dir ? join(dir, "worktrees") : null },
53
66
  paths: dir
@@ -124,21 +137,44 @@ function assertUniqueMemberThreadTitle(team) {
124
137
  }
125
138
  }
126
139
 
127
- function assertTeamReadyForThreadBinding(team) {
140
+ function assertUniqueMemberTaskName(team) {
141
+ const seen = new Map();
142
+ for (const member of team.members) {
143
+ const taskName = parseTaskName(member.taskName);
144
+ const previous = seen.get(taskName);
145
+ if (previous) {
146
+ throw new Error(`member taskName "${taskName}" duplicates "${previous.taskName}" (no two members may share an agent path)`);
147
+ }
148
+ if (member.agentPath !== agentPathForTaskName(taskName)) {
149
+ throw new Error(`member "${member.id}" agentPath "${member.agentPath}" does not match task name "${taskName}"`);
150
+ }
151
+ seen.set(taskName, member);
152
+ }
153
+ }
154
+
155
+ function assertUniqueTransportIdentity(team) {
156
+ if (isMultiAgentV2(team)) assertUniqueMemberTaskName(team);
157
+ else assertUniqueMemberThreadTitle(team);
158
+ }
159
+
160
+ function assertTeamReadyForBinding(team) {
128
161
  if (isUnderstaffed(team)) {
129
162
  throw new Error(
130
- `cannot bind member threads until the team has at least ${MIN_MEMBERS} distinct members; current count is ${team.members.length}`,
163
+ `cannot bind members until the team has at least ${MIN_MEMBERS} distinct members; current count is ${team.members.length}`,
131
164
  );
132
165
  }
133
166
  assertUniqueMemberFocus(team);
134
167
  assertUniqueMemberName(team);
135
- assertUniqueMemberThreadTitle(team);
168
+ assertUniqueTransportIdentity(team);
136
169
  }
137
170
 
138
- export function addMember(team, { id, focus, lens, deliverable = "", branch = null, name = null }) {
171
+ export function addMember(team, { id, focus, lens, deliverable = "", branch = null, name = null, taskName = null }) {
139
172
  if (!id?.trim()) throw new Error("member id is required");
140
173
  if (!focus?.trim()) throw new Error("member focus is required - a concrete part, ownership area, or perspective");
141
174
  if (!LENSES.includes(lens)) throw new Error(`invalid lens "${lens}" - use one of: ${LENSES.join(", ")}`);
175
+ const v2 = isMultiAgentV2(team);
176
+ if (!v2 && taskName !== null) throw new Error("--task-name is only valid on multi_agent_v2 teams");
177
+ const memberTaskName = v2 ? parseTaskName(taskName) : null;
142
178
  const memberId = id.trim();
143
179
  const memberFocus = focus.trim();
144
180
  // The member name is the short role label that titles this member's thread. Fall back to the
@@ -151,14 +187,20 @@ export function addMember(team, { id, focus, lens, deliverable = "", branch = nu
151
187
  if (duplicateName) {
152
188
  throw new Error(`member name "${memberName}" duplicates "${duplicateName.name ?? duplicateName.focus}" (no two members may produce the same thread title)`);
153
189
  }
190
+ if (v2) {
191
+ const duplicateTask = team.members.find((m) => m.taskName === memberTaskName);
192
+ if (duplicateTask) throw new Error(`member taskName "${memberTaskName}" duplicates "${duplicateTask.taskName}" (no two members may share an agent path)`);
193
+ }
154
194
  team.members.push({
155
195
  id: memberId,
156
196
  name: memberName,
157
197
  focus: memberFocus,
158
198
  lens,
159
199
  deliverable: deliverable.trim(),
200
+ taskName: memberTaskName,
201
+ agentPath: v2 ? agentPathForTaskName(memberTaskName) : null,
160
202
  threadId: null,
161
- threadTitle: `[${team.teamName}] ${memberName}`,
203
+ threadTitle: v2 ? null : `[${team.teamName}] ${memberName}`,
162
204
  cwd: null,
163
205
  worktree: { path: null, branch: branch ?? null },
164
206
  status: "pending",
@@ -167,8 +209,9 @@ export function addMember(team, { id, focus, lens, deliverable = "", branch = nu
167
209
  }
168
210
 
169
211
  export function bindThread(team, { id, threadId, cwd = null, worktreePath = null }) {
212
+ assertTransport(team, "codex_app", "bind-thread");
170
213
  if (!threadId?.trim()) throw new Error("thread id is required");
171
- assertTeamReadyForThreadBinding(team);
214
+ assertTeamReadyForBinding(team);
172
215
  const m = memberById(team, id);
173
216
  m.threadId = threadId.trim();
174
217
  m.status = "active";
@@ -177,6 +220,21 @@ export function bindThread(team, { id, threadId, cwd = null, worktreePath = null
177
220
  return touch(team, "bind-thread", `member ${id} -> thread ${threadId.trim()}`);
178
221
  }
179
222
 
223
+ export function bindAgent(team, { id, agentPath, cwd = null, worktreePath = null }) {
224
+ assertTransport(team, "multi_agent_v2", "bind-agent");
225
+ if (!agentPath?.trim()) throw new Error("agent path is required");
226
+ assertTeamReadyForBinding(team);
227
+ const m = memberById(team, id);
228
+ const boundPath = agentPath.trim();
229
+ if (boundPath !== m.agentPath) {
230
+ throw new Error(`agent path mismatch for member ${m.id}: expected "${m.agentPath}" (task name "${m.taskName}"), got "${boundPath}"`);
231
+ }
232
+ m.status = "active";
233
+ if (cwd) m.cwd = cwd;
234
+ if (team.worktree.enabled) m.worktree.path = worktreePath ?? cwd ?? m.worktree.path;
235
+ return touch(team, "bind-agent", `member ${id} -> agent ${boundPath}`);
236
+ }
237
+
180
238
  export function setMemberStatus(team, { id, status, note = "" }) {
181
239
  if (!MEMBER_STATUSES.includes(status)) throw new Error(`invalid status "${status}" - use one of: ${MEMBER_STATUSES.join(", ")}`);
182
240
  memberById(team, id).status = status;
@@ -204,22 +262,44 @@ export function clearMemberWorktree(team, { id }) {
204
262
  export function archive(team, { id = null, note = "" } = {}) {
205
263
  if (id) {
206
264
  memberById(team, id).status = "archived";
207
- return touch(team, "archive-member", `member ${id}${note ? `: ${note}` : ""}`);
265
+ const memberQualifier = isMultiAgentV2(team) ? " (team state only; V2 exposes no runtime archive operation)" : "";
266
+ return touch(team, "archive-member", `member ${id}${memberQualifier}${note ? `: ${note}` : ""}`);
208
267
  }
209
268
  for (const m of team.members) m.status = "archived";
210
269
  team.status = "archived";
211
270
  team.archivedAt = isoNow();
212
- return touch(team, "archive", note || "team archived; all members closed");
271
+ // The default detail must never claim a runtime closure that did not happen: V2 exposes no
272
+ // runtime archive operation, so only the durable team state is archived there.
273
+ const defaultDetail = isMultiAgentV2(team)
274
+ ? "team archived; V2 exposes no runtime archive operation - durable team state is the archive"
275
+ : "team archived; all members closed";
276
+ return touch(team, "archive", note || defaultDetail);
277
+ }
278
+
279
+ // schemaVersion 2 predates transports; that skill allowed ONLY Codex App threads as members,
280
+ // so a legacy team is migrated in memory as codex_app and persisted on the next mutation.
281
+ function migrateLegacyTeam(team) {
282
+ if (team?.schemaVersion !== 2) return team;
283
+ team.schemaVersion = TEAM_SCHEMA_VERSION;
284
+ team.transport = "codex_app";
285
+ if (team.leader && typeof team.leader === "object") team.leader.agentPath = null;
286
+ for (const member of team.members ?? []) {
287
+ member.taskName = null;
288
+ member.agentPath = null;
289
+ }
290
+ return team;
213
291
  }
214
292
 
215
293
  export function validateTeam(team) {
216
- if (team?.schemaVersion !== 2) throw new Error("invalid team: schemaVersion must be 2");
294
+ migrateLegacyTeam(team);
295
+ if (team?.schemaVersion !== TEAM_SCHEMA_VERSION) throw new Error(`invalid team: schemaVersion must be ${TEAM_SCHEMA_VERSION}`);
296
+ parseTeamTransport(team.transport);
217
297
  if (!team.teamId || !team.teamName) throw new Error("invalid team: teamId and teamName are required");
218
298
  if (team.leader?.kind !== "main-session") throw new Error("invalid team: leader.kind must be main-session");
219
299
  if (!Array.isArray(team.members)) throw new Error("invalid team: members must be an array");
220
300
  assertUniqueMemberFocus(team);
221
301
  assertUniqueMemberName(team);
222
- assertUniqueMemberThreadTitle(team);
302
+ assertUniqueTransportIdentity(team);
223
303
  return team;
224
304
  }
225
305
 
@@ -0,0 +1,55 @@
1
+ // team-transport.mjs - the single source of transport identity for teammode state.
2
+ //
3
+ // A team runs on exactly ONE transport, chosen at init and immutable afterwards:
4
+ // - "multi_agent_v2": members are native Codex MultiAgentV2 agents addressed by task
5
+ // name / canonical agent path (`/root/<task_name>`), messaged with flat `send_message`
6
+ // and `followup_task`.
7
+ // - "codex_app": members are durable Codex App threads addressed by thread id, messaged
8
+ // with `codex_app.send_message_to_thread` (the fallback when flat V2 tools are absent).
9
+ //
10
+ // Zero external dependencies (node builtins only), same portability contract as the
11
+ // sibling scripts.
12
+
13
+ export const TEAM_SCHEMA_VERSION = 3;
14
+ export const TEAM_TRANSPORTS = ["multi_agent_v2", "codex_app"];
15
+ export const DEFAULT_TEAM_TRANSPORT = "codex_app";
16
+ export const LEADER_AGENT_PATH = "/root";
17
+
18
+ // Codex validates V2 task names as lowercase letters, digits, and underscores; "root" is
19
+ // the leader's own path segment and can never name a member.
20
+ const TASK_NAME_PATTERN = /^[a-z0-9_]+$/;
21
+
22
+ export function parseTeamTransport(value = DEFAULT_TEAM_TRANSPORT) {
23
+ if (!TEAM_TRANSPORTS.includes(value)) {
24
+ throw new Error(`invalid transport "${value}" - use one of: ${TEAM_TRANSPORTS.join(", ")}`);
25
+ }
26
+ return value;
27
+ }
28
+
29
+ export function isMultiAgentV2(team) {
30
+ return team?.transport === "multi_agent_v2";
31
+ }
32
+
33
+ export function isCodexApp(team) {
34
+ return team?.transport === "codex_app";
35
+ }
36
+
37
+ export function assertTransport(team, transport, operation) {
38
+ if (team?.transport !== transport) {
39
+ throw new Error(`${operation} is only valid on ${transport} teams; this team's transport is "${team?.transport}"`);
40
+ }
41
+ }
42
+
43
+ export function parseTaskName(taskName) {
44
+ const trimmed = typeof taskName === "string" ? taskName.trim() : "";
45
+ if (!trimmed) throw new Error("member task name is required on multi_agent_v2 teams (--task-name)");
46
+ if (!TASK_NAME_PATTERN.test(trimmed)) {
47
+ throw new Error(`invalid task name "${trimmed}" - use lowercase letters, digits, and underscores`);
48
+ }
49
+ if (trimmed === "root") throw new Error('invalid task name "root" - it names the leader, never a member');
50
+ return trimmed;
51
+ }
52
+
53
+ export function agentPathForTaskName(taskName) {
54
+ return `${LEADER_AGENT_PATH}/${taskName}`;
55
+ }
@@ -6,9 +6,10 @@
6
6
  // Zero external dependencies (node builtins only): runs identically under node and bun on
7
7
  // macOS, Linux, and Windows.
8
8
  //
9
- // node "<skill-root>/scripts/team.mjs" init --name "<team>" --session-name "<sess>" [--session <id>] [--worktree] [--base-branch dev]
10
- // node "<skill-root>/scripts/team.mjs" add-member --team <id> --id A --name "<short role>" --focus "<part/ownership/perspective>" --lens area|ownership|perspective --deliverable "<...>" [--branch <b>]
11
- // node "<skill-root>/scripts/team.mjs" bind-thread --team <id> --id A --thread <thread-id> [--cwd <path>]
9
+ // node "<skill-root>/scripts/team.mjs" init --name "<team>" --session-name "<sess>" [--transport multi_agent_v2|codex_app] [--session <id>] [--worktree] [--base-branch dev]
10
+ // node "<skill-root>/scripts/team.mjs" add-member --team <id> --id A --name "<short role>" --focus "<part/ownership/perspective>" --lens area|ownership|perspective --deliverable "<...>" [--task-name <v2 task name>] [--branch <b>]
11
+ // node "<skill-root>/scripts/team.mjs" bind-agent --team <id> --id A --agent-path /root/<task_name> [--cwd <path>] (multi_agent_v2 teams)
12
+ // node "<skill-root>/scripts/team.mjs" bind-thread --team <id> --id A --thread <thread-id> [--cwd <path>] (codex_app teams)
12
13
  // node "<skill-root>/scripts/team.mjs" member-prompt --team <id> --id A
13
14
  // node "<skill-root>/scripts/team.mjs" set-status --team <id> --id A --status reported|blocked|active|archived [--note "<...>"]
14
15
  // node "<skill-root>/scripts/team.mjs" archive --team <id> [--id A] [--note "<...>"]
@@ -25,6 +26,7 @@ import {
25
26
  addMember,
26
27
  archive,
27
28
  assertSafeTeamDir,
29
+ bindAgent,
28
30
  bindThread,
29
31
  buildTeam,
30
32
  clearMemberWorktree,
@@ -39,6 +41,7 @@ import {
39
41
  writeGuideAtomic,
40
42
  writeTeamAtomic,
41
43
  } from "./team-state.mjs";
44
+ import { isMultiAgentV2, parseTeamTransport } from "./team-transport.mjs";
42
45
  import { addMemberWorktree, integrateMemberBranch, removeMemberWorktree } from "./team-worktree.mjs";
43
46
 
44
47
  function parseFlags(args) {
@@ -95,11 +98,20 @@ const handlers = {
95
98
  async init(cwd, flags) {
96
99
  const teamName = requireFlag(flags, "name");
97
100
  const sessionName = requireFlag(flags, "session-name");
101
+ // Validate the transport BEFORE any directory exists so a bad value leaves no state behind.
102
+ // A valueless `--transport` parses as `true` and must fail loudly, never default silently.
103
+ const transport = parseTeamTransport(flags.transport === undefined ? undefined : flags.transport);
98
104
  const sessionId = typeof flags.session === "string" ? flags.session : `team-${randomUUID().slice(0, 8)}`;
99
105
  const dir = await ensureTeamDir(cwd, sessionId);
100
106
  await withTeamLock(dir, "init", async () => {
101
107
  if (await teamExists(dir)) {
102
- process.stdout.write(`exists: ${dir} (left untouched; re-init is a safe no-op)\n`);
108
+ const existing = await readTeam(dir);
109
+ if (flags.transport !== undefined && existing.transport !== transport) {
110
+ throw new Error(
111
+ `team "${sessionId}" already exists with transport "${existing.transport}" - transport is immutable; keep using it or delete the team first`,
112
+ );
113
+ }
114
+ process.stdout.write(`exists: ${dir} (transport: ${existing.transport}; left untouched; re-init is a safe no-op)\n`);
103
115
  return;
104
116
  }
105
117
  const team = buildTeam({
@@ -107,13 +119,15 @@ const handlers = {
107
119
  sessionName,
108
120
  sessionId,
109
121
  dir,
122
+ transport,
110
123
  worktreeEnabled: flags.worktree === true,
111
124
  baseBranch: typeof flags["base-branch"] === "string" ? flags["base-branch"] : "dev",
112
125
  });
113
126
  await persist(team, dir);
114
- process.stdout.write(`created: ${dir}\n`);
127
+ const taskNameFlag = isMultiAgentV2(team) ? ' --task-name "<lowercase_digits_underscores>"' : "";
128
+ process.stdout.write(`created: ${dir} (transport: ${team.transport})\n`);
115
129
  process.stdout.write(`team.json + guide.md written; artifacts/ ready. session id: ${sessionId}\n`);
116
- process.stdout.write(`next: add-member --team ${sessionId} --id A --name "<short role>" --focus "<part/ownership/perspective>" --lens area|ownership|perspective --deliverable "<...>"\n`);
130
+ process.stdout.write(`next: add-member --team ${sessionId} --id A --name "<short role>"${taskNameFlag} --focus "<part/ownership/perspective>" --lens area|ownership|perspective --deliverable "<...>"\n`);
117
131
  });
118
132
  },
119
133
 
@@ -127,13 +141,17 @@ const handlers = {
127
141
  lens: requireFlag(flags, "lens"),
128
142
  deliverable: typeof flags.deliverable === "string" ? flags.deliverable : "",
129
143
  branch: typeof flags.branch === "string" ? flags.branch : null,
144
+ taskName: typeof flags["task-name"] === "string" ? flags["task-name"] : null,
130
145
  };
131
146
  const { team, member } = await mutateTeam(cwd, sessionId, "add-member", async (team, dir) => {
132
147
  addMember(team, memberInput);
133
148
  await persist(team, dir);
134
149
  return { team, member: team.members.find((m) => m.id === memberId) };
135
150
  });
136
- process.stdout.write(`added member ${memberId} to team ${sessionId}.\n\nSend this as the new thread's first message (title the thread "${member.threadTitle}"):\n---\n${buildMemberPrompt(team, memberId)}\n---\n`);
151
+ const delivery = isMultiAgentV2(team)
152
+ ? `Send this as spawn_agent message (task_name "${member.taskName}", fork_turns "none"), then bind-agent --agent-path "${member.agentPath}"`
153
+ : `Send this as the new thread's first message (title the thread "${member.threadTitle}")`;
154
+ process.stdout.write(`added member ${memberId} to team ${sessionId}.\n\n${delivery}:\n---\n${buildMemberPrompt(team, memberId)}\n---\n`);
137
155
  },
138
156
 
139
157
  async "bind-thread"(cwd, flags) {
@@ -151,6 +169,21 @@ const handlers = {
151
169
  process.stdout.write(`bound member ${flags.id} to thread ${flags.thread}.\n`);
152
170
  },
153
171
 
172
+ async "bind-agent"(cwd, flags) {
173
+ const sessionId = requireFlag(flags, "team");
174
+ const input = {
175
+ id: requireFlag(flags, "id"),
176
+ agentPath: requireFlag(flags, "agent-path"),
177
+ cwd: typeof flags.cwd === "string" ? flags.cwd : null,
178
+ worktreePath: typeof flags["worktree-path"] === "string" ? flags["worktree-path"] : null,
179
+ };
180
+ await mutateTeam(cwd, sessionId, "bind-agent", async (team, dir) => {
181
+ bindAgent(team, input);
182
+ await persist(team, dir);
183
+ });
184
+ process.stdout.write(`bound member ${flags.id} to agent ${flags["agent-path"]}.\n`);
185
+ },
186
+
154
187
  async "member-prompt"(cwd, flags) {
155
188
  const sessionId = requireFlag(flags, "team");
156
189
  const { team } = await loadTeam(cwd, sessionId);
@@ -175,19 +208,24 @@ const handlers = {
175
208
  const sessionId = requireFlag(flags, "team");
176
209
  const memberId = requireFlag(flags, "id");
177
210
  const baseBranch = typeof flags["base-branch"] === "string" ? flags["base-branch"] : null;
178
- const { member, result } = await mutateTeam(cwd, sessionId, "worktree-add", async (team, dir) => {
211
+ const { team, member, result } = await mutateTeam(cwd, sessionId, "worktree-add", async (team, dir) => {
179
212
  const member = memberOrThrow(team, memberId);
180
213
  const result = addMemberWorktree(cwd, team, member, { baseBranch });
181
214
  setMemberWorktree(team, { id: member.id, path: result.path, branch: result.branch });
182
215
  await persist(team, dir);
183
- return { member, result };
216
+ return { team, member, result };
184
217
  });
185
218
  const note = result.created ? "" : " (already exists)";
186
- const thread = member.threadId
187
- ? `\nMember thread: ${codexThreadLink(member.threadId)}\nTell that member to: cd "${result.path}"`
188
- : "\nMember thread is not bound yet; wait for the real Codex thread id, then bind-thread before sending bootstrap. After binding, send the member this worktree path.";
219
+ const v2 = isMultiAgentV2(team);
220
+ const followUp = v2
221
+ ? member.status === "active"
222
+ ? `\nMember agent: ${member.agentPath}\nSend it a followup_task to: cd "${result.path}"`
223
+ : `\nMember agent is not spawned yet; include this worktree path in its spawn_agent message, then bind-agent.`
224
+ : member.threadId
225
+ ? `\nMember thread: ${codexThreadLink(member.threadId)}\nTell that member to: cd "${result.path}"`
226
+ : "\nMember thread is not bound yet; wait for the real Codex thread id, then bind-thread before sending bootstrap. After binding, send the member this worktree path.";
189
227
  process.stdout.write(
190
- `worktree for member ${member.id}${note}: ${result.path} on branch ${result.branch} (off ${result.base}).${thread}\n`,
228
+ `worktree for member ${member.id}${note}: ${result.path} on branch ${result.branch} (off ${result.base}).${followUp}\n`,
191
229
  );
192
230
  },
193
231
 
@@ -235,11 +273,19 @@ const handlers = {
235
273
  id: typeof flags.id === "string" ? flags.id : null,
236
274
  note: typeof flags.note === "string" ? flags.note : "",
237
275
  };
238
- await mutateTeam(cwd, sessionId, "archive", async (team, dir) => {
276
+ const team = await mutateTeam(cwd, sessionId, "archive", async (team, dir) => {
239
277
  archive(team, input);
240
278
  await persist(team, dir);
279
+ return team;
241
280
  });
242
- process.stdout.write(flags.id ? `archived member ${flags.id}\n` : `archived team ${sessionId} and closed all members\n`);
281
+ const v2 = isMultiAgentV2(team);
282
+ const teamSummary = v2
283
+ ? `archived team ${sessionId}; V2 has no runtime archive operation - the durable team state is the archive (interrupt_agent any member still mid-turn)\n`
284
+ : `archived team ${sessionId} and closed all members\n`;
285
+ const memberSummary = v2
286
+ ? `archived member ${flags.id} in team state; V2 has no runtime archive operation (interrupt_agent it if still mid-turn)\n`
287
+ : `archived member ${flags.id}\n`;
288
+ process.stdout.write(flags.id ? memberSummary : teamSummary);
243
289
  },
244
290
 
245
291
  async delete(cwd, flags) {
@@ -261,10 +307,18 @@ const handlers = {
261
307
  async status(cwd, flags) {
262
308
  const sessionId = requireFlag(flags, "team");
263
309
  const { team } = await loadTeam(cwd, sessionId);
264
- process.stdout.write(`Team ${team.teamName} [${team.status}] - leader: main session - ${team.members.length} member(s)\n`);
310
+ process.stdout.write(
311
+ `Team ${team.teamName} [${team.status}] - transport=${team.transport} - leader: main session - ${team.members.length} member(s)\n`,
312
+ );
265
313
  for (const m of team.members) {
266
- const thread = m.threadId ? ` thread=${m.threadId} link=${codexThreadLink(m.threadId)}` : "";
267
- process.stdout.write(` ${m.id} (${m.lens}) ${m.focus} -> ${m.deliverable || "(no deliverable)"} [${m.status}]${thread}${m.cwd ? ` cwd=${m.cwd}` : ""}\n`);
314
+ const endpoint = isMultiAgentV2(team)
315
+ ? m.agentPath
316
+ ? ` agent=${m.agentPath}`
317
+ : ""
318
+ : m.threadId
319
+ ? ` thread=${m.threadId} link=${codexThreadLink(m.threadId)}`
320
+ : "";
321
+ process.stdout.write(` ${m.id} (${m.lens}) ${m.focus} -> ${m.deliverable || "(no deliverable)"} [${m.status}]${endpoint}${m.cwd ? ` cwd=${m.cwd}` : ""}\n`);
268
322
  }
269
323
  if (isUnderstaffed(team)) {
270
324
  process.stdout.write(
@@ -87,6 +87,29 @@ describe("thread title PostToolUse guidance", () => {
87
87
  expect(actual).toBe("");
88
88
  });
89
89
 
90
+ it("#given MultiAgentV2 spawn_agent completed #when the hook runs #then it leaves task-name identity untouched", () => {
91
+ // given
92
+ const output = runPostToolUseHook({
93
+ hook_event_name: "PostToolUse",
94
+ session_id: "s-team",
95
+ turn_id: "t-team",
96
+ transcript_path: null,
97
+ cwd: "/repo",
98
+ model: "gpt-5.6-sol",
99
+ permission_mode: "default",
100
+ tool_name: "spawn_agent",
101
+ tool_use_id: "tool-spawn-agent",
102
+ tool_input: { task_name: "runtime_core", message: "Inspect runtime state" },
103
+ tool_response: { task_name: "/root/runtime_core" },
104
+ });
105
+
106
+ // when
107
+ const actual = output;
108
+
109
+ // then
110
+ expect(actual).toBe("");
111
+ });
112
+
90
113
  it("#given worktree-backed thread creation is pending #when the hook runs #then it tells Codex to wait for the real thread before bootstrapping", () => {
91
114
  // given
92
115
  const output = runPostToolUseHook({