@jaggerxtrm/specialists 3.16.0 → 3.17.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/README.md +3 -1
- package/config/mandatory-rules/research-tool-routing.md +4 -0
- package/config/mandatory-rules/test-runner-execution-scope.md +1 -1
- package/config/skills/specialists-creator/SKILL.md +28 -1
- package/config/skills/using-specialists-auto/SKILL.md +1 -1
- package/config/skills/using-specialists-v2/SKILL.md +7 -7
- package/config/skills/using-specialists-v3/SKILL.md +223 -147
- package/config/specialists/changelog-drafter.specialist.json +4 -2
- package/config/specialists/changelog-keeper.specialist.json +4 -2
- package/config/specialists/debugger.specialist.json +6 -4
- package/config/specialists/executor.specialist.json +7 -5
- package/config/specialists/explorer.specialist.json +4 -2
- package/config/specialists/memory-processor.specialist.json +4 -2
- package/config/specialists/node-coordinator.specialist.json +4 -2
- package/config/specialists/obligations-scanner.specialist.json +99 -0
- package/config/specialists/overthinker.specialist.json +4 -2
- package/config/specialists/planner.specialist.json +4 -2
- package/config/specialists/researcher.specialist.json +12 -7
- package/config/specialists/reviewer.specialist.json +10 -8
- package/config/specialists/seconder.specialist.json +170 -0
- package/config/specialists/security-auditor.specialist.json +4 -2
- package/config/specialists/service-skills-sync.specialist.json +78 -0
- package/config/specialists/specialists-creator.specialist.json +6 -4
- package/config/specialists/sync-docs.specialist.json +5 -3
- package/config/specialists/test-engineer.specialist.json +134 -0
- package/config/specialists/test-runner.specialist.json +8 -6
- package/config/specialists/transcriber.specialist.json +59 -0
- package/config/specialists/xt-merge.specialist.json +4 -2
- package/dist/asset-contract.json +20 -8
- package/dist/index.js +1136 -293
- package/dist/lib.js +1 -0
- package/dist/types/cli/format-helpers.d.ts.map +1 -1
- package/dist/types/cli/help.d.ts.map +1 -1
- package/dist/types/cli/log.d.ts +2 -0
- package/dist/types/cli/log.d.ts.map +1 -0
- package/dist/types/cli/result.d.ts.map +1 -1
- package/dist/types/cli/resume.d.ts.map +1 -1
- package/dist/types/cli/steer.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/specialist/control.d.ts.map +1 -1
- package/dist/types/specialist/job-file-output.d.ts +2 -0
- package/dist/types/specialist/job-file-output.d.ts.map +1 -1
- package/dist/types/specialist/launch.d.ts.map +1 -1
- package/dist/types/specialist/runner.d.ts +3 -0
- package/dist/types/specialist/runner.d.ts.map +1 -1
- package/dist/types/specialist/schema.d.ts +18 -9
- package/dist/types/specialist/schema.d.ts.map +1 -1
- package/dist/types/specialist/supervisor.d.ts +25 -2
- package/dist/types/specialist/supervisor.d.ts.map +1 -1
- package/dist/types/specialist/timeline-events.d.ts +20 -1
- package/dist/types/specialist/timeline-events.d.ts.map +1 -1
- package/package.json +13 -11
- package/config/specialists/code-sanity.specialist.json +0 -108
package/dist/index.js
CHANGED
|
@@ -7895,7 +7895,7 @@ var require_core = __commonJS((exports) => {
|
|
|
7895
7895
|
constructor(opts = {}) {
|
|
7896
7896
|
this.schemas = {};
|
|
7897
7897
|
this.refs = {};
|
|
7898
|
-
this.formats =
|
|
7898
|
+
this.formats = Object.create(null);
|
|
7899
7899
|
this._compilations = new Set;
|
|
7900
7900
|
this._loading = {};
|
|
7901
7901
|
this._cache = new Map;
|
|
@@ -17734,6 +17734,7 @@ var init_schema = __esm(() => {
|
|
|
17734
17734
|
stall_detection: StallDetectionSchema,
|
|
17735
17735
|
mandatory_rules: MandatoryRulesSchema,
|
|
17736
17736
|
output_file: stringType().optional(),
|
|
17737
|
+
notes_mode: enumType(["full-trail", "final-only"]).default("full-trail"),
|
|
17737
17738
|
beads_integration: enumType(["auto", "always", "never"]).default("auto"),
|
|
17738
17739
|
beads_write_notes: booleanType().default(true)
|
|
17739
17740
|
}).passthrough()
|
|
@@ -17926,6 +17927,14 @@ function detectJobFileOutputMode(env = process.env) {
|
|
|
17926
17927
|
function isJobFileOutputEnabled(env = process.env) {
|
|
17927
17928
|
return detectJobFileOutputMode(env) === "on";
|
|
17928
17929
|
}
|
|
17930
|
+
async function writeJobFileOutput(path, content, mode) {
|
|
17931
|
+
const { appendFile, writeFile } = await import("fs/promises");
|
|
17932
|
+
if (mode === "append") {
|
|
17933
|
+
await appendFile(path, content, "utf-8");
|
|
17934
|
+
return;
|
|
17935
|
+
}
|
|
17936
|
+
await writeFile(path, content, "utf-8");
|
|
17937
|
+
}
|
|
17929
17938
|
|
|
17930
17939
|
// src/specialist/templateEngine.ts
|
|
17931
17940
|
function renderTemplate(template, variables) {
|
|
@@ -22088,7 +22097,6 @@ __export(exports_runner, {
|
|
|
22088
22097
|
SpecialistRunner: () => SpecialistRunner
|
|
22089
22098
|
});
|
|
22090
22099
|
import { createHash as createHash2 } from "crypto";
|
|
22091
|
-
import { writeFile } from "fs/promises";
|
|
22092
22100
|
import { execSync as execSync2, spawnSync as spawnSync4 } from "child_process";
|
|
22093
22101
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
22094
22102
|
import { basename as basename2, resolve as resolve4 } from "path";
|
|
@@ -23061,8 +23069,8 @@ ${outputContractWarnings.map((msg) => ` \u26A0 ${msg}`).join(`
|
|
|
23061
23069
|
`)}
|
|
23062
23070
|
`);
|
|
23063
23071
|
}
|
|
23064
|
-
if (output_file &&
|
|
23065
|
-
await
|
|
23072
|
+
if (output_file && !options.suppressRunnerFileOutput) {
|
|
23073
|
+
await writeJobFileOutput(output_file, output, "overwrite").catch(() => {});
|
|
23066
23074
|
}
|
|
23067
23075
|
await hooks.emit("post_execute", invocationId, metadata.name, metadata.version, {
|
|
23068
23076
|
status: "COMPLETE",
|
|
@@ -23916,6 +23924,14 @@ function createRunCompleteEvent(status, elapsed_s, options) {
|
|
|
23916
23924
|
...options
|
|
23917
23925
|
};
|
|
23918
23926
|
}
|
|
23927
|
+
function createControlSignalEvent(action, options) {
|
|
23928
|
+
return {
|
|
23929
|
+
t: Date.now(),
|
|
23930
|
+
type: TIMELINE_EVENT_TYPES.CONTROL_SIGNAL,
|
|
23931
|
+
action,
|
|
23932
|
+
...options
|
|
23933
|
+
};
|
|
23934
|
+
}
|
|
23919
23935
|
function createAutoCommitEvent(status, options) {
|
|
23920
23936
|
const type = status === "success" ? TIMELINE_EVENT_TYPES.AUTO_COMMIT_SUCCESS : status === "skipped" ? TIMELINE_EVENT_TYPES.AUTO_COMMIT_SKIPPED : TIMELINE_EVENT_TYPES.AUTO_COMMIT_FAILED;
|
|
23921
23937
|
return {
|
|
@@ -23991,6 +24007,7 @@ var init_timeline_events = __esm(() => {
|
|
|
23991
24007
|
AUTO_COMMIT_SUCCESS: "auto_commit_success",
|
|
23992
24008
|
AUTO_COMMIT_SKIPPED: "auto_commit_skipped",
|
|
23993
24009
|
AUTO_COMMIT_FAILED: "auto_commit_failed",
|
|
24010
|
+
CONTROL_SIGNAL: "control_signal",
|
|
23994
24011
|
DONE: "done",
|
|
23995
24012
|
AGENT_END: "agent_end"
|
|
23996
24013
|
};
|
|
@@ -24498,24 +24515,37 @@ function getCurrentGitSha() {
|
|
|
24498
24515
|
const sha = result.stdout?.trim();
|
|
24499
24516
|
return sha || undefined;
|
|
24500
24517
|
}
|
|
24501
|
-
function
|
|
24502
|
-
|
|
24503
|
-
|
|
24504
|
-
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
|
|
24508
|
-
|
|
24509
|
-
|
|
24510
|
-
|
|
24511
|
-
|
|
24512
|
-
`
|
|
24513
|
-
|
|
24518
|
+
function normalizeHandoffModel(model) {
|
|
24519
|
+
return model.split("/").at(-1) ?? model;
|
|
24520
|
+
}
|
|
24521
|
+
function formatHandoffBlock(result, options) {
|
|
24522
|
+
const statusToken = options.final ? `FINAL \xB7 ${result.status === "cancelled" ? "CANCELLED" : result.status === "error" ? "ERROR" : "DONE"}` : result.status === "waiting" ? "WAITING" : "WORKING";
|
|
24523
|
+
const model = normalizeHandoffModel(result.model);
|
|
24524
|
+
const header = options.final ? `## ${result.specialist} \xB7 ${model} \xB7 [${statusToken}]` : `### ${result.specialist} \xB7 ${model} \xB7 [turn ${result.turnIndex ?? "unknown"} \xB7 ${statusToken}]`;
|
|
24525
|
+
const tokenUsage = result.tokenUsage;
|
|
24526
|
+
const gitSha = getCurrentGitSha();
|
|
24527
|
+
const footerParts = [
|
|
24528
|
+
options.final ? "final" : `turn ${result.turnIndex ?? "unknown"}`,
|
|
24529
|
+
result.durationMs !== undefined ? `${Math.round(result.durationMs)} ms` : undefined,
|
|
24530
|
+
tokenUsage?.input_tokens && tokenUsage?.output_tokens ? `${tokenUsage.input_tokens} to ${tokenUsage.output_tokens} tok` : tokenUsage?.input_tokens ? `${tokenUsage.input_tokens} tok in` : tokenUsage?.output_tokens ? `${tokenUsage.output_tokens} tok out` : undefined,
|
|
24531
|
+
result.timestamp ? new Date(result.timestamp).toISOString().slice(0, 16).replace("T", " ") : undefined,
|
|
24532
|
+
gitSha ? `git ${gitSha.slice(0, 8)}` : undefined
|
|
24533
|
+
].filter((part) => Boolean(part));
|
|
24534
|
+
const footer = footerParts.length > 0 ? `_${footerParts.join(" \xB7 ")}_` : "";
|
|
24535
|
+
return `
|
|
24514
24536
|
|
|
24515
|
-
${
|
|
24537
|
+
${header}
|
|
24516
24538
|
|
|
24517
|
-
|
|
24518
|
-
|
|
24539
|
+
${result.output}${footer ? `
|
|
24540
|
+
|
|
24541
|
+
${footer}` : ""}`;
|
|
24542
|
+
}
|
|
24543
|
+
function shouldPersistHandoffBlock(params) {
|
|
24544
|
+
if (!params.output.trim())
|
|
24545
|
+
return false;
|
|
24546
|
+
if (params.notesMode === "final-only" && !params.final)
|
|
24547
|
+
return false;
|
|
24548
|
+
return true;
|
|
24519
24549
|
}
|
|
24520
24550
|
function getModelContextWindow(model) {
|
|
24521
24551
|
if (!model)
|
|
@@ -24912,10 +24942,6 @@ class Supervisor {
|
|
|
24912
24942
|
observabilityDbPath() {
|
|
24913
24943
|
return resolveObservabilityDbLocation(this.opts.runOptions?.workingDirectory ?? process.cwd()).dbPath;
|
|
24914
24944
|
}
|
|
24915
|
-
shouldWriteJobFiles() {
|
|
24916
|
-
const mode = String(process.env.SPECIALISTS_JOB_FILE_OUTPUT ?? "").trim().toLowerCase();
|
|
24917
|
-
return mode === "" || mode === "on" || mode === "true" || mode === "1";
|
|
24918
|
-
}
|
|
24919
24945
|
eventsPath(id) {
|
|
24920
24946
|
return join9(this.jobDir(id), "events.jsonl");
|
|
24921
24947
|
}
|
|
@@ -24932,6 +24958,45 @@ class Supervisor {
|
|
|
24932
24958
|
is_dead: isJobDead(status)
|
|
24933
24959
|
};
|
|
24934
24960
|
}
|
|
24961
|
+
reconcileDeadStatus(id, status) {
|
|
24962
|
+
if (!isJobDead(status) || status.status !== "running" && status.status !== "starting") {
|
|
24963
|
+
return this.withComputedLiveness(status);
|
|
24964
|
+
}
|
|
24965
|
+
if (!Number.isFinite(status.started_at_ms)) {
|
|
24966
|
+
return this.withComputedLiveness(status);
|
|
24967
|
+
}
|
|
24968
|
+
const now = Date.now();
|
|
24969
|
+
const recoveredStatus = {
|
|
24970
|
+
...status,
|
|
24971
|
+
status: "error",
|
|
24972
|
+
current_event: undefined,
|
|
24973
|
+
error: "Process crashed or was killed",
|
|
24974
|
+
last_event_at_ms: now
|
|
24975
|
+
};
|
|
24976
|
+
const elapsed = Math.max(0, Math.round((now - status.started_at_ms) / 1000));
|
|
24977
|
+
const runCompleteEvent = createRunCompleteEvent("ERROR", elapsed, {
|
|
24978
|
+
error: recoveredStatus.error,
|
|
24979
|
+
exit_reason: "crashed"
|
|
24980
|
+
});
|
|
24981
|
+
if (this.sqliteClient) {
|
|
24982
|
+
const persisted = this.withSqliteOperation("upsertStatusWithEvent:readStatus", (client) => {
|
|
24983
|
+
client.upsertStatusWithEvent(recoveredStatus, runCompleteEvent);
|
|
24984
|
+
return true;
|
|
24985
|
+
});
|
|
24986
|
+
if (persisted === undefined) {
|
|
24987
|
+
throw new Error("[supervisor] SQLite upsertStatusWithEvent failed during readStatus recovery: database client unavailable");
|
|
24988
|
+
}
|
|
24989
|
+
} else {
|
|
24990
|
+
this.writeStatusFileOnly(id, recoveredStatus);
|
|
24991
|
+
if (this.isJobFileOutputEnabled) {
|
|
24992
|
+
const eventsPath = this.eventsPath(id);
|
|
24993
|
+
mkdirSync4(this.jobDir(id), { recursive: true });
|
|
24994
|
+
appendFileSync(eventsPath, JSON.stringify(runCompleteEvent) + `
|
|
24995
|
+
`, "utf-8");
|
|
24996
|
+
}
|
|
24997
|
+
}
|
|
24998
|
+
return this.withComputedLiveness(recoveredStatus);
|
|
24999
|
+
}
|
|
24935
25000
|
readStatus(id) {
|
|
24936
25001
|
try {
|
|
24937
25002
|
if (this.isDisposed) {
|
|
@@ -24939,7 +25004,7 @@ class Supervisor {
|
|
|
24939
25004
|
}
|
|
24940
25005
|
const sqliteStatus = this.withSqliteOperation("readStatus", (client) => client.readStatus(id));
|
|
24941
25006
|
if (sqliteStatus)
|
|
24942
|
-
return this.
|
|
25007
|
+
return this.reconcileDeadStatus(id, sqliteStatus);
|
|
24943
25008
|
} catch (error2) {
|
|
24944
25009
|
if (!(error2 instanceof Error && error2.message.includes("supervisor is disposed"))) {
|
|
24945
25010
|
console.warn(`[supervisor] SQLite readStatus failed, falling back to file state: ${String(error2)}`);
|
|
@@ -24950,7 +25015,7 @@ class Supervisor {
|
|
|
24950
25015
|
return null;
|
|
24951
25016
|
try {
|
|
24952
25017
|
const status = JSON.parse(readFileSync8(path, "utf-8"));
|
|
24953
|
-
return this.
|
|
25018
|
+
return this.reconcileDeadStatus(id, status);
|
|
24954
25019
|
} catch {
|
|
24955
25020
|
return null;
|
|
24956
25021
|
}
|
|
@@ -24983,7 +25048,7 @@ class Supervisor {
|
|
|
24983
25048
|
throw this.createDisposedSqliteError("readResult");
|
|
24984
25049
|
}
|
|
24985
25050
|
const sqliteResult = this.withSqliteOperation("readResult", (client) => client.readResult(id));
|
|
24986
|
-
if (sqliteResult)
|
|
25051
|
+
if (typeof sqliteResult === "string" && sqliteResult.trim().length > 0)
|
|
24987
25052
|
return sqliteResult;
|
|
24988
25053
|
} catch (error2) {
|
|
24989
25054
|
if (!(error2 instanceof Error && error2.message.includes("supervisor is disposed"))) {
|
|
@@ -25013,25 +25078,52 @@ class Supervisor {
|
|
|
25013
25078
|
if (!finalized)
|
|
25014
25079
|
return null;
|
|
25015
25080
|
this.aggregateJobMetricsBestEffort(id);
|
|
25081
|
+
if (finalized.bead_id && this.opts.beadsClient) {
|
|
25082
|
+
const outputPath = this.resultPath(id);
|
|
25083
|
+
const output = existsSync10(outputPath) ? readFileSync8(outputPath, "utf-8") || this.readResult(id) || "" : this.readResult(id) || "";
|
|
25084
|
+
if (output.trim()) {
|
|
25085
|
+
this.opts.beadsClient.updateBeadNotes(finalized.bead_id, formatHandoffBlock({
|
|
25086
|
+
output,
|
|
25087
|
+
model: finalized.model ?? "unknown",
|
|
25088
|
+
backend: finalized.backend ?? "unknown",
|
|
25089
|
+
specialist: finalized.specialist,
|
|
25090
|
+
jobId: id,
|
|
25091
|
+
status: "done",
|
|
25092
|
+
timestamp: new Date().toISOString()
|
|
25093
|
+
}, { final: true }));
|
|
25094
|
+
}
|
|
25095
|
+
}
|
|
25016
25096
|
return finalized;
|
|
25017
25097
|
}
|
|
25098
|
+
appendEventBestEffort(jobId, operation, event) {
|
|
25099
|
+
try {
|
|
25100
|
+
const status = this.readStatus(jobId);
|
|
25101
|
+
const persisted = this.withSqliteOperation(operation, (client) => {
|
|
25102
|
+
client.appendEvent(jobId, status?.specialist ?? "unknown", status?.bead_id, event);
|
|
25103
|
+
return true;
|
|
25104
|
+
});
|
|
25105
|
+
if (persisted === undefined) {
|
|
25106
|
+
console.warn(`[supervisor] SQLite ${operation} skipped: database client unavailable`);
|
|
25107
|
+
}
|
|
25108
|
+
} catch (error2) {
|
|
25109
|
+
console.warn(`[supervisor] SQLite ${operation} failed: ${String(error2)}`);
|
|
25110
|
+
}
|
|
25111
|
+
}
|
|
25018
25112
|
emitMetaEvent(jobId, model, backend) {
|
|
25019
25113
|
if (this.isDisposed)
|
|
25020
25114
|
return;
|
|
25021
|
-
|
|
25022
|
-
|
|
25023
|
-
|
|
25024
|
-
|
|
25025
|
-
return
|
|
25026
|
-
|
|
25027
|
-
if (persisted === undefined) {
|
|
25028
|
-
throw new Error("[supervisor] SQLite appendEvent failed: database client unavailable");
|
|
25029
|
-
}
|
|
25115
|
+
this.appendEventBestEffort(jobId, "appendEvent", createMetaEvent(model, backend));
|
|
25116
|
+
}
|
|
25117
|
+
emitControlEvent(jobId, action, options) {
|
|
25118
|
+
if (this.isDisposed)
|
|
25119
|
+
return;
|
|
25120
|
+
this.appendEventBestEffort(jobId, "appendEvent", createControlSignalEvent(action, options));
|
|
25030
25121
|
}
|
|
25031
25122
|
updateJobStatus(id, status, error2) {
|
|
25032
25123
|
const currentStatus = this.readStatus(id);
|
|
25033
25124
|
if (!currentStatus)
|
|
25034
25125
|
return null;
|
|
25126
|
+
const previousStatus = currentStatus.status;
|
|
25035
25127
|
const updatedStatus = {
|
|
25036
25128
|
...currentStatus,
|
|
25037
25129
|
status,
|
|
@@ -25039,7 +25131,10 @@ class Supervisor {
|
|
|
25039
25131
|
error: error2,
|
|
25040
25132
|
last_event_at_ms: Date.now()
|
|
25041
25133
|
};
|
|
25042
|
-
this.writeStatusFile(id, updatedStatus);
|
|
25134
|
+
this.writeStatusFile(id, updatedStatus, { sqliteFailureMode: "warn" });
|
|
25135
|
+
if (previousStatus !== status) {
|
|
25136
|
+
this.appendEventBestEffort(id, "appendEvent:status_change", createStatusChangeEvent(status, previousStatus));
|
|
25137
|
+
}
|
|
25043
25138
|
return this.withComputedLiveness(updatedStatus);
|
|
25044
25139
|
}
|
|
25045
25140
|
aggregateJobMetricsBestEffort(jobId) {
|
|
@@ -25109,41 +25204,49 @@ class Supervisor {
|
|
|
25109
25204
|
writeFileSync3(tmp, JSON.stringify(normalizedStatus, null, 2), "utf-8");
|
|
25110
25205
|
renameSync(tmp, path);
|
|
25111
25206
|
}
|
|
25112
|
-
writeStatusFile(id, data) {
|
|
25207
|
+
writeStatusFile(id, data, options = {}) {
|
|
25113
25208
|
const normalizedStatus = this.withStatusLineageDefaults(id, data);
|
|
25114
25209
|
this.writeStatusFileOnly(id, normalizedStatus);
|
|
25115
|
-
|
|
25116
|
-
|
|
25117
|
-
|
|
25118
|
-
|
|
25119
|
-
|
|
25120
|
-
|
|
25121
|
-
|
|
25122
|
-
|
|
25123
|
-
status: "open",
|
|
25124
|
-
updated_at_ms: Date.now(),
|
|
25125
|
-
status_json: JSON.stringify({
|
|
25210
|
+
try {
|
|
25211
|
+
const persisted = this.withSqliteOperation("upsertStatus", (client) => {
|
|
25212
|
+
client.upsertStatus(normalizedStatus);
|
|
25213
|
+
const chainId = resolveChainId(normalizedStatus);
|
|
25214
|
+
if (!normalizedStatus.epic_id || !chainId) {
|
|
25215
|
+
return true;
|
|
25216
|
+
}
|
|
25217
|
+
client.upsertEpicRun({
|
|
25126
25218
|
epic_id: normalizedStatus.epic_id,
|
|
25127
25219
|
status: "open",
|
|
25128
|
-
|
|
25220
|
+
updated_at_ms: Date.now(),
|
|
25221
|
+
status_json: JSON.stringify({
|
|
25222
|
+
epic_id: normalizedStatus.epic_id,
|
|
25223
|
+
status: "open",
|
|
25224
|
+
source: "supervisor",
|
|
25225
|
+
chain_id: chainId,
|
|
25226
|
+
chain_root_bead_id: normalizedStatus.chain_root_bead_id ?? null,
|
|
25227
|
+
chain_root_job_id: normalizedStatus.chain_root_job_id ?? normalizedStatus.id
|
|
25228
|
+
})
|
|
25229
|
+
});
|
|
25230
|
+
client.upsertEpicChainMembership({
|
|
25129
25231
|
chain_id: chainId,
|
|
25130
|
-
|
|
25131
|
-
|
|
25132
|
-
|
|
25133
|
-
|
|
25134
|
-
|
|
25135
|
-
|
|
25136
|
-
|
|
25137
|
-
|
|
25138
|
-
chain_root_job_id: normalizedStatus.chain_root_job_id ?? normalizedStatus.id,
|
|
25139
|
-
updated_at_ms: Date.now()
|
|
25232
|
+
epic_id: normalizedStatus.epic_id,
|
|
25233
|
+
chain_root_bead_id: normalizedStatus.chain_root_bead_id,
|
|
25234
|
+
chain_root_job_id: normalizedStatus.chain_root_job_id ?? normalizedStatus.id,
|
|
25235
|
+
updated_at_ms: Date.now()
|
|
25236
|
+
});
|
|
25237
|
+
const readiness = loadEpicReadinessSummary(client, normalizedStatus.epic_id);
|
|
25238
|
+
syncEpicStateFromReadiness(client, readiness);
|
|
25239
|
+
return true;
|
|
25140
25240
|
});
|
|
25141
|
-
|
|
25142
|
-
|
|
25143
|
-
|
|
25144
|
-
})
|
|
25145
|
-
|
|
25146
|
-
|
|
25241
|
+
if (persisted === undefined) {
|
|
25242
|
+
throw new Error("[supervisor] SQLite upsertStatus failed: database client unavailable");
|
|
25243
|
+
}
|
|
25244
|
+
} catch (error2) {
|
|
25245
|
+
if (options.sqliteFailureMode === "warn") {
|
|
25246
|
+
console.warn(`[supervisor] SQLite upsertStatus failed: ${String(error2)}`);
|
|
25247
|
+
return;
|
|
25248
|
+
}
|
|
25249
|
+
throw error2;
|
|
25147
25250
|
}
|
|
25148
25251
|
}
|
|
25149
25252
|
gc() {
|
|
@@ -25475,6 +25578,10 @@ class Supervisor {
|
|
|
25475
25578
|
const shouldAutoCloseReadOnlyKeepAlive = (output) => isReadOnlySpecialist && TERMINAL_COMPLIANCE_VERDICT_REGEX.test(output);
|
|
25476
25579
|
const shouldAutoFinalizeKeepAlive = (output) => PASS_COMPLIANCE_VERDICT_REGEX.test(output);
|
|
25477
25580
|
const shouldWriteExternalBeadNotes = runOptions.beadsWriteNotes ?? true;
|
|
25581
|
+
const notesMode = runOptions.notesMode ?? "full-trail";
|
|
25582
|
+
const outputFile = runOptions.output_file;
|
|
25583
|
+
let lastTurnSummaryTextContent = "";
|
|
25584
|
+
let lastTurnSummaryIndex = 0;
|
|
25478
25585
|
let skipFinalKeepAliveInputBeadAppend = false;
|
|
25479
25586
|
let lastGitnexusAnalyzedSha;
|
|
25480
25587
|
const triggerGitnexusAnalyzeIfNeeded = (sha, source) => {
|
|
@@ -25491,12 +25598,10 @@ class Supervisor {
|
|
|
25491
25598
|
appendTimelineEvent(createMetaEvent("gitnexus_analyze_start_failed", `${source}: ${String(err?.message ?? err)}`));
|
|
25492
25599
|
}
|
|
25493
25600
|
};
|
|
25494
|
-
const
|
|
25495
|
-
|
|
25496
|
-
const shouldAppendResultToInputBead = Boolean(shouldWriteExternalBeadNotes && inputBeadId && this.opts.beadsClient);
|
|
25497
|
-
if (!shouldAppendResultToInputBead || !inputBeadId || !this.opts.beadsClient)
|
|
25601
|
+
const writeUnifiedHandoff = (params) => {
|
|
25602
|
+
if (!shouldPersistHandoffBlock({ output: params.output, notesMode, final: params.final }))
|
|
25498
25603
|
return false;
|
|
25499
|
-
const
|
|
25604
|
+
const rendered = formatHandoffBlock({
|
|
25500
25605
|
output: params.output,
|
|
25501
25606
|
promptHash: params.promptHash,
|
|
25502
25607
|
durationMs: params.durationMs,
|
|
@@ -25505,24 +25610,42 @@ class Supervisor {
|
|
|
25505
25610
|
specialist: runOptions.name,
|
|
25506
25611
|
jobId: id,
|
|
25507
25612
|
status: params.status,
|
|
25508
|
-
timestamp: new Date().toISOString()
|
|
25509
|
-
|
|
25510
|
-
|
|
25511
|
-
|
|
25512
|
-
|
|
25513
|
-
|
|
25514
|
-
|
|
25515
|
-
|
|
25516
|
-
|
|
25517
|
-
|
|
25518
|
-
|
|
25613
|
+
timestamp: new Date().toISOString(),
|
|
25614
|
+
tokenUsage: params.tokenUsage,
|
|
25615
|
+
turnIndex: params.turnIndex
|
|
25616
|
+
}, { final: params.final });
|
|
25617
|
+
const inputBeadId = runOptions.inputBeadId;
|
|
25618
|
+
if (shouldWriteExternalBeadNotes && inputBeadId && this.opts.beadsClient) {
|
|
25619
|
+
const noteText = notesMode === "final-only" && !params.final ? "" : rendered;
|
|
25620
|
+
if (noteText) {
|
|
25621
|
+
const appendResult = this.opts.beadsClient.updateBeadNotes(inputBeadId, noteText);
|
|
25622
|
+
if (!appendResult.ok) {
|
|
25623
|
+
const appendError = `[bead-append-failed] ${appendResult.error ?? "Unknown error"}`;
|
|
25624
|
+
appendTimelineEvent(createMetaEvent("bead_append_failed", appendError));
|
|
25625
|
+
setStatus({ current_event: "bead_append_failed", last_event_at_ms: Date.now() });
|
|
25626
|
+
if (this.isJobFileOutputEnabled) {
|
|
25627
|
+
try {
|
|
25628
|
+
appendFileSync(this.resultPath(id), `
|
|
25519
25629
|
|
|
25520
25630
|
${appendError}
|
|
25521
25631
|
`, "utf-8");
|
|
25632
|
+
} catch {}
|
|
25633
|
+
}
|
|
25634
|
+
}
|
|
25635
|
+
}
|
|
25636
|
+
}
|
|
25637
|
+
if (outputFile) {
|
|
25638
|
+
try {
|
|
25639
|
+
if (notesMode === "final-only") {
|
|
25640
|
+
writeFileSync3(outputFile, rendered, "utf-8");
|
|
25641
|
+
} else {
|
|
25642
|
+
appendFileSync(outputFile, rendered, "utf-8");
|
|
25643
|
+
}
|
|
25522
25644
|
} catch {}
|
|
25523
25645
|
}
|
|
25524
|
-
return
|
|
25646
|
+
return true;
|
|
25525
25647
|
};
|
|
25648
|
+
const appendResultToInputBead = (params) => writeUnifiedHandoff(params);
|
|
25526
25649
|
const applyAutoCommitCheckpoint = (target, autoCommitPolicy2) => {
|
|
25527
25650
|
const autoCommitResult = runAutoCommitCheckpoint({
|
|
25528
25651
|
autoCommitPolicy: autoCommitPolicy2,
|
|
@@ -25558,14 +25681,24 @@ ${appendError}
|
|
|
25558
25681
|
return;
|
|
25559
25682
|
const now = Date.now();
|
|
25560
25683
|
lastActivityMs = now;
|
|
25684
|
+
const previousStatus = statusSnapshot.status;
|
|
25561
25685
|
setStatus({ status: "running", current_event: "starting", last_event_at_ms: now });
|
|
25686
|
+
if (previousStatus !== "running") {
|
|
25687
|
+
appendTimelineEvent(createStatusChangeEvent("running", previousStatus));
|
|
25688
|
+
}
|
|
25689
|
+
appendTimelineEvent(createControlSignalEvent("resume_consumed", {
|
|
25690
|
+
source: "runtime",
|
|
25691
|
+
previous_status: previousStatus,
|
|
25692
|
+
next_status: "running",
|
|
25693
|
+
task_preview: task.replace(/\s+/g, " ").slice(0, 240)
|
|
25694
|
+
}));
|
|
25562
25695
|
silenceWarnEmitted = false;
|
|
25563
25696
|
try {
|
|
25564
25697
|
const output = await resumeFn(task);
|
|
25565
25698
|
latestOutput = output;
|
|
25566
25699
|
if (this.isJobFileOutputEnabled) {
|
|
25567
25700
|
mkdirSync4(this.jobDir(id), { recursive: true });
|
|
25568
|
-
writeFileSync3(this.resultPath(id), output, "utf-8");
|
|
25701
|
+
writeFileSync3(this.resultPath(id), lastTurnSummaryTextContent || output, "utf-8");
|
|
25569
25702
|
}
|
|
25570
25703
|
try {
|
|
25571
25704
|
this.withSqliteOperation("upsertResult:resume_turn", (client) => client.upsertResult(id, output));
|
|
@@ -25582,11 +25715,14 @@ ${appendError}
|
|
|
25582
25715
|
const readOnlyClose = shouldAutoCloseReadOnlyKeepAlive(output);
|
|
25583
25716
|
const isWaitingTurn = !readOnlyClose && !passFinalize;
|
|
25584
25717
|
applyAutoCommitCheckpoint(isWaitingTurn ? "waiting" : "terminal", autoCommitPolicy);
|
|
25585
|
-
|
|
25718
|
+
writeUnifiedHandoff({
|
|
25586
25719
|
output,
|
|
25587
25720
|
model: statusSnapshot.model ?? "unknown",
|
|
25588
25721
|
backend: statusSnapshot.backend ?? "unknown",
|
|
25589
|
-
status: isWaitingTurn ? "waiting" : "done"
|
|
25722
|
+
status: isWaitingTurn ? "waiting" : "done",
|
|
25723
|
+
final: false,
|
|
25724
|
+
turnIndex: runMetrics.turns,
|
|
25725
|
+
tokenUsage: runMetrics.token_usage
|
|
25590
25726
|
});
|
|
25591
25727
|
if (!isWaitingTurn) {
|
|
25592
25728
|
closeKeepAliveSession();
|
|
@@ -25663,11 +25799,14 @@ ${appendError}
|
|
|
25663
25799
|
if (keepAliveSession) {
|
|
25664
25800
|
const hasPendingKeepAliveOutput = Boolean(latestOutput && statusSnapshot.status === "waiting" && !shouldAutoCloseReadOnlyKeepAlive(latestOutput));
|
|
25665
25801
|
if (hasPendingKeepAliveOutput) {
|
|
25666
|
-
const appendSucceeded =
|
|
25802
|
+
const appendSucceeded = writeUnifiedHandoff({
|
|
25667
25803
|
output: latestOutput,
|
|
25668
25804
|
model: statusSnapshot.model ?? "unknown",
|
|
25669
25805
|
backend: statusSnapshot.backend ?? "unknown",
|
|
25670
|
-
status: "cancelled"
|
|
25806
|
+
status: "cancelled",
|
|
25807
|
+
final: false,
|
|
25808
|
+
turnIndex: runMetrics.turns,
|
|
25809
|
+
tokenUsage: runMetrics.token_usage
|
|
25671
25810
|
});
|
|
25672
25811
|
if (appendSucceeded) {
|
|
25673
25812
|
skipFinalKeepAliveInputBeadAppend = true;
|
|
@@ -25679,7 +25818,7 @@ ${appendError}
|
|
|
25679
25818
|
killFn?.();
|
|
25680
25819
|
};
|
|
25681
25820
|
process.once("SIGTERM", sigtermHandler);
|
|
25682
|
-
const runOptionsWithBoundary = runOptions.workingDirectory ? { ...runOptions, worktreeBoundary: runOptions.workingDirectory } : runOptions;
|
|
25821
|
+
const runOptionsWithBoundary = runOptions.workingDirectory ? { ...runOptions, worktreeBoundary: runOptions.workingDirectory, suppressRunnerFileOutput: true } : { ...runOptions, suppressRunnerFileOutput: true };
|
|
25683
25822
|
try {
|
|
25684
25823
|
const result = await runner.run(runOptionsWithBoundary, (delta) => {
|
|
25685
25824
|
const toolMatch = delta.match(/\u2699 (.+?)\u2026/);
|
|
@@ -25847,7 +25986,20 @@ ${appendError}
|
|
|
25847
25986
|
context_pct: contextUtilization?.context_pct,
|
|
25848
25987
|
context_health: contextUtilization?.context_health
|
|
25849
25988
|
});
|
|
25989
|
+
lastTurnSummaryIndex = metricEvent.turn_index;
|
|
25990
|
+
lastTurnSummaryTextContent = turnTextAccumulator;
|
|
25850
25991
|
appendTimelineEvent(createTurnSummaryEvent(metricEvent.turn_index, metricEvent.token_usage, metricEvent.finish_reason, turnTextAccumulator || undefined, contextUtilization?.context_pct, contextUtilization?.context_health));
|
|
25992
|
+
if (!keepAliveSession && !runOptions.keepAlive) {
|
|
25993
|
+
writeUnifiedHandoff({
|
|
25994
|
+
output: turnTextAccumulator,
|
|
25995
|
+
model: statusSnapshot.model ?? "unknown",
|
|
25996
|
+
backend: statusSnapshot.backend ?? "unknown",
|
|
25997
|
+
status: "done",
|
|
25998
|
+
final: false,
|
|
25999
|
+
turnIndex: metricEvent.turn_index,
|
|
26000
|
+
tokenUsage: metricEvent.token_usage ?? runMetrics.token_usage
|
|
26001
|
+
});
|
|
26002
|
+
}
|
|
25851
26003
|
turnTextAccumulator = "";
|
|
25852
26004
|
return;
|
|
25853
26005
|
}
|
|
@@ -25891,16 +26043,33 @@ ${appendError}
|
|
|
25891
26043
|
try {
|
|
25892
26044
|
const parsed = JSON.parse(line);
|
|
25893
26045
|
if (parsed?.type === "steer" && typeof parsed.message === "string") {
|
|
25894
|
-
|
|
26046
|
+
appendTimelineEvent(createControlSignalEvent("steer_consumed", {
|
|
26047
|
+
source: "runtime",
|
|
26048
|
+
previous_status: statusSnapshot.status,
|
|
26049
|
+
message_preview: parsed.message.replace(/\s+/g, " ").slice(0, 240)
|
|
26050
|
+
}));
|
|
26051
|
+
steerFn?.(parsed.message).catch((error2) => {
|
|
26052
|
+
appendTimelineEvent(createControlSignalEvent("steer_failed", {
|
|
26053
|
+
source: "runtime",
|
|
26054
|
+
previous_status: statusSnapshot.status,
|
|
26055
|
+
error_message: error2 instanceof Error ? error2.message : String(error2)
|
|
26056
|
+
}));
|
|
26057
|
+
});
|
|
25895
26058
|
} else if (parsed?.type === "resume" && typeof parsed.task === "string") {
|
|
25896
26059
|
handleResumeTurn(parsed.task);
|
|
25897
26060
|
} else if (parsed?.type === "prompt" && typeof parsed.message === "string") {
|
|
25898
26061
|
console.error('[specialists] DEPRECATED: FIFO message {type:"prompt"} is deprecated. Use {type:"resume", task:"..."} instead.');
|
|
25899
26062
|
handleResumeTurn(parsed.message);
|
|
25900
26063
|
} else if (parsed?.type === "close") {
|
|
26064
|
+
appendTimelineEvent(createControlSignalEvent("close_consumed", { source: "runtime", previous_status: statusSnapshot.status }));
|
|
25901
26065
|
closeKeepAliveSession();
|
|
25902
26066
|
}
|
|
25903
|
-
} catch {
|
|
26067
|
+
} catch (error2) {
|
|
26068
|
+
appendTimelineEvent(createControlSignalEvent("fifo_parse_error", {
|
|
26069
|
+
source: "runtime",
|
|
26070
|
+
error_message: error2 instanceof Error ? error2.message : String(error2)
|
|
26071
|
+
}));
|
|
26072
|
+
}
|
|
25904
26073
|
});
|
|
25905
26074
|
fifoReadline.on("error", (error2) => {
|
|
25906
26075
|
console.error(`[supervisor] FIFO read error: ${String(error2)}`);
|
|
@@ -25976,7 +26145,7 @@ ${appendError}
|
|
|
25976
26145
|
latestOutput = result.output;
|
|
25977
26146
|
if (this.isJobFileOutputEnabled) {
|
|
25978
26147
|
mkdirSync4(this.jobDir(id), { recursive: true });
|
|
25979
|
-
writeFileSync3(this.resultPath(id), latestOutput, "utf-8");
|
|
26148
|
+
writeFileSync3(this.resultPath(id), lastTurnSummaryTextContent || latestOutput, "utf-8");
|
|
25980
26149
|
}
|
|
25981
26150
|
const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
|
|
25982
26151
|
const finalResult = {
|
|
@@ -26010,13 +26179,16 @@ ${appendError}
|
|
|
26010
26179
|
} else if (shouldAutoFinalizeKeepAlive(finalResult.output)) {
|
|
26011
26180
|
await closeKeepAliveSession();
|
|
26012
26181
|
} else {
|
|
26013
|
-
|
|
26182
|
+
writeUnifiedHandoff({
|
|
26014
26183
|
output: finalResult.output,
|
|
26015
26184
|
model: finalResult.model,
|
|
26016
26185
|
backend: finalResult.backend,
|
|
26017
26186
|
status: "waiting",
|
|
26187
|
+
final: false,
|
|
26188
|
+
turnIndex: runMetrics.turns,
|
|
26018
26189
|
promptHash: finalResult.promptHash,
|
|
26019
|
-
durationMs: finalResult.durationMs
|
|
26190
|
+
durationMs: finalResult.durationMs,
|
|
26191
|
+
tokenUsage: finalResult.metrics?.token_usage
|
|
26020
26192
|
});
|
|
26021
26193
|
skipFinalKeepAliveInputBeadAppend = true;
|
|
26022
26194
|
setWaitingStatus({
|
|
@@ -26035,18 +26207,21 @@ ${appendError}
|
|
|
26035
26207
|
const appendedStatus = keepAliveSession && !shouldAutoCloseReadOnlyKeepAlive(finalResult.output) ? "waiting" : "done";
|
|
26036
26208
|
const shouldSkipFinalInputBeadAppend = keepAliveSession && skipFinalKeepAliveInputBeadAppend;
|
|
26037
26209
|
if (!shouldSkipFinalInputBeadAppend) {
|
|
26038
|
-
|
|
26039
|
-
output: finalResult.output,
|
|
26210
|
+
writeUnifiedHandoff({
|
|
26211
|
+
output: lastTurnSummaryTextContent || finalResult.output,
|
|
26040
26212
|
model: finalResult.model,
|
|
26041
26213
|
backend: finalResult.backend,
|
|
26042
26214
|
status: appendedStatus,
|
|
26215
|
+
final: true,
|
|
26216
|
+
turnIndex: lastTurnSummaryIndex || runMetrics.turns,
|
|
26043
26217
|
promptHash: finalResult.promptHash,
|
|
26044
|
-
durationMs: finalResult.durationMs
|
|
26218
|
+
durationMs: finalResult.durationMs,
|
|
26219
|
+
tokenUsage: finalResult.metrics?.token_usage
|
|
26045
26220
|
});
|
|
26046
26221
|
}
|
|
26047
26222
|
if (ownsBead && finalResult.beadId) {
|
|
26048
|
-
this.opts.beadsClient?.updateBeadNotes(finalResult.beadId,
|
|
26049
|
-
output: finalResult.output,
|
|
26223
|
+
this.opts.beadsClient?.updateBeadNotes(finalResult.beadId, formatHandoffBlock({
|
|
26224
|
+
output: lastTurnSummaryTextContent || finalResult.output,
|
|
26050
26225
|
promptHash: finalResult.promptHash,
|
|
26051
26226
|
durationMs: finalResult.durationMs,
|
|
26052
26227
|
model: finalResult.model,
|
|
@@ -26054,11 +26229,13 @@ ${appendError}
|
|
|
26054
26229
|
specialist: runOptions.name,
|
|
26055
26230
|
jobId: id,
|
|
26056
26231
|
status: "done",
|
|
26057
|
-
timestamp: new Date().toISOString()
|
|
26058
|
-
|
|
26232
|
+
timestamp: new Date().toISOString(),
|
|
26233
|
+
tokenUsage: finalResult.metrics?.token_usage,
|
|
26234
|
+
turnIndex: lastTurnSummaryIndex || runMetrics.turns
|
|
26235
|
+
}, { final: true }));
|
|
26059
26236
|
} else if (shouldWriteExternalBeadNotes && !inputBeadId && finalResult.beadId) {
|
|
26060
|
-
this.opts.beadsClient?.updateBeadNotes(finalResult.beadId,
|
|
26061
|
-
output: finalResult.output,
|
|
26237
|
+
this.opts.beadsClient?.updateBeadNotes(finalResult.beadId, formatHandoffBlock({
|
|
26238
|
+
output: lastTurnSummaryTextContent || finalResult.output,
|
|
26062
26239
|
promptHash: finalResult.promptHash,
|
|
26063
26240
|
durationMs: finalResult.durationMs,
|
|
26064
26241
|
model: finalResult.model,
|
|
@@ -26066,8 +26243,10 @@ ${appendError}
|
|
|
26066
26243
|
specialist: runOptions.name,
|
|
26067
26244
|
jobId: id,
|
|
26068
26245
|
status: "done",
|
|
26069
|
-
timestamp: new Date().toISOString()
|
|
26070
|
-
|
|
26246
|
+
timestamp: new Date().toISOString(),
|
|
26247
|
+
tokenUsage: finalResult.metrics?.token_usage,
|
|
26248
|
+
turnIndex: lastTurnSummaryIndex || runMetrics.turns
|
|
26249
|
+
}, { final: true }));
|
|
26071
26250
|
}
|
|
26072
26251
|
if (finalResult.beadId) {
|
|
26073
26252
|
const liveJobs = this.listLiveJobsForBead(finalResult.beadId).filter((liveJobId) => liveJobId !== id);
|
|
@@ -26171,7 +26350,10 @@ ${appendError}
|
|
|
26171
26350
|
output: latestOutput || errorMsg,
|
|
26172
26351
|
model: statusSnapshot.model ?? "unknown",
|
|
26173
26352
|
backend: statusSnapshot.backend ?? "unknown",
|
|
26174
|
-
status: "error"
|
|
26353
|
+
status: "error",
|
|
26354
|
+
final: false,
|
|
26355
|
+
turnIndex: runMetrics.turns,
|
|
26356
|
+
tokenUsage: runMetrics.token_usage
|
|
26175
26357
|
});
|
|
26176
26358
|
this.writeReadyMarker(id);
|
|
26177
26359
|
throw err;
|
|
@@ -30132,11 +30314,13 @@ async function launchSpecialist(opts) {
|
|
|
30132
30314
|
keepAlive: opts.args.keepAlive,
|
|
30133
30315
|
noKeepAlive: opts.args.noKeepAlive,
|
|
30134
30316
|
beadsWriteNotes: opts.beadsWriteNotes,
|
|
30317
|
+
notesMode: opts.specialist.specialist.notes_mode,
|
|
30135
30318
|
forceJob: opts.args.forceJob,
|
|
30136
30319
|
permissionRequired: opts.perm,
|
|
30137
30320
|
workingDirectory: opts.workingDirectory,
|
|
30138
30321
|
reusedFromJobId: opts.reusedFromJobId,
|
|
30139
|
-
worktreeOwnerJobId: opts.worktreeOwnerJobId
|
|
30322
|
+
worktreeOwnerJobId: opts.worktreeOwnerJobId,
|
|
30323
|
+
output_file: opts.specialist.specialist.output_file
|
|
30140
30324
|
},
|
|
30141
30325
|
beadsClient: opts.beadsClient,
|
|
30142
30326
|
stallDetection: opts.specialist.specialist.stall_detection,
|
|
@@ -39646,6 +39830,7 @@ var init_stdin_buffer = __esm(() => {
|
|
|
39646
39830
|
import * as fs2 from "fs";
|
|
39647
39831
|
import { createRequire as createRequire3 } from "module";
|
|
39648
39832
|
import * as path2 from "path";
|
|
39833
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
39649
39834
|
|
|
39650
39835
|
class ProcessTerminal {
|
|
39651
39836
|
wasRaw = false;
|
|
@@ -39730,17 +39915,23 @@ class ProcessTerminal {
|
|
|
39730
39915
|
if (process.platform !== "win32")
|
|
39731
39916
|
return;
|
|
39732
39917
|
try {
|
|
39733
|
-
const
|
|
39734
|
-
|
|
39735
|
-
|
|
39736
|
-
const
|
|
39737
|
-
const
|
|
39738
|
-
const
|
|
39739
|
-
|
|
39740
|
-
|
|
39741
|
-
|
|
39742
|
-
|
|
39743
|
-
|
|
39918
|
+
const arch = process.arch;
|
|
39919
|
+
if (arch !== "x64" && arch !== "arm64")
|
|
39920
|
+
return;
|
|
39921
|
+
const moduleDir = path2.dirname(fileURLToPath4(import.meta.url));
|
|
39922
|
+
const nativePath = path2.join("native", "win32", "prebuilds", `win32-${arch}`, "win32-console-mode.node");
|
|
39923
|
+
const candidates = [
|
|
39924
|
+
path2.join(moduleDir, "..", nativePath),
|
|
39925
|
+
path2.join(moduleDir, nativePath),
|
|
39926
|
+
path2.join(path2.dirname(process.execPath), nativePath)
|
|
39927
|
+
];
|
|
39928
|
+
for (const modulePath of candidates) {
|
|
39929
|
+
try {
|
|
39930
|
+
const helper = cjsRequire(modulePath);
|
|
39931
|
+
helper.enableVirtualTerminalInput?.();
|
|
39932
|
+
return;
|
|
39933
|
+
} catch {}
|
|
39934
|
+
}
|
|
39744
39935
|
} catch {}
|
|
39745
39936
|
}
|
|
39746
39937
|
async drainInput(maxMs = 1000, idleMs = 50) {
|
|
@@ -40309,6 +40500,16 @@ var init_control = __esm(() => {
|
|
|
40309
40500
|
function formatTime(t) {
|
|
40310
40501
|
return new Date(t).toISOString().slice(11, 19);
|
|
40311
40502
|
}
|
|
40503
|
+
function formatDateTime(t) {
|
|
40504
|
+
const d = new Date(t);
|
|
40505
|
+
const yyyy = d.getFullYear();
|
|
40506
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
40507
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
40508
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
40509
|
+
const mi = String(d.getMinutes()).padStart(2, "0");
|
|
40510
|
+
const ss = String(d.getSeconds()).padStart(2, "0");
|
|
40511
|
+
return `${yyyy}-${mm}-${dd} ${hh}:${mi}:${ss}`;
|
|
40512
|
+
}
|
|
40312
40513
|
function formatElapsed(seconds) {
|
|
40313
40514
|
if (seconds < 60)
|
|
40314
40515
|
return `${seconds}s`;
|
|
@@ -40415,6 +40616,26 @@ function formatEventLine(event, options2) {
|
|
|
40415
40616
|
} else if (event.type === "error") {
|
|
40416
40617
|
detailParts.push(`source=${event.source}`);
|
|
40417
40618
|
detailParts.push(`error=${event.error_message}`);
|
|
40619
|
+
} else if (event.type === "control_signal") {
|
|
40620
|
+
detailParts.push(`action=${event.action}`);
|
|
40621
|
+
detailParts.push(`source=${event.source}`);
|
|
40622
|
+
if (event.previous_status || event.next_status) {
|
|
40623
|
+
detailParts.push(`status=${event.previous_status ?? "?"}->${event.next_status ?? "?"}`);
|
|
40624
|
+
}
|
|
40625
|
+
if (event.pid !== undefined)
|
|
40626
|
+
detailParts.push(`pid=${event.pid}`);
|
|
40627
|
+
if (event.signal)
|
|
40628
|
+
detailParts.push(`signal=${event.signal}`);
|
|
40629
|
+
if (event.force !== undefined)
|
|
40630
|
+
detailParts.push(`force=${event.force}`);
|
|
40631
|
+
if (event.reason)
|
|
40632
|
+
detailParts.push(`reason=${event.reason}`);
|
|
40633
|
+
if (event.message_preview)
|
|
40634
|
+
detailParts.push(`message="${event.message_preview}"`);
|
|
40635
|
+
if (event.task_preview)
|
|
40636
|
+
detailParts.push(`task="${event.task_preview}"`);
|
|
40637
|
+
if (event.error_message)
|
|
40638
|
+
detailParts.push(`error=${event.error_message}`);
|
|
40418
40639
|
} else if (event.type === "auto_commit_success" || event.type === "auto_commit_skipped" || event.type === "auto_commit_failed") {
|
|
40419
40640
|
const status = event.type.replace("auto_commit_", "");
|
|
40420
40641
|
detailParts.push(`status=${status}`);
|
|
@@ -40518,6 +40739,8 @@ function formatEventInline(event) {
|
|
|
40518
40739
|
}
|
|
40519
40740
|
case "stale_warning":
|
|
40520
40741
|
return yellow10(`[warning] ${event.reason}: ${Math.round(event.silence_ms / 1000)}s silent`);
|
|
40742
|
+
case "control_signal":
|
|
40743
|
+
return dim9(`[control] ${event.action}`);
|
|
40521
40744
|
case "error":
|
|
40522
40745
|
return red2(`[error] ${event.source}: ${event.error_message}`);
|
|
40523
40746
|
default:
|
|
@@ -40556,7 +40779,8 @@ var init_format_helpers = __esm(() => {
|
|
|
40556
40779
|
error: "ERROR",
|
|
40557
40780
|
auto_commit_success: "AUTO+",
|
|
40558
40781
|
auto_commit_skipped: "AUTO-",
|
|
40559
|
-
auto_commit_failed: "AUTO!"
|
|
40782
|
+
auto_commit_failed: "AUTO!",
|
|
40783
|
+
control_signal: "CTRL"
|
|
40560
40784
|
};
|
|
40561
40785
|
});
|
|
40562
40786
|
|
|
@@ -40614,7 +40838,7 @@ function createFinalizeSupervisor(jobsDir) {
|
|
|
40614
40838
|
throw new Error("finalize supervisor runner is not used");
|
|
40615
40839
|
} };
|
|
40616
40840
|
const runOptions = {};
|
|
40617
|
-
return new Supervisor({ runner, runOptions, jobsDir });
|
|
40841
|
+
return new Supervisor({ runner, runOptions, jobsDir, beadsClient: new BeadsClient });
|
|
40618
40842
|
}
|
|
40619
40843
|
function findReviewerPassInChain(supervisor, chainId) {
|
|
40620
40844
|
for (const id of supervisor.listChainJobIds(chainId)) {
|
|
@@ -40628,13 +40852,27 @@ function findReviewerPassInChain(supervisor, chainId) {
|
|
|
40628
40852
|
}
|
|
40629
40853
|
async function stopJob(jobId, opts = {}) {
|
|
40630
40854
|
const jobsDir = opts.jobsDir ?? resolveJobsDir(process.cwd());
|
|
40631
|
-
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
40855
|
+
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir, beadsClient: new BeadsClient });
|
|
40632
40856
|
try {
|
|
40633
40857
|
const status = supervisor.readStatus(jobId);
|
|
40634
40858
|
if (!status)
|
|
40635
40859
|
throw new Error(`No job found: ${jobId}`);
|
|
40636
40860
|
if (status.status === "done" || status.status === "error" || status.status === "cancelled") {
|
|
40637
40861
|
process.stderr.write(`${dim10(`Job ${jobId} already finalized (${status.status}).`)}
|
|
40862
|
+
`);
|
|
40863
|
+
return;
|
|
40864
|
+
}
|
|
40865
|
+
let statusForBeadClose = status;
|
|
40866
|
+
let finalizedFromWaiting = false;
|
|
40867
|
+
if (status.status === "waiting" && status.bead_id) {
|
|
40868
|
+
const finalized = supervisor.finalizeWaitingJob(jobId);
|
|
40869
|
+
if (finalized) {
|
|
40870
|
+
statusForBeadClose = finalized;
|
|
40871
|
+
finalizedFromWaiting = true;
|
|
40872
|
+
}
|
|
40873
|
+
}
|
|
40874
|
+
if (!finalizedFromWaiting && (statusForBeadClose.status === "done" || statusForBeadClose.status === "error" || statusForBeadClose.status === "cancelled")) {
|
|
40875
|
+
process.stderr.write(`${dim10(`Job ${jobId} already finalized (${statusForBeadClose.status}).`)}
|
|
40638
40876
|
`);
|
|
40639
40877
|
return;
|
|
40640
40878
|
}
|
|
@@ -40644,8 +40882,18 @@ async function stopJob(jobId, opts = {}) {
|
|
|
40644
40882
|
const tmuxSession = status.tmux_session;
|
|
40645
40883
|
const isAlreadyDead = !isProcessAlive(pid, status.started_at_ms);
|
|
40646
40884
|
const force = opts.force ?? false;
|
|
40885
|
+
supervisor.emitControlEvent(jobId, "stop_requested", {
|
|
40886
|
+
source: "cli",
|
|
40887
|
+
pid,
|
|
40888
|
+
previous_status: status.status,
|
|
40889
|
+
force,
|
|
40890
|
+
reason: isAlreadyDead ? "pid_already_dead" : "operator_stop",
|
|
40891
|
+
signal: force ? "SIGTERM/SIGKILL" : "SIGTERM",
|
|
40892
|
+
tmux_session: tmuxSession
|
|
40893
|
+
});
|
|
40647
40894
|
if (force && isAlreadyDead) {
|
|
40648
|
-
supervisor.updateJobStatus(jobId, "error");
|
|
40895
|
+
supervisor.updateJobStatus(jobId, "error", `Force stop requested; PID ${pid} already dead`);
|
|
40896
|
+
supervisor.emitControlEvent(jobId, "stop_marked_error", { source: "cli", pid, previous_status: status.status, next_status: "error", force, reason: "pid_already_dead" });
|
|
40649
40897
|
supervisor.aggregateJobMetricsBestEffort(jobId);
|
|
40650
40898
|
tryKillProcessGroup(pid);
|
|
40651
40899
|
process.stdout.write(`${green10("\u2713")} Marked ${jobId} as error (PID ${pid} already dead)
|
|
@@ -40653,15 +40901,18 @@ async function stopJob(jobId, opts = {}) {
|
|
|
40653
40901
|
} else {
|
|
40654
40902
|
const terminalStatus = resolveTerminalStatus(jobId);
|
|
40655
40903
|
supervisor.updateJobStatus(jobId, terminalStatus);
|
|
40904
|
+
supervisor.emitControlEvent(jobId, "status_marked_before_signal", { source: "cli", pid, previous_status: status.status, next_status: terminalStatus, force });
|
|
40656
40905
|
supervisor.aggregateJobMetricsBestEffort(jobId);
|
|
40657
40906
|
try {
|
|
40658
40907
|
process.kill(pid, "SIGTERM");
|
|
40908
|
+
supervisor.emitControlEvent(jobId, "signal_sent", { source: "cli", pid, signal: "SIGTERM", force, next_status: terminalStatus });
|
|
40659
40909
|
process.stdout.write(`${green10("\u2713")} Marked ${jobId} as ${terminalStatus} and sent SIGTERM to PID ${pid}
|
|
40660
40910
|
`);
|
|
40661
40911
|
if (force) {
|
|
40662
40912
|
const exited = await waitForProcessExit(pid, 5000);
|
|
40663
40913
|
if (!exited) {
|
|
40664
|
-
supervisor.updateJobStatus(jobId, "error");
|
|
40914
|
+
supervisor.updateJobStatus(jobId, "error", `Force stop escalated; PID ${pid} ignored SIGTERM`);
|
|
40915
|
+
supervisor.emitControlEvent(jobId, "force_stop_escalated", { source: "cli", pid, signal: "SIGKILL", previous_status: terminalStatus, next_status: "error", force: true, reason: "sigterm_timeout" });
|
|
40665
40916
|
tryKillProcessGroup(pid);
|
|
40666
40917
|
process.stderr.write(`${red3("Force stop:")} PID ${pid} ignored SIGTERM, marked ${jobId} as error and killed process group.
|
|
40667
40918
|
`);
|
|
@@ -40670,7 +40921,8 @@ async function stopJob(jobId, opts = {}) {
|
|
|
40670
40921
|
} catch (err) {
|
|
40671
40922
|
if (err.code === "ESRCH") {
|
|
40672
40923
|
if (force) {
|
|
40673
|
-
supervisor.updateJobStatus(jobId, "error");
|
|
40924
|
+
supervisor.updateJobStatus(jobId, "error", `Force stop escalated; PID ${pid} ignored SIGTERM`);
|
|
40925
|
+
supervisor.emitControlEvent(jobId, "force_stop_escalated", { source: "cli", pid, signal: "SIGKILL", previous_status: terminalStatus, next_status: "error", force: true, reason: "sigterm_timeout" });
|
|
40674
40926
|
tryKillProcessGroup(pid);
|
|
40675
40927
|
process.stdout.write(`${green10("\u2713")} Marked ${jobId} as error (PID ${pid} already gone)
|
|
40676
40928
|
`);
|
|
@@ -40684,12 +40936,13 @@ async function stopJob(jobId, opts = {}) {
|
|
|
40684
40936
|
}
|
|
40685
40937
|
}
|
|
40686
40938
|
if (tmuxSession) {
|
|
40939
|
+
supervisor.emitControlEvent(jobId, "tmux_kill_requested", { source: "cli", pid, tmux_session: tmuxSession });
|
|
40687
40940
|
killTmuxSession(tmuxSession);
|
|
40688
40941
|
process.stdout.write(`${dim10(` tmux session ${tmuxSession} killed`)}
|
|
40689
40942
|
`);
|
|
40690
40943
|
}
|
|
40691
40944
|
if (status.bead_id) {
|
|
40692
|
-
const finalStatus = supervisor.readStatus(jobId)?.status ?? "cancelled";
|
|
40945
|
+
const finalStatus = supervisor.readStatus(jobId)?.status ?? statusForBeadClose.status ?? "cancelled";
|
|
40693
40946
|
const beads = new BeadsClient;
|
|
40694
40947
|
const liveJobs = supervisor.listLiveJobsForBead(status.bead_id).filter((liveJobId) => liveJobId !== jobId);
|
|
40695
40948
|
if (opts.closeBeadAnyway || liveJobs.length === 0) {
|
|
@@ -40774,7 +41027,7 @@ async function appendBeadNote(beadId, text, opts = {}) {
|
|
|
40774
41027
|
if (!beadId || !text)
|
|
40775
41028
|
return { ok: false, error: "beads unavailable or empty payload" };
|
|
40776
41029
|
return await new Promise((resolve9) => {
|
|
40777
|
-
const child = spawn5("bd", ["update", beadId, "--notes", text], {
|
|
41030
|
+
const child = spawn5("bd", ["update", beadId, "--append-notes", text], {
|
|
40778
41031
|
stdio: ["ignore", "ignore", "pipe"]
|
|
40779
41032
|
});
|
|
40780
41033
|
const timer = opts.timeoutMs ? setTimeout(() => {
|
|
@@ -47152,7 +47405,7 @@ var init_epic = __esm(() => {
|
|
|
47152
47405
|
// src/cli/version-check.ts
|
|
47153
47406
|
import { spawnSync as spawnSync16 } from "child_process";
|
|
47154
47407
|
import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync22, writeFileSync as writeFileSync13 } from "fs";
|
|
47155
|
-
import { dirname as
|
|
47408
|
+
import { dirname as dirname12, join as join26 } from "path";
|
|
47156
47409
|
import { createRequire as createRequire4 } from "module";
|
|
47157
47410
|
function readBundledPackageVersion(requireFn = require3) {
|
|
47158
47411
|
for (const candidate of ["../package.json", "../../package.json"]) {
|
|
@@ -47186,7 +47439,7 @@ function readCachedVersionCheck() {
|
|
|
47186
47439
|
return readCache();
|
|
47187
47440
|
}
|
|
47188
47441
|
function writeCache(cache) {
|
|
47189
|
-
mkdirSync11(
|
|
47442
|
+
mkdirSync11(dirname12(CACHE_PATH), { recursive: true });
|
|
47190
47443
|
writeFileSync13(CACHE_PATH, `${JSON.stringify(cache, null, 2)}
|
|
47191
47444
|
`, "utf8");
|
|
47192
47445
|
}
|
|
@@ -49110,8 +49363,32 @@ function deriveStartupSnapshot(status, events) {
|
|
|
49110
49363
|
return Object.keys(merged).length > 0 ? merged : null;
|
|
49111
49364
|
}
|
|
49112
49365
|
function deriveApiError(events) {
|
|
49113
|
-
const
|
|
49114
|
-
|
|
49366
|
+
for (const event of [...events].reverse()) {
|
|
49367
|
+
if (event.type === "error")
|
|
49368
|
+
return event.error_message;
|
|
49369
|
+
if (event.type === "run_complete" && event.error)
|
|
49370
|
+
return event.error;
|
|
49371
|
+
if (event.type === "control_signal" && event.error_message)
|
|
49372
|
+
return event.error_message;
|
|
49373
|
+
}
|
|
49374
|
+
return null;
|
|
49375
|
+
}
|
|
49376
|
+
function deriveTerminalReason(events) {
|
|
49377
|
+
for (const event of [...events].reverse()) {
|
|
49378
|
+
if (event.type === "run_complete") {
|
|
49379
|
+
return event.error ?? event.exit_reason ?? event.status;
|
|
49380
|
+
}
|
|
49381
|
+
if (event.type === "control_signal") {
|
|
49382
|
+
return event.error_message ?? event.reason ?? event.action;
|
|
49383
|
+
}
|
|
49384
|
+
if (event.type === "status_change") {
|
|
49385
|
+
return `status ${event.previous_status ?? "?"} -> ${event.status}`;
|
|
49386
|
+
}
|
|
49387
|
+
}
|
|
49388
|
+
return null;
|
|
49389
|
+
}
|
|
49390
|
+
function logHint(jobId) {
|
|
49391
|
+
return ` Inspect with: specialists log ${jobId} --limit 200`;
|
|
49115
49392
|
}
|
|
49116
49393
|
function formatPayloadPreamble(payloadJson) {
|
|
49117
49394
|
if (!payloadJson)
|
|
@@ -49269,7 +49546,7 @@ async function run18() {
|
|
|
49269
49546
|
const apiError2 = status2.error ?? deriveApiError(events2);
|
|
49270
49547
|
const output3 = readResultOutput();
|
|
49271
49548
|
if (!output3) {
|
|
49272
|
-
const message = apiError2 ? `Job ${jobId} failed: ${apiError2}` : `Result not found for job ${jobId}`;
|
|
49549
|
+
const message = apiError2 ? `Job ${jobId} failed: ${apiError2}.${logHint(jobId)}` : `Result not found for job ${jobId}.${logHint(jobId)}`;
|
|
49273
49550
|
if (args.json) {
|
|
49274
49551
|
emitJson(status2, null, message, startupContext2);
|
|
49275
49552
|
} else {
|
|
@@ -49288,11 +49565,28 @@ async function run18() {
|
|
|
49288
49565
|
}
|
|
49289
49566
|
if (status2.status === "error") {
|
|
49290
49567
|
const startupContext2 = deriveStartupSnapshot(status2, readTimelineEventsForResult(sqliteClient, jobsDir, jobId));
|
|
49291
|
-
const
|
|
49568
|
+
const events2 = readTimelineEventsForResult(sqliteClient, jobsDir, jobId);
|
|
49569
|
+
const reason = status2.error ?? deriveApiError(events2) ?? deriveTerminalReason(events2) ?? "unknown error";
|
|
49570
|
+
const message = `Job ${jobId} failed: ${reason}.${logHint(jobId)}`;
|
|
49571
|
+
if (args.json) {
|
|
49572
|
+
emitJson(status2, null, message, startupContext2);
|
|
49573
|
+
} else {
|
|
49574
|
+
process.stderr.write(`${red4(`Job ${jobId} failed:`)} ${reason}
|
|
49575
|
+
${dim12(logHint(jobId).trim())}
|
|
49576
|
+
`);
|
|
49577
|
+
}
|
|
49578
|
+
process.exit(1);
|
|
49579
|
+
}
|
|
49580
|
+
if (status2.status === "cancelled") {
|
|
49581
|
+
const events2 = readTimelineEventsForResult(sqliteClient, jobsDir, jobId);
|
|
49582
|
+
const startupContext2 = deriveStartupSnapshot(status2, events2);
|
|
49583
|
+
const reason = status2.error ?? deriveTerminalReason(events2) ?? "cancelled";
|
|
49584
|
+
const message = `Job ${jobId} cancelled: ${reason}.${logHint(jobId)}`;
|
|
49292
49585
|
if (args.json) {
|
|
49293
49586
|
emitJson(status2, null, message, startupContext2);
|
|
49294
49587
|
} else {
|
|
49295
|
-
process.stderr.write(`${red4(`Job ${jobId}
|
|
49588
|
+
process.stderr.write(`${red4(`Job ${jobId} cancelled:`)} ${reason}
|
|
49589
|
+
${dim12(logHint(jobId).trim())}
|
|
49296
49590
|
`);
|
|
49297
49591
|
}
|
|
49298
49592
|
process.exit(1);
|
|
@@ -49368,14 +49662,30 @@ async function run18() {
|
|
|
49368
49662
|
}
|
|
49369
49663
|
return;
|
|
49370
49664
|
}
|
|
49665
|
+
if (status.status === "cancelled") {
|
|
49666
|
+
const events2 = readTimelineEventsForResult(sqliteClient, jobsDir, jobId);
|
|
49667
|
+
const startupContext2 = deriveStartupSnapshot(status, events2);
|
|
49668
|
+
const reason = status.error ?? deriveTerminalReason(events2) ?? "cancelled";
|
|
49669
|
+
const message = `Job ${jobId} cancelled: ${reason}.${logHint(jobId)}`;
|
|
49670
|
+
if (args.json) {
|
|
49671
|
+
emitJson(status, null, message, startupContext2);
|
|
49672
|
+
} else {
|
|
49673
|
+
process.stderr.write(`${red4(`Job ${jobId} cancelled:`)} ${reason}
|
|
49674
|
+
${dim12(logHint(jobId).trim())}
|
|
49675
|
+
`);
|
|
49676
|
+
}
|
|
49677
|
+
process.exit(1);
|
|
49678
|
+
}
|
|
49371
49679
|
if (status.status === "error") {
|
|
49372
49680
|
const events2 = readTimelineEventsForResult(sqliteClient, jobsDir, jobId);
|
|
49373
49681
|
const startupContext2 = deriveStartupSnapshot(status, events2);
|
|
49374
|
-
const
|
|
49682
|
+
const reason = status.error ?? deriveApiError(events2) ?? deriveTerminalReason(events2) ?? "unknown error";
|
|
49683
|
+
const message = `Job ${jobId} failed: ${reason}.${logHint(jobId)}`;
|
|
49375
49684
|
if (args.json) {
|
|
49376
49685
|
emitJson(status, null, message, startupContext2);
|
|
49377
49686
|
} else {
|
|
49378
|
-
process.stderr.write(`${red4(`Job ${jobId} failed:`)} ${
|
|
49687
|
+
process.stderr.write(`${red4(`Job ${jobId} failed:`)} ${reason}
|
|
49688
|
+
${dim12(logHint(jobId).trim())}
|
|
49379
49689
|
`);
|
|
49380
49690
|
}
|
|
49381
49691
|
process.exit(1);
|
|
@@ -49384,7 +49694,7 @@ async function run18() {
|
|
|
49384
49694
|
const apiError = status.error ?? deriveApiError(events);
|
|
49385
49695
|
const output2 = readResultOutput();
|
|
49386
49696
|
if (!output2) {
|
|
49387
|
-
const message = apiError ? `Job ${jobId} failed: ${apiError}` : `Result not found for job ${jobId}`;
|
|
49697
|
+
const message = apiError ? `Job ${jobId} failed: ${apiError}.${logHint(jobId)}` : `Result not found for job ${jobId}.${logHint(jobId)}`;
|
|
49388
49698
|
if (args.json) {
|
|
49389
49699
|
emitJson(status, null, message);
|
|
49390
49700
|
} else {
|
|
@@ -50250,13 +50560,482 @@ var init_feed2 = __esm(() => {
|
|
|
50250
50560
|
init_format_helpers();
|
|
50251
50561
|
});
|
|
50252
50562
|
|
|
50563
|
+
// src/cli/log.ts
|
|
50564
|
+
var exports_log = {};
|
|
50565
|
+
__export(exports_log, {
|
|
50566
|
+
run: () => run20
|
|
50567
|
+
});
|
|
50568
|
+
import { existsSync as existsSync28, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
|
|
50569
|
+
import { basename as basename9, join as join32 } from "path";
|
|
50570
|
+
function parseSince2(value) {
|
|
50571
|
+
if (value.includes("T") || value.includes("-"))
|
|
50572
|
+
return new Date(value).getTime();
|
|
50573
|
+
const match = value.match(/^(\d+)([smhd])$/);
|
|
50574
|
+
if (!match)
|
|
50575
|
+
return;
|
|
50576
|
+
const n = Number(match[1]);
|
|
50577
|
+
const unit = match[2];
|
|
50578
|
+
const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
50579
|
+
return Date.now() - n * ms[unit];
|
|
50580
|
+
}
|
|
50581
|
+
function parseArgs12(argv) {
|
|
50582
|
+
let jobId;
|
|
50583
|
+
let specialist;
|
|
50584
|
+
let beadId;
|
|
50585
|
+
let nodeId;
|
|
50586
|
+
let repo;
|
|
50587
|
+
let since;
|
|
50588
|
+
let limit = 200;
|
|
50589
|
+
let follow2 = false;
|
|
50590
|
+
let json = false;
|
|
50591
|
+
let allEvents = false;
|
|
50592
|
+
for (let i = 0;i < argv.length; i += 1) {
|
|
50593
|
+
const token = argv[i];
|
|
50594
|
+
if ((token === "--job" || token === "--job-id") && argv[i + 1]) {
|
|
50595
|
+
jobId = argv[++i];
|
|
50596
|
+
continue;
|
|
50597
|
+
}
|
|
50598
|
+
if (token === "--specialist" && argv[i + 1]) {
|
|
50599
|
+
specialist = argv[++i];
|
|
50600
|
+
continue;
|
|
50601
|
+
}
|
|
50602
|
+
if ((token === "--bead" || token === "--bead-id") && argv[i + 1]) {
|
|
50603
|
+
beadId = argv[++i];
|
|
50604
|
+
continue;
|
|
50605
|
+
}
|
|
50606
|
+
if (token === "--node" && argv[i + 1]) {
|
|
50607
|
+
nodeId = argv[++i];
|
|
50608
|
+
continue;
|
|
50609
|
+
}
|
|
50610
|
+
if (token === "--repo" && argv[i + 1]) {
|
|
50611
|
+
repo = argv[++i];
|
|
50612
|
+
continue;
|
|
50613
|
+
}
|
|
50614
|
+
if (token === "--since" && argv[i + 1]) {
|
|
50615
|
+
since = parseSince2(argv[++i]);
|
|
50616
|
+
continue;
|
|
50617
|
+
}
|
|
50618
|
+
if (token === "--limit" && argv[i + 1]) {
|
|
50619
|
+
limit = Math.max(1, Number(argv[++i]) || limit);
|
|
50620
|
+
continue;
|
|
50621
|
+
}
|
|
50622
|
+
if (token === "--follow" || token === "-f") {
|
|
50623
|
+
follow2 = true;
|
|
50624
|
+
continue;
|
|
50625
|
+
}
|
|
50626
|
+
if (token === "--json") {
|
|
50627
|
+
json = true;
|
|
50628
|
+
continue;
|
|
50629
|
+
}
|
|
50630
|
+
if (token === "--all-events" || token === "--verbose") {
|
|
50631
|
+
allEvents = true;
|
|
50632
|
+
continue;
|
|
50633
|
+
}
|
|
50634
|
+
if (!token.startsWith("-") && !jobId) {
|
|
50635
|
+
jobId = token;
|
|
50636
|
+
continue;
|
|
50637
|
+
}
|
|
50638
|
+
throw new Error(`Unknown option: ${token}`);
|
|
50639
|
+
}
|
|
50640
|
+
return { jobId, specialist, beadId, nodeId, repo, since, limit, follow: follow2, json, allEvents };
|
|
50641
|
+
}
|
|
50642
|
+
function discoverDbTargets(cwd, repoFilter) {
|
|
50643
|
+
const cwdLocation = resolveObservabilityDbLocation(cwd);
|
|
50644
|
+
if (existsSync28(cwdLocation.dbPath)) {
|
|
50645
|
+
const repo = basename9(cwdLocation.gitRoot);
|
|
50646
|
+
if (!repoFilter || repo === repoFilter) {
|
|
50647
|
+
return [{ repo, root: cwdLocation.gitRoot, dbPath: cwdLocation.dbPath, source: "cwd" }];
|
|
50648
|
+
}
|
|
50649
|
+
return [];
|
|
50650
|
+
}
|
|
50651
|
+
const targets = [];
|
|
50652
|
+
let entries = [];
|
|
50653
|
+
try {
|
|
50654
|
+
entries = readdirSync14(cwd);
|
|
50655
|
+
} catch {
|
|
50656
|
+
return [];
|
|
50657
|
+
}
|
|
50658
|
+
for (const entry of entries) {
|
|
50659
|
+
const root = join32(cwd, entry);
|
|
50660
|
+
try {
|
|
50661
|
+
if (!statSync7(root).isDirectory())
|
|
50662
|
+
continue;
|
|
50663
|
+
} catch {
|
|
50664
|
+
continue;
|
|
50665
|
+
}
|
|
50666
|
+
const dbPath = join32(root, ".specialists", "db", "observability.db");
|
|
50667
|
+
if (!existsSync28(dbPath))
|
|
50668
|
+
continue;
|
|
50669
|
+
if (repoFilter && entry !== repoFilter)
|
|
50670
|
+
continue;
|
|
50671
|
+
targets.push({ repo: entry, root, dbPath, source: "child" });
|
|
50672
|
+
}
|
|
50673
|
+
return targets.sort((a, b) => a.repo.localeCompare(b.repo));
|
|
50674
|
+
}
|
|
50675
|
+
function matches(status, options2) {
|
|
50676
|
+
if (options2.jobId && status.id !== options2.jobId)
|
|
50677
|
+
return false;
|
|
50678
|
+
if (options2.specialist && status.specialist !== options2.specialist)
|
|
50679
|
+
return false;
|
|
50680
|
+
if (options2.beadId && status.bead_id !== options2.beadId)
|
|
50681
|
+
return false;
|
|
50682
|
+
if (options2.nodeId && status.node_id !== options2.nodeId)
|
|
50683
|
+
return false;
|
|
50684
|
+
return true;
|
|
50685
|
+
}
|
|
50686
|
+
function isRuntimeEvent(event) {
|
|
50687
|
+
if (RUNTIME_EVENT_TYPES.has(event.type))
|
|
50688
|
+
return true;
|
|
50689
|
+
if (event.type === "meta") {
|
|
50690
|
+
return Boolean(event.model.startsWith("gitnexus_") || event.model.startsWith("bead_") || event.backend === "supervisor");
|
|
50691
|
+
}
|
|
50692
|
+
return false;
|
|
50693
|
+
}
|
|
50694
|
+
function toRows(target, statuses, options2, readEvents) {
|
|
50695
|
+
const rows = [];
|
|
50696
|
+
for (const status of statuses) {
|
|
50697
|
+
if (!matches(status, options2))
|
|
50698
|
+
continue;
|
|
50699
|
+
const rowPath = status.worktree_path ?? target.root;
|
|
50700
|
+
const events = readEvents(status.id).filter((event) => {
|
|
50701
|
+
if (options2.since !== undefined && event.t < options2.since)
|
|
50702
|
+
return false;
|
|
50703
|
+
if (options2.allEvents)
|
|
50704
|
+
return true;
|
|
50705
|
+
return isRuntimeEvent(event);
|
|
50706
|
+
});
|
|
50707
|
+
for (const event of events) {
|
|
50708
|
+
rows.push({
|
|
50709
|
+
jobId: status.id,
|
|
50710
|
+
specialist: status.specialist,
|
|
50711
|
+
beadId: status.bead_id,
|
|
50712
|
+
nodeId: status.node_id,
|
|
50713
|
+
repo: target.repo,
|
|
50714
|
+
path: rowPath,
|
|
50715
|
+
dbPath: target.dbPath,
|
|
50716
|
+
branch: status.branch,
|
|
50717
|
+
status: status.status,
|
|
50718
|
+
pid: status.pid,
|
|
50719
|
+
model: status.model,
|
|
50720
|
+
backend: status.backend,
|
|
50721
|
+
chainId: status.chain_id,
|
|
50722
|
+
chainRootJobId: status.chain_root_job_id,
|
|
50723
|
+
chainRootBeadId: status.chain_root_bead_id,
|
|
50724
|
+
event
|
|
50725
|
+
});
|
|
50726
|
+
}
|
|
50727
|
+
if (events.length === 0 && (options2.jobId || options2.beadId || options2.specialist || options2.nodeId)) {
|
|
50728
|
+
rows.push({
|
|
50729
|
+
jobId: status.id,
|
|
50730
|
+
specialist: status.specialist,
|
|
50731
|
+
beadId: status.bead_id,
|
|
50732
|
+
nodeId: status.node_id,
|
|
50733
|
+
repo: target.repo,
|
|
50734
|
+
path: rowPath,
|
|
50735
|
+
dbPath: target.dbPath,
|
|
50736
|
+
branch: status.branch,
|
|
50737
|
+
status: status.status,
|
|
50738
|
+
pid: status.pid,
|
|
50739
|
+
model: status.model,
|
|
50740
|
+
backend: status.backend,
|
|
50741
|
+
chainId: status.chain_id,
|
|
50742
|
+
chainRootJobId: status.chain_root_job_id,
|
|
50743
|
+
chainRootBeadId: status.chain_root_bead_id,
|
|
50744
|
+
event: { t: status.last_event_at_ms ?? status.started_at_ms, type: "status_snapshot" }
|
|
50745
|
+
});
|
|
50746
|
+
}
|
|
50747
|
+
}
|
|
50748
|
+
rows.sort((a, b) => a.event.t - b.event.t || a.jobId.localeCompare(b.jobId) || (a.event.seq ?? 0) - (b.event.seq ?? 0));
|
|
50749
|
+
const displayRows = options2.json ? rows : collapseDisplayDuplicates(rows);
|
|
50750
|
+
return displayRows.slice(Math.max(0, displayRows.length - options2.limit));
|
|
50751
|
+
}
|
|
50752
|
+
function normalizedEventForDisplay(event) {
|
|
50753
|
+
const normalized = { ...event };
|
|
50754
|
+
delete normalized.t;
|
|
50755
|
+
delete normalized.seq;
|
|
50756
|
+
if (event.type === "run_complete")
|
|
50757
|
+
delete normalized.elapsed_s;
|
|
50758
|
+
return normalized;
|
|
50759
|
+
}
|
|
50760
|
+
function displayDuplicateKey(row) {
|
|
50761
|
+
return JSON.stringify({
|
|
50762
|
+
jobId: row.jobId,
|
|
50763
|
+
event: normalizedEventForDisplay(row.event)
|
|
50764
|
+
});
|
|
50765
|
+
}
|
|
50766
|
+
function collapseDisplayDuplicates(rows) {
|
|
50767
|
+
const collapsed = [];
|
|
50768
|
+
const lastByKey = new Map;
|
|
50769
|
+
for (const row of rows) {
|
|
50770
|
+
const key = displayDuplicateKey(row);
|
|
50771
|
+
const previous = lastByKey.get(key);
|
|
50772
|
+
if (previous && row.event.t - previous.event.t <= DISPLAY_DUPLICATE_WINDOW_MS)
|
|
50773
|
+
continue;
|
|
50774
|
+
collapsed.push(row);
|
|
50775
|
+
lastByKey.set(key, row);
|
|
50776
|
+
}
|
|
50777
|
+
return collapsed;
|
|
50778
|
+
}
|
|
50779
|
+
function compact(value, max = 240) {
|
|
50780
|
+
const raw = typeof value === "string" ? value : JSON.stringify(value);
|
|
50781
|
+
const flat = (raw ?? "").replace(/\s+/g, " ").trim();
|
|
50782
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
50783
|
+
}
|
|
50784
|
+
function eventDetail(event) {
|
|
50785
|
+
if (event.type === "run_start")
|
|
50786
|
+
return "started";
|
|
50787
|
+
if (event.type === "control_signal") {
|
|
50788
|
+
return [
|
|
50789
|
+
`action=${event.action}`,
|
|
50790
|
+
`source=${event.source}`,
|
|
50791
|
+
event.previous_status || event.next_status ? `status=${event.previous_status ?? "?"}->${event.next_status ?? "?"}` : null,
|
|
50792
|
+
event.pid !== undefined ? `pid=${event.pid}` : null,
|
|
50793
|
+
event.signal ? `signal=${event.signal}` : null,
|
|
50794
|
+
event.force !== undefined ? `force=${event.force}` : null,
|
|
50795
|
+
event.reason ? `reason=${compact(event.reason, 160)}` : null,
|
|
50796
|
+
event.message_preview ? `message="${compact(event.message_preview, 180)}"` : null,
|
|
50797
|
+
event.task_preview ? `task="${compact(event.task_preview, 180)}"` : null,
|
|
50798
|
+
event.error_message ? `error=${compact(event.error_message, 180)}` : null
|
|
50799
|
+
].filter(Boolean).join(" ");
|
|
50800
|
+
}
|
|
50801
|
+
if (event.type === "status_change") {
|
|
50802
|
+
return `status=${event.previous_status ?? "?"}->${event.status}`;
|
|
50803
|
+
}
|
|
50804
|
+
if (event.type === "run_complete") {
|
|
50805
|
+
return [
|
|
50806
|
+
`status=${event.status}`,
|
|
50807
|
+
`elapsed=${formatElapsed(event.elapsed_s)}`,
|
|
50808
|
+
event.exit_reason ? `exit=${event.exit_reason}` : null,
|
|
50809
|
+
event.finish_reason ? `finish=${event.finish_reason}` : null,
|
|
50810
|
+
event.error ? `error=${compact(event.error, 500)}` : null,
|
|
50811
|
+
...formatTokenUsageSummary(event.token_usage ?? event.metrics?.token_usage),
|
|
50812
|
+
event.tool_calls ? `tools=${event.tool_calls.length}` : null
|
|
50813
|
+
].filter(Boolean).join(" ");
|
|
50814
|
+
}
|
|
50815
|
+
if (event.type === "tool") {
|
|
50816
|
+
const args = event.args ? ` args=${compact(event.args, 300)}` : "";
|
|
50817
|
+
const result = event.result_summary ? ` result=${compact(event.result_summary, 300)}` : "";
|
|
50818
|
+
return `tool=${event.tool} phase=${event.phase}${event.is_error ? " error=true" : ""}${args}${result}`;
|
|
50819
|
+
}
|
|
50820
|
+
if (event.type === "error")
|
|
50821
|
+
return `source=${event.source} error=${compact(event.error_message, 500)}`;
|
|
50822
|
+
if (event.type === "meta")
|
|
50823
|
+
return `model=${event.model} backend=${event.backend}${event.source ? ` source=${event.source}` : ""}`;
|
|
50824
|
+
if (event.type === "stale_warning")
|
|
50825
|
+
return `reason=${event.reason} silence_ms=${event.silence_ms} threshold_ms=${event.threshold_ms}${event.tool ? ` tool=${event.tool}` : ""}`;
|
|
50826
|
+
if (event.type === "token_usage")
|
|
50827
|
+
return `${formatTokenUsageSummary(event.token_usage).join(" ")} source=${event.source}`;
|
|
50828
|
+
if (event.type === "turn_summary")
|
|
50829
|
+
return `turn=${event.turn_index}${event.finish_reason ? ` finish=${event.finish_reason}` : ""}${event.context_pct !== undefined ? ` context=${event.context_pct.toFixed(2)}%` : ""}${event.text_content ? ` text="${compact(event.text_content, 160)}"` : ""}`;
|
|
50830
|
+
if (event.type === "auto_commit_success" || event.type === "auto_commit_skipped" || event.type === "auto_commit_failed")
|
|
50831
|
+
return `reason=${event.reason ?? ""}${event.commit_sha ? ` commit=${event.commit_sha}` : ""}${event.committed_files ? ` files=${event.committed_files.join(",")}` : ""}`.trim();
|
|
50832
|
+
if (event.type === "finish_reason")
|
|
50833
|
+
return `reason=${event.finish_reason} source=${event.source}`;
|
|
50834
|
+
if (event.type === "compaction" || event.type === "retry")
|
|
50835
|
+
return `phase=${event.phase}`;
|
|
50836
|
+
if (event.type === "message")
|
|
50837
|
+
return `phase=${event.phase} role=${event.role}`;
|
|
50838
|
+
if (event.type === "turn")
|
|
50839
|
+
return `phase=${event.phase}`;
|
|
50840
|
+
return compact(event, 500);
|
|
50841
|
+
}
|
|
50842
|
+
function eventColor(event) {
|
|
50843
|
+
switch (event.type) {
|
|
50844
|
+
case "run_complete":
|
|
50845
|
+
return event.status === "ERROR" ? red2 : event.status === "CANCELLED" ? yellow10 : green9;
|
|
50846
|
+
case "stale_warning":
|
|
50847
|
+
case "control_signal":
|
|
50848
|
+
case "auto_commit_skipped":
|
|
50849
|
+
return yellow10;
|
|
50850
|
+
case "error":
|
|
50851
|
+
case "extension_error":
|
|
50852
|
+
case "auto_commit_failed":
|
|
50853
|
+
return red2;
|
|
50854
|
+
case "auto_commit_success":
|
|
50855
|
+
return green9;
|
|
50856
|
+
default:
|
|
50857
|
+
return dim9;
|
|
50858
|
+
}
|
|
50859
|
+
}
|
|
50860
|
+
function eventLabel(event) {
|
|
50861
|
+
const rawType = event.type;
|
|
50862
|
+
if (rawType === "status_snapshot")
|
|
50863
|
+
return "SNAP";
|
|
50864
|
+
switch (event.type) {
|
|
50865
|
+
case "run_start":
|
|
50866
|
+
return "START";
|
|
50867
|
+
case "run_complete":
|
|
50868
|
+
return event.status;
|
|
50869
|
+
case "status_change":
|
|
50870
|
+
return "STATUS";
|
|
50871
|
+
case "control_signal":
|
|
50872
|
+
return "CTRL";
|
|
50873
|
+
case "stale_warning":
|
|
50874
|
+
return "WARN";
|
|
50875
|
+
case "auto_commit_success":
|
|
50876
|
+
return "AUTO+";
|
|
50877
|
+
case "auto_commit_skipped":
|
|
50878
|
+
return "AUTO-";
|
|
50879
|
+
case "auto_commit_failed":
|
|
50880
|
+
return "AUTO!";
|
|
50881
|
+
default:
|
|
50882
|
+
return rawType.toUpperCase().slice(0, 6);
|
|
50883
|
+
}
|
|
50884
|
+
}
|
|
50885
|
+
function formatWorktree(row) {
|
|
50886
|
+
const base = row.path ? basename9(row.path) : row.repo ?? "-";
|
|
50887
|
+
if (!row.repo || base === row.repo)
|
|
50888
|
+
return base;
|
|
50889
|
+
return `${row.repo}/${base}`;
|
|
50890
|
+
}
|
|
50891
|
+
function statusColor2(status) {
|
|
50892
|
+
switch (status) {
|
|
50893
|
+
case "done":
|
|
50894
|
+
return green9;
|
|
50895
|
+
case "error":
|
|
50896
|
+
return red2;
|
|
50897
|
+
case "cancelled":
|
|
50898
|
+
return yellow10;
|
|
50899
|
+
case "running":
|
|
50900
|
+
return cyan6;
|
|
50901
|
+
case "waiting":
|
|
50902
|
+
return yellow10;
|
|
50903
|
+
case "starting":
|
|
50904
|
+
return yellow10;
|
|
50905
|
+
default:
|
|
50906
|
+
return dim9;
|
|
50907
|
+
}
|
|
50908
|
+
}
|
|
50909
|
+
function printRow(row, json) {
|
|
50910
|
+
if (json) {
|
|
50911
|
+
console.log(JSON.stringify({
|
|
50912
|
+
timestamp: new Date(row.event.t).toISOString(),
|
|
50913
|
+
job_id: row.jobId,
|
|
50914
|
+
specialist: row.specialist,
|
|
50915
|
+
bead_id: row.beadId ?? null,
|
|
50916
|
+
node_id: row.nodeId ?? null,
|
|
50917
|
+
repo: row.repo ?? null,
|
|
50918
|
+
path: row.path ?? null,
|
|
50919
|
+
db_path: row.dbPath ?? null,
|
|
50920
|
+
branch: row.branch ?? null,
|
|
50921
|
+
worktree: formatWorktree(row),
|
|
50922
|
+
status: row.status ?? null,
|
|
50923
|
+
pid: row.pid ?? null,
|
|
50924
|
+
model: row.model ?? null,
|
|
50925
|
+
backend: row.backend ?? null,
|
|
50926
|
+
chain_id: row.chainId ?? null,
|
|
50927
|
+
chain_root_job_id: row.chainRootJobId ?? null,
|
|
50928
|
+
chain_root_bead_id: row.chainRootBeadId ?? null,
|
|
50929
|
+
event: row.event
|
|
50930
|
+
}));
|
|
50931
|
+
return;
|
|
50932
|
+
}
|
|
50933
|
+
const color = eventColor(row.event);
|
|
50934
|
+
const label = color(bold11(eventLabel(row.event).padEnd(6)));
|
|
50935
|
+
const status = row.status ?? "-";
|
|
50936
|
+
const statusSegment = statusColor2(row.status)(`status=${status}`);
|
|
50937
|
+
const head = [
|
|
50938
|
+
dim9(formatDateTime(row.event.t)),
|
|
50939
|
+
label,
|
|
50940
|
+
row.jobId,
|
|
50941
|
+
bold11(row.specialist),
|
|
50942
|
+
dim9(`bead=${row.beadId ?? "-"}`),
|
|
50943
|
+
row.nodeId ? dim9(`node=${row.nodeId}`) : null,
|
|
50944
|
+
dim9(`worktree=${formatWorktree(row)}`),
|
|
50945
|
+
statusSegment,
|
|
50946
|
+
row.pid !== undefined ? dim9(`pid=${row.pid}`) : null
|
|
50947
|
+
].filter(Boolean).join(" ");
|
|
50948
|
+
console.log(`${head} ${eventDetail(row.event)}`.trim());
|
|
50949
|
+
}
|
|
50950
|
+
async function run20(argv = process.argv.slice(3)) {
|
|
50951
|
+
let options2;
|
|
50952
|
+
try {
|
|
50953
|
+
options2 = parseArgs12(argv);
|
|
50954
|
+
} catch (error2) {
|
|
50955
|
+
console.error(error2 instanceof Error ? error2.message : String(error2));
|
|
50956
|
+
console.error("Usage: specialists|sp log [job-id] [--specialist <name>] [--bead <id>] [--node <id>] [--since <5m|iso>] [--limit <n>] [-f|--follow] [--json] [--all-events]");
|
|
50957
|
+
process.exit(1);
|
|
50958
|
+
}
|
|
50959
|
+
const targets = discoverDbTargets(process.cwd(), options2.repo);
|
|
50960
|
+
if (targets.length === 0) {
|
|
50961
|
+
const suffix = options2.repo ? ` for repo '${options2.repo}'` : "";
|
|
50962
|
+
console.error(`No specialists observability DB found${suffix}. Run from a repo root or a parent containing repos with .specialists/db/observability.db.`);
|
|
50963
|
+
process.exit(1);
|
|
50964
|
+
}
|
|
50965
|
+
const clients = targets.map((target) => ({ target, client: createObservabilitySqliteClientAtPath(target.dbPath) }));
|
|
50966
|
+
if (clients.every((entry) => !entry.client)) {
|
|
50967
|
+
console.error("Observability SQLite DB is unavailable. Run: specialists db setup");
|
|
50968
|
+
process.exit(1);
|
|
50969
|
+
}
|
|
50970
|
+
const printed = new Set;
|
|
50971
|
+
const printSnapshot2 = () => {
|
|
50972
|
+
const rows = clients.flatMap(({ target, client }) => {
|
|
50973
|
+
if (!client)
|
|
50974
|
+
return [];
|
|
50975
|
+
return toRows(target, client.listStatuses(), options2, (jobId) => client.readEvents(jobId));
|
|
50976
|
+
});
|
|
50977
|
+
rows.sort((a, b) => a.event.t - b.event.t || (a.repo ?? "").localeCompare(b.repo ?? "") || a.jobId.localeCompare(b.jobId) || (a.event.seq ?? 0) - (b.event.seq ?? 0));
|
|
50978
|
+
const limitedRows = rows.slice(Math.max(0, rows.length - options2.limit));
|
|
50979
|
+
for (const row of limitedRows) {
|
|
50980
|
+
const key = `${row.repo ?? ""}:${row.jobId}:${row.event.seq ?? row.event.t}:${row.event.type}`;
|
|
50981
|
+
if (printed.has(key))
|
|
50982
|
+
continue;
|
|
50983
|
+
printed.add(key);
|
|
50984
|
+
printRow(row, options2.json);
|
|
50985
|
+
}
|
|
50986
|
+
if (limitedRows.length === 0 && !options2.json && printed.size === 0)
|
|
50987
|
+
console.error("No matching specialist log rows.");
|
|
50988
|
+
};
|
|
50989
|
+
try {
|
|
50990
|
+
printSnapshot2();
|
|
50991
|
+
if (!options2.follow)
|
|
50992
|
+
return;
|
|
50993
|
+
if (!options2.json)
|
|
50994
|
+
console.error(`Following specialist runtime logs across ${targets.length} repo${targets.length === 1 ? "" : "s"}... (Ctrl+C to stop)`);
|
|
50995
|
+
await new Promise((resolve11) => {
|
|
50996
|
+
const interval = setInterval(printSnapshot2, 750);
|
|
50997
|
+
const stop = () => {
|
|
50998
|
+
clearInterval(interval);
|
|
50999
|
+
resolve11();
|
|
51000
|
+
};
|
|
51001
|
+
process.once("SIGINT", stop);
|
|
51002
|
+
process.once("SIGTERM", stop);
|
|
51003
|
+
});
|
|
51004
|
+
} finally {
|
|
51005
|
+
for (const { client } of clients)
|
|
51006
|
+
client?.close();
|
|
51007
|
+
}
|
|
51008
|
+
}
|
|
51009
|
+
var DISPLAY_DUPLICATE_WINDOW_MS = 2000, RUNTIME_EVENT_TYPES;
|
|
51010
|
+
var init_log = __esm(() => {
|
|
51011
|
+
init_observability_sqlite();
|
|
51012
|
+
init_observability_db();
|
|
51013
|
+
init_format_helpers();
|
|
51014
|
+
RUNTIME_EVENT_TYPES = new Set([
|
|
51015
|
+
"run_start",
|
|
51016
|
+
"run_complete",
|
|
51017
|
+
"status_change",
|
|
51018
|
+
"control_signal",
|
|
51019
|
+
"stale_warning",
|
|
51020
|
+
"error",
|
|
51021
|
+
"extension_error",
|
|
51022
|
+
"model_change",
|
|
51023
|
+
"retry",
|
|
51024
|
+
"compaction",
|
|
51025
|
+
"auto_commit_success",
|
|
51026
|
+
"auto_commit_skipped",
|
|
51027
|
+
"auto_commit_failed",
|
|
51028
|
+
"status_snapshot"
|
|
51029
|
+
]);
|
|
51030
|
+
});
|
|
51031
|
+
|
|
50253
51032
|
// src/cli/steer.ts
|
|
50254
51033
|
var exports_steer = {};
|
|
50255
51034
|
__export(exports_steer, {
|
|
50256
|
-
run: () =>
|
|
51035
|
+
run: () => run21
|
|
50257
51036
|
});
|
|
50258
51037
|
import { writeFileSync as writeFileSync14 } from "fs";
|
|
50259
|
-
async function
|
|
51038
|
+
async function run21() {
|
|
50260
51039
|
const jobId = process.argv[3];
|
|
50261
51040
|
const message = process.argv[4];
|
|
50262
51041
|
if (!jobId || !message) {
|
|
@@ -50287,6 +51066,12 @@ async function run20() {
|
|
|
50287
51066
|
const payload = JSON.stringify({ type: "steer", message }) + `
|
|
50288
51067
|
`;
|
|
50289
51068
|
writeFileSync14(status.fifo_path, payload, { flag: "a" });
|
|
51069
|
+
supervisor.emitControlEvent(jobId, "steer_sent", {
|
|
51070
|
+
source: "cli",
|
|
51071
|
+
previous_status: status.status,
|
|
51072
|
+
fifo_path: status.fifo_path,
|
|
51073
|
+
message_preview: message.replace(/\s+/g, " ").slice(0, 240)
|
|
51074
|
+
});
|
|
50290
51075
|
process.stdout.write(`${green11("\u2713")} Steer message sent to job ${jobId}
|
|
50291
51076
|
`);
|
|
50292
51077
|
} catch (err) {
|
|
@@ -50307,10 +51092,10 @@ var init_steer = __esm(() => {
|
|
|
50307
51092
|
// src/cli/resume.ts
|
|
50308
51093
|
var exports_resume = {};
|
|
50309
51094
|
__export(exports_resume, {
|
|
50310
|
-
run: () =>
|
|
51095
|
+
run: () => run22
|
|
50311
51096
|
});
|
|
50312
51097
|
import { writeFileSync as writeFileSync15 } from "fs";
|
|
50313
|
-
async function
|
|
51098
|
+
async function run22() {
|
|
50314
51099
|
const jobId = process.argv[3];
|
|
50315
51100
|
const task = process.argv[4];
|
|
50316
51101
|
if (!jobId || !task) {
|
|
@@ -50337,19 +51122,31 @@ async function run21() {
|
|
|
50337
51122
|
`);
|
|
50338
51123
|
process.exit(1);
|
|
50339
51124
|
}
|
|
50340
|
-
|
|
50341
|
-
const payload = JSON.stringify({ type: "resume", task }) + `
|
|
51125
|
+
const payload = JSON.stringify({ type: "resume", task }) + `
|
|
50342
51126
|
`;
|
|
51127
|
+
try {
|
|
50343
51128
|
writeFileSync15(status.fifo_path, payload, { flag: "a" });
|
|
50344
|
-
process.stdout.write(`${green12("\u2713")} Resume sent to job ${jobId}
|
|
50345
|
-
`);
|
|
50346
|
-
process.stdout.write(` Use 'specialists feed ${jobId} --follow' to watch the response.
|
|
50347
|
-
`);
|
|
50348
51129
|
} catch (err) {
|
|
50349
51130
|
process.stderr.write(`${red6("Error:")} Failed to write to steer pipe: ${err?.message}
|
|
50350
51131
|
`);
|
|
50351
51132
|
process.exit(1);
|
|
50352
51133
|
}
|
|
51134
|
+
try {
|
|
51135
|
+
supervisor.emitControlEvent(jobId, "resume_sent", {
|
|
51136
|
+
source: "cli",
|
|
51137
|
+
previous_status: status.status,
|
|
51138
|
+
next_status: "running",
|
|
51139
|
+
fifo_path: status.fifo_path,
|
|
51140
|
+
task_preview: task.replace(/\s+/g, " ").slice(0, 240)
|
|
51141
|
+
});
|
|
51142
|
+
} catch (err) {
|
|
51143
|
+
process.stderr.write(`Warning: resume delivered, but telemetry could not be recorded: ${err?.message ?? String(err)}
|
|
51144
|
+
`);
|
|
51145
|
+
}
|
|
51146
|
+
process.stdout.write(`${green12("\u2713")} Resume sent to job ${jobId}
|
|
51147
|
+
`);
|
|
51148
|
+
process.stdout.write(` Use 'specialists feed ${jobId} --follow' to watch the response.
|
|
51149
|
+
`);
|
|
50353
51150
|
} finally {
|
|
50354
51151
|
await supervisor.dispose();
|
|
50355
51152
|
}
|
|
@@ -50363,21 +51160,21 @@ var init_resume = __esm(() => {
|
|
|
50363
51160
|
// src/cli/follow-up.ts
|
|
50364
51161
|
var exports_follow_up = {};
|
|
50365
51162
|
__export(exports_follow_up, {
|
|
50366
|
-
run: () =>
|
|
51163
|
+
run: () => run23
|
|
50367
51164
|
});
|
|
50368
|
-
async function
|
|
51165
|
+
async function run23() {
|
|
50369
51166
|
process.stderr.write("\x1B[33m\u26A0 DEPRECATED:\x1B[0m `specialists follow-up` is deprecated. Use `specialists resume` instead.\n\n");
|
|
50370
51167
|
const { run: resumeRun } = await Promise.resolve().then(() => (init_resume(), exports_resume));
|
|
50371
51168
|
return resumeRun();
|
|
50372
51169
|
}
|
|
50373
51170
|
|
|
50374
51171
|
// src/specialist/worktree-gc.ts
|
|
50375
|
-
import { existsSync as
|
|
50376
|
-
import { join as
|
|
51172
|
+
import { existsSync as existsSync29, readdirSync as readdirSync15, readFileSync as readFileSync28 } from "fs";
|
|
51173
|
+
import { join as join33 } from "path";
|
|
50377
51174
|
import { spawnSync as spawnSync19 } from "child_process";
|
|
50378
51175
|
function readJobStatus2(jobDir) {
|
|
50379
|
-
const statusPath =
|
|
50380
|
-
if (!
|
|
51176
|
+
const statusPath = join33(jobDir, "status.json");
|
|
51177
|
+
if (!existsSync29(statusPath))
|
|
50381
51178
|
return null;
|
|
50382
51179
|
try {
|
|
50383
51180
|
return JSON.parse(readFileSync28(statusPath, "utf-8"));
|
|
@@ -50402,7 +51199,7 @@ function collectWorktreeGcCandidates(jobsDir) {
|
|
|
50402
51199
|
const worktreePath = status.worktree_path;
|
|
50403
51200
|
if (!worktreePath)
|
|
50404
51201
|
return null;
|
|
50405
|
-
if (!
|
|
51202
|
+
if (!existsSync29(worktreePath))
|
|
50406
51203
|
return null;
|
|
50407
51204
|
return {
|
|
50408
51205
|
jobId: status.id,
|
|
@@ -50414,13 +51211,13 @@ function collectWorktreeGcCandidates(jobsDir) {
|
|
|
50414
51211
|
}
|
|
50415
51212
|
if (!getFileFallbackEnabled())
|
|
50416
51213
|
return [];
|
|
50417
|
-
if (!
|
|
51214
|
+
if (!existsSync29(jobsDir))
|
|
50418
51215
|
return [];
|
|
50419
51216
|
const candidates = [];
|
|
50420
|
-
for (const entry of
|
|
51217
|
+
for (const entry of readdirSync15(jobsDir, { withFileTypes: true })) {
|
|
50421
51218
|
if (!entry.isDirectory())
|
|
50422
51219
|
continue;
|
|
50423
|
-
const status = readJobStatus2(
|
|
51220
|
+
const status = readJobStatus2(join33(jobsDir, entry.name));
|
|
50424
51221
|
if (!status)
|
|
50425
51222
|
continue;
|
|
50426
51223
|
if (isActive(status.status))
|
|
@@ -50430,7 +51227,7 @@ function collectWorktreeGcCandidates(jobsDir) {
|
|
|
50430
51227
|
const { worktree_path: worktreePath, branch } = status;
|
|
50431
51228
|
if (!worktreePath)
|
|
50432
51229
|
continue;
|
|
50433
|
-
if (!
|
|
51230
|
+
if (!existsSync29(worktreePath))
|
|
50434
51231
|
continue;
|
|
50435
51232
|
candidates.push({
|
|
50436
51233
|
jobId: status.id,
|
|
@@ -50474,10 +51271,10 @@ var init_worktree_gc = __esm(() => {
|
|
|
50474
51271
|
// src/cli/clean.ts
|
|
50475
51272
|
var exports_clean = {};
|
|
50476
51273
|
__export(exports_clean, {
|
|
50477
|
-
run: () =>
|
|
51274
|
+
run: () => run24
|
|
50478
51275
|
});
|
|
50479
|
-
import { existsSync as
|
|
50480
|
-
import { join as
|
|
51276
|
+
import { existsSync as existsSync30, readFileSync as readFileSync29, readdirSync as readdirSync16, rmSync as rmSync4, statSync as statSync8 } from "fs";
|
|
51277
|
+
import { join as join34 } from "path";
|
|
50481
51278
|
function parseDuration2(raw) {
|
|
50482
51279
|
const match = /^(\d+)(ms|s|m|h|d)$/i.exec(raw.trim());
|
|
50483
51280
|
if (!match)
|
|
@@ -50631,16 +51428,16 @@ function parseOptions2(argv) {
|
|
|
50631
51428
|
}
|
|
50632
51429
|
function readDirectorySizeBytes(directoryPath) {
|
|
50633
51430
|
let totalBytes = 0;
|
|
50634
|
-
for (const entry of
|
|
50635
|
-
const entryPath =
|
|
50636
|
-
const stats =
|
|
51431
|
+
for (const entry of readdirSync16(directoryPath, { withFileTypes: true })) {
|
|
51432
|
+
const entryPath = join34(directoryPath, entry.name);
|
|
51433
|
+
const stats = statSync8(entryPath);
|
|
50637
51434
|
totalBytes += stats.isDirectory() ? readDirectorySizeBytes(entryPath) : stats.size;
|
|
50638
51435
|
}
|
|
50639
51436
|
return totalBytes;
|
|
50640
51437
|
}
|
|
50641
51438
|
function containsProtectedSqliteArtifact(directoryPath) {
|
|
50642
|
-
for (const entry of
|
|
50643
|
-
const entryPath =
|
|
51439
|
+
for (const entry of readdirSync16(directoryPath, { withFileTypes: true })) {
|
|
51440
|
+
const entryPath = join34(directoryPath, entry.name);
|
|
50644
51441
|
if (entry.isDirectory()) {
|
|
50645
51442
|
if (containsProtectedSqliteArtifact(entryPath))
|
|
50646
51443
|
return true;
|
|
@@ -50661,11 +51458,11 @@ function getJobTimestamps(status) {
|
|
|
50661
51458
|
function readCompletedJobDirectory(baseDirectory, entry) {
|
|
50662
51459
|
if (!entry.isDirectory())
|
|
50663
51460
|
return null;
|
|
50664
|
-
const directoryPath =
|
|
51461
|
+
const directoryPath = join34(baseDirectory, entry.name);
|
|
50665
51462
|
if (containsProtectedSqliteArtifact(directoryPath))
|
|
50666
51463
|
return null;
|
|
50667
|
-
const statusFilePath =
|
|
50668
|
-
if (!
|
|
51464
|
+
const statusFilePath = join34(directoryPath, "status.json");
|
|
51465
|
+
if (!existsSync30(statusFilePath))
|
|
50669
51466
|
return null;
|
|
50670
51467
|
let statusData;
|
|
50671
51468
|
try {
|
|
@@ -50683,8 +51480,8 @@ function collectCompletedJobs(jobsDirectoryPath) {
|
|
|
50683
51480
|
const statuses = sqliteClient?.listStatuses() ?? [];
|
|
50684
51481
|
if (statuses.length > 0) {
|
|
50685
51482
|
return statuses.filter((status) => COMPLETED_STATUSES.has(status.status)).map((status) => {
|
|
50686
|
-
const directoryPath =
|
|
50687
|
-
if (!
|
|
51483
|
+
const directoryPath = join34(jobsDirectoryPath, status.id);
|
|
51484
|
+
if (!existsSync30(directoryPath) || containsProtectedSqliteArtifact(directoryPath))
|
|
50688
51485
|
return null;
|
|
50689
51486
|
const { createdAtMs, completedAtMs } = getJobTimestamps(status);
|
|
50690
51487
|
return { id: status.id, directoryPath, completedAtMs, createdAtMs, sizeBytes: readDirectorySizeBytes(directoryPath) };
|
|
@@ -50692,7 +51489,7 @@ function collectCompletedJobs(jobsDirectoryPath) {
|
|
|
50692
51489
|
}
|
|
50693
51490
|
if (process.env.SPECIALISTS_JOB_FILE_OUTPUT !== "on")
|
|
50694
51491
|
return [];
|
|
50695
|
-
return
|
|
51492
|
+
return readdirSync16(jobsDirectoryPath, { withFileTypes: true }).map((entry) => readCompletedJobDirectory(jobsDirectoryPath, entry)).filter((job) => job !== null);
|
|
50696
51493
|
}
|
|
50697
51494
|
function selectJobsToRemove(completedJobs, options2, protectedJobIds) {
|
|
50698
51495
|
const jobsByNewest = [...completedJobs].sort((left, right) => {
|
|
@@ -50962,7 +51759,7 @@ function removeStaleProcesses(statuses, dryRun) {
|
|
|
50962
51759
|
}
|
|
50963
51760
|
return updatedCount;
|
|
50964
51761
|
}
|
|
50965
|
-
async function
|
|
51762
|
+
async function run24() {
|
|
50966
51763
|
let options2;
|
|
50967
51764
|
try {
|
|
50968
51765
|
options2 = parseOptions2(process.argv.slice(3));
|
|
@@ -51014,7 +51811,7 @@ async function run23() {
|
|
|
51014
51811
|
console.log(`Hid ${hiddenCount} terminal row(s) from default ps.`);
|
|
51015
51812
|
return;
|
|
51016
51813
|
}
|
|
51017
|
-
if (!
|
|
51814
|
+
if (!existsSync30(jobsDirectoryPath)) {
|
|
51018
51815
|
console.log("No jobs directory found.");
|
|
51019
51816
|
return;
|
|
51020
51817
|
}
|
|
@@ -51067,7 +51864,7 @@ var init_clean = __esm(() => {
|
|
|
51067
51864
|
// src/cli/end.ts
|
|
51068
51865
|
var exports_end = {};
|
|
51069
51866
|
__export(exports_end, {
|
|
51070
|
-
run: () =>
|
|
51867
|
+
run: () => run25
|
|
51071
51868
|
});
|
|
51072
51869
|
import { spawnSync as spawnSync20 } from "child_process";
|
|
51073
51870
|
function parseOptions3(argv) {
|
|
@@ -51151,7 +51948,7 @@ async function publishChain(beadId, options2) {
|
|
|
51151
51948
|
console.log("Publication mode: direct merge");
|
|
51152
51949
|
}
|
|
51153
51950
|
}
|
|
51154
|
-
async function
|
|
51951
|
+
async function run25() {
|
|
51155
51952
|
let options2;
|
|
51156
51953
|
try {
|
|
51157
51954
|
options2 = parseOptions3(process.argv.slice(3));
|
|
@@ -51192,7 +51989,7 @@ var init_end = __esm(() => {
|
|
|
51192
51989
|
// src/cli/stop.ts
|
|
51193
51990
|
var exports_stop = {};
|
|
51194
51991
|
__export(exports_stop, {
|
|
51195
|
-
run: () =>
|
|
51992
|
+
run: () => run26
|
|
51196
51993
|
});
|
|
51197
51994
|
function parseStopArgs(argv) {
|
|
51198
51995
|
let jobId;
|
|
@@ -51215,7 +52012,7 @@ function parseStopArgs(argv) {
|
|
|
51215
52012
|
}
|
|
51216
52013
|
return { jobId, force, closeBeadAnyway };
|
|
51217
52014
|
}
|
|
51218
|
-
async function
|
|
52015
|
+
async function run26() {
|
|
51219
52016
|
let parsed;
|
|
51220
52017
|
try {
|
|
51221
52018
|
parsed = parseStopArgs(process.argv.slice(3));
|
|
@@ -51243,13 +52040,13 @@ var init_stop = __esm(() => {
|
|
|
51243
52040
|
// src/cli/finalize.ts
|
|
51244
52041
|
var exports_finalize = {};
|
|
51245
52042
|
__export(exports_finalize, {
|
|
51246
|
-
run: () =>
|
|
52043
|
+
run: () => run27
|
|
51247
52044
|
});
|
|
51248
52045
|
function parseFinalizeArgs(argv) {
|
|
51249
52046
|
const jobId = argv.find((token) => !token.startsWith("-"));
|
|
51250
52047
|
return { jobId };
|
|
51251
52048
|
}
|
|
51252
|
-
async function
|
|
52049
|
+
async function run27() {
|
|
51253
52050
|
const parsed = parseFinalizeArgs(process.argv.slice(3));
|
|
51254
52051
|
const jobId = parsed.jobId;
|
|
51255
52052
|
if (!jobId) {
|
|
@@ -51271,9 +52068,9 @@ var init_finalize = __esm(() => {
|
|
|
51271
52068
|
// src/cli/attach-tui.ts
|
|
51272
52069
|
var exports_attach_tui = {};
|
|
51273
52070
|
__export(exports_attach_tui, {
|
|
51274
|
-
run: () =>
|
|
52071
|
+
run: () => run28
|
|
51275
52072
|
});
|
|
51276
|
-
async function
|
|
52073
|
+
async function run28(target, deps = {}) {
|
|
51277
52074
|
const piTui = await Promise.resolve().then(() => (init_dist2(), exports_dist));
|
|
51278
52075
|
const { TUI: TUI2, ProcessTerminal: ProcessTerminal2, Container: Container2, Input: Input2, matchesKey: matchesKey2, Key: Key2 } = piTui;
|
|
51279
52076
|
const terminal = new ProcessTerminal2;
|
|
@@ -51393,7 +52190,7 @@ var init_attach_tui = __esm(() => {
|
|
|
51393
52190
|
// src/cli/attach.ts
|
|
51394
52191
|
var exports_attach = {};
|
|
51395
52192
|
__export(exports_attach, {
|
|
51396
|
-
run: () =>
|
|
52193
|
+
run: () => run29
|
|
51397
52194
|
});
|
|
51398
52195
|
import readline3 from "readline";
|
|
51399
52196
|
function exitWithError(message) {
|
|
@@ -51510,7 +52307,7 @@ function pickTarget(targets) {
|
|
|
51510
52307
|
render2();
|
|
51511
52308
|
});
|
|
51512
52309
|
}
|
|
51513
|
-
async function
|
|
52310
|
+
async function run29(deps = {}) {
|
|
51514
52311
|
const [jobId] = process.argv.slice(3);
|
|
51515
52312
|
if (!jobId) {
|
|
51516
52313
|
if (!process.stdout.isTTY || !process.stdin.isTTY)
|
|
@@ -51526,8 +52323,8 @@ async function run28(deps = {}) {
|
|
|
51526
52323
|
}
|
|
51527
52324
|
async function attachTarget(target, deps) {
|
|
51528
52325
|
const runTui = deps.runTui ?? (async (resolvedTarget) => {
|
|
51529
|
-
const { run:
|
|
51530
|
-
return
|
|
52326
|
+
const { run: run30 } = await Promise.resolve().then(() => (init_attach_tui(), exports_attach_tui));
|
|
52327
|
+
return run30(resolvedTarget, deps);
|
|
51531
52328
|
});
|
|
51532
52329
|
return runTui(target);
|
|
51533
52330
|
}
|
|
@@ -51536,15 +52333,15 @@ var init_attach = __esm(() => {
|
|
|
51536
52333
|
});
|
|
51537
52334
|
|
|
51538
52335
|
// src/specialist/drift-detector.ts
|
|
51539
|
-
import { existsSync as
|
|
51540
|
-
import { join as
|
|
52336
|
+
import { existsSync as existsSync31, readFileSync as readFileSync30, readdirSync as readdirSync17, rmSync as rmSync5 } from "fs";
|
|
52337
|
+
import { join as join35, resolve as resolve11, relative as relative3 } from "path";
|
|
51541
52338
|
function listFiles(root) {
|
|
51542
|
-
if (!
|
|
52339
|
+
if (!existsSync31(root))
|
|
51543
52340
|
return [];
|
|
51544
52341
|
const out = [];
|
|
51545
52342
|
const visit2 = (dir) => {
|
|
51546
|
-
for (const entry of
|
|
51547
|
-
const full =
|
|
52343
|
+
for (const entry of readdirSync17(dir, { withFileTypes: true })) {
|
|
52344
|
+
const full = join35(dir, entry.name);
|
|
51548
52345
|
if (entry.isDirectory()) {
|
|
51549
52346
|
visit2(full);
|
|
51550
52347
|
continue;
|
|
@@ -51579,12 +52376,12 @@ function detectDriftForRepo(repoRoot) {
|
|
|
51579
52376
|
{ scope: "user", dir: resolve11(repoRoot, ".specialists/user") }
|
|
51580
52377
|
];
|
|
51581
52378
|
for (const { scope, dir } of scopes) {
|
|
51582
|
-
if (!
|
|
52379
|
+
if (!existsSync31(dir))
|
|
51583
52380
|
continue;
|
|
51584
52381
|
for (const file of listFiles(dir)) {
|
|
51585
52382
|
const rel = relPath(file, dir);
|
|
51586
|
-
const canonicalPath =
|
|
51587
|
-
if (!
|
|
52383
|
+
const canonicalPath = join35(asset.canonicalDir, rel);
|
|
52384
|
+
if (!existsSync31(canonicalPath))
|
|
51588
52385
|
continue;
|
|
51589
52386
|
const bytesEqual = readFileSync30(file).equals(readFileSync30(canonicalPath));
|
|
51590
52387
|
findings.push(makeFinding(repoRoot, asset.kind, scope, file, canonicalPath, bytesEqual));
|
|
@@ -51605,12 +52402,12 @@ function detectDriftUnderRoot(root) {
|
|
|
51605
52402
|
repos.push({ root: dir, findings });
|
|
51606
52403
|
return;
|
|
51607
52404
|
}
|
|
51608
|
-
for (const entry of
|
|
52405
|
+
for (const entry of readdirSync17(dir, { withFileTypes: true })) {
|
|
51609
52406
|
if (!entry.isDirectory())
|
|
51610
52407
|
continue;
|
|
51611
52408
|
if (entry.name === "node_modules" || entry.name === ".git")
|
|
51612
52409
|
continue;
|
|
51613
|
-
visit2(
|
|
52410
|
+
visit2(join35(dir, entry.name));
|
|
51614
52411
|
}
|
|
51615
52412
|
};
|
|
51616
52413
|
visit2(resolve11(root));
|
|
@@ -51650,10 +52447,10 @@ var init_drift_detector = __esm(() => {
|
|
|
51650
52447
|
// src/cli/prune-stale-defaults.ts
|
|
51651
52448
|
var exports_prune_stale_defaults = {};
|
|
51652
52449
|
__export(exports_prune_stale_defaults, {
|
|
51653
|
-
run: () =>
|
|
52450
|
+
run: () => run30
|
|
51654
52451
|
});
|
|
51655
52452
|
import { resolve as resolve12 } from "path";
|
|
51656
|
-
function
|
|
52453
|
+
function parseArgs13(argv) {
|
|
51657
52454
|
let dryRun = false;
|
|
51658
52455
|
let root = process.cwd();
|
|
51659
52456
|
let help = false;
|
|
@@ -51690,8 +52487,8 @@ function printHelp() {
|
|
|
51690
52487
|
console.log(" --keep-diverged Preserve diverged .specialists/default entries");
|
|
51691
52488
|
console.log(" --root Repo root to scan");
|
|
51692
52489
|
}
|
|
51693
|
-
async function
|
|
51694
|
-
const { dryRun, root, help, keepDiverged } =
|
|
52490
|
+
async function run30(argv = process.argv.slice(3)) {
|
|
52491
|
+
const { dryRun, root, help, keepDiverged } = parseArgs13(argv);
|
|
51695
52492
|
if (help) {
|
|
51696
52493
|
printHelp();
|
|
51697
52494
|
return;
|
|
@@ -51722,7 +52519,7 @@ var init_prune_stale_defaults = __esm(() => {
|
|
|
51722
52519
|
// src/cli/quickstart.ts
|
|
51723
52520
|
var exports_quickstart = {};
|
|
51724
52521
|
__export(exports_quickstart, {
|
|
51725
|
-
run: () =>
|
|
52522
|
+
run: () => run31
|
|
51726
52523
|
});
|
|
51727
52524
|
function section2(title) {
|
|
51728
52525
|
const bar = "\u2500".repeat(60);
|
|
@@ -51736,7 +52533,7 @@ function cmd2(s) {
|
|
|
51736
52533
|
function flag(s) {
|
|
51737
52534
|
return green13(s);
|
|
51738
52535
|
}
|
|
51739
|
-
async function
|
|
52536
|
+
async function run31() {
|
|
51740
52537
|
const lines = [
|
|
51741
52538
|
"",
|
|
51742
52539
|
bold12("specialists \xB7 Quick Start Guide"),
|
|
@@ -51958,7 +52755,7 @@ var bold12 = (s) => `\x1B[1m${s}\x1B[0m`, dim13 = (s) => `\x1B[2m${s}\x1B[0m`, y
|
|
|
51958
52755
|
var exports_doctor = {};
|
|
51959
52756
|
__export(exports_doctor, {
|
|
51960
52757
|
setStatusError: () => setStatusError,
|
|
51961
|
-
run: () =>
|
|
52758
|
+
run: () => run32,
|
|
51962
52759
|
resolvePackageAssetDir: () => resolvePackageAssetDir,
|
|
51963
52760
|
renderProcessSummary: () => renderProcessSummary,
|
|
51964
52761
|
parseVersionTuple: () => parseVersionTuple,
|
|
@@ -51967,8 +52764,8 @@ __export(exports_doctor, {
|
|
|
51967
52764
|
});
|
|
51968
52765
|
import { createHash as createHash5 } from "crypto";
|
|
51969
52766
|
import { spawnSync as spawnSync21 } from "child_process";
|
|
51970
|
-
import { existsSync as
|
|
51971
|
-
import { dirname as
|
|
52767
|
+
import { existsSync as existsSync32, lstatSync as lstatSync2, mkdirSync as mkdirSync12, readdirSync as readdirSync18, readFileSync as readFileSync31, readlinkSync as readlinkSync3, writeFileSync as writeFileSync16 } from "fs";
|
|
52768
|
+
import { dirname as dirname13, join as join36, relative as relative4, resolve as resolve13 } from "path";
|
|
51972
52769
|
function ok3(msg) {
|
|
51973
52770
|
console.log(` ${green14("\u2713")} ${msg}`);
|
|
51974
52771
|
}
|
|
@@ -51997,7 +52794,7 @@ function isInstalled3(bin) {
|
|
|
51997
52794
|
return spawnSync21("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
51998
52795
|
}
|
|
51999
52796
|
function loadJson2(path3) {
|
|
52000
|
-
if (!
|
|
52797
|
+
if (!existsSync32(path3))
|
|
52001
52798
|
return null;
|
|
52002
52799
|
try {
|
|
52003
52800
|
return JSON.parse(readFileSync31(path3, "utf8"));
|
|
@@ -52043,7 +52840,7 @@ function checkBd() {
|
|
|
52043
52840
|
return false;
|
|
52044
52841
|
}
|
|
52045
52842
|
ok3(`bd installed ${dim14(sp("bd", ["--version"]).stdout || "")}`);
|
|
52046
|
-
if (
|
|
52843
|
+
if (existsSync32(join36(CWD, ".beads")))
|
|
52047
52844
|
ok3(".beads/ present in project");
|
|
52048
52845
|
else
|
|
52049
52846
|
warn3(".beads/ not found in project");
|
|
@@ -52063,18 +52860,18 @@ function checkHooks() {
|
|
|
52063
52860
|
section3("Claude Code hooks (2 expected)");
|
|
52064
52861
|
let allPresent = true;
|
|
52065
52862
|
for (const name of HOOK_NAMES) {
|
|
52066
|
-
const canonicalPath =
|
|
52067
|
-
if (!
|
|
52863
|
+
const canonicalPath = join36(HOOKS_DIR, name);
|
|
52864
|
+
if (!existsSync32(canonicalPath)) {
|
|
52068
52865
|
fail4(`${relative4(CWD, canonicalPath)} ${red7("missing")}`);
|
|
52069
52866
|
fix("specialists init");
|
|
52070
52867
|
allPresent = false;
|
|
52071
52868
|
} else {
|
|
52072
52869
|
ok3(relative4(CWD, canonicalPath));
|
|
52073
52870
|
}
|
|
52074
|
-
const claudeHookPath =
|
|
52871
|
+
const claudeHookPath = join36(CLAUDE_HOOKS_DIR, name);
|
|
52075
52872
|
const symlinkState = isSymlinkTo(claudeHookPath, canonicalPath);
|
|
52076
52873
|
if (symlinkState.ok) {
|
|
52077
|
-
ok3(`${relative4(CWD, claudeHookPath)} -> ${relative4(
|
|
52874
|
+
ok3(`${relative4(CWD, claudeHookPath)} -> ${relative4(dirname13(claudeHookPath), canonicalPath)}`);
|
|
52078
52875
|
continue;
|
|
52079
52876
|
}
|
|
52080
52877
|
allPresent = false;
|
|
@@ -52152,8 +52949,8 @@ function hashFile(path3) {
|
|
|
52152
52949
|
function collectFileHashes(rootDir) {
|
|
52153
52950
|
const hashes = new Map;
|
|
52154
52951
|
const visit2 = (dir) => {
|
|
52155
|
-
for (const entry of
|
|
52156
|
-
const fullPath =
|
|
52952
|
+
for (const entry of readdirSync18(dir, { withFileTypes: true })) {
|
|
52953
|
+
const fullPath = join36(dir, entry.name);
|
|
52157
52954
|
if (entry.isDirectory()) {
|
|
52158
52955
|
visit2(fullPath);
|
|
52159
52956
|
continue;
|
|
@@ -52164,12 +52961,12 @@ function collectFileHashes(rootDir) {
|
|
|
52164
52961
|
hashes.set(relPath2, hashFile(fullPath));
|
|
52165
52962
|
}
|
|
52166
52963
|
};
|
|
52167
|
-
if (
|
|
52964
|
+
if (existsSync32(rootDir))
|
|
52168
52965
|
visit2(rootDir);
|
|
52169
52966
|
return hashes;
|
|
52170
52967
|
}
|
|
52171
52968
|
function isSymlinkTo(linkPath, expectedTargetPath) {
|
|
52172
|
-
if (!
|
|
52969
|
+
if (!existsSync32(linkPath))
|
|
52173
52970
|
return { ok: false, reason: "missing" };
|
|
52174
52971
|
let stats;
|
|
52175
52972
|
try {
|
|
@@ -52181,7 +52978,7 @@ function isSymlinkTo(linkPath, expectedTargetPath) {
|
|
|
52181
52978
|
return { ok: false, reason: "not-symlink" };
|
|
52182
52979
|
try {
|
|
52183
52980
|
const rawTarget = readlinkSync3(linkPath);
|
|
52184
|
-
const resolvedTarget = resolve13(
|
|
52981
|
+
const resolvedTarget = resolve13(dirname13(linkPath), rawTarget);
|
|
52185
52982
|
const resolvedExpected = resolve13(expectedTargetPath);
|
|
52186
52983
|
if (resolvedTarget !== resolvedExpected) {
|
|
52187
52984
|
return { ok: false, reason: "wrong-target", target: rawTarget };
|
|
@@ -52192,7 +52989,7 @@ function isSymlinkTo(linkPath, expectedTargetPath) {
|
|
|
52192
52989
|
}
|
|
52193
52990
|
}
|
|
52194
52991
|
function resolvePackageAssetDir(relativePath) {
|
|
52195
|
-
return resolveCanonicalAssetDir(relativePath) ?? (
|
|
52992
|
+
return resolveCanonicalAssetDir(relativePath) ?? (existsSync32(join36(CWD, "config", relativePath)) ? join36(CWD, "config", relativePath) : null);
|
|
52196
52993
|
}
|
|
52197
52994
|
function checkSkillDrift() {
|
|
52198
52995
|
section3("Category A package-live skill sync");
|
|
@@ -52202,7 +52999,7 @@ function checkSkillDrift() {
|
|
|
52202
52999
|
fix("restore config/skills/ or install package assets");
|
|
52203
53000
|
return false;
|
|
52204
53001
|
}
|
|
52205
|
-
if (!
|
|
53002
|
+
if (!existsSync32(XTRM_DEFAULT_SKILLS_DIR)) {
|
|
52206
53003
|
fail4(".xtrm/skills/default/ missing");
|
|
52207
53004
|
fix("specialists init --sync-skills");
|
|
52208
53005
|
return false;
|
|
@@ -52242,11 +53039,11 @@ function checkSkillDrift() {
|
|
|
52242
53039
|
}
|
|
52243
53040
|
fix("specialists init --sync-skills");
|
|
52244
53041
|
}
|
|
52245
|
-
const defaultSkills =
|
|
53042
|
+
const defaultSkills = readdirSync18(XTRM_DEFAULT_SKILLS_DIR, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
52246
53043
|
let linksOk = true;
|
|
52247
53044
|
for (const skillName of defaultSkills) {
|
|
52248
|
-
const activeLinkPath =
|
|
52249
|
-
const expectedTarget =
|
|
53045
|
+
const activeLinkPath = join36(XTRM_ACTIVE_SKILLS_DIR, skillName);
|
|
53046
|
+
const expectedTarget = join36(XTRM_DEFAULT_SKILLS_DIR, skillName);
|
|
52250
53047
|
const state = isSymlinkTo(activeLinkPath, expectedTarget);
|
|
52251
53048
|
if (state.ok)
|
|
52252
53049
|
continue;
|
|
@@ -52264,11 +53061,11 @@ function checkSkillDrift() {
|
|
|
52264
53061
|
fix("specialists init --sync-skills");
|
|
52265
53062
|
}
|
|
52266
53063
|
const legacyActiveRoots = [
|
|
52267
|
-
{ scope: "claude", root:
|
|
52268
|
-
{ scope: "pi", root:
|
|
53064
|
+
{ scope: "claude", root: join36(XTRM_ACTIVE_SKILLS_DIR, "claude") },
|
|
53065
|
+
{ scope: "pi", root: join36(XTRM_ACTIVE_SKILLS_DIR, "pi") }
|
|
52269
53066
|
];
|
|
52270
53067
|
for (const { root } of legacyActiveRoots) {
|
|
52271
|
-
if (!
|
|
53068
|
+
if (!existsSync32(root))
|
|
52272
53069
|
continue;
|
|
52273
53070
|
if (isSymlinkTo(root, XTRM_ACTIVE_SKILLS_DIR).ok)
|
|
52274
53071
|
continue;
|
|
@@ -52281,14 +53078,14 @@ function checkSkillDrift() {
|
|
|
52281
53078
|
fix("specialists init --sync-skills");
|
|
52282
53079
|
}
|
|
52283
53080
|
const skillRootChecks = [
|
|
52284
|
-
{ root:
|
|
52285
|
-
{ root:
|
|
53081
|
+
{ root: join36(CLAUDE_DIR, "skills"), expected: XTRM_ACTIVE_SKILLS_DIR },
|
|
53082
|
+
{ root: join36(PI_DIR, "skills"), expected: XTRM_ACTIVE_SKILLS_DIR }
|
|
52286
53083
|
];
|
|
52287
53084
|
let rootLinksOk = true;
|
|
52288
53085
|
for (const check2 of skillRootChecks) {
|
|
52289
53086
|
const state = isSymlinkTo(check2.root, check2.expected);
|
|
52290
53087
|
if (state.ok) {
|
|
52291
|
-
ok3(`${relative4(CWD, check2.root)} -> ${relative4(
|
|
53088
|
+
ok3(`${relative4(CWD, check2.root)} -> ${relative4(dirname13(check2.root), check2.expected)}`);
|
|
52292
53089
|
continue;
|
|
52293
53090
|
}
|
|
52294
53091
|
rootLinksOk = false;
|
|
@@ -52314,7 +53111,7 @@ function checkManagedMirror(label, canonicalRelativePath, mirrorDir, fixHint) {
|
|
|
52314
53111
|
fix(fixHint);
|
|
52315
53112
|
return false;
|
|
52316
53113
|
}
|
|
52317
|
-
if (!
|
|
53114
|
+
if (!existsSync32(mirrorDir)) {
|
|
52318
53115
|
fail4(`${label} mirror missing: ${relative4(CWD, mirrorDir)}`);
|
|
52319
53116
|
fix(fixHint);
|
|
52320
53117
|
return false;
|
|
@@ -52346,31 +53143,31 @@ function checkManagedMirror(label, canonicalRelativePath, mirrorDir, fixHint) {
|
|
|
52346
53143
|
function checkManagedAssetMirrors() {
|
|
52347
53144
|
section3("Category B xtrm-managed asset mirrors");
|
|
52348
53145
|
const specialistsOk = checkManagedMirror("specialists", "specialists", DEFAULT_SPECIALISTS_DIR, "sp prune-stale-defaults --apply");
|
|
52349
|
-
const rulesOk = checkManagedMirror("mandatory-rules", "mandatory-rules",
|
|
52350
|
-
const nodesOk = checkManagedMirror("nodes", "nodes",
|
|
53146
|
+
const rulesOk = checkManagedMirror("mandatory-rules", "mandatory-rules", join36(DEFAULT_SPECIALISTS_DIR, "mandatory-rules"), "sp prune-stale-defaults --apply");
|
|
53147
|
+
const nodesOk = checkManagedMirror("nodes", "nodes", join36(DEFAULT_SPECIALISTS_DIR, "nodes"), "sp prune-stale-defaults --apply");
|
|
52351
53148
|
return specialistsOk && rulesOk && nodesOk;
|
|
52352
53149
|
}
|
|
52353
53150
|
function checkUserOverlayDrift() {
|
|
52354
53151
|
section3("User specialist overlays");
|
|
52355
|
-
if (!
|
|
53152
|
+
if (!existsSync32(USER_SPECIALISTS_DIR)) {
|
|
52356
53153
|
ok3("no user overlays present");
|
|
52357
53154
|
return true;
|
|
52358
53155
|
}
|
|
52359
|
-
const overlays =
|
|
53156
|
+
const overlays = readdirSync18(USER_SPECIALISTS_DIR).filter((name) => name.endsWith(".specialist.json"));
|
|
52360
53157
|
if (overlays.length === 0) {
|
|
52361
53158
|
ok3("no user overlays present");
|
|
52362
53159
|
return true;
|
|
52363
53160
|
}
|
|
52364
53161
|
let allOk = true;
|
|
52365
53162
|
for (const name of overlays) {
|
|
52366
|
-
const userPath =
|
|
52367
|
-
const defaultPath =
|
|
53163
|
+
const userPath = join36(USER_SPECIALISTS_DIR, name);
|
|
53164
|
+
const defaultPath = join36(DEFAULT_SPECIALISTS_DIR, name);
|
|
52368
53165
|
const userSpec = loadJson2(userPath);
|
|
52369
53166
|
if (!userSpec) {
|
|
52370
53167
|
warn3(`${name}: failed to parse \u2014 skipping drift check`);
|
|
52371
53168
|
continue;
|
|
52372
53169
|
}
|
|
52373
|
-
if (!
|
|
53170
|
+
if (!existsSync32(defaultPath)) {
|
|
52374
53171
|
ok3(`${name}: user-only overlay (no default to drift from)`);
|
|
52375
53172
|
continue;
|
|
52376
53173
|
}
|
|
@@ -52398,18 +53195,18 @@ function checkUserOverlayDrift() {
|
|
|
52398
53195
|
}
|
|
52399
53196
|
function checkRuntimeDirs() {
|
|
52400
53197
|
section3(".specialists/ runtime directories");
|
|
52401
|
-
const rootDir =
|
|
52402
|
-
const jobsDir =
|
|
52403
|
-
const readyDir =
|
|
53198
|
+
const rootDir = join36(CWD, ".specialists");
|
|
53199
|
+
const jobsDir = join36(rootDir, "jobs");
|
|
53200
|
+
const readyDir = join36(rootDir, "ready");
|
|
52404
53201
|
let allOk = true;
|
|
52405
|
-
if (!
|
|
53202
|
+
if (!existsSync32(rootDir)) {
|
|
52406
53203
|
warn3(".specialists/ not found in current project");
|
|
52407
53204
|
fix("specialists init");
|
|
52408
53205
|
allOk = false;
|
|
52409
53206
|
} else {
|
|
52410
53207
|
ok3(".specialists/ present");
|
|
52411
53208
|
for (const [subDir, label] of [[jobsDir, "jobs"], [readyDir, "ready"]]) {
|
|
52412
|
-
if (!
|
|
53209
|
+
if (!existsSync32(subDir)) {
|
|
52413
53210
|
warn3(`.specialists/${label}/ missing \u2014 auto-creating`);
|
|
52414
53211
|
mkdirSync12(subDir, { recursive: true });
|
|
52415
53212
|
ok3(`.specialists/${label}/ created`);
|
|
@@ -52423,8 +53220,8 @@ function checkRuntimeDirs() {
|
|
|
52423
53220
|
function checkClaudeMdFragments() {
|
|
52424
53221
|
section3("CLAUDE.md fragments");
|
|
52425
53222
|
const projectRoot = process.cwd();
|
|
52426
|
-
const claudeMd =
|
|
52427
|
-
if (!
|
|
53223
|
+
const claudeMd = join36(projectRoot, "CLAUDE.md");
|
|
53224
|
+
if (!existsSync32(claudeMd)) {
|
|
52428
53225
|
warn3("No CLAUDE.md in project root \u2014 skipping fragment check");
|
|
52429
53226
|
return true;
|
|
52430
53227
|
}
|
|
@@ -52592,7 +53389,7 @@ function cleanupProcesses(jobsDir, dryRun) {
|
|
|
52592
53389
|
}
|
|
52593
53390
|
let entries;
|
|
52594
53391
|
try {
|
|
52595
|
-
entries =
|
|
53392
|
+
entries = readdirSync18(jobsDir);
|
|
52596
53393
|
} catch {
|
|
52597
53394
|
entries = [];
|
|
52598
53395
|
}
|
|
@@ -52604,8 +53401,8 @@ function cleanupProcesses(jobsDir, dryRun) {
|
|
|
52604
53401
|
zombieJobIds: []
|
|
52605
53402
|
};
|
|
52606
53403
|
for (const jobId of entries) {
|
|
52607
|
-
const statusPath =
|
|
52608
|
-
if (!
|
|
53404
|
+
const statusPath = join36(jobsDir, jobId, "status.json");
|
|
53405
|
+
if (!existsSync32(statusPath))
|
|
52609
53406
|
continue;
|
|
52610
53407
|
try {
|
|
52611
53408
|
const status = JSON.parse(readFileSync31(statusPath, "utf8"));
|
|
@@ -52693,8 +53490,8 @@ function resolveWatchdogMode() {
|
|
|
52693
53490
|
function checkZombieJobs() {
|
|
52694
53491
|
section3("Background jobs");
|
|
52695
53492
|
hint(`watchdog mode: ${resolveWatchdogMode()}`);
|
|
52696
|
-
const jobsDir =
|
|
52697
|
-
if (!
|
|
53493
|
+
const jobsDir = join36(CWD, ".specialists", "jobs");
|
|
53494
|
+
if (!existsSync32(jobsDir)) {
|
|
52698
53495
|
hint("No .specialists/jobs/ \u2014 skipping");
|
|
52699
53496
|
return true;
|
|
52700
53497
|
}
|
|
@@ -52712,7 +53509,7 @@ function checkZombieJobs() {
|
|
|
52712
53509
|
}
|
|
52713
53510
|
return result.zombies === 0;
|
|
52714
53511
|
}
|
|
52715
|
-
async function
|
|
53512
|
+
async function run32(argv = process.argv.slice(3)) {
|
|
52716
53513
|
const subcommand = argv[0];
|
|
52717
53514
|
if (subcommand === "orphans") {
|
|
52718
53515
|
runDoctorOrphans();
|
|
@@ -52760,18 +53557,18 @@ var init_doctor = __esm(() => {
|
|
|
52760
53557
|
init_drift_detector();
|
|
52761
53558
|
init_version_check();
|
|
52762
53559
|
CWD = process.cwd();
|
|
52763
|
-
CLAUDE_DIR =
|
|
52764
|
-
PI_DIR =
|
|
52765
|
-
XTRM_SKILLS_DIR =
|
|
52766
|
-
XTRM_DEFAULT_SKILLS_DIR =
|
|
52767
|
-
XTRM_ACTIVE_SKILLS_DIR =
|
|
52768
|
-
SPECIALISTS_DIR =
|
|
52769
|
-
DEFAULT_SPECIALISTS_DIR =
|
|
52770
|
-
USER_SPECIALISTS_DIR =
|
|
52771
|
-
HOOKS_DIR =
|
|
52772
|
-
CLAUDE_HOOKS_DIR =
|
|
52773
|
-
SETTINGS_FILE =
|
|
52774
|
-
MCP_FILE2 =
|
|
53560
|
+
CLAUDE_DIR = join36(CWD, ".claude");
|
|
53561
|
+
PI_DIR = join36(CWD, ".pi");
|
|
53562
|
+
XTRM_SKILLS_DIR = join36(CWD, ".xtrm", "skills");
|
|
53563
|
+
XTRM_DEFAULT_SKILLS_DIR = join36(XTRM_SKILLS_DIR, "default");
|
|
53564
|
+
XTRM_ACTIVE_SKILLS_DIR = join36(XTRM_SKILLS_DIR, "active");
|
|
53565
|
+
SPECIALISTS_DIR = join36(CWD, ".specialists");
|
|
53566
|
+
DEFAULT_SPECIALISTS_DIR = join36(SPECIALISTS_DIR, "default");
|
|
53567
|
+
USER_SPECIALISTS_DIR = join36(SPECIALISTS_DIR, "user");
|
|
53568
|
+
HOOKS_DIR = join36(CWD, ".xtrm", "hooks", "specialists");
|
|
53569
|
+
CLAUDE_HOOKS_DIR = join36(CLAUDE_DIR, "hooks");
|
|
53570
|
+
SETTINGS_FILE = join36(CLAUDE_DIR, "settings.json");
|
|
53571
|
+
MCP_FILE2 = join36(CWD, ".mcp.json");
|
|
52775
53572
|
HOOK_NAMES = [
|
|
52776
53573
|
"specialists-complete.mjs",
|
|
52777
53574
|
"specialists-session-start.mjs"
|
|
@@ -52781,9 +53578,9 @@ var init_doctor = __esm(() => {
|
|
|
52781
53578
|
// src/cli/setup.ts
|
|
52782
53579
|
var exports_setup = {};
|
|
52783
53580
|
__export(exports_setup, {
|
|
52784
|
-
run: () =>
|
|
53581
|
+
run: () => run33
|
|
52785
53582
|
});
|
|
52786
|
-
async function
|
|
53583
|
+
async function run33() {
|
|
52787
53584
|
console.log("");
|
|
52788
53585
|
console.log(yellow13("\u26A0 DEPRECATED: `specialists setup` is deprecated"));
|
|
52789
53586
|
console.log("");
|
|
@@ -52804,20 +53601,20 @@ async function run32() {
|
|
|
52804
53601
|
var bold14 = (s) => `\x1B[1m${s}\x1B[0m`, yellow13 = (s) => `\x1B[33m${s}\x1B[0m`, dim15 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
52805
53602
|
|
|
52806
53603
|
// src/cli/serve-hot-reload.ts
|
|
52807
|
-
import { existsSync as
|
|
52808
|
-
import { join as
|
|
53604
|
+
import { existsSync as existsSync33, readdirSync as readdirSync19, statSync as statSync9, watch as fsWatch } from "fs";
|
|
53605
|
+
import { join as join37 } from "path";
|
|
52809
53606
|
function specialistNameFromFile(file) {
|
|
52810
53607
|
const match = file.match(/^(.+)\.specialist\.(json|yaml)$/);
|
|
52811
53608
|
return match ? match[1] : null;
|
|
52812
53609
|
}
|
|
52813
53610
|
function snapshotMtimes(dir) {
|
|
52814
53611
|
const out = new Map;
|
|
52815
|
-
if (!
|
|
53612
|
+
if (!existsSync33(dir))
|
|
52816
53613
|
return out;
|
|
52817
|
-
const entries =
|
|
53614
|
+
const entries = readdirSync19(dir).filter((name) => specialistNameFromFile(name) !== null);
|
|
52818
53615
|
for (const name of entries) {
|
|
52819
53616
|
try {
|
|
52820
|
-
out.set(name,
|
|
53617
|
+
out.set(name, statSync9(join37(dir, name)).mtimeMs);
|
|
52821
53618
|
} catch {}
|
|
52822
53619
|
}
|
|
52823
53620
|
return out;
|
|
@@ -52874,7 +53671,7 @@ function createUserDirWatcher(opts) {
|
|
|
52874
53671
|
for (const file of changed)
|
|
52875
53672
|
queue(file);
|
|
52876
53673
|
}, opts.pollMs);
|
|
52877
|
-
} else if (
|
|
53674
|
+
} else if (existsSync33(opts.userDir)) {
|
|
52878
53675
|
try {
|
|
52879
53676
|
watcher = fsWatch(opts.userDir, { persistent: false }, (_eventType, filename) => {
|
|
52880
53677
|
queue(filename ? String(filename) : null);
|
|
@@ -52906,7 +53703,7 @@ var init_serve_hot_reload = () => {};
|
|
|
52906
53703
|
var exports_serve = {};
|
|
52907
53704
|
__export(exports_serve, {
|
|
52908
53705
|
startServe: () => startServe,
|
|
52909
|
-
run: () =>
|
|
53706
|
+
run: () => run34,
|
|
52910
53707
|
recordAuditFailure: () => recordAuditFailure,
|
|
52911
53708
|
evaluateReadiness: () => evaluateReadiness2,
|
|
52912
53709
|
createReadinessState: () => createReadinessState,
|
|
@@ -52917,9 +53714,9 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
52917
53714
|
import { once } from "events";
|
|
52918
53715
|
import { spawnSync as spawnSync22 } from "child_process";
|
|
52919
53716
|
import { access, readdir as readdir2, readFile as readFile5, constants } from "fs/promises";
|
|
52920
|
-
import { existsSync as
|
|
53717
|
+
import { existsSync as existsSync34 } from "fs";
|
|
52921
53718
|
import { homedir as homedir5 } from "os";
|
|
52922
|
-
import { join as
|
|
53719
|
+
import { join as join38 } from "path";
|
|
52923
53720
|
function createReadinessState() {
|
|
52924
53721
|
return { shuttingDown: false, auditFailures: [], dbWriteFailuresTotal: 0 };
|
|
52925
53722
|
}
|
|
@@ -52935,7 +53732,7 @@ function pruneAuditFailures(state, now = Date.now()) {
|
|
|
52935
53732
|
}
|
|
52936
53733
|
}
|
|
52937
53734
|
async function checkUserDirSpecs(userDir) {
|
|
52938
|
-
if (!
|
|
53735
|
+
if (!existsSync34(userDir))
|
|
52939
53736
|
return "empty";
|
|
52940
53737
|
const entries = await readdir2(userDir).catch(() => []);
|
|
52941
53738
|
const specFiles = entries.filter((name) => name.endsWith(".specialist.json") || name.endsWith(".specialist.yaml"));
|
|
@@ -52944,7 +53741,7 @@ async function checkUserDirSpecs(userDir) {
|
|
|
52944
53741
|
let validCount = 0;
|
|
52945
53742
|
for (const file of specFiles) {
|
|
52946
53743
|
try {
|
|
52947
|
-
const content = await readFile5(
|
|
53744
|
+
const content = await readFile5(join38(userDir, file), "utf-8");
|
|
52948
53745
|
const json = file.endsWith(".json") ? content : null;
|
|
52949
53746
|
if (!json)
|
|
52950
53747
|
continue;
|
|
@@ -52962,7 +53759,7 @@ async function evaluateReadiness2(opts) {
|
|
|
52962
53759
|
if (opts.state.auditFailures.length > opts.auditFailureThreshold) {
|
|
52963
53760
|
return { ready: false, reason: "degraded:audit" };
|
|
52964
53761
|
}
|
|
52965
|
-
const piConfigPath = opts.piConfigPath ??
|
|
53762
|
+
const piConfigPath = opts.piConfigPath ?? join38(homedir5(), ".pi", "agent", "auth.json");
|
|
52966
53763
|
try {
|
|
52967
53764
|
await access(piConfigPath, constants.R_OK);
|
|
52968
53765
|
} catch {
|
|
@@ -52983,7 +53780,7 @@ async function evaluateReadiness2(opts) {
|
|
|
52983
53780
|
warning = canaryFailure;
|
|
52984
53781
|
}
|
|
52985
53782
|
}
|
|
52986
|
-
const userDir =
|
|
53783
|
+
const userDir = join38(opts.projectDir, ".specialists", "user");
|
|
52987
53784
|
const userDirResult = await checkUserDirSpecs(userDir);
|
|
52988
53785
|
if (userDirResult === "empty")
|
|
52989
53786
|
return { ready: false, reason: "empty_user_dir" };
|
|
@@ -52991,7 +53788,7 @@ async function evaluateReadiness2(opts) {
|
|
|
52991
53788
|
return { ready: false, reason: "invalid_spec_in_user_dir" };
|
|
52992
53789
|
return warning ? { ready: true, warning } : { ready: true };
|
|
52993
53790
|
}
|
|
52994
|
-
function
|
|
53791
|
+
function parseArgs14(argv) {
|
|
52995
53792
|
let port = 8000;
|
|
52996
53793
|
let concurrency = 4;
|
|
52997
53794
|
let queueTimeoutMs = 5000;
|
|
@@ -53095,7 +53892,7 @@ async function waitForSlot(limit, timeoutMs, getActive) {
|
|
|
53095
53892
|
return true;
|
|
53096
53893
|
}
|
|
53097
53894
|
async function startServe(argv = process.argv.slice(3)) {
|
|
53098
|
-
const args =
|
|
53895
|
+
const args = parseArgs14(argv);
|
|
53099
53896
|
const loader = new SpecialistLoader({ projectDir: args.projectDir });
|
|
53100
53897
|
const dbLocation = resolveObservabilityDbLocation(args.projectDir);
|
|
53101
53898
|
const dbPath = args.dbPath ?? dbLocation.dbPath;
|
|
@@ -53104,7 +53901,7 @@ async function startServe(argv = process.argv.slice(3)) {
|
|
|
53104
53901
|
return createObservabilitySqliteClient(args.projectDir);
|
|
53105
53902
|
})();
|
|
53106
53903
|
const readinessState = createReadinessState();
|
|
53107
|
-
const userDir =
|
|
53904
|
+
const userDir = join38(args.projectDir, ".specialists", "user");
|
|
53108
53905
|
const hotReload = createUserDirWatcher({ loader, userDir, pollMs: args.reloadPollMs });
|
|
53109
53906
|
let active = 0;
|
|
53110
53907
|
const children = new Set;
|
|
@@ -53269,7 +54066,7 @@ async function startServe(argv = process.argv.slice(3)) {
|
|
|
53269
54066
|
console.log(`sp serve listening on ${args.port}`);
|
|
53270
54067
|
return { server, args, db, readinessState };
|
|
53271
54068
|
}
|
|
53272
|
-
async function
|
|
54069
|
+
async function run34(argv = process.argv.slice(3)) {
|
|
53273
54070
|
await startServe(argv);
|
|
53274
54071
|
}
|
|
53275
54072
|
var AUDIT_WINDOW_MS = 60000, DEFAULT_REQUIRED_PI_FLAGS;
|
|
@@ -53287,8 +54084,8 @@ var init_serve = __esm(() => {
|
|
|
53287
54084
|
var exports_script = {};
|
|
53288
54085
|
__export(exports_script, {
|
|
53289
54086
|
scriptCli: () => scriptCli,
|
|
53290
|
-
run: () =>
|
|
53291
|
-
parseArgs: () =>
|
|
54087
|
+
run: () => run35,
|
|
54088
|
+
parseArgs: () => parseArgs15,
|
|
53292
54089
|
mapExitCode: () => mapExitCode
|
|
53293
54090
|
});
|
|
53294
54091
|
import { spawnSync as spawnSync23 } from "child_process";
|
|
@@ -53298,7 +54095,7 @@ function parseVar(entry) {
|
|
|
53298
54095
|
throw new Error(`Invalid --vars entry: ${entry}`);
|
|
53299
54096
|
return [entry.slice(0, index), entry.slice(index + 1)];
|
|
53300
54097
|
}
|
|
53301
|
-
function
|
|
54098
|
+
function parseArgs15(argv) {
|
|
53302
54099
|
if (argv.length === 0)
|
|
53303
54100
|
throw new Error("Missing specialist name");
|
|
53304
54101
|
const specialist = argv[0];
|
|
@@ -53404,8 +54201,8 @@ function runUnderLock(lockPath, argv) {
|
|
|
53404
54201
|
return 75;
|
|
53405
54202
|
return flock.status ?? 1;
|
|
53406
54203
|
}
|
|
53407
|
-
async function
|
|
53408
|
-
const args =
|
|
54204
|
+
async function run35(argv = process.argv.slice(3)) {
|
|
54205
|
+
const args = parseArgs15(argv);
|
|
53409
54206
|
if (args.singleInstance && !process.env.SP_SCRIPT_NO_LOCK) {
|
|
53410
54207
|
process.exit(runUnderLock(args.singleInstance, argv));
|
|
53411
54208
|
}
|
|
@@ -53422,19 +54219,19 @@ var scriptCli;
|
|
|
53422
54219
|
var init_script = __esm(() => {
|
|
53423
54220
|
init_loader();
|
|
53424
54221
|
init_script_runner();
|
|
53425
|
-
scriptCli = { parseArgs:
|
|
54222
|
+
scriptCli = { parseArgs: parseArgs15, mapExitCode };
|
|
53426
54223
|
});
|
|
53427
54224
|
|
|
53428
54225
|
// src/cli/help.ts
|
|
53429
54226
|
var exports_help = {};
|
|
53430
54227
|
__export(exports_help, {
|
|
53431
|
-
run: () =>
|
|
54228
|
+
run: () => run36
|
|
53432
54229
|
});
|
|
53433
54230
|
function formatCommands(entries) {
|
|
53434
54231
|
const width = Math.max(...entries.map(([cmd3]) => cmd3.length));
|
|
53435
54232
|
return entries.map(([cmd3, desc]) => ` ${cmd3.padEnd(width)} ${desc}`);
|
|
53436
54233
|
}
|
|
53437
|
-
async function
|
|
54234
|
+
async function run36() {
|
|
53438
54235
|
const lines = [
|
|
53439
54236
|
"",
|
|
53440
54237
|
"Specialists lets you run project-scoped specialist agents with a bead-first workflow.",
|
|
@@ -53476,14 +54273,15 @@ async function run35() {
|
|
|
53476
54273
|
" Async patterns",
|
|
53477
54274
|
" MCP: use_specialist (foreground, returns result directly)",
|
|
53478
54275
|
' CLI: specialists run <name> --prompt "..." # job ID prints on stderr',
|
|
53479
|
-
" specialists ps|feed|result <job-id>
|
|
54276
|
+
" specialists ps|feed|log|result <job-id> # observe/progress/debug/final output",
|
|
53480
54277
|
' Shell: specialists run <name> --prompt "..." & # native shell backgrounding',
|
|
53481
54278
|
"",
|
|
53482
54279
|
" Background workflow",
|
|
53483
54280
|
' specialists run executor --background --prompt "..." # \u2192 prints job-id',
|
|
53484
54281
|
" specialists list --live # \u2192 interactive session picker",
|
|
53485
54282
|
" specialists attach <job-id> # \u2192 legacy tmux attach only",
|
|
53486
|
-
" specialists feed <job-id> --follow # \u2192 structured event stream",
|
|
54283
|
+
" specialists feed <job-id> --follow # \u2192 compact structured event stream",
|
|
54284
|
+
" specialists log <job-id> -f # \u2192 full runtime/control/error log",
|
|
53487
54285
|
" specialists merge <bead-id> [--rebuild] # \u2192 publish standalone chain only",
|
|
53488
54286
|
" specialists epic merge <epic-id> [--pr] # \u2192 publish epic-owned chains",
|
|
53489
54287
|
" specialists end [--pr] # \u2192 session-close publish helper",
|
|
@@ -53507,7 +54305,8 @@ async function run35() {
|
|
|
53507
54305
|
" specialists run debugger --bead unitAI-123",
|
|
53508
54306
|
' specialists run codebase-explorer --prompt "Map the CLI architecture"',
|
|
53509
54307
|
" specialists ps abc123 --json # check job status",
|
|
53510
|
-
" specialists feed -f # stream
|
|
54308
|
+
" specialists feed -f # stream compact job events",
|
|
54309
|
+
" specialists log --specialist reviewer -f # full runtime/debug log",
|
|
53511
54310
|
" specialists chat explorer --bead unitAI-123 # live TUI: feed + result + input",
|
|
53512
54311
|
' specialists steer <job-id> "focus only on supervisor.ts"',
|
|
53513
54312
|
' specialists resume <job-id> "now write the fix"',
|
|
@@ -53530,7 +54329,8 @@ async function run35() {
|
|
|
53530
54329
|
" specialists steer --help Mid-run steering details",
|
|
53531
54330
|
" specialists resume --help Multi-turn keep-alive details",
|
|
53532
54331
|
" specialists init --help Bootstrap behavior and workflow injection",
|
|
53533
|
-
" specialists feed --help
|
|
54332
|
+
" specialists feed --help Compact job event streaming details",
|
|
54333
|
+
" specialists log --help Full runtime/control/error log details",
|
|
53534
54334
|
" specialists chat <name> ... Interactive TUI launch/control surface",
|
|
53535
54335
|
"",
|
|
53536
54336
|
dim16("Project model: specialists are project-only; user-scope discovery is deprecated."),
|
|
@@ -53554,7 +54354,8 @@ var init_help = __esm(() => {
|
|
|
53554
54354
|
["release", "Deprecated alias: proxy to xt release prepare/publish"],
|
|
53555
54355
|
["node", "Run and inspect NodeSupervisor nodes (run/status)"],
|
|
53556
54356
|
["epic", "Epic lifecycle management: list/status/resolve wave-bound chain groups"],
|
|
53557
|
-
["feed", "Tail job events; use -f to follow all jobs"],
|
|
54357
|
+
["feed", "Tail compact job events; use -f to follow all jobs"],
|
|
54358
|
+
["log", "Full runtime logs with job/bead/repo/path metadata; -f follows control/error events"],
|
|
53558
54359
|
["chat", "Launch an interactive TUI: feed-style timeline, status row, result, and steer/resume input"],
|
|
53559
54360
|
["result", "Print final output of a completed job; --wait polls until done, --timeout <ms> sets a limit"],
|
|
53560
54361
|
["clean", "Clean job dirs, dashboard history (--ps), and orphan processes"],
|
|
@@ -61109,7 +61910,7 @@ var next = process.argv[3];
|
|
|
61109
61910
|
function wantsHelp() {
|
|
61110
61911
|
return next === "--help" || next === "-h";
|
|
61111
61912
|
}
|
|
61112
|
-
async function
|
|
61913
|
+
async function run37() {
|
|
61113
61914
|
if (sub === "install") {
|
|
61114
61915
|
if (wantsHelp()) {
|
|
61115
61916
|
console.log([
|
|
@@ -61763,6 +62564,48 @@ async function run36() {
|
|
|
61763
62564
|
const { run: handler } = await Promise.resolve().then(() => (init_feed2(), exports_feed));
|
|
61764
62565
|
return handler();
|
|
61765
62566
|
}
|
|
62567
|
+
if (sub === "log") {
|
|
62568
|
+
if (wantsHelp()) {
|
|
62569
|
+
console.log([
|
|
62570
|
+
"",
|
|
62571
|
+
"Usage: specialists log [job-id] [options]",
|
|
62572
|
+
" specialists log -f [--specialist <name>] [--bead <id>]",
|
|
62573
|
+
"",
|
|
62574
|
+
"Runtime-oriented specialist log stream. From a repo root it reads that repo;",
|
|
62575
|
+
"from a parent directory it aggregates child repos with observability DBs.",
|
|
62576
|
+
"Unlike feed, it does not suppress",
|
|
62577
|
+
"control/lifecycle rows and every row includes timestamp, job, specialist,",
|
|
62578
|
+
"bead, compact worktree, status, pid, and runtime event detail.",
|
|
62579
|
+
"",
|
|
62580
|
+
"Options:",
|
|
62581
|
+
" --job <id> Filter to one job id",
|
|
62582
|
+
" --specialist <name> Filter by specialist role",
|
|
62583
|
+
" --bead <id> Filter by bead id",
|
|
62584
|
+
" --node <id> Filter by node id",
|
|
62585
|
+
" --repo <name> In parent/global mode, filter to one child repo",
|
|
62586
|
+
" --since <5m|iso> Show rows after a relative/ISO timestamp",
|
|
62587
|
+
" --limit <n> Max rows per snapshot (default 200)",
|
|
62588
|
+
" -f, --follow Continue polling for new rows",
|
|
62589
|
+
" --json Emit NDJSON rows with full event payloads",
|
|
62590
|
+
" --all-events Include agent-internal feed events (tool/turn/text/etc.)",
|
|
62591
|
+
"",
|
|
62592
|
+
"Examples:",
|
|
62593
|
+
" specialists log 49adda",
|
|
62594
|
+
" specialists log --bead unitAI-123 --limit 500",
|
|
62595
|
+
" specialists log --specialist reviewer -f",
|
|
62596
|
+
" specialists log -f --json",
|
|
62597
|
+
"",
|
|
62598
|
+
"Default output is runtime-only; use --all-events for raw agent internals.",
|
|
62599
|
+
"Use feed for compact human progress; use log for debugging crashes,",
|
|
62600
|
+
"dispatch/resume/steer/stop signals, and terminal error provenance.",
|
|
62601
|
+
""
|
|
62602
|
+
].join(`
|
|
62603
|
+
`));
|
|
62604
|
+
return;
|
|
62605
|
+
}
|
|
62606
|
+
const { run: handler } = await Promise.resolve().then(() => (init_log(), exports_log));
|
|
62607
|
+
return handler();
|
|
62608
|
+
}
|
|
61766
62609
|
if (sub === "steer") {
|
|
61767
62610
|
if (wantsHelp()) {
|
|
61768
62611
|
console.log([
|
|
@@ -62158,7 +63001,7 @@ Run 'specialists help' to see available commands.`);
|
|
|
62158
63001
|
const server = new SpecialistsServer;
|
|
62159
63002
|
await server.start();
|
|
62160
63003
|
}
|
|
62161
|
-
|
|
63004
|
+
run37().catch((error2) => {
|
|
62162
63005
|
logger.error(`Fatal error: ${error2}`);
|
|
62163
63006
|
process.exit(1);
|
|
62164
63007
|
});
|