oh-my-opencode 4.16.1 → 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.
- package/.agents/skills/pre-publish-review/SKILL.md +3 -0
- package/README.md +2 -2
- package/dist/agents/hephaestus/agent.d.ts +1 -1
- package/dist/agents/hephaestus/gpt-5-6.d.ts +2 -0
- package/dist/agents/momus-gpt-5-6.d.ts +1 -0
- package/dist/agents/types.d.ts +2 -0
- package/dist/cli/index.js +800 -536
- package/dist/cli-node/index.js +800 -536
- package/dist/features/background-agent/error-classifier.d.ts +5 -0
- package/dist/features/background-agent/parent-wake-dedupe.d.ts +1 -0
- package/dist/features/background-agent/parent-wake-flush-runner.d.ts +2 -0
- package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -0
- package/dist/features/builtin-commands/templates/refactor-sections/intro-and-analysis.d.ts +1 -1
- package/dist/features/builtin-commands/templates/remove-ai-slops.d.ts +1 -1
- package/dist/index.js +464 -54
- package/dist/shared/live-server-route.d.ts +1 -1
- package/dist/skills/review-work/SKILL.md +4 -2
- package/dist/skills/start-work/SKILL.md +2 -0
- package/dist/skills/ulw-plan/references/full-workflow.md +1 -1
- package/dist/skills/ulw-research/SKILL.md +5 -3
- package/dist/skills/visual-qa/SKILL.md +1 -1
- package/dist/tui.js +125 -13
- package/package.json +13 -13
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +398 -246
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/{hephaestus.md → hephaestus/gpt-5.5.md} +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +93 -0
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +43 -24
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/src/codex-hook.ts +2 -1
- package/packages/omo-codex/plugin/components/rules/src/dynamic-target-fingerprints.ts +15 -6
- package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +7 -2
- package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +2 -2
- package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +32 -0
- package/packages/omo-codex/plugin/components/rules/test/hephaestus-model-variant.test.ts +106 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +1 -1
- package/packages/omo-codex/plugin/components/teammode/AGENTS.md +12 -9
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +136 -76
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-guide.mjs +94 -23
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +94 -14
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-transport.mjs +55 -0
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +72 -18
- package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +23 -0
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +24 -41
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-executor.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-qa-executor.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +21 -49
- package/packages/omo-codex/plugin/components/ultrawork/agents/metis.toml +17 -29
- package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +20 -50
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +18 -39
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +1 -0
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +1 -0
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +2 -0
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +3 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/skill-contract.test.ts +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +73 -19
- package/packages/omo-codex/plugin/scripts/entry-guard.mjs +26 -0
- package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +3 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +221 -20
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +71 -3
- package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +35 -7
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +2 -2
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +2 -2
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +7 -5
- package/packages/omo-codex/plugin/scripts/sync-version.mjs +4 -2
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/teammode/SKILL.md +136 -76
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-guide.mjs +94 -23
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +94 -14
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-transport.mjs +55 -0
- package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +72 -18
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +1 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +2 -0
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +3 -1
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +3 -3
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +3 -3
- package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +47 -1
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +1 -1
- package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +4 -2
- package/packages/omo-codex/plugin/test/auto-update-restart-notice.test.mjs +33 -0
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +35 -1
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +412 -19
- package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +71 -0
- package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +65 -0
- package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +13 -4
- package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +345 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +436 -284
- package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +111 -1
- package/packages/shared-skills/skills/review-work/SKILL.md +4 -2
- package/packages/shared-skills/skills/start-work/SKILL.md +2 -0
- package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +1 -1
- package/packages/shared-skills/skills/ulw-research/SKILL.md +5 -3
- package/packages/shared-skills/skills/visual-qa/SKILL.md +1 -1
|
@@ -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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
187
|
-
|
|
188
|
-
|
|
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}).${
|
|
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
|
-
|
|
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(
|
|
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
|
|
267
|
-
|
|
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(
|
|
@@ -266,6 +266,7 @@ Every `multi_agent_v1.spawn_agent` message is self-contained and starts with
|
|
|
266
266
|
handoff. Use `fork_context: false` unless full history is truly
|
|
267
267
|
required; paste only the context the child needs. Full-history forks can
|
|
268
268
|
make the child continue old parent context instead of the delegated task.
|
|
269
|
+
If your tool list has a flat `spawn_agent` with a required `task_name` instead of `multi_agent_v1.*` (`multi_agent_v2`), rewrite: `fork_context: false` becomes `fork_turns: "none"`, `send_input` becomes `send_message`, finished agents end on their own (no `close_agent`; `followup_task` re-tasks, `interrupt_agent` stops), and `wait_agent` takes only `timeout_ms`, returning on any child mailbox activity.
|
|
269
270
|
|
|
270
271
|
# TOML-backed subagent routing compatibility
|
|
271
272
|
Treat TOML-backed role routing as **routing-unverified**. The
|
|
@@ -46,4 +46,6 @@ The full workflow may mention OpenCode-style orchestration examples. In Codex, t
|
|
|
46
46
|
| Wait for background result | `multi_agent_v1.wait_agent(...)` |
|
|
47
47
|
| Clean up finished worker | `multi_agent_v1.close_agent(...)` |
|
|
48
48
|
|
|
49
|
+
Flat `spawn_agent` requiring `task_name` instead (`multi_agent_v2`)? Rewrite rows: add `"task_name"`, `"fork_context":false` → `"fork_turns":"none"`, `wait_agent` takes only `timeout_ms`, no `close_agent` — finished agents end on their own.
|
|
50
|
+
|
|
49
51
|
When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`.
|
|
@@ -161,7 +161,7 @@ Loop per goal. Cap at 5 cycles per goal. Cap identical same-criterion failures a
|
|
|
161
161
|
2. Register atomic todos via `update_plan` — one ultra-granular step per action, `path: <action> for <criterion> - verify by <check>`. Call `update_plan` on every transition (start → `in_progress`, finish → `completed`); exactly one `in_progress`, mark completed immediately, never batch, never let the rendered plan lag behind reality.
|
|
162
162
|
3. DELEGATE-IN-PARALLEL: dispatch every independent task in the wave at once via right-sized `multi_agent_v1.spawn_agent` workers (Delegation table). Each worker captures evidence failing-first: when the task touches EXISTING behavior, PIN it FIRST — a characterization test that asserts the current observable behavior and PASSES on the unchanged code, as rigorous as the new-behavior scenario (exact inputs, exact observable, exact assertion). Then RED through the cheapest faithful channel — a unit test where a seam exists, an integration/e2e test where the behavior lives in wiring, or the criterion's scenario captured failing when no test seam exists — failing for the RIGHT reason (no syntax/import error). A test that mirrors its implementation (mock-call assertions, pinned constants, cannot fail under plausible regression) is not evidence; use the scenario as the failing proof instead. Then the SMALLEST GREEN change; before GREEN work that depends on external review, PR, issue, or branch state, refresh current branch/PR/issue state, preserve existing ordering/policy, and separate compatibility detection from policy changes unless the goal explicitly asks to change policy. A GREEN far larger than the criterion implies means the proof was too coarse — instruct a split. Serialize only on a NAMED dependency.
|
|
163
163
|
4. INTEGRATE + CRITICAL SELF-QA + GIT CHECKPOINT (EVERY WORKER RETURN): do NOT trust the worker's report. Read the diff yourself, re-run its tests, and run LSP diagnostics on the changed files. Treat "done" as a claim to disprove. If the diff drifts, the test is hollow, or evidence is missing, RESPAWN the worker with the specific failure context. Once the work unit is verified, use `git-master` before staging: inspect recent repository commits and touched-path history to infer commit language, Conventional Commit scope, message shape, and unit size. Stage only that unit's files and commit in the observed style; do not carry verified work forward into a later omnibus commit. If no git-tracked files changed or committing is unsafe, record the no-commit reason as evidence. Forward every finding/learning to subsequent workers.
|
|
164
|
-
5. EXECUTE-AS-SCENARIO: ACTUALLY run the Manual-QA scenario the criterion named (channel table above). Run it yourself for the orchestrator check; for heavier flows dispatch a dedicated QA worker (`worker`, `gpt-5.
|
|
164
|
+
5. EXECUTE-AS-SCENARIO: ACTUALLY run the Manual-QA scenario the criterion named (channel table above). Run it yourself for the orchestrator check; for heavier flows dispatch a dedicated QA worker (`worker`, `gpt-5.6-sol`, `xhigh`) whose ONLY job is to drive the channel and write the artifact to the named evidence path. If the scenario FAILS, respawn the implementing worker with the captured failure — do not hand-patch around it.
|
|
165
165
|
6. CAPTURE: collect the observable artifact path: transcript, stdout, screenshot, assertion, status+body, diff, or parsed dump. No artifact written at the evidence path — not done; record BLOCKED and respawn QA.
|
|
166
166
|
7. CLEAN (PAIRED, NEVER SKIP): tear down every runtime artifact step 5 spawned BEFORE recording — server PIDs (`kill`, verify `kill -0` fails), `tmux` sessions (`tmux kill-session -t ulw-qa-<criterion>`; confirm `tmux ls`), browser / Playwright contexts (`.close()`), containers (`docker rm -f`), bound ports (`lsof -i :<port>` empty), temp sockets / files / dirs (`rm -rf` the `mktemp` paths), QA-only env vars, AND `multi_agent_v1.close_agent` on every finished worker. Register each teardown as its own todo the moment the QA spawns the resource (scripts, tmux assets, browsers / agent-browser sessions, PIDs, ports) so none is forgotten. Embed a one-line cleanup receipt in the evidence string, e.g. `cleanup: killed 12345; tmux kill-session ulw-qa-foo; rm -rf /tmp/ulw.aB12cD; multi_agent_v1.close_agent w-3`. Missing receipt → record BLOCKED, not PASS.
|
|
167
167
|
8. RECORD exactly one result:
|
|
@@ -67,6 +67,8 @@ Fan out read-only research before deciding. Every spawn names DELIVERABLE / SCOP
|
|
|
67
67
|
multi_agent_v1.spawn_agent({"message":"TASK: act as an explorer. DELIVERABLE: ... SCOPE: ... VERIFY: ...","agent_type":"explorer","fork_context":false})
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
If your tool list has a flat `spawn_agent` with a required `task_name` instead of `multi_agent_v1.*` (`multi_agent_v2`), rewrite: add `"task_name":"<lowercase_digits_underscores>"`, replace `"fork_context":false` with `"fork_turns":"none"`, and `wait_agent` takes only `timeout_ms`, returning on any child mailbox activity (finished agents end on their own).
|
|
71
|
+
|
|
70
72
|
Spawn every independent child for the current wave first. After the wave
|
|
71
73
|
is launched, use `multi_agent_v1.wait_agent` for each child until each
|
|
72
74
|
reaches terminal status. A timeout is not terminal status. Do not start dependent planning, drafting, approval-gate work, or final handoff until each child result is integrated or recorded as inconclusive.
|
|
@@ -85,7 +85,7 @@ Runs in parallel; ALL must APPROVE; surface results and wait for the user's expl
|
|
|
85
85
|
- UNCLEAR: run the high-accuracy review AUTOMATICALLY before presenting (unless Classify=Trivial), then present a brief that LEADS with the derived approach and the adopted defaults; still wait for the user's explicit okay.
|
|
86
86
|
|
|
87
87
|
### High-accuracy review (dual review)
|
|
88
|
-
The high-accuracy review is DUAL and both passes must return OKAY before handoff: (1) the native `momus` reviewer subagent, and (2) an independent Codex CLI review on gpt-5.
|
|
88
|
+
The high-accuracy review is DUAL and both passes must return OKAY before handoff: (1) the native `momus` reviewer subagent, and (2) an independent Codex CLI review on gpt-5.6-sol at xhigh reasoning, run in a disposable isolated workspace and `CODEX_HOME` with the harness's normal approval and sandbox policy. Do not add flags that disable approvals or sandboxing. Momus runs at Ultra and may take substantially longer than other agents. One round = exactly ONE `momus` + ONE independent review, dispatched together against the COMPLETE plan file (todos + TL;DR filled). Keep Momus in flight and wait for its terminal result: elapsed time alone never justifies cancelling, duplicating, replacing, or treating it as failed. After both verdicts return, fix every cited issue and resubmit both fresh until each approves. CLEAR: runs when the user opts in or `review_required: true`. UNCLEAR: runs automatically unless Classify=Trivial.
|
|
89
89
|
|
|
90
90
|
The draft must record the native Momus session/result, the independent Codex CLI review command/result, and the fix/retry summary. Do not say "high-accuracy review completed" unless both receipts exist and both final verdicts are unconditional approval.
|
|
91
91
|
|
|
@@ -96,6 +96,8 @@ Every spawn starts with `TASK:`, then DELIVERABLE / SCOPE / VERIFY inside `messa
|
|
|
96
96
|
multi_agent_v1.spawn_agent({"message":"TASK: act as an explorer. DELIVERABLE: ... SCOPE: ... VERIFY: ...","agent_type":"explorer","fork_context":false})
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
If your tool list has a flat `spawn_agent` with a required `task_name` instead of `multi_agent_v1.*` (`multi_agent_v2`), rewrite: add `"task_name":"<lowercase_digits_underscores>"`, replace `"fork_context":false` with `"fork_turns":"none"`, and `wait_agent` takes only `timeout_ms`, returning on any child mailbox activity (finished agents end on their own — skip the close step).
|
|
100
|
+
|
|
99
101
|
Roles: `explorer`, `librarian`, `metis`, `momus`. Spawn long plan/reviewer agents in the background and poll with short waits; require the child to send `WORKING: <task> - <phase>` before long passes and `BLOCKED: <reason>` only when progress stops. A wait timeout only means no new mailbox update arrived; treat a running child as alive. Fall back only when the child completed without the deliverable, is ack-only after followup, explicitly `BLOCKED:`, or no longer running; then respawn a smaller `fork_context: false` job. Close each agent after integrating its result.
|
|
100
102
|
|
|
101
103
|
## Stop rules
|
|
@@ -15,11 +15,11 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
15
15
|
| `task(subagent_type="oracle", ...)` for final verification | `multi_agent_v1.spawn_agent({"message":"TASK: act as a rigorous reviewer. ...","agent_type":"lazycodex-gate-reviewer","fork_context":false})` |
|
|
16
16
|
| `task(category="...", ...)` for implementation or QA | `multi_agent_v1.spawn_agent({"message":"TASK: act as an implementation or QA worker. ...","fork_context":false})` |
|
|
17
17
|
| `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
|
|
18
|
-
| `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent
|
|
18
|
+
| `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent` and `multi_agent_v1.wait_agent`; use `multi_agent_v1.send_input` and `multi_agent_v1.close_agent` only when exposed in the active tools list |
|
|
19
19
|
|
|
20
20
|
Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` - pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. `agent_type` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
|
|
23
23
|
|
|
24
24
|
When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
|
|
25
25
|
|
|
@@ -69,7 +69,7 @@ Observation candidates and claim candidates travel back from workers as message
|
|
|
69
69
|
|
|
70
70
|
## Run the swarm as a cooperating team
|
|
71
71
|
|
|
72
|
-
Saturation research defaults to teammode, not isolated fire-and-forget workers: a lead one worker surfaces almost always reshapes what another should search next. When your harness gives you real cooperating members — Codex: the `teammode` skill (`codex_app` threads); OpenCode: `team_mode` — run this swarm as a team. Fall back to the background-worker swarm below only when team mode is unavailable, or the axes are genuinely independent with no cross-pollination expected.
|
|
72
|
+
Saturation research defaults to teammode, not isolated fire-and-forget workers: a lead one worker surfaces almost always reshapes what another should search next. When your harness gives you real cooperating members — Codex: the `teammode` skill (MultiAgentV2 agents, or `codex_app` threads as its fallback); OpenCode: `team_mode` — run this swarm as a team. Fall back to the background-worker swarm below only when team mode is unavailable, or the axes are genuinely independent with no cross-pollination expected.
|
|
73
73
|
|
|
74
74
|
- **One member per axis — by part, ownership, or perspective, never a job title.** Each Phase 0 axis is one member owning one concrete slice: a codebase part, a source territory, or a question lens. No two members share an angle. "Backend researcher" or "the web person" gives no real boundary and invites overlap — name what the member owns.
|
|
75
75
|
- **Many teammates by default.** Prefer a larger roster, usually 5-8 teammates, whenever the axes can be made distinct. Add at least one skeptic or red-team perspective for hyperdebate/ultradebate: cross-critique claims, evidence quality, synthesis structure, and visual-report choices before they reach the final deliverable.
|
|
@@ -15,11 +15,11 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
15
15
|
| `task(subagent_type="oracle", ...)` for final verification | `multi_agent_v1.spawn_agent({"message":"TASK: act as a rigorous reviewer. ...","agent_type":"lazycodex-gate-reviewer","fork_context":false})` |
|
|
16
16
|
| `task(category="...", ...)` for implementation or QA | `multi_agent_v1.spawn_agent({"message":"TASK: act as an implementation or QA worker. ...","fork_context":false})` |
|
|
17
17
|
| `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
|
|
18
|
-
| `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent
|
|
18
|
+
| `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent` and `multi_agent_v1.wait_agent`; use `multi_agent_v1.send_input` and `multi_agent_v1.close_agent` only when exposed in the active tools list |
|
|
19
19
|
|
|
20
20
|
Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` - pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. `agent_type` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
|
|
23
23
|
|
|
24
24
|
When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
|
|
25
25
|
|
|
@@ -271,7 +271,7 @@ If any page fails, you are not done: fix it, re-capture the full set, re-dispatc
|
|
|
271
271
|
|
|
272
272
|
Run this step IN ADDITION to Steps 1-4 when the original user task has a concrete visual target: "clone this site", "move this Figma design to code", "rebuild this screen", "make it look exactly like X", or "build this Imagen/Stitch/generated mockup and overview". For these tasks the normal dual-oracle is necessary but NOT sufficient. After it returns, run the following TWO additional MANDATORY verifications and LOOP until BOTH pass.
|
|
273
273
|
|
|
274
|
-
1. Pixel-perfect design-compare subagent (visual oracle). Dispatch a focused, read-only design-compare reviewer (recommend `gpt-5.
|
|
274
|
+
1. Pixel-perfect design-compare subagent (visual oracle). Dispatch a focused, read-only design-compare reviewer (recommend `gpt-5.6-sol` with xhigh reasoning). It must crop/zoom BOTH the reference (target / Figma export / source-site screenshot / generated page snapshot) and the ACTUAL screenshot into matching regions and read them **pixel-by-pixel** - header, nav, each card, spacing, type ramp, color tokens - not at a glance. It must also compare the overview text or annotations against the rendered content and DOM text. Anchor every claim with the bundled tool:
|
|
275
275
|
|
|
276
276
|
```
|
|
277
277
|
node "$SKILL_DIR/scripts/visual-qa.mjs" image-diff <reference.png> <actual.png>
|