@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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Generated by pipeline/contract/build.mjs from pipeline/schemas/*.schema.json.
|
|
2
2
|
// Do not edit: change the schema and run `node pipeline/contract/build.mjs --write`.
|
|
3
3
|
|
|
4
|
-
export type ContractVersion = "1.
|
|
5
|
-
export type SurfaceId = "runs" | "run" | "commands" | "questions" | "issues" | "worktrees" | "launch-spec" | "repos" | "launch" | "answer" | "run-log" | "resume" | "kill" | "gc" | "autopilot" | "autopilot-off" | "phone-runs" | "phone-run" | "phone-answer" | "phone-launch" | "autopilot-on" | "steer" | "activity" | "activity-stream" | "agent-chat" | "routines" | "routine-delete" | "stack" | "stack-set" | "ledger" | "run-results" | "run-evidence" | "review-verdict" | "issue-maturity" | "prs" | "tools" | "tool-call" | "service-confluence" | "service-crashlytics" | "service-figma" | "permissions";
|
|
4
|
+
export type ContractVersion = "1.6.0";
|
|
5
|
+
export type SurfaceId = "runs" | "run" | "commands" | "questions" | "issues" | "worktrees" | "launch-spec" | "repos" | "repo-add" | "repo-remove" | "launch" | "answer" | "run-log" | "resume" | "kill" | "gc" | "autopilot" | "autopilot-off" | "phone-runs" | "phone-run" | "phone-answer" | "phone-launch" | "autopilot-on" | "steer" | "activity" | "activity-stream" | "agent-chat" | "routines" | "routine-delete" | "jobs" | "job-start" | "stack" | "stack-set" | "ledger" | "system" | "prune-logs" | "settings" | "settings-set" | "credentials" | "memory" | "run-results" | "run-evidence" | "review-verdict" | "issue-maturity" | "prs" | "tools" | "tool-call" | "service-confluence" | "service-crashlytics" | "service-figma" | "permissions";
|
|
6
6
|
|
|
7
7
|
/** What `runs-index.mjs --json` prints: every run under the log root and the unattended log root (one record per task id), grouped per status/SKILL.md 3b. Fields are only ever added - a decoder ignores keys it does not know. `--redact` drops every x-sensitivity local field (and `logsRoot`) and scrubs the free text in the title, `now`, sub-step names, task subjects and the pending question. */
|
|
8
8
|
export interface RunsIndex {
|
|
@@ -51,6 +51,8 @@ export interface RunsIndexRun {
|
|
|
51
51
|
};
|
|
52
52
|
estUsd: number;
|
|
53
53
|
unlockedWrites: number;
|
|
54
|
+
/** agent-state.json mode as the run recorded it (analysis, test-gen, review...), so a reader can name the command; null when none was recorded. */
|
|
55
|
+
mode?: string | null;
|
|
54
56
|
/** autopilot when the run is in autopilot mode; analysis for an analysis-family run (agent-state mode analysis, or a tracker with no agent state); development otherwise. Added in 1.2.0. */
|
|
55
57
|
runType?: "analysis" | "development" | "autopilot";
|
|
56
58
|
/** The issue summary Phase 0 recorded (agent-state title), else the input the run was launched with (launchRequest.input), else null. Scrubbed under --redact. Added in 1.2.0. */
|
|
@@ -64,6 +66,27 @@ export interface RunsIndexRun {
|
|
|
64
66
|
subject: string;
|
|
65
67
|
activeForm: string | null;
|
|
66
68
|
} | null;
|
|
69
|
+
/** Where the work came from: a Jira key, a GitHub issue (owner/repo#N) or free text; a GitHub issue that got a Jira has both. Null when the run recorded neither. Added in 1.3.0. */
|
|
70
|
+
source?: {
|
|
71
|
+
kind: "jira" | "github" | "text";
|
|
72
|
+
jira: string | null;
|
|
73
|
+
issue: string | null;
|
|
74
|
+
} | null;
|
|
75
|
+
/** The pull request's number, from the state or its URL; the URL itself is prUrl (local). Added in 1.3.0. */
|
|
76
|
+
prNumber?: number | null;
|
|
77
|
+
/** Where Phase 5 sent the report, per adapter (state.channelResults); empty before then. Added in 1.3.0. */
|
|
78
|
+
channels?: Array<{
|
|
79
|
+
channel: "pr" | "jira" | "confluence" | "wiki" | "issue";
|
|
80
|
+
status: "posted" | "skipped" | "failed";
|
|
81
|
+
}>;
|
|
82
|
+
/** The address each adapter posted to, https only; dropped from a redacted answer. Added in 1.3.0. */
|
|
83
|
+
channelUrls?: {
|
|
84
|
+
pr?: string;
|
|
85
|
+
jira?: string;
|
|
86
|
+
confluence?: string;
|
|
87
|
+
wiki?: string;
|
|
88
|
+
issue?: string;
|
|
89
|
+
};
|
|
67
90
|
}
|
|
68
91
|
|
|
69
92
|
export interface RunsIndexQuestion {
|
|
@@ -490,7 +513,7 @@ export interface AutopilotOff {
|
|
|
490
513
|
stoppedInFlight: boolean;
|
|
491
514
|
}
|
|
492
515
|
|
|
493
|
-
/** What GET /v1/repos returns and `launch-request.mjs repos` prints: the repositories POST /v1/launch?repo= accepts, by real path. source autopilot is a configured autopilot repo (config.json repos[], named by nameWithOwner); registered is one added with `launch-request.mjs register-repo` (launch-repos.json, named by its directory). path is dropped under redact. */
|
|
516
|
+
/** What GET /v1/repos returns and `launch-request.mjs repos` prints: the repositories POST /v1/launch?repo= accepts, by real path. source autopilot is a configured autopilot repo (config.json repos[], named by nameWithOwner); registered is one added with `launch-request.mjs register-repo` (launch-repos.json, named by its directory). path is dropped under redact. With --all (?all=1): suggested lists checkouts earlier runs worked in that launch does not accept yet, and missing lists registered entries that are no longer a directory; each carries an id that POST /v1/repos and DELETE /v1/repos/{id} take. */
|
|
494
517
|
export interface Repos {
|
|
495
518
|
contractVersion: string;
|
|
496
519
|
repos: Array<{
|
|
@@ -498,6 +521,97 @@ export interface Repos {
|
|
|
498
521
|
name: string;
|
|
499
522
|
source: "autopilot" | "registered";
|
|
500
523
|
}>;
|
|
524
|
+
suggested?: Array<{
|
|
525
|
+
id: string;
|
|
526
|
+
path?: string;
|
|
527
|
+
name: string;
|
|
528
|
+
}>;
|
|
529
|
+
missing?: Array<{
|
|
530
|
+
id: string;
|
|
531
|
+
path?: string;
|
|
532
|
+
name: string;
|
|
533
|
+
}>;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** The body of POST /v1/repos. The first request names suggestion (an id from GET /v1/repos?all=1) or path (an absolute path to a git checkout); the second sends only confirmToken. */
|
|
537
|
+
export interface RepoRequest {
|
|
538
|
+
contractVersion: string;
|
|
539
|
+
suggestion?: string;
|
|
540
|
+
path?: string;
|
|
541
|
+
confirmToken?: string;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** What POST /v1/repos and DELETE /v1/repos/{id} return. Without a token: preview (the repository's name) with confirmToken and expiresAt. With it: registered or removed, by name. Paths never leave the server here. */
|
|
545
|
+
export interface RepoChange {
|
|
546
|
+
contractVersion: string;
|
|
547
|
+
preview?: {
|
|
548
|
+
name: string;
|
|
549
|
+
};
|
|
550
|
+
confirmToken?: string;
|
|
551
|
+
expiresAt?: string;
|
|
552
|
+
registered?: {
|
|
553
|
+
name: string;
|
|
554
|
+
};
|
|
555
|
+
removed?: {
|
|
556
|
+
name: string;
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** The catalog of jobs the office can start (office-jobs.json), read by job-launch.mjs and served by GET /v1/jobs. */
|
|
561
|
+
export interface OfficeJobs {
|
|
562
|
+
$schema?: string;
|
|
563
|
+
contractVersion: string;
|
|
564
|
+
description?: string;
|
|
565
|
+
jobs: Array<{
|
|
566
|
+
id: string;
|
|
567
|
+
command: string;
|
|
568
|
+
role: string;
|
|
569
|
+
team: boolean;
|
|
570
|
+
launch?: "development" | "analysis" | "autopilot";
|
|
571
|
+
available?: boolean;
|
|
572
|
+
reason?: string | null;
|
|
573
|
+
params: Array<{
|
|
574
|
+
name: string;
|
|
575
|
+
kind: "work-item" | "pull-request" | "branch" | "run-id" | "jira-key" | "github-issue" | "enum" | "repo-path";
|
|
576
|
+
values?: Array<string>;
|
|
577
|
+
required: boolean;
|
|
578
|
+
}>;
|
|
579
|
+
writes: "none" | "branch" | "branch-pr" | "pr-comments" | "issue-comment";
|
|
580
|
+
}>;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/** The body of POST /v1/jobs. The first request names jobId (from GET /v1/jobs), repo (a name from GET /v1/repos or its path) and args (by parameter name), plus answers for a pipeline run; the second sends only confirmToken. */
|
|
584
|
+
export interface JobRequest {
|
|
585
|
+
contractVersion: string;
|
|
586
|
+
jobId?: string;
|
|
587
|
+
repo?: string;
|
|
588
|
+
args?: {
|
|
589
|
+
[key: string]: string;
|
|
590
|
+
};
|
|
591
|
+
answers?: Record<string, unknown>;
|
|
592
|
+
confirmToken?: string;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** What POST /v1/jobs returns. Without a token: preview (the job, the role that does it, team or solo, the repository by name, the exact prompt the session receives and what it writes outside the machine) with confirmToken and expiresAt. With it: started, the session the server began. */
|
|
596
|
+
export interface JobChange {
|
|
597
|
+
contractVersion: string;
|
|
598
|
+
preview?: {
|
|
599
|
+
jobId: string;
|
|
600
|
+
role: string;
|
|
601
|
+
team: boolean;
|
|
602
|
+
repo: string;
|
|
603
|
+
prompt: string;
|
|
604
|
+
writes: "none" | "branch" | "branch-pr" | "pr-comments" | "issue-comment";
|
|
605
|
+
};
|
|
606
|
+
confirmToken?: string;
|
|
607
|
+
expiresAt?: string;
|
|
608
|
+
started?: {
|
|
609
|
+
sessionId: string;
|
|
610
|
+
jobId: string;
|
|
611
|
+
role: string;
|
|
612
|
+
team: boolean;
|
|
613
|
+
repo: string;
|
|
614
|
+
};
|
|
501
615
|
}
|
|
502
616
|
|
|
503
617
|
/** What GET /v1/activity returns and `activity-snapshot.mjs --json` prints, and what each GET /v1/activity/stream event carries: one room per repository, one table per run (its team; a session no run claims sits at a table of its own), one seat per agent seen in the recent window, the orchestrator at the head. Derived from disk on every call. No field is machine-local: there are no paths, seat labels come from allow-listed fields only (tool name, file basename, sidecar description), and every free-text string has been through the redactor, so the redacted and the full view are the same document. */
|
|
@@ -526,8 +640,12 @@ export interface Activity {
|
|
|
526
640
|
|
|
527
641
|
export interface ActivityTable {
|
|
528
642
|
id: string;
|
|
529
|
-
/** run: a pipeline run; session: a session no run claims (outside the pipeline); unmatched: a session more than one run could claim, kept apart rather than guessed. */
|
|
530
|
-
kind: "run" | "session" | "unmatched";
|
|
643
|
+
/** run: a pipeline run; session: a session no run claims (outside the pipeline); unmatched: a session more than one run could claim, kept apart rather than guessed. solo: a session the office started for a one-person job, seated at a desk. */
|
|
644
|
+
kind: "run" | "session" | "solo" | "unmatched";
|
|
645
|
+
/** The command the table runs, for its room sign: /multi-agent, /multi-agent:analysis, /multi-agent:test-gen. Null for a session no command owns. */
|
|
646
|
+
command: string | null;
|
|
647
|
+
/** Which pipeline the table runs: /multi-agent (development), /multi-agent:analysis, or an autopilot run. Null for a session no pipeline owns. The office names the room after it. */
|
|
648
|
+
runType: "development" | "analysis" | "autopilot" | null;
|
|
531
649
|
taskId: string | null;
|
|
532
650
|
title: string | null;
|
|
533
651
|
host: "claude" | "copilot" | "codex";
|
|
@@ -543,6 +661,9 @@ export interface ActivityTable {
|
|
|
543
661
|
/** The last phase id, for an n of last display. */
|
|
544
662
|
last: number;
|
|
545
663
|
} | null;
|
|
664
|
+
/** The task in progress in the phase in progress, from the mirrored task list. */
|
|
665
|
+
currentTask: string | null;
|
|
666
|
+
progress: number | null;
|
|
546
667
|
now: string | null;
|
|
547
668
|
status: string | null;
|
|
548
669
|
waitingForYou: boolean;
|
|
@@ -570,6 +691,13 @@ export interface ActivityTable {
|
|
|
570
691
|
branch: string | null;
|
|
571
692
|
phaseHistory: Array<{
|
|
572
693
|
id: number | null;
|
|
694
|
+
/** How far the phase is, from its mirrored task list; null when it has none. */
|
|
695
|
+
progress: number | null;
|
|
696
|
+
/** The phase's task list as the host task widget shows it (mirrored by phase-tracker.sh task), redacted and cut short. */
|
|
697
|
+
tasks: Array<{
|
|
698
|
+
subject: string;
|
|
699
|
+
status: "pending" | "in_progress" | "completed";
|
|
700
|
+
}>;
|
|
573
701
|
name: string;
|
|
574
702
|
status: string;
|
|
575
703
|
startedAt: string | null;
|
|
@@ -582,9 +710,45 @@ export interface ActivityTable {
|
|
|
582
710
|
/** The last seat of an earlier phase, for drawing the handoff. */
|
|
583
711
|
previousHolder: string | null;
|
|
584
712
|
seats: Array<ActivitySeat>;
|
|
713
|
+
/** The office job a session was started for (run tables carry none). */
|
|
714
|
+
job?: null | {
|
|
715
|
+
id: string;
|
|
716
|
+
role: string;
|
|
717
|
+
team: boolean;
|
|
718
|
+
};
|
|
719
|
+
/** Where the run's work came from, for the table sign: a Jira key, a GitHub issue or free text. Null for a session or a run that recorded neither. */
|
|
720
|
+
source: {
|
|
721
|
+
kind: "jira" | "github" | "text";
|
|
722
|
+
jira: string | null;
|
|
723
|
+
issue: string | null;
|
|
724
|
+
} | null;
|
|
725
|
+
/** The pull request the run opened, by number only. */
|
|
726
|
+
pr: {
|
|
727
|
+
number: number;
|
|
728
|
+
} | null;
|
|
729
|
+
/** What a waiting run waits for: a step it paused at (the run's waitingFor), or terminal when its session holds a question open in its own window. */
|
|
730
|
+
waitingFor: "maturity" | "user-channels-choice" | "local-test" | "attended-reply" | "question" | "terminal" | null;
|
|
731
|
+
/** Where the run's report went, per adapter, for the run window; empty for a session or before Phase 5. */
|
|
732
|
+
channels: Array<{
|
|
733
|
+
channel: "pr" | "jira" | "confluence" | "wiki" | "issue";
|
|
734
|
+
status: "posted" | "skipped" | "failed";
|
|
735
|
+
}>;
|
|
585
736
|
}
|
|
586
737
|
|
|
587
738
|
export interface ActivitySeat {
|
|
739
|
+
/** The desk's own task list from its transcript (TaskCreate / TaskUpdate / TodoWrite), phase tiles left out, redacted and cut short. */
|
|
740
|
+
tasks: Array<{
|
|
741
|
+
subject: string;
|
|
742
|
+
status: "pending" | "in_progress" | "completed";
|
|
743
|
+
}>;
|
|
744
|
+
/** The desk's last MCP calls, newest last: server, tool, whether it is still running, failed or answered, and how long it took. No input or result. */
|
|
745
|
+
mcp: Array<{
|
|
746
|
+
server: string;
|
|
747
|
+
tool: string;
|
|
748
|
+
state: "running" | "ok" | "error";
|
|
749
|
+
ms: number | null;
|
|
750
|
+
at: string | null;
|
|
751
|
+
}>;
|
|
588
752
|
id: string;
|
|
589
753
|
sessionId: string | null;
|
|
590
754
|
/** null for the session's own (orchestrator) desk. */
|
|
@@ -837,6 +1001,105 @@ export interface LedgerWindow {
|
|
|
837
1001
|
byPhase: Array<LedgerRow>;
|
|
838
1002
|
}
|
|
839
1003
|
|
|
1004
|
+
/** What GET /v1/system returns and `system-status.mjs --json` prints: CPU load, memory, free disk and what the pipeline itself uses. Numbers only: no path, process argument, user name or host name. A value that could not be measured is null, never 0. */
|
|
1005
|
+
export interface SystemStatus {
|
|
1006
|
+
contractVersion: string;
|
|
1007
|
+
generatedAt: string;
|
|
1008
|
+
cpu: {
|
|
1009
|
+
cores: number;
|
|
1010
|
+
/** Busy share of all cores over a short sampling window. */
|
|
1011
|
+
usage: number | null;
|
|
1012
|
+
/** Load average over 1, 5 and 15 minutes. */
|
|
1013
|
+
load: Array<number>;
|
|
1014
|
+
};
|
|
1015
|
+
memory: {
|
|
1016
|
+
totalMb: number;
|
|
1017
|
+
/** Total minus what the OS reports free; on macOS that counts the file cache as used. */
|
|
1018
|
+
usedMb: number;
|
|
1019
|
+
};
|
|
1020
|
+
disk: {
|
|
1021
|
+
home: SystemStatusDisk;
|
|
1022
|
+
/** The disk the run logs live on, when it is not the home disk; null when it is the same disk. */
|
|
1023
|
+
logs: SystemStatusDisk;
|
|
1024
|
+
};
|
|
1025
|
+
pipeline: {
|
|
1026
|
+
/** Claude Code processes running. */
|
|
1027
|
+
sessions: number | null;
|
|
1028
|
+
/** multi-agent-toolkit server processes running. */
|
|
1029
|
+
toolkit: number | null;
|
|
1030
|
+
/** Their resident memory together. */
|
|
1031
|
+
rssMb: number | null;
|
|
1032
|
+
/** Size of the run log folder; null when it could not be measured in time. */
|
|
1033
|
+
logsMb: number | null;
|
|
1034
|
+
};
|
|
1035
|
+
/** disk-low: under 10 percent of the home disk is free. cpu-busy: the 5-minute load is above the core count. */
|
|
1036
|
+
warnings: (Array<"disk-low" | "cpu-busy">);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
export type SystemStatusDisk = {
|
|
1040
|
+
totalMb: number;
|
|
1041
|
+
freeMb: number;
|
|
1042
|
+
} | null;
|
|
1043
|
+
|
|
1044
|
+
/** What GET /v1/credentials returns and `office-credentials.mjs --json` prints: for each credential the pipeline knows by logical name, whether the store holds it and, with probe, whether its service accepted it. Never a value: lib/credential-inventory.sh reads each through the store with stdout discarded. Renewal stays in the terminal (/multi-agent:setup). */
|
|
1045
|
+
export interface OfficeCredentials {
|
|
1046
|
+
contractVersion: string;
|
|
1047
|
+
generatedAt: string;
|
|
1048
|
+
probed: boolean;
|
|
1049
|
+
/** False when the inventory could not run on this host. */
|
|
1050
|
+
available: boolean;
|
|
1051
|
+
credentials: Array<{
|
|
1052
|
+
logical: string;
|
|
1053
|
+
state: "present" | "mapped-but-missing" | "unmapped";
|
|
1054
|
+
reachability: "not-probed" | "reachable" | "auth-rejected" | "unreachable" | "no-host-configured" | "well-formed" | "not-probeable" | "malformed" | "no-credential" | "probe-error" | "server-error" | "tier-1-ready" | "tier-2-ready" | "tier-1-no-grant" | "unknown";
|
|
1055
|
+
capability: string | null;
|
|
1056
|
+
}>;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/** What GET /v1/memory returns and `office-memory.mjs --json` prints: per repository, the code graph's age and size and how many learnings, past review findings and session notes the learning loop holds, with the newest learnings redacted and cut short. An empty shelf is reported, never left out. */
|
|
1060
|
+
export interface OfficeMemory {
|
|
1061
|
+
contractVersion: string;
|
|
1062
|
+
generatedAt: string;
|
|
1063
|
+
shelves: Array<{
|
|
1064
|
+
repo: string;
|
|
1065
|
+
graph: null | {
|
|
1066
|
+
builtAt: string | null;
|
|
1067
|
+
ageDays: number;
|
|
1068
|
+
sizeKb: number;
|
|
1069
|
+
};
|
|
1070
|
+
learnings: number;
|
|
1071
|
+
findings: number;
|
|
1072
|
+
sessionNotes: number;
|
|
1073
|
+
recent: Array<{
|
|
1074
|
+
kind: string | null;
|
|
1075
|
+
statement: string;
|
|
1076
|
+
at: string | null;
|
|
1077
|
+
}>;
|
|
1078
|
+
lastWrite: string | null;
|
|
1079
|
+
}>;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/** What POST /v1/prune-logs returns. The preview (body {olderThanDays}) is prune-logs.sh's dry run for that age with a confirm token; the confirmation (body {confirmToken}) runs it again with the same age and --yes. The audit trail, the metrics corpus and task counters are never removed, and a folder written in the last minutes is spared. */
|
|
1083
|
+
export interface PruneLogs {
|
|
1084
|
+
contractVersion: string;
|
|
1085
|
+
olderThanDays: 7 | 30 | 90;
|
|
1086
|
+
applied: boolean;
|
|
1087
|
+
count: number;
|
|
1088
|
+
freedMb: number;
|
|
1089
|
+
/** project/task folders, at most 200 listed. */
|
|
1090
|
+
folders: Array<string>;
|
|
1091
|
+
confirmToken?: string;
|
|
1092
|
+
expiresAt?: string;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/** The body of POST /v1/prune-logs. Step 1 names the age in days (7, 30 or 90); step 2 sends only the confirmToken step 1 returned. */
|
|
1096
|
+
export interface PruneLogsRequest {
|
|
1097
|
+
contractVersion: string;
|
|
1098
|
+
olderThanDays?: 7 | 30 | 90;
|
|
1099
|
+
/** The confirmToken the preview returned. Single use, bound to the route. */
|
|
1100
|
+
confirmToken?: string;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
840
1103
|
/** What GET /v1/runs/{id}/results returns (`run-results.mjs <id> --json`): what a run produced, read from its own files. diff from agent-state diffRisk or .review-diff.txt; findings from triage-output.json or agent-state review; tests from the test-summary gate and uiTest; evidence the screenshot names under .pipeline/evidence; analysis the documents an analysis run wrote. A part with no source is null. Text is scrubbed and redacted; paths are reduced to file names. */
|
|
841
1104
|
export interface RunResults {
|
|
842
1105
|
contractVersion: string;
|
|
@@ -921,6 +1184,8 @@ export interface Prs {
|
|
|
921
1184
|
contractVersion: string;
|
|
922
1185
|
items: Array<{
|
|
923
1186
|
source: "github" | "bitbucket";
|
|
1187
|
+
/** The GitHub login the pull request was listed under, when gh is logged in to more than one account (personal and corporate). Null for Bitbucket. */
|
|
1188
|
+
account?: string | null;
|
|
924
1189
|
repo: string;
|
|
925
1190
|
number: number;
|
|
926
1191
|
title: string;
|
|
@@ -973,6 +1238,8 @@ export interface Tools {
|
|
|
973
1238
|
allowed: boolean;
|
|
974
1239
|
reason: string | null;
|
|
975
1240
|
}>;
|
|
1241
|
+
/** The exact toolkit version the panel starts: the registration's own when it names one, else the version the install recorded from the release. */
|
|
1242
|
+
version?: string | null;
|
|
976
1243
|
}
|
|
977
1244
|
|
|
978
1245
|
/** The body of POST /v1/tools/{name}/call: the tool's arguments, which must satisfy its inputSchema. At most 64 KiB. */
|
|
@@ -1030,10 +1297,41 @@ export interface Permissions {
|
|
|
1030
1297
|
/** Every non-2xx body contract-server.mjs sends, on the desktop and the phone routes alike. `error` is a fixed code a client switches on; `message` is for a person and names no path, host or token. `errors` lists schema or rule violations for invalid-body and invalid-request; `supported` lists the majors ?v= accepts for unsupported-version. run-active and not-resumable answer POST /v1/runs/{id}/resume; confirm-expired and confirm-mismatch answer the confirming request of a kill or gc. */
|
|
1031
1298
|
export interface ContractError {
|
|
1032
1299
|
contractVersion: string;
|
|
1033
|
-
error: "unauthorized" | "forbidden-host" | "forbidden-origin" | "not-found" | "method-not-allowed" | "invalid-query" | "unsupported-version" | "unsupported-media-type" | "invalid-body" | "invalid-request" | "no-launch-shape" | "not-waiting" | "wrong-question" | "wrong-arity" | "not-an-option" | "state-moved" | "unsigned" | "unknown-device" | "revoked-device" | "bad-signature" | "stale-request" | "replayed" | "replay-store-full" | "unknown-verb" | "forbidden-scope" | "launch-disabled" | "repo-not-allowed" | "run-active" | "not-resumable" | "confirm-expired" | "confirm-mismatch" | "unattended-profile-missing" | "workspace-not-trusted" | "no-selection" | "repo-not-configured" | "invalid-config" | "not-installed" | "repo-ambiguous" | "unreadable-settings" | "not-removable" | "not-steerable" | "too-many-streams" | "no-review" | "upstream-failed" | "tool-not-allowed" | "busy" | "review-incomplete" | "host-not-allowed" | "internal";
|
|
1300
|
+
error: "unauthorized" | "forbidden-host" | "forbidden-origin" | "not-found" | "method-not-allowed" | "invalid-query" | "unsupported-version" | "unsupported-media-type" | "invalid-body" | "invalid-request" | "no-launch-shape" | "not-waiting" | "wrong-question" | "wrong-arity" | "not-an-option" | "state-moved" | "unsigned" | "unknown-device" | "revoked-device" | "bad-signature" | "stale-request" | "replayed" | "replay-store-full" | "unknown-verb" | "forbidden-scope" | "launch-disabled" | "repo-not-allowed" | "run-active" | "not-resumable" | "confirm-expired" | "confirm-mismatch" | "unattended-profile-missing" | "workspace-not-trusted" | "no-selection" | "repo-not-configured" | "invalid-config" | "not-installed" | "repo-ambiguous" | "unreadable-settings" | "not-removable" | "not-steerable" | "too-many-streams" | "no-review" | "upstream-failed" | "tool-not-allowed" | "busy" | "review-incomplete" | "host-not-allowed" | "internal" | "unknown-job" | "command-not-installed" | "invalid-repo" | "invalid-args" | "room-busy" | "sandbox-unavailable" | "launch-failed" | "guard-missing" | "name-taken" | "already-registered" | "not-registered";
|
|
1034
1301
|
message: string;
|
|
1035
1302
|
errors?: Array<string>;
|
|
1036
1303
|
supported?: Array<number>;
|
|
1037
1304
|
/** What fixes the refusal: for unattended-profile-missing the installer step that writes the unattended permission profile; for workspace-not-trusted opening Claude Code once in the repository to accept the trust prompt (the path is left out under redact). */
|
|
1038
1305
|
remedy?: string;
|
|
1039
1306
|
}
|
|
1307
|
+
|
|
1308
|
+
/** What GET /v1/settings returns and POST /v1/settings returns after a change: the output language, the fable rung and model routing. A preview (the first POST) adds the change it would make and a confirmToken. */
|
|
1309
|
+
export interface OfficeSettings {
|
|
1310
|
+
contractVersion: string;
|
|
1311
|
+
language: "en" | "tr";
|
|
1312
|
+
/** Whether the fable rung is in play. */
|
|
1313
|
+
model: "on" | "off";
|
|
1314
|
+
/** Whether model routing is on. */
|
|
1315
|
+
route: "on" | "off";
|
|
1316
|
+
change?: {
|
|
1317
|
+
setting: "language" | "model" | "route";
|
|
1318
|
+
from: string;
|
|
1319
|
+
to: string;
|
|
1320
|
+
/** Present when turning routing on brings back a saved free provider chain: the confirmation names it. */
|
|
1321
|
+
freeChain?: {
|
|
1322
|
+
enabled: true;
|
|
1323
|
+
allowTraining: boolean;
|
|
1324
|
+
};
|
|
1325
|
+
};
|
|
1326
|
+
confirmToken?: string;
|
|
1327
|
+
expiresAt?: string;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/** The body of POST /v1/settings. Step 1 names the setting and its new value; step 2 sends only the confirmToken step 1 returned. */
|
|
1331
|
+
export interface OfficeSettingsRequest {
|
|
1332
|
+
contractVersion: string;
|
|
1333
|
+
setting?: "language" | "model" | "route";
|
|
1334
|
+
value?: "en" | "tr" | "on" | "off";
|
|
1335
|
+
/** The confirmToken the preview returned. Single use, bound to the route. */
|
|
1336
|
+
confirmToken?: string;
|
|
1337
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bg-launch.mjs - start one unattended `claude --bg` session.
|
|
3
|
+
*
|
|
4
|
+
* The autopilot runner and the office both start background sessions, and
|
|
5
|
+
* both go through here so there is one way to do it: the caller's
|
|
6
|
+
* precondition (the unattended sandbox check) runs first and a failing one
|
|
7
|
+
* starts nothing; the binary is started without a shell, with the argv and
|
|
8
|
+
* environment the caller built; `claude --bg` prints the session and returns,
|
|
9
|
+
* so a bounded synchronous wait is enough.
|
|
10
|
+
*
|
|
11
|
+
* @module pipeline/lib/bg-launch
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
15
|
+
|
|
16
|
+
import { resolveClaudeBin } from "./claude-sessions.mjs";
|
|
17
|
+
|
|
18
|
+
export const LAUNCH_TIMEOUT_MS = 2 * 60 * 1000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {{argv: string[], cwd: string, env: Record<string,string|undefined>,
|
|
22
|
+
* precondition?: (o: {cwd: string}) => {ok: boolean, reason?: string},
|
|
23
|
+
* bin?: string, timeoutMs?: number}} args
|
|
24
|
+
* @returns {{status: number|null, stdout?: string, stderr?: string, error?: Error, blocked?: string}}
|
|
25
|
+
*/
|
|
26
|
+
export function startBackground({
|
|
27
|
+
argv,
|
|
28
|
+
cwd,
|
|
29
|
+
env,
|
|
30
|
+
precondition,
|
|
31
|
+
bin = resolveClaudeBin(env),
|
|
32
|
+
timeoutMs = LAUNCH_TIMEOUT_MS,
|
|
33
|
+
}) {
|
|
34
|
+
if (!Array.isArray(argv) || !argv.every((a) => typeof a === "string")) {
|
|
35
|
+
throw new TypeError("argv is a list of strings");
|
|
36
|
+
}
|
|
37
|
+
if (precondition) {
|
|
38
|
+
const check = precondition({ cwd });
|
|
39
|
+
if (!check.ok) return { status: 1, stderr: check.reason, blocked: check.reason };
|
|
40
|
+
}
|
|
41
|
+
return spawnSync(bin, argv, {
|
|
42
|
+
cwd,
|
|
43
|
+
env,
|
|
44
|
+
encoding: "utf-8",
|
|
45
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
46
|
+
timeout: timeoutMs,
|
|
47
|
+
shell: false,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* startBackground for a caller that must not block, such as the contract
|
|
53
|
+
* server answering other requests meanwhile. It settles when the launcher
|
|
54
|
+
* exits rather than when its output pipes close, and keeps draining them
|
|
55
|
+
* afterwards, so a process left holding a pipe never writes into a closed one.
|
|
56
|
+
*
|
|
57
|
+
* @param {Parameters<typeof startBackground>[0]} args
|
|
58
|
+
* @returns {Promise<ReturnType<typeof startBackground>>}
|
|
59
|
+
*/
|
|
60
|
+
export function startBackgroundAsync({
|
|
61
|
+
argv,
|
|
62
|
+
cwd,
|
|
63
|
+
env,
|
|
64
|
+
precondition,
|
|
65
|
+
bin = resolveClaudeBin(env),
|
|
66
|
+
timeoutMs = LAUNCH_TIMEOUT_MS,
|
|
67
|
+
}) {
|
|
68
|
+
if (!Array.isArray(argv) || !argv.every((a) => typeof a === "string")) {
|
|
69
|
+
throw new TypeError("argv is a list of strings");
|
|
70
|
+
}
|
|
71
|
+
if (precondition) {
|
|
72
|
+
const check = precondition({ cwd });
|
|
73
|
+
if (!check.ok)
|
|
74
|
+
return Promise.resolve({ status: 1, stderr: check.reason, blocked: check.reason });
|
|
75
|
+
}
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
let stdout = "";
|
|
78
|
+
let stderr = "";
|
|
79
|
+
let settled = false;
|
|
80
|
+
const child = spawn(bin, argv, { cwd, env, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
81
|
+
const done = (result) => {
|
|
82
|
+
if (settled) return;
|
|
83
|
+
settled = true;
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
for (const s of [child.stdout, child.stderr]) {
|
|
86
|
+
s?.removeAllListeners("data");
|
|
87
|
+
s?.resume();
|
|
88
|
+
}
|
|
89
|
+
resolve(result);
|
|
90
|
+
};
|
|
91
|
+
const timer = setTimeout(() => {
|
|
92
|
+
child.kill("SIGTERM");
|
|
93
|
+
done({
|
|
94
|
+
status: null,
|
|
95
|
+
stdout,
|
|
96
|
+
stderr,
|
|
97
|
+
error: new Error("the launcher timed out"),
|
|
98
|
+
timedOut: true,
|
|
99
|
+
});
|
|
100
|
+
}, timeoutMs);
|
|
101
|
+
child.stdout.setEncoding("utf-8").on("data", (d) => (stdout += d));
|
|
102
|
+
child.stderr.setEncoding("utf-8").on("data", (d) => (stderr += d));
|
|
103
|
+
child.on("error", (error) => done({ status: null, stdout, stderr, error }));
|
|
104
|
+
child.on("exit", (status) => setImmediate(() => done({ status, stdout, stderr })));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* code-digest.mjs - one digest for the code a long-lived server loaded.
|
|
3
|
+
*
|
|
4
|
+
* The contract server imports modules from scripts/ and lib/, and an ES module
|
|
5
|
+
* keeps the copy it loaded at start. A digest of the server file alone misses
|
|
6
|
+
* a release that changed only an imported module, so this hashes every .mjs
|
|
7
|
+
* file in the server's directory and in the lib/ directory beside it, by name
|
|
8
|
+
* and content. office-control compares it with the digest the running server
|
|
9
|
+
* recorded; the server compares it with its own start-up value for
|
|
10
|
+
* /office/version.
|
|
11
|
+
*
|
|
12
|
+
* @module pipeline/lib/code-digest
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} scriptsDir the directory holding contract-server.mjs
|
|
21
|
+
* @returns {string|null} 16 hex characters, or null when nothing could be read
|
|
22
|
+
*/
|
|
23
|
+
export function codeDigest(scriptsDir) {
|
|
24
|
+
const hash = createHash("sha256");
|
|
25
|
+
let read = 0;
|
|
26
|
+
for (const dir of [scriptsDir, join(dirname(scriptsDir), "lib")]) {
|
|
27
|
+
let names;
|
|
28
|
+
try {
|
|
29
|
+
names = readdirSync(dir)
|
|
30
|
+
.filter((n) => n.endsWith(".mjs"))
|
|
31
|
+
.sort();
|
|
32
|
+
} catch {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
for (const n of names) {
|
|
36
|
+
try {
|
|
37
|
+
const body = readFileSync(join(dir, n));
|
|
38
|
+
hash
|
|
39
|
+
.update(`${dir === scriptsDir ? "scripts" : "lib"}/${n}\0`)
|
|
40
|
+
.update(body)
|
|
41
|
+
.update("\0");
|
|
42
|
+
read += 1;
|
|
43
|
+
} catch {
|
|
44
|
+
// A file that vanished mid-read changes the digest by its absence.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return read ? hash.digest("hex").slice(0, 16) : null;
|
|
49
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
-
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { execFile, spawnSync } from "node:child_process";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
|
|
@@ -27,7 +27,7 @@ export function resolveToken(name, envVar) {
|
|
|
27
27
|
try {
|
|
28
28
|
const key = JSON.parse(readFileSync(prefsPath, "utf8"))?.global?.keychainMapping?.[name];
|
|
29
29
|
if (key) {
|
|
30
|
-
const r = spawnSync("bash", [store, "get", key], { encoding: "utf8" });
|
|
30
|
+
const r = spawnSync("bash", [store, "get", key], { encoding: "utf8", timeout: 10_000 });
|
|
31
31
|
const token = (r.stdout || "").trim();
|
|
32
32
|
if (r.status === 0 && token) return token;
|
|
33
33
|
}
|
|
@@ -37,3 +37,43 @@ export function resolveToken(name, envVar) {
|
|
|
37
37
|
}
|
|
38
38
|
return process.env[envVar] || null;
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* resolveToken for a request path: the store is read without blocking the
|
|
43
|
+
* event loop and given a deadline, so a store waiting on an unlock prompt
|
|
44
|
+
* yields "no token" instead of stalling the caller.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} name
|
|
47
|
+
* @param {string} envVar
|
|
48
|
+
* @param {{timeoutMs?: number}} [opts]
|
|
49
|
+
* @returns {Promise<string|null>}
|
|
50
|
+
*/
|
|
51
|
+
export async function resolveTokenAsync(
|
|
52
|
+
name,
|
|
53
|
+
envVar,
|
|
54
|
+
{ timeoutMs = 5000, env = process.env } = {},
|
|
55
|
+
) {
|
|
56
|
+
const home = env.HOME || homedir();
|
|
57
|
+
const store = join(home, ".claude", "lib", "credential-store.sh");
|
|
58
|
+
const prefsPath = env.MULTI_AGENT_PREFS || join(home, ".claude", "multi-agent-preferences.json");
|
|
59
|
+
if (existsSync(store) && existsSync(prefsPath)) {
|
|
60
|
+
let key = null;
|
|
61
|
+
try {
|
|
62
|
+
key = JSON.parse(readFileSync(prefsPath, "utf8"))?.global?.keychainMapping?.[name] ?? null;
|
|
63
|
+
} catch {
|
|
64
|
+
key = null;
|
|
65
|
+
}
|
|
66
|
+
if (key) {
|
|
67
|
+
const token = await new Promise((resolve) => {
|
|
68
|
+
execFile(
|
|
69
|
+
"bash",
|
|
70
|
+
[store, "get", key],
|
|
71
|
+
{ encoding: "utf8", timeout: timeoutMs, killSignal: "SIGKILL", env },
|
|
72
|
+
(err, stdout) => resolve(err ? "" : String(stdout || "").trim()),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
if (token) return token;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return env[envVar] || null;
|
|
79
|
+
}
|