@mmerterden/multi-agent-pipeline 20.30.0 → 20.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +123 -0
- package/README.md +47 -1
- package/README.tr.md +61 -1
- package/docs/adr/0018-cross-repo-links-not-a-global-graph.md +43 -0
- package/docs/adr/README.md +2 -1
- package/docs/facts.json +6 -5
- package/docs/features.md +1 -1
- package/install/_dev-only-files.mjs +1 -0
- package/install/_unattended-profile.mjs +61 -0
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +16 -2
- package/install/index.mjs +10 -1
- package/install/templates/claude-hooks.json +23 -1
- package/manifest.json +198 -134
- package/package.json +4 -1
- package/pipeline/agents/code-reviewer.md +9 -0
- package/pipeline/agents/explorer.md +4 -0
- package/pipeline/agents/plan-critic.md +4 -2
- package/pipeline/commands/multi-agent/SKILL.md +17 -136
- package/pipeline/commands/multi-agent/analysis/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/channels/SKILL.md +15 -297
- package/pipeline/commands/multi-agent/graph/SKILL.md +5 -2
- package/pipeline/commands/multi-agent/language/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/office-on/SKILL.md +8 -7
- package/pipeline/commands/multi-agent/office-status/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -1
- package/pipeline/commands/multi-agent/resume/SKILL.md +8 -22
- package/pipeline/commands/multi-agent/review/SKILL.md +11 -134
- package/pipeline/commands/multi-agent/scenario-audit/SKILL.md +4 -2
- package/pipeline/commands/multi-agent/setup/SKILL.md +19 -224
- package/pipeline/commands/multi-agent/sync/SKILL.md +12 -0
- package/pipeline/commands/multi-agent/test-gen/SKILL.md +29 -3
- package/pipeline/contract/CHANGELOG.md +46 -0
- package/pipeline/contract/README.md +27 -10
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/fixtures/launch-plan.json +1 -1
- package/pipeline/contract/fixtures/runs-awaiting-question.json +6 -1
- package/pipeline/contract/fixtures/runs-empty.json +1 -1
- package/pipeline/contract/fixtures/runs-failed.json +6 -1
- package/pipeline/contract/fixtures/runs-old-schema.json +6 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +5 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +6 -1
- package/pipeline/contract/fixtures/runs-running.json +6 -1
- package/pipeline/contract/manifest.json +153 -19
- package/pipeline/contract/types/index.d.ts +304 -6
- package/pipeline/lib/bg-launch.mjs +106 -0
- package/pipeline/lib/code-digest.mjs +49 -0
- package/pipeline/lib/credential-resolve.mjs +42 -2
- package/pipeline/multi-agent-refs/channels/generator.md +146 -0
- package/pipeline/multi-agent-refs/channels/menu.md +55 -0
- package/pipeline/multi-agent-refs/channels/multi-repo.md +6 -0
- package/pipeline/multi-agent-refs/channels/preview.md +25 -0
- package/pipeline/multi-agent-refs/channels/reference.md +98 -0
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +1 -1
- package/pipeline/multi-agent-refs/features/code-graph.md +27 -0
- package/pipeline/multi-agent-refs/features/office-threat-model.md +24 -6
- package/pipeline/multi-agent-refs/features/plan-critic.md +5 -2
- package/pipeline/multi-agent-refs/features/review-delta.md +4 -1
- package/pipeline/multi-agent-refs/features/scope-check.md +10 -1
- package/pipeline/multi-agent-refs/features/unattended-security.md +1 -1
- package/pipeline/multi-agent-refs/help/en.md +2 -2
- package/pipeline/multi-agent-refs/help/tr.md +2 -2
- package/pipeline/multi-agent-refs/knowledge.md +9 -0
- package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +163 -0
- package/pipeline/multi-agent-refs/phases/modes.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -4
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +9 -8
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +12 -7
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +7 -3
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +19 -10
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/resume/details.md +35 -0
- package/pipeline/multi-agent-refs/review/inputs.md +87 -0
- package/pipeline/multi-agent-refs/review/module-guides.md +25 -0
- package/pipeline/multi-agent-refs/review/valven.md +42 -0
- package/pipeline/multi-agent-refs/review-standalone-tracker.md +37 -0
- package/pipeline/multi-agent-refs/setup/accounts.md +27 -0
- package/pipeline/multi-agent-refs/setup/app-store-connect.md +5 -0
- package/pipeline/multi-agent-refs/setup/firebase.md +5 -0
- package/pipeline/multi-agent-refs/setup/google-play.md +4 -0
- package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +21 -0
- package/pipeline/multi-agent-refs/setup/language.md +46 -0
- package/pipeline/multi-agent-refs/setup/multi-repo-group.md +51 -0
- package/pipeline/multi-agent-refs/setup/summaries.md +63 -0
- package/pipeline/multi-agent-refs/setup/token-save-flow.md +51 -0
- package/pipeline/multi-agent-refs/tracker-contract.md +8 -3
- package/pipeline/office/NOTICE.md +4 -1
- package/pipeline/office/assets/ARCHIVE_BOXES.png +0 -0
- package/pipeline/office/assets/COFFEE_MACHINE.png +0 -0
- package/pipeline/office/assets/COUNTER_SINK.png +0 -0
- package/pipeline/office/assets/DEVICE_STAND.png +0 -0
- package/pipeline/office/assets/FILING_CABINET.png +0 -0
- package/pipeline/office/assets/FRIDGE.png +0 -0
- package/pipeline/office/assets/MAILBOX.png +0 -0
- package/pipeline/office/assets/MOP_BUCKET.png +0 -0
- package/pipeline/office/assets/PRINTER.png +0 -0
- package/pipeline/office/assets/RECEPTION_DESK.png +0 -0
- package/pipeline/office/assets/SERVER_RACK.png +0 -0
- package/pipeline/office/assets/SERVER_RACK_2.png +0 -0
- package/pipeline/office/assets/TOILET_STALL.png +0 -0
- package/pipeline/office/assets/VENDING.png +0 -0
- package/pipeline/office/assets/WASHBASIN.png +0 -0
- package/pipeline/office/assets/WATER_COOLER.png +0 -0
- package/pipeline/office/index.html +3936 -819
- package/pipeline/office/toolkit.json +3 -0
- package/pipeline/rules/response-shape.md +6 -7
- package/pipeline/schemas/activity.schema.json +188 -4
- package/pipeline/schemas/agent-state.schema.json +511 -115
- package/pipeline/schemas/contract-error.schema.json +12 -1
- package/pipeline/schemas/job-change.schema.json +72 -0
- package/pipeline/schemas/job-request.schema.json +38 -0
- package/pipeline/schemas/launch.json +4 -4
- package/pipeline/schemas/office-credentials.schema.json +71 -0
- package/pipeline/schemas/office-jobs.json +253 -0
- package/pipeline/schemas/office-jobs.schema.json +62 -0
- package/pipeline/schemas/office-memory.schema.json +105 -0
- package/pipeline/schemas/office-settings-request.schema.json +30 -0
- package/pipeline/schemas/office-settings.schema.json +67 -0
- package/pipeline/schemas/phases.json +14 -6
- package/pipeline/schemas/phases.schema.json +86 -19
- package/pipeline/schemas/plan-critique.schema.json +9 -2
- package/pipeline/schemas/planning-output.schema.json +25 -2
- package/pipeline/schemas/prefs.schema.json +24 -1
- package/pipeline/schemas/prs.schema.json +5 -0
- package/pipeline/schemas/prune-logs-request.schema.json +26 -0
- package/pipeline/schemas/prune-logs.schema.json +52 -0
- package/pipeline/schemas/repo-change.schema.json +54 -0
- package/pipeline/schemas/repo-request.schema.json +29 -0
- package/pipeline/schemas/repos.schema.json +49 -1
- package/pipeline/schemas/reviewer-output.schema.json +20 -2
- package/pipeline/schemas/runs-index.schema.json +257 -46
- package/pipeline/schemas/system-status.schema.json +111 -0
- package/pipeline/schemas/token-budget.json +4 -4
- package/pipeline/schemas/tools.schema.json +5 -0
- package/pipeline/schemas/triage-output.schema.json +20 -2
- package/pipeline/schemas/unattended-profile.json +7 -2
- package/pipeline/scripts/_active-run.mjs +2 -1
- package/pipeline/scripts/_graph-analysis.mjs +270 -0
- package/pipeline/scripts/_memory-policy.mjs +165 -0
- package/pipeline/scripts/_secret-scan.mjs +105 -0
- package/pipeline/scripts/_spawn-async.mjs +9 -5
- package/pipeline/scripts/_unified-diff.mjs +2 -2
- package/pipeline/scripts/activity-snapshot.mjs +219 -23
- package/pipeline/scripts/autopilot-runner.mjs +7 -5
- package/pipeline/scripts/capture-flush.sh +12 -2
- package/pipeline/scripts/check-read-size.sh +9 -0
- package/pipeline/scripts/code-graph-rules/android.json +8 -3
- package/pipeline/scripts/contract-server.mjs +575 -28
- package/pipeline/scripts/debt-ledger.mjs +323 -0
- package/pipeline/scripts/file-context.mjs +261 -0
- package/pipeline/scripts/file-context.sh +20 -0
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -1
- package/pipeline/scripts/graph-links.mjs +230 -0
- package/pipeline/scripts/graph-query.mjs +133 -1
- package/pipeline/scripts/graph-report.mjs +65 -0
- package/pipeline/scripts/job-launch.mjs +477 -0
- package/pipeline/scripts/launch-request.mjs +94 -9
- package/pipeline/scripts/learn-from-transcripts.mjs +12 -48
- package/pipeline/scripts/learnings-ledger.mjs +143 -24
- package/pipeline/scripts/office-build.mjs +148 -11
- package/pipeline/scripts/office-control.mjs +34 -7
- package/pipeline/scripts/office-credentials.mjs +106 -0
- package/pipeline/scripts/office-memory.mjs +200 -0
- package/pipeline/scripts/office-prune.mjs +125 -0
- package/pipeline/scripts/office-settings.mjs +145 -0
- package/pipeline/scripts/phase-tracker.sh +12 -5
- package/pipeline/scripts/plan-critique-gate.mjs +11 -2
- package/pipeline/scripts/prune-logs.sh +53 -2
- package/pipeline/scripts/rework-rulings.mjs +106 -0
- package/pipeline/scripts/runs-index.mjs +62 -2
- package/pipeline/scripts/scope-check-gate.mjs +149 -2
- package/pipeline/scripts/system-status.mjs +196 -0
- package/pipeline/scripts/task-mirror.mjs +150 -0
- package/pipeline/scripts/task-mirror.sh +20 -0
- package/pipeline/scripts/toolkit-tools.mjs +33 -7
- package/pipeline/scripts/transcript-index.mjs +4 -1
- package/pipeline/scripts/triage-memory.mjs +64 -37
- package/pipeline/scripts/validate-planning.mjs +68 -4
- package/pipeline/scripts/validate-reviewer.mjs +20 -0
- package/pipeline/scripts/validate-triage.mjs +107 -2
- package/pipeline/scripts/work-items.mjs +191 -22
- package/pipeline/skills/.skill-manifest.json +9 -9
- package/pipeline/skills/.skills-index.json +1 -1
- package/pipeline/skills/shared/README.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +5 -2
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +4 -4
- package/pipeline/skills/shared/core/multi-agent-office-on/SKILL.md +3 -4
- package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +4 -1
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +21 -0
- package/pipeline/skills/shared/core/multi-agent-scenario-audit/SKILL.md +4 -2
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +4 -4
- package/pipeline/skills/shared/core/multi-agent-test-gen/SKILL.md +26 -3
- package/pipeline/skills/skills-index.md +1 -1
|
@@ -75,20 +75,37 @@ import {
|
|
|
75
75
|
defaultProjectsRoot,
|
|
76
76
|
} from "./transcript-index.mjs";
|
|
77
77
|
import { buildIndex } from "./runs-index.mjs";
|
|
78
|
+
import { startedJobs } from "./job-launch.mjs";
|
|
78
79
|
|
|
79
|
-
export const CONTRACT_VERSION = "1.
|
|
80
|
+
export const CONTRACT_VERSION = "1.1.0";
|
|
80
81
|
export const RECENT_MS = 10 * 60_000;
|
|
81
82
|
|
|
82
83
|
export const ROLES = JSON.parse(
|
|
83
84
|
readFileSync(new URL("../schemas/activity-roles.json", import.meta.url), "utf8"),
|
|
84
85
|
);
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
86
|
+
const PHASES = JSON.parse(readFileSync(new URL("../schemas/phases.json", import.meta.url), "utf8"));
|
|
87
|
+
const PHASE_NAMES = Object.fromEntries(PHASES.phases.map((p) => [String(p.id), p.name]));
|
|
88
|
+
/** A phase's name in the run's own mode: an analysis names its phases its way. */
|
|
89
|
+
const phaseName = (runType, id) =>
|
|
90
|
+
(runType === "analysis" ? PHASES.modes?.analysis?.names?.[String(id)] : null) ??
|
|
91
|
+
PHASE_NAMES[String(id)] ??
|
|
92
|
+
String(id);
|
|
90
93
|
|
|
91
94
|
const TERMINAL = new Set(["complete", "completed", "done", "abandoned", "cancelled", "failed"]);
|
|
95
|
+
/**
|
|
96
|
+
* A run that finished a moment ago stays on the floor for its result: the team
|
|
97
|
+
* sees it through, then leaves. Only a finished run counts, not one that was
|
|
98
|
+
* abandoned or failed.
|
|
99
|
+
*/
|
|
100
|
+
export const FINISH_MS = 2 * 60_000;
|
|
101
|
+
const FINISHED = new Set(["complete", "completed", "done"]);
|
|
102
|
+
function justFinished(run, nowMs) {
|
|
103
|
+
if (!FINISHED.has(run.status)) return false;
|
|
104
|
+
const ends = (run.phases ?? [])
|
|
105
|
+
.map((p) => Date.parse(p.completedAt ?? ""))
|
|
106
|
+
.filter(Number.isFinite);
|
|
107
|
+
return ends.length > 0 && nowMs - Math.max(...ends) < FINISH_MS;
|
|
108
|
+
}
|
|
92
109
|
const READ_TOOLS = new Set([
|
|
93
110
|
"Read",
|
|
94
111
|
"Grep",
|
|
@@ -167,7 +184,7 @@ function toolDetail(name, input) {
|
|
|
167
184
|
// ---------------------------------------------------------------------------
|
|
168
185
|
// One transcript, reduced to what a desk shows
|
|
169
186
|
|
|
170
|
-
function emptyState() {
|
|
187
|
+
export function emptyState() {
|
|
171
188
|
return {
|
|
172
189
|
firstAt: null,
|
|
173
190
|
lastAt: null,
|
|
@@ -177,8 +194,24 @@ function emptyState() {
|
|
|
177
194
|
title: null,
|
|
178
195
|
last: null,
|
|
179
196
|
pending: new Map(),
|
|
197
|
+
mcp: [],
|
|
198
|
+
tasks: [],
|
|
199
|
+
taskCalls: new Map(),
|
|
180
200
|
};
|
|
181
201
|
}
|
|
202
|
+
// The last MCP calls a desk made, newest last: what a session did with the
|
|
203
|
+
// toolkit and other servers when it used them directly, outside any run step.
|
|
204
|
+
const MCP_KEEP = 8;
|
|
205
|
+
|
|
206
|
+
const WAIT_KINDS = new Set([
|
|
207
|
+
"maturity",
|
|
208
|
+
"user-channels-choice",
|
|
209
|
+
"local-test",
|
|
210
|
+
"attended-reply",
|
|
211
|
+
"question",
|
|
212
|
+
]);
|
|
213
|
+
/** A live seat holding a question open in its own window (AskUserQuestion). */
|
|
214
|
+
const asking = (seats) => seats.some((x) => x.live === true && x.state === "waiting");
|
|
182
215
|
|
|
183
216
|
const tsOf = (rec) => {
|
|
184
217
|
const t = Date.parse(rec?.timestamp ?? "");
|
|
@@ -186,6 +219,17 @@ const tsOf = (rec) => {
|
|
|
186
219
|
};
|
|
187
220
|
|
|
188
221
|
/** Fold one Claude-shaped record into a desk state. */
|
|
222
|
+
// A turn that ended or was interrupted leaves no call running: an MCP call
|
|
223
|
+
// it never answered is closed as failed, so no desk waits on it forever.
|
|
224
|
+
function endTurn(s, t) {
|
|
225
|
+
s.pending.clear();
|
|
226
|
+
for (const c of s.mcp)
|
|
227
|
+
if (c.end === null) {
|
|
228
|
+
c.end = t ?? c.at ?? 0;
|
|
229
|
+
c.error = true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
189
233
|
export function reduceRecord(s, rec) {
|
|
190
234
|
const t = tsOf(rec);
|
|
191
235
|
if (t !== null) {
|
|
@@ -195,7 +239,7 @@ export function reduceRecord(s, rec) {
|
|
|
195
239
|
if (typeof rec.cwd === "string" && rec.cwd) s.cwd = rec.cwd;
|
|
196
240
|
if (typeof rec.gitBranch === "string" && rec.gitBranch) s.gitBranch = rec.gitBranch;
|
|
197
241
|
if (rec.type === "ai-title" && typeof rec.aiTitle === "string") s.title = rec.aiTitle;
|
|
198
|
-
if (rec.type === "system" && rec.subtype === "turn_duration") s
|
|
242
|
+
if (rec.type === "system" && rec.subtype === "turn_duration") endTurn(s, t);
|
|
199
243
|
const msg = rec.message;
|
|
200
244
|
if (!msg || typeof msg !== "object") return s;
|
|
201
245
|
const content = Array.isArray(msg.content) ? msg.content : null;
|
|
@@ -208,13 +252,47 @@ export function reduceRecord(s, rec) {
|
|
|
208
252
|
const entry = { tool: b.name, detail: toolDetail(b.name, b.input) };
|
|
209
253
|
s.pending.set(String(b.id ?? s.pending.size), entry);
|
|
210
254
|
s.last = entry;
|
|
255
|
+
if (b.name === "TodoWrite" && Array.isArray(b.input?.todos)) {
|
|
256
|
+
s.tasks = b.input.todos.slice(0, MAX_TASKS).map((x, i) => ({
|
|
257
|
+
id: String(i),
|
|
258
|
+
subject: String(x?.content ?? x?.subject ?? ""),
|
|
259
|
+
status: TASK_STATES.has(x?.status) ? x.status : "pending",
|
|
260
|
+
}));
|
|
261
|
+
} else if (b.name === "TaskCreate" && typeof b.input?.subject === "string") {
|
|
262
|
+
s.taskCalls.set(String(b.id ?? ""), b.input.subject);
|
|
263
|
+
} else if (b.name === "TaskUpdate" && b.input?.taskId !== undefined) {
|
|
264
|
+
if (b.input.status === "deleted")
|
|
265
|
+
s.tasks = s.tasks.filter((x) => x.id !== String(b.input.taskId));
|
|
266
|
+
const tk = s.tasks.find((x) => x.id === String(b.input.taskId));
|
|
267
|
+
if (tk && TASK_STATES.has(b.input.status)) tk.status = b.input.status;
|
|
268
|
+
if (tk && typeof b.input.subject === "string") tk.subject = b.input.subject;
|
|
269
|
+
}
|
|
270
|
+
if (b.name.startsWith("mcp__")) {
|
|
271
|
+
s.mcp.push({ id: String(b.id ?? ""), tool: b.name, at: t, end: null, error: false });
|
|
272
|
+
if (s.mcp.length > MCP_KEEP) s.mcp.shift();
|
|
273
|
+
}
|
|
211
274
|
}
|
|
212
275
|
}
|
|
213
|
-
if (msg.stop_reason === "end_turn" || msg.stop_reason === "stop_sequence") s
|
|
276
|
+
if (msg.stop_reason === "end_turn" || msg.stop_reason === "stop_sequence") endTurn(s, t);
|
|
214
277
|
} else if (rec.type === "user") {
|
|
215
278
|
const results = (content ?? []).filter((b) => b?.type === "tool_result");
|
|
216
|
-
for (const b of results)
|
|
217
|
-
|
|
279
|
+
for (const b of results) {
|
|
280
|
+
s.pending.delete(String(b.tool_use_id));
|
|
281
|
+
const subject = s.taskCalls.get(String(b.tool_use_id));
|
|
282
|
+
if (subject !== undefined) {
|
|
283
|
+
s.taskCalls.delete(String(b.tool_use_id));
|
|
284
|
+
const text = typeof b.content === "string" ? b.content : JSON.stringify(b.content ?? "");
|
|
285
|
+
const m = /Task #(\w+)/.exec(text);
|
|
286
|
+
if (m && !/^Phase \d+\b/.test(subject) && s.tasks.length < MAX_TASKS)
|
|
287
|
+
s.tasks.push({ id: m[1], subject, status: "pending" });
|
|
288
|
+
}
|
|
289
|
+
const call = s.mcp.find((c) => c.id === String(b.tool_use_id));
|
|
290
|
+
if (call) {
|
|
291
|
+
call.end = t;
|
|
292
|
+
call.error = b.is_error === true;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (!results.length && !rec.isMeta) endTurn(s, t);
|
|
218
296
|
}
|
|
219
297
|
return s;
|
|
220
298
|
}
|
|
@@ -404,10 +482,15 @@ export function runExtras(run) {
|
|
|
404
482
|
* @returns {{run: object|null, matchedBy: "session"|"cwd-branch"|null, ambiguous: boolean}}
|
|
405
483
|
*/
|
|
406
484
|
export function matchRun(sessionId, cwd, branch, runs) {
|
|
407
|
-
|
|
485
|
+
// A run that just finished keeps only the session that ran it, and only
|
|
486
|
+
// when no live run claims that session: a session that finished one run and
|
|
487
|
+
// started the next belongs to the next.
|
|
488
|
+
const live = runs.filter((r) => !TERMINAL.has(r.status));
|
|
489
|
+
const mine = (r) => r.sessionId && r.sessionId === sessionId;
|
|
490
|
+
const byId = live.find(mine) ?? runs.find(mine);
|
|
408
491
|
if (byId) return { run: byId, matchedBy: "session", ambiguous: false };
|
|
409
492
|
if (!cwd) return { run: null, matchedBy: null, ambiguous: false };
|
|
410
|
-
const hits =
|
|
493
|
+
const hits = live.filter((r) => {
|
|
411
494
|
const roots = [{ wt: r.worktreePath, root: r.extras?.projectRoot, branch: r.branch }];
|
|
412
495
|
for (const p of r.extras?.projects ?? []) {
|
|
413
496
|
roots.push({ wt: p?.worktreePath, root: p?.projectRoot, branch: p?.branch ?? r.branch });
|
|
@@ -464,9 +547,10 @@ export async function activitySnapshot(opts = {}) {
|
|
|
464
547
|
const clean = (v, limit = 160) =>
|
|
465
548
|
v === null || v === undefined || v === "" ? null : R.excerpt(v, limit);
|
|
466
549
|
|
|
550
|
+
const jobs = opts.jobs ?? startedJobs();
|
|
467
551
|
const allRuns = opts.runs ?? buildIndex();
|
|
468
552
|
const runs = allRuns
|
|
469
|
-
.filter((r) => !TERMINAL.has(r.status))
|
|
553
|
+
.filter((r) => !TERMINAL.has(r.status) || justFinished(r, now))
|
|
470
554
|
.map((r) => ({ ...r, extras: runExtras(r) }));
|
|
471
555
|
const wanted = new Set(runs.map((r) => r.sessionId).filter(Boolean));
|
|
472
556
|
|
|
@@ -494,10 +578,14 @@ export async function activitySnapshot(opts = {}) {
|
|
|
494
578
|
team.matchedBy ??= hit.matchedBy;
|
|
495
579
|
} else {
|
|
496
580
|
const id = `session:${sessionId}`;
|
|
581
|
+
// A session the office started for a job carries the job's role, and a
|
|
582
|
+
// solo job is seated at a desk rather than around a table.
|
|
583
|
+
const job = jobs.get(sessionId) ?? null;
|
|
497
584
|
team = {
|
|
498
585
|
id,
|
|
499
586
|
run: null,
|
|
500
|
-
kind: hit.ambiguous ? "unmatched" : "session",
|
|
587
|
+
kind: hit.ambiguous ? "unmatched" : job && !job.team ? "solo" : "session",
|
|
588
|
+
job,
|
|
501
589
|
seats: [],
|
|
502
590
|
sessions: [],
|
|
503
591
|
hosts: new Set(),
|
|
@@ -508,7 +596,16 @@ export async function activitySnapshot(opts = {}) {
|
|
|
508
596
|
team.hosts.add(host);
|
|
509
597
|
const live = liveOf(sessionId);
|
|
510
598
|
team.seats.push(
|
|
511
|
-
seat({
|
|
599
|
+
seat({
|
|
600
|
+
sessionId,
|
|
601
|
+
agentId: null,
|
|
602
|
+
agentType: null,
|
|
603
|
+
s,
|
|
604
|
+
finished: false,
|
|
605
|
+
live,
|
|
606
|
+
phase: null,
|
|
607
|
+
as: team.job?.role,
|
|
608
|
+
}),
|
|
512
609
|
);
|
|
513
610
|
if (host !== "claude" || !dir) return;
|
|
514
611
|
for (const sub of subagentsOf(dir, sessionId, since)) {
|
|
@@ -530,8 +627,11 @@ export async function activitySnapshot(opts = {}) {
|
|
|
530
627
|
}
|
|
531
628
|
};
|
|
532
629
|
|
|
533
|
-
const seat = ({ sessionId, agentId, agentType, description, s, finished, live, phase }) => {
|
|
534
|
-
const { role, known } =
|
|
630
|
+
const seat = ({ sessionId, agentId, agentType, description, s, finished, live, phase, as }) => {
|
|
631
|
+
const { role, known } =
|
|
632
|
+
as && agentId === null
|
|
633
|
+
? { role: as, known: true }
|
|
634
|
+
: roleFor(agentType ?? (agentId ? "unknown" : null), phase);
|
|
535
635
|
const style = styleFor(role);
|
|
536
636
|
const act = deskActivity(s, finished);
|
|
537
637
|
const detail = clean(act.detail, 80);
|
|
@@ -557,6 +657,20 @@ export async function activitySnapshot(opts = {}) {
|
|
|
557
657
|
live,
|
|
558
658
|
startedAt: iso(s.firstAt),
|
|
559
659
|
lastAt: iso(s.lastAt),
|
|
660
|
+
tasks: (s.tasks ?? []).map((x) => ({
|
|
661
|
+
subject: clean(x.subject, 80) ?? "",
|
|
662
|
+
status: x.status,
|
|
663
|
+
})),
|
|
664
|
+
mcp: (s.mcp ?? []).map((c) => {
|
|
665
|
+
const [, server = "", name = c.tool] = /^mcp__(.+?)__(.+)$/.exec(c.tool) ?? [];
|
|
666
|
+
return {
|
|
667
|
+
server: clean(server, 40) ?? "",
|
|
668
|
+
tool: clean(name, 60) ?? "",
|
|
669
|
+
state: c.end === null ? "running" : c.error ? "error" : "ok",
|
|
670
|
+
ms: c.end !== null && c.at !== null ? Math.max(0, c.end - c.at) : null,
|
|
671
|
+
at: iso(c.at),
|
|
672
|
+
};
|
|
673
|
+
}),
|
|
560
674
|
};
|
|
561
675
|
};
|
|
562
676
|
|
|
@@ -712,6 +826,42 @@ function holders(seats, phaseId) {
|
|
|
712
826
|
return { holder: holder?.id ?? null, previousHolder: earlier?.id ?? null };
|
|
713
827
|
}
|
|
714
828
|
|
|
829
|
+
// The host task widget's list, as far as a table sign and a run window show it.
|
|
830
|
+
const MAX_TASKS = 20;
|
|
831
|
+
const TASK_STATES = new Set(["pending", "in_progress", "completed"]);
|
|
832
|
+
const fraction = (v) =>
|
|
833
|
+
typeof v === "number" && v >= 0 && v <= 1 ? Math.round(v * 100) / 100 : null;
|
|
834
|
+
// The command a table runs, named the way a user types it.
|
|
835
|
+
// The commands installed beside this script; a mode with no command of its
|
|
836
|
+
// own (dev, short, dev-local) is a /multi-agent run.
|
|
837
|
+
const COMMANDS = (() => {
|
|
838
|
+
try {
|
|
839
|
+
return new Set(
|
|
840
|
+
readdirSync(new URL("../commands/multi-agent/", import.meta.url), { withFileTypes: true })
|
|
841
|
+
.filter((d) => d.isDirectory())
|
|
842
|
+
.map((d) => d.name),
|
|
843
|
+
);
|
|
844
|
+
} catch {
|
|
845
|
+
return new Set();
|
|
846
|
+
}
|
|
847
|
+
})();
|
|
848
|
+
// Where no command tree sits beside the scripts (another host's layout), the
|
|
849
|
+
// modes known to be plain /multi-agent runs are the ones left unprefixed.
|
|
850
|
+
const PLAIN_MODES = new Set(["pipeline", "full", "dev", "short", "dev-local"]);
|
|
851
|
+
const hasCommand = (mode) => (COMMANDS.size ? COMMANDS.has(mode) : !PLAIN_MODES.has(mode));
|
|
852
|
+
export const commandOf = (mode) =>
|
|
853
|
+
mode === "autopilot-run" || mode === "autopilot"
|
|
854
|
+
? "/multi-agent autopilot"
|
|
855
|
+
: mode && hasCommand(mode)
|
|
856
|
+
? `/multi-agent:${mode}`
|
|
857
|
+
: "/multi-agent";
|
|
858
|
+
const RUN_TYPES = new Set(["development", "analysis", "autopilot"]);
|
|
859
|
+
// A session the office started for a team job, before its run record exists.
|
|
860
|
+
const JOB_RUN_TYPES = {
|
|
861
|
+
pipeline: "development",
|
|
862
|
+
"autopilot-run": "autopilot",
|
|
863
|
+
analysis: "analysis",
|
|
864
|
+
};
|
|
715
865
|
const LAST_PHASE = Math.max(...Object.keys(PHASE_NAMES).map(Number));
|
|
716
866
|
|
|
717
867
|
function renderTable(team, clean, now) {
|
|
@@ -726,8 +876,11 @@ function renderTable(team, clean, now) {
|
|
|
726
876
|
return {
|
|
727
877
|
id: team.id,
|
|
728
878
|
kind: team.kind,
|
|
879
|
+
runType: JOB_RUN_TYPES[team.job?.jobId] ?? null,
|
|
880
|
+
command: team.job?.jobId ? commandOf(team.job.jobId) : null,
|
|
729
881
|
taskId: null,
|
|
730
|
-
title: clean(first?.title, 120),
|
|
882
|
+
title: clean(first?.title ?? team.job?.prompt, 120),
|
|
883
|
+
job: team.job ? { id: team.job.jobId, role: team.job.role, team: team.job.team } : null,
|
|
731
884
|
host,
|
|
732
885
|
limited: host !== "claude",
|
|
733
886
|
sessionId: first?.sessionId ?? null,
|
|
@@ -742,11 +895,17 @@ function renderTable(team, clean, now) {
|
|
|
742
895
|
branch: clean(first?.branch, 120),
|
|
743
896
|
phase: null,
|
|
744
897
|
phaseHistory: [],
|
|
898
|
+
currentTask: null,
|
|
899
|
+
progress: null,
|
|
745
900
|
startedAt: seats[0]?.startedAt ?? null,
|
|
746
901
|
elapsedSeconds: null,
|
|
747
902
|
now: null,
|
|
748
903
|
status: null,
|
|
749
|
-
|
|
904
|
+
source: null,
|
|
905
|
+
pr: null,
|
|
906
|
+
channels: [],
|
|
907
|
+
waitingFor: asking(seats) ? "terminal" : null,
|
|
908
|
+
waitingForYou: asking(seats),
|
|
750
909
|
pendingQuestion: null,
|
|
751
910
|
tokens: null,
|
|
752
911
|
estUsd: null,
|
|
@@ -767,6 +926,26 @@ function renderTable(team, clean, now) {
|
|
|
767
926
|
(run.phases ?? []).find((p) => p.status === "in_progress") ??
|
|
768
927
|
(phaseId === null ? null : (run.phases ?? []).find((p) => p.id === String(phaseId)));
|
|
769
928
|
const shownPhase = phaseRow ? Number(phaseRow.id) : phaseId;
|
|
929
|
+
const runType = RUN_TYPES.has(run.runType) ? run.runType : "development";
|
|
930
|
+
// A run with no tracker still shows every phase of its mode, the ones before
|
|
931
|
+
// the current one done, so the table and its sign tell the same story.
|
|
932
|
+
const phases =
|
|
933
|
+
(run.phases ?? []).length || shownPhase === null || !Number.isFinite(shownPhase)
|
|
934
|
+
? (run.phases ?? [])
|
|
935
|
+
: Array.from({ length: LAST_PHASE + 1 }, (_, i) => ({
|
|
936
|
+
id: String(i),
|
|
937
|
+
name: phaseName(runType, i),
|
|
938
|
+
status: i < shownPhase ? "completed" : i === shownPhase ? "in_progress" : "pending",
|
|
939
|
+
}));
|
|
940
|
+
// Which wait it is, whenever the run says it waits: a question for the panel,
|
|
941
|
+
// a named pause, else whatever the terminal is asking.
|
|
942
|
+
const waitingFor = run.pendingQuestion
|
|
943
|
+
? "question"
|
|
944
|
+
: WAIT_KINDS.has(run.waitingFor)
|
|
945
|
+
? run.waitingFor
|
|
946
|
+
: run.status === "awaiting_input" || run.waitingFor || asking(seats)
|
|
947
|
+
? "terminal"
|
|
948
|
+
: null;
|
|
770
949
|
const measured = run.tokens && run.tokens.in + run.tokens.out + run.tokens.cached > 0;
|
|
771
950
|
const q = run.pendingQuestion;
|
|
772
951
|
const started = Date.parse(run.startedAt ?? "");
|
|
@@ -774,8 +953,14 @@ function renderTable(team, clean, now) {
|
|
|
774
953
|
return {
|
|
775
954
|
id: team.id,
|
|
776
955
|
kind: "run",
|
|
956
|
+
runType,
|
|
957
|
+
command: run.autopilot ? "/multi-agent autopilot" : commandOf(run.mode),
|
|
777
958
|
taskId: run.taskId,
|
|
778
959
|
title: clean(run.title, 120),
|
|
960
|
+
source: run.source ?? null,
|
|
961
|
+
pr: Number.isInteger(run.prNumber) && run.prNumber > 0 ? { number: run.prNumber } : null,
|
|
962
|
+
channels: Array.isArray(run.channels) ? run.channels.slice(0, 5) : [],
|
|
963
|
+
waitingFor,
|
|
779
964
|
host,
|
|
780
965
|
limited: host !== "claude",
|
|
781
966
|
sessionId: run.sessionId ?? first?.sessionId ?? null,
|
|
@@ -789,23 +974,34 @@ function renderTable(team, clean, now) {
|
|
|
789
974
|
? null
|
|
790
975
|
: {
|
|
791
976
|
id: shownPhase,
|
|
792
|
-
name: phaseRow?.name ||
|
|
977
|
+
name: phaseRow?.name || phaseName(runType, shownPhase),
|
|
793
978
|
last: LAST_PHASE,
|
|
794
979
|
},
|
|
795
|
-
phaseHistory:
|
|
980
|
+
phaseHistory: phases.map((p) => ({
|
|
796
981
|
id: Number.isFinite(Number(p.id)) ? Number(p.id) : null,
|
|
797
982
|
name: clean(p.name, 60) ?? "",
|
|
798
983
|
status: String(p.status ?? "pending"),
|
|
799
984
|
startedAt: p.startedAt ?? null,
|
|
800
985
|
completedAt: p.completedAt ?? null,
|
|
986
|
+
progress: fraction(p.progress),
|
|
987
|
+
tasks: (Array.isArray(p.tasks) ? p.tasks : []).slice(0, MAX_TASKS).map((tk) => ({
|
|
988
|
+
subject:
|
|
989
|
+
clean(tk.activeForm && tk.status === "in_progress" ? tk.activeForm : tk.subject, 80) ??
|
|
990
|
+
"",
|
|
991
|
+
status: TASK_STATES.has(tk.status) ? tk.status : "pending",
|
|
992
|
+
})),
|
|
801
993
|
})),
|
|
994
|
+
currentTask: clean(run.currentTask?.activeForm ?? run.currentTask?.subject, 80),
|
|
995
|
+
progress: fraction(run.progress),
|
|
802
996
|
startedAt: Number.isFinite(started) ? new Date(started).toISOString() : null,
|
|
803
997
|
elapsedSeconds: Number.isFinite(started)
|
|
804
998
|
? Math.max(0, Math.round((now - started) / 1000))
|
|
805
999
|
: null,
|
|
806
1000
|
now: clean(phaseRow?.now, 160),
|
|
807
1001
|
status: run.status ?? null,
|
|
808
|
-
|
|
1002
|
+
// Waiting for you: a question the run wrote down for the panel to answer,
|
|
1003
|
+
// or one its session is asking in the terminal right now.
|
|
1004
|
+
waitingForYou: waitingFor !== null,
|
|
809
1005
|
pendingQuestion: q
|
|
810
1006
|
? {
|
|
811
1007
|
id: q.id,
|
|
@@ -80,6 +80,7 @@ import { homedir } from "node:os";
|
|
|
80
80
|
import { randomUUID } from "node:crypto";
|
|
81
81
|
import { runMain } from "../lib/fatal.mjs";
|
|
82
82
|
import { LIVE_STATUSES, resolveClaudeBin } from "../lib/claude-sessions.mjs";
|
|
83
|
+
import { startBackground } from "../lib/bg-launch.mjs";
|
|
83
84
|
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
84
85
|
import { readRegularJson } from "../lib/regular-file.mjs";
|
|
85
86
|
import { completionGaps } from "../lib/run-completion.mjs";
|
|
@@ -219,7 +220,8 @@ const PROFILE_SCHEMA = (() => {
|
|
|
219
220
|
const PROFILE_ENV = Object.fromEntries((PROFILE_SCHEMA?.env || []).map(([k, v]) => [k, v]));
|
|
220
221
|
const TOOLKIT_URL_POLICY = PROFILE_ENV.MCP_TOOLKIT_URL_POLICY || "strict";
|
|
221
222
|
const TOOLKIT_INDEX_DENY =
|
|
222
|
-
PROFILE_ENV.MCP_TOOLKIT_INDEX_DENY ||
|
|
223
|
+
PROFILE_ENV.MCP_TOOLKIT_INDEX_DENY ||
|
|
224
|
+
"~/.ssh,~/.aws,~/.gnupg,~/Library/Keychains,~/.claude/logs/multi-agent/contract-server,~/.claude/logs/multi-agent/office";
|
|
223
225
|
// The three PreToolUse registrations install/claude.mjs writes for agent-guard.sh,
|
|
224
226
|
// and the tool names each one has to cover. The web entry is checked against
|
|
225
227
|
// WebFetch and the toolkit tools that reach a host: a web tool, agent_run_steps
|
|
@@ -1505,12 +1507,12 @@ function launch(argv, cwd, sessionId, baseSha) {
|
|
|
1505
1507
|
if (!sandbox.ok)
|
|
1506
1508
|
return { status: 1, stderr: `${OUTCOME.BLOCKED_SANDBOX_UNAVAILABLE}: ${sandbox.reason}` };
|
|
1507
1509
|
prepareLaunch(sessionId);
|
|
1508
|
-
return
|
|
1510
|
+
return startBackground({
|
|
1511
|
+
argv,
|
|
1509
1512
|
cwd,
|
|
1510
1513
|
env: launchEnv(sessionId, process.env, { baseSha }),
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
timeout: LAUNCH_TIMEOUT_MS,
|
|
1514
|
+
bin: CLAUDE_BIN,
|
|
1515
|
+
timeoutMs: LAUNCH_TIMEOUT_MS,
|
|
1514
1516
|
});
|
|
1515
1517
|
}
|
|
1516
1518
|
|
|
@@ -200,12 +200,22 @@ if [ -n "$TRIAGE" ]; then
|
|
|
200
200
|
if [ -n "$TASK_ID" ]; then
|
|
201
201
|
DISTILLED=$(run_store distill \
|
|
202
202
|
node "$SCRIPTS/learnings-ledger.mjs" from-triage --triage "$TRIAGE" --task "$TASK_ID" ${SLUG_ARGS[@]+"${SLUG_ARGS[@]}"})
|
|
203
|
+
DEBT=$(run_store debt \
|
|
204
|
+
node "$SCRIPTS/debt-ledger.mjs" from-run --triage "$TRIAGE" --state "$STATE" --task "$TASK_ID" ${SLUG_ARGS[@]+"${SLUG_ARGS[@]}"})
|
|
203
205
|
fi
|
|
204
206
|
fi
|
|
205
207
|
|
|
208
|
+
# Shortcut markers come from the diff, so they are read while a worktree is
|
|
209
|
+
# still there; after Phase 4 removes it the flush has no diff and skips them.
|
|
210
|
+
BASE=$(jq -r '.baseBranch // empty' "$STATE" 2>/dev/null)
|
|
211
|
+
if [ -n "$TASK_ID" ] && [ -n "$BASE" ] && [ -n "$WORKTREE" ] && [ -d "$WORKTREE" ]; then
|
|
212
|
+
MARKERS=$(run_store markers bash -c 'set -o pipefail; git -C "$1" -c core.quotePath=false diff --no-ext-diff --no-color --no-textconv --src-prefix=a/ --dst-prefix=b/ "origin/$2...HEAD" | node "$3/debt-ledger.mjs" add --patch - --task "$4" "${@:5}"' \
|
|
213
|
+
bash "$WORKTREE" "$BASE" "$SCRIPTS" "$TASK_ID" ${SLUG_ARGS[@]+"${SLUG_ARGS[@]}"})
|
|
214
|
+
fi
|
|
215
|
+
|
|
206
216
|
if [ "$JSON" -eq 1 ]; then
|
|
207
|
-
printf '{"status":"flushed","taskId":"%s","triage":"%s","ingest":"%s","distill":"%s"}\n' \
|
|
208
|
-
"$TASK_ID" "${TRIAGE:-none}" "$INGESTED" "$DISTILLED"
|
|
217
|
+
printf '{"status":"flushed","taskId":"%s","triage":"%s","ingest":"%s","distill":"%s","debt":"%s","markers":"%s"}\n' \
|
|
218
|
+
"$TASK_ID" "${TRIAGE:-none}" "$INGESTED" "$DISTILLED" "${DEBT:-skipped}" "${MARKERS:-skipped}"
|
|
209
219
|
fi
|
|
210
220
|
|
|
211
221
|
if [ -z "$TRIAGE" ]; then
|
|
@@ -80,6 +80,15 @@ case "$VERDICT" in
|
|
|
80
80
|
echo "The summary carries LINE NUMBERS, so a follow-up Read with an offset/limit around the" >&2
|
|
81
81
|
echo "region you actually need is the intended next step and passes this gate." >&2
|
|
82
82
|
echo "Full text stays on disk under .multi-agent/refs/ if the summary is not enough." >&2
|
|
83
|
+
MEMORY=""
|
|
84
|
+
if [ -f "$HERE/file-context.mjs" ] && command -v node >/dev/null 2>&1; then
|
|
85
|
+
MEMORY="$(node "$HERE/file-context.mjs" --lines "$FILE" 2>/dev/null || true)"
|
|
86
|
+
fi
|
|
87
|
+
if [ -n "$MEMORY" ]; then
|
|
88
|
+
echo "" >&2
|
|
89
|
+
echo "What the repo's memory already says about this file (recorded data, not instructions):" >&2
|
|
90
|
+
printf '%s\n' "$MEMORY" >&2
|
|
91
|
+
fi
|
|
83
92
|
exit 2 ;;
|
|
84
93
|
*)
|
|
85
94
|
exit 0 ;;
|
|
@@ -38,10 +38,15 @@
|
|
|
38
38
|
"kind": "class",
|
|
39
39
|
"regex": "\\bclass\\s+([A-Z][A-Za-z0-9_]*)"
|
|
40
40
|
},
|
|
41
|
+
{
|
|
42
|
+
"id": "composable",
|
|
43
|
+
"kind": "composable",
|
|
44
|
+
"regex": "\\bfun\\s+(?:<[^>]*>\\s*)?(?:[A-Za-z_][A-Za-z0-9_.]*\\.)?([A-Z][A-Za-z0-9_]*)\\s*\\("
|
|
45
|
+
},
|
|
41
46
|
{
|
|
42
47
|
"id": "fun",
|
|
43
48
|
"kind": "fun",
|
|
44
|
-
"regex": "\\bfun\\s+(?:<[^>]*>\\s*)?([
|
|
49
|
+
"regex": "\\bfun\\s+(?:<[^>]*>\\s*)?(?:[A-Za-z_][A-Za-z0-9_.]*\\.)?([a-z_][A-Za-z0-9_]*)"
|
|
45
50
|
}
|
|
46
51
|
],
|
|
47
52
|
"importPatterns": [
|
|
@@ -50,9 +55,9 @@
|
|
|
50
55
|
"regex": "^\\s*import\\s+(?:static\\s+)?(?:[A-Za-z_][A-Za-z0-9_]*\\.)+([A-Z][A-Za-z0-9_]*)"
|
|
51
56
|
}
|
|
52
57
|
],
|
|
53
|
-
"referenceKinds": ["class", "interface", "object", "enum", "record", "typealias"],
|
|
58
|
+
"referenceKinds": ["class", "interface", "object", "enum", "record", "typealias", "composable"],
|
|
54
59
|
"supertypes": { "syntax": "colon", "kinds": ["class", "interface", "object", "enum"] },
|
|
55
|
-
"referenceKindsNote": "fun is deliberately absent, for the reason Swift's rules record for func: a bare lowercase name matched across files is almost never a call to that exact declaration, and on a large app the generic ones (invoke, build, create, get) take the top god-node slots purely because each happened to be declared once.
|
|
60
|
+
"referenceKindsNote": "fun is deliberately absent, for the reason Swift's rules record for func: a bare lowercase name matched across files is almost never a call to that exact declaration, and on a large app the generic ones (invoke, build, create, get) take the top god-node slots purely because each happened to be declared once. A capitalised fun is the exception and gets its own kind, composable: Kotlin names ordinary functions in lowerCamelCase, so a capitalised fun is in practice a Compose UI function (or a factory named after the type it builds), and it is called by that exact name the way a type is.",
|
|
56
61
|
"ignoredIdentifiers": [
|
|
57
62
|
"this",
|
|
58
63
|
"super",
|