@whittlelabs/sifter 0.15.0 → 0.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/bin.js +473 -63
- package/bin.js.map +4 -4
- package/package.json +1 -1
package/bin.js
CHANGED
|
@@ -15420,6 +15420,15 @@ var require_client = __commonJS({
|
|
|
15420
15420
|
async createOutput(jobId, options) {
|
|
15421
15421
|
return this.request("POST", `/api/jobs/${jobId}/output`, options);
|
|
15422
15422
|
}
|
|
15423
|
+
/**
|
|
15424
|
+
* Post a batch of live progress frames for a running job. Best-effort
|
|
15425
|
+
* telemetry: the service publishes it over core NATS and never derives
|
|
15426
|
+
* authoritative state from it, so callers should swallow failures rather than
|
|
15427
|
+
* let a dropped frame affect the job.
|
|
15428
|
+
*/
|
|
15429
|
+
async postProgress(jobId, options) {
|
|
15430
|
+
await this.request("POST", `/api/jobs/${jobId}/progress`, options);
|
|
15431
|
+
}
|
|
15423
15432
|
// ── Attendance (subscriber dispatch lifecycle) ───────────────────
|
|
15424
15433
|
/**
|
|
15425
15434
|
* Accept a dispatched job. The subscribe loop calls this; direct
|
|
@@ -15722,8 +15731,12 @@ var require_self_update = __commonJS({
|
|
|
15722
15731
|
});
|
|
15723
15732
|
}
|
|
15724
15733
|
function defaultRespawn() {
|
|
15725
|
-
const child = (0, child_process_1.spawn)(process.execPath, process.argv.slice(1), {
|
|
15726
|
-
|
|
15734
|
+
const child = (0, child_process_1.spawn)(process.execPath, process.argv.slice(1), {
|
|
15735
|
+
detached: true,
|
|
15736
|
+
stdio: "inherit"
|
|
15737
|
+
});
|
|
15738
|
+
child.unref();
|
|
15739
|
+
process.exit(0);
|
|
15727
15740
|
}
|
|
15728
15741
|
}
|
|
15729
15742
|
});
|
|
@@ -15790,6 +15803,17 @@ var require_prompt_execution = __commonJS({
|
|
|
15790
15803
|
}
|
|
15791
15804
|
}
|
|
15792
15805
|
}
|
|
15806
|
+
if (spec.gates !== void 0) {
|
|
15807
|
+
if (!Array.isArray(spec.gates)) {
|
|
15808
|
+
throw new Error("prompt-execution: spec.gates must be an array when present");
|
|
15809
|
+
}
|
|
15810
|
+
for (const gate of spec.gates) {
|
|
15811
|
+
const g = gate;
|
|
15812
|
+
if (typeof gate !== "object" || gate === null || typeof g.name !== "string" || typeof g.primitive !== "string" || typeof g.params !== "object" || g.params === null) {
|
|
15813
|
+
throw new Error("prompt-execution: each spec.gates entry must be { name, primitive, params }");
|
|
15814
|
+
}
|
|
15815
|
+
}
|
|
15816
|
+
}
|
|
15793
15817
|
return spec;
|
|
15794
15818
|
}
|
|
15795
15819
|
function renderPromptExecution(inputs) {
|
|
@@ -16007,6 +16031,10 @@ var require_prompt_execution2 = __commonJS({
|
|
|
16007
16031
|
spec.providerHints = options.providerHints;
|
|
16008
16032
|
if (options.costCapHint)
|
|
16009
16033
|
spec.costCapHint = options.costCapHint;
|
|
16034
|
+
if (options.progressChannel !== void 0)
|
|
16035
|
+
spec.progressChannel = options.progressChannel;
|
|
16036
|
+
if (options.outputGates && options.outputGates.length > 0)
|
|
16037
|
+
spec.gates = options.outputGates;
|
|
16010
16038
|
if (options.rendering === "producer" && !options.prompt) {
|
|
16011
16039
|
throw new Error("rendering='producer' requires `prompt`");
|
|
16012
16040
|
}
|
|
@@ -18066,6 +18094,7 @@ var require_canonicalise = __commonJS({
|
|
|
18066
18094
|
promptTemplate: templateText,
|
|
18067
18095
|
promptTemplatePath: promptOptions.promptTemplate,
|
|
18068
18096
|
outputSchema: toJsonSchema(promptOptions.outputSchema),
|
|
18097
|
+
...promptOptions.outputGates ? { outputGates: promptOptions.outputGates } : {},
|
|
18069
18098
|
outputLabel,
|
|
18070
18099
|
rendering: promptOptions.rendering,
|
|
18071
18100
|
...promptOptions.maxAttempts !== void 0 ? { maxAttempts: promptOptions.maxAttempts } : {},
|
|
@@ -18348,6 +18377,128 @@ var require_chain = __commonJS({
|
|
|
18348
18377
|
}
|
|
18349
18378
|
});
|
|
18350
18379
|
|
|
18380
|
+
// ../../packages/shuttle/dist/executors/output-gates.js
|
|
18381
|
+
var require_output_gates = __commonJS({
|
|
18382
|
+
"../../packages/shuttle/dist/executors/output-gates.js"(exports2) {
|
|
18383
|
+
"use strict";
|
|
18384
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18385
|
+
exports2.resolvePath = resolvePath;
|
|
18386
|
+
exports2.evaluateOutputGates = evaluateOutputGates;
|
|
18387
|
+
exports2.renderRepairPrompt = renderRepairPrompt;
|
|
18388
|
+
var INPUT_REF = "input:";
|
|
18389
|
+
function resolvePath(root, path) {
|
|
18390
|
+
let ctx = root;
|
|
18391
|
+
for (const seg of path.split(".")) {
|
|
18392
|
+
const project = seg.endsWith("[*]");
|
|
18393
|
+
const key = project ? seg.slice(0, -3) : seg;
|
|
18394
|
+
if (Array.isArray(ctx)) {
|
|
18395
|
+
ctx = ctx.map((el) => key ? el?.[key] : el);
|
|
18396
|
+
} else {
|
|
18397
|
+
ctx = key ? ctx?.[key] : ctx;
|
|
18398
|
+
}
|
|
18399
|
+
if (project && !Array.isArray(ctx)) {
|
|
18400
|
+
ctx = ctx == null ? [] : [ctx];
|
|
18401
|
+
}
|
|
18402
|
+
}
|
|
18403
|
+
return ctx;
|
|
18404
|
+
}
|
|
18405
|
+
function resolveRef(ref, output, inputs) {
|
|
18406
|
+
if (ref.startsWith(INPUT_REF)) {
|
|
18407
|
+
const label = ref.slice(INPUT_REF.length);
|
|
18408
|
+
const row = inputs.find((i) => i.label === label) ?? inputs.find((i) => i.label === "artifacts" && i.payload.label === label);
|
|
18409
|
+
if (!row)
|
|
18410
|
+
return void 0;
|
|
18411
|
+
return Object.prototype.hasOwnProperty.call(row.payload, "value") ? row.payload.value : row.payload;
|
|
18412
|
+
}
|
|
18413
|
+
return resolvePath(output, ref);
|
|
18414
|
+
}
|
|
18415
|
+
function asStrings(value) {
|
|
18416
|
+
if (Array.isArray(value))
|
|
18417
|
+
return value.map((v) => String(v));
|
|
18418
|
+
if (value == null)
|
|
18419
|
+
return [];
|
|
18420
|
+
return [String(value)];
|
|
18421
|
+
}
|
|
18422
|
+
var PRIMITIVES = {
|
|
18423
|
+
/**
|
|
18424
|
+
* `coverage` — the values at `got` must exactly cover the set at `want`:
|
|
18425
|
+
* every wanted value present, nothing invented, none repeated. Params:
|
|
18426
|
+
* `{ got: <output path>, want: <ref>, subject?: <noun> }`.
|
|
18427
|
+
*/
|
|
18428
|
+
coverage(params, output, inputs) {
|
|
18429
|
+
const subject = typeof params.subject === "string" ? params.subject : "item";
|
|
18430
|
+
const got = asStrings(resolveRef(String(params.got), output, inputs));
|
|
18431
|
+
const want = asStrings(resolveRef(String(params.want), output, inputs));
|
|
18432
|
+
const gotSet = new Set(got);
|
|
18433
|
+
const wantSet = new Set(want);
|
|
18434
|
+
const missing = want.filter((v) => !gotSet.has(v));
|
|
18435
|
+
const unknown = [...new Set(got.filter((v) => !wantSet.has(v)))];
|
|
18436
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18437
|
+
const duplicated = [];
|
|
18438
|
+
for (const v of got) {
|
|
18439
|
+
if (seen.has(v))
|
|
18440
|
+
duplicated.push(v);
|
|
18441
|
+
else
|
|
18442
|
+
seen.add(v);
|
|
18443
|
+
}
|
|
18444
|
+
if (missing.length === 0 && unknown.length === 0 && duplicated.length === 0)
|
|
18445
|
+
return [];
|
|
18446
|
+
const parts = [];
|
|
18447
|
+
if (missing.length)
|
|
18448
|
+
parts.push(`missing ${subject}(s): [${missing.join(", ")}]`);
|
|
18449
|
+
if (unknown.length)
|
|
18450
|
+
parts.push(`not a known ${subject}: [${unknown.join(", ")}]`);
|
|
18451
|
+
if (duplicated.length)
|
|
18452
|
+
parts.push(`assessed more than once: [${[...new Set(duplicated)].join(", ")}]`);
|
|
18453
|
+
return [
|
|
18454
|
+
`every ${subject} must be covered exactly once \u2014 ${parts.join("; ")}. Return one entry per ${subject}, using its exact id.`
|
|
18455
|
+
];
|
|
18456
|
+
},
|
|
18457
|
+
/**
|
|
18458
|
+
* `non_empty_when` — the array at `path` must be non-empty, optionally only
|
|
18459
|
+
* when `when.path` equals `when.equals`. Params:
|
|
18460
|
+
* `{ path: <output path>, when?: { path: <output path>, equals: <value> } }`.
|
|
18461
|
+
*/
|
|
18462
|
+
non_empty_when(params, output) {
|
|
18463
|
+
const when = params.when;
|
|
18464
|
+
if (when && typeof when.path === "string") {
|
|
18465
|
+
if (resolvePath(output, when.path) !== when.equals)
|
|
18466
|
+
return [];
|
|
18467
|
+
}
|
|
18468
|
+
const path = String(params.path);
|
|
18469
|
+
const value = resolvePath(output, path);
|
|
18470
|
+
if (Array.isArray(value) && value.length > 0)
|
|
18471
|
+
return [];
|
|
18472
|
+
const cond = when && typeof when.path === "string" ? ` when ${when.path} is ${JSON.stringify(when.equals)}` : "";
|
|
18473
|
+
return [
|
|
18474
|
+
`'${path}' must be a non-empty array${cond}; it was ${Array.isArray(value) ? "empty" : "absent"}.`
|
|
18475
|
+
];
|
|
18476
|
+
}
|
|
18477
|
+
};
|
|
18478
|
+
function evaluateOutputGates(gates, output, inputs) {
|
|
18479
|
+
const findings = [];
|
|
18480
|
+
for (const gate of gates) {
|
|
18481
|
+
const primitive = PRIMITIVES[gate.primitive];
|
|
18482
|
+
if (!primitive) {
|
|
18483
|
+
findings.push({ gate: gate.name, message: `unknown gate primitive '${gate.primitive}'` });
|
|
18484
|
+
continue;
|
|
18485
|
+
}
|
|
18486
|
+
for (const message of primitive(gate.params, output, inputs)) {
|
|
18487
|
+
findings.push({ gate: gate.name, message });
|
|
18488
|
+
}
|
|
18489
|
+
}
|
|
18490
|
+
return { ok: findings.length === 0, findings };
|
|
18491
|
+
}
|
|
18492
|
+
function renderRepairPrompt(findings) {
|
|
18493
|
+
return [
|
|
18494
|
+
"Your previous answer did not satisfy these output checks. Return the corrected, complete answer that resolves every item below. Produce the same output shape with the problems fixed, and nothing else \u2014 no commentary, no explanation of the changes.",
|
|
18495
|
+
"",
|
|
18496
|
+
...findings.map((f) => `- [${f.gate}] ${f.message}`)
|
|
18497
|
+
].join("\n");
|
|
18498
|
+
}
|
|
18499
|
+
}
|
|
18500
|
+
});
|
|
18501
|
+
|
|
18351
18502
|
// ../../packages/keep/dist/client.js
|
|
18352
18503
|
var require_client2 = __commonJS({
|
|
18353
18504
|
"../../packages/keep/dist/client.js"(exports2) {
|
|
@@ -18880,6 +19031,155 @@ var require_validate_output = __commonJS({
|
|
|
18880
19031
|
}
|
|
18881
19032
|
});
|
|
18882
19033
|
|
|
19034
|
+
// ../../packages/shuttle/dist/executors/progress.js
|
|
19035
|
+
var require_progress = __commonJS({
|
|
19036
|
+
"../../packages/shuttle/dist/executors/progress.js"(exports2) {
|
|
19037
|
+
"use strict";
|
|
19038
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
19039
|
+
exports2.StreamJsonTap = exports2.ProgressReporter = void 0;
|
|
19040
|
+
var ProgressReporter = class {
|
|
19041
|
+
sink;
|
|
19042
|
+
seq = 0;
|
|
19043
|
+
pending = [];
|
|
19044
|
+
textBuf = "";
|
|
19045
|
+
timer = null;
|
|
19046
|
+
closed = false;
|
|
19047
|
+
flushMs;
|
|
19048
|
+
maxChars;
|
|
19049
|
+
constructor(sink, options = {}) {
|
|
19050
|
+
this.sink = sink;
|
|
19051
|
+
this.flushMs = options.flushMs ?? 400;
|
|
19052
|
+
this.maxChars = options.maxChars ?? 1200;
|
|
19053
|
+
}
|
|
19054
|
+
reasoning(text) {
|
|
19055
|
+
if (this.closed || !text)
|
|
19056
|
+
return;
|
|
19057
|
+
this.textBuf += text;
|
|
19058
|
+
if (this.textBuf.length >= this.maxChars) {
|
|
19059
|
+
this.flush();
|
|
19060
|
+
} else {
|
|
19061
|
+
this.arm();
|
|
19062
|
+
}
|
|
19063
|
+
}
|
|
19064
|
+
tool(name, target) {
|
|
19065
|
+
if (this.closed || !name)
|
|
19066
|
+
return;
|
|
19067
|
+
this.drainText();
|
|
19068
|
+
this.push({ kind: "tool", tool: target ? { name, target } : { name } });
|
|
19069
|
+
this.flush();
|
|
19070
|
+
}
|
|
19071
|
+
/** Flush pending text + frames now. */
|
|
19072
|
+
flush() {
|
|
19073
|
+
if (this.closed)
|
|
19074
|
+
return;
|
|
19075
|
+
this.drainText();
|
|
19076
|
+
this.clearTimer();
|
|
19077
|
+
if (this.pending.length === 0)
|
|
19078
|
+
return;
|
|
19079
|
+
const batch = this.pending;
|
|
19080
|
+
this.pending = [];
|
|
19081
|
+
try {
|
|
19082
|
+
this.sink(batch);
|
|
19083
|
+
} catch {
|
|
19084
|
+
}
|
|
19085
|
+
}
|
|
19086
|
+
/** Final flush; no further frames are emitted after this. */
|
|
19087
|
+
close() {
|
|
19088
|
+
this.flush();
|
|
19089
|
+
this.closed = true;
|
|
19090
|
+
this.clearTimer();
|
|
19091
|
+
}
|
|
19092
|
+
drainText() {
|
|
19093
|
+
if (this.textBuf.length === 0)
|
|
19094
|
+
return;
|
|
19095
|
+
this.push({ kind: "reasoning", text: this.textBuf });
|
|
19096
|
+
this.textBuf = "";
|
|
19097
|
+
}
|
|
19098
|
+
push(frame) {
|
|
19099
|
+
this.pending.push({ seq: this.seq++, at: (/* @__PURE__ */ new Date()).toISOString(), ...frame });
|
|
19100
|
+
}
|
|
19101
|
+
arm() {
|
|
19102
|
+
if (this.timer)
|
|
19103
|
+
return;
|
|
19104
|
+
this.timer = setTimeout(() => {
|
|
19105
|
+
this.timer = null;
|
|
19106
|
+
this.flush();
|
|
19107
|
+
}, this.flushMs);
|
|
19108
|
+
this.timer.unref?.();
|
|
19109
|
+
}
|
|
19110
|
+
clearTimer() {
|
|
19111
|
+
if (this.timer) {
|
|
19112
|
+
clearTimeout(this.timer);
|
|
19113
|
+
this.timer = null;
|
|
19114
|
+
}
|
|
19115
|
+
}
|
|
19116
|
+
};
|
|
19117
|
+
exports2.ProgressReporter = ProgressReporter;
|
|
19118
|
+
var StreamJsonTap = class {
|
|
19119
|
+
handlers;
|
|
19120
|
+
buf = "";
|
|
19121
|
+
constructor(handlers) {
|
|
19122
|
+
this.handlers = handlers;
|
|
19123
|
+
}
|
|
19124
|
+
/** Feed a raw stdout chunk. Emits handlers for every complete line parsed. */
|
|
19125
|
+
push(chunk) {
|
|
19126
|
+
this.buf += chunk;
|
|
19127
|
+
let nl;
|
|
19128
|
+
while ((nl = this.buf.indexOf("\n")) !== -1) {
|
|
19129
|
+
const line = this.buf.slice(0, nl);
|
|
19130
|
+
this.buf = this.buf.slice(nl + 1);
|
|
19131
|
+
this.consumeLine(line);
|
|
19132
|
+
}
|
|
19133
|
+
}
|
|
19134
|
+
/** Flush a trailing partial line (best-effort; usually empty at process close). */
|
|
19135
|
+
end() {
|
|
19136
|
+
if (this.buf.trim().length > 0)
|
|
19137
|
+
this.consumeLine(this.buf);
|
|
19138
|
+
this.buf = "";
|
|
19139
|
+
}
|
|
19140
|
+
consumeLine(line) {
|
|
19141
|
+
const trimmed = line.trim();
|
|
19142
|
+
if (!trimmed.startsWith("{"))
|
|
19143
|
+
return;
|
|
19144
|
+
let event;
|
|
19145
|
+
try {
|
|
19146
|
+
event = JSON.parse(trimmed);
|
|
19147
|
+
} catch {
|
|
19148
|
+
return;
|
|
19149
|
+
}
|
|
19150
|
+
if (event.type === "assistant") {
|
|
19151
|
+
const content = event.message?.content;
|
|
19152
|
+
if (!Array.isArray(content))
|
|
19153
|
+
return;
|
|
19154
|
+
for (const block of content) {
|
|
19155
|
+
const b = block;
|
|
19156
|
+
if (b?.type === "text" && typeof b.text === "string" && b.text.length > 0) {
|
|
19157
|
+
this.handlers.onReasoning?.(b.text);
|
|
19158
|
+
} else if (b?.type === "thinking" && typeof b.thinking === "string" && b.thinking.length > 0) {
|
|
19159
|
+
this.handlers.onReasoning?.(b.thinking);
|
|
19160
|
+
} else if (b?.type === "tool_use" && typeof b.name === "string") {
|
|
19161
|
+
this.handlers.onTool?.(b.name, toolTarget(b.input));
|
|
19162
|
+
}
|
|
19163
|
+
}
|
|
19164
|
+
} else if (event.type === "result") {
|
|
19165
|
+
this.handlers.onResult?.();
|
|
19166
|
+
}
|
|
19167
|
+
}
|
|
19168
|
+
};
|
|
19169
|
+
exports2.StreamJsonTap = StreamJsonTap;
|
|
19170
|
+
function toolTarget(input) {
|
|
19171
|
+
if (!input)
|
|
19172
|
+
return void 0;
|
|
19173
|
+
for (const key of ["file_path", "path", "pattern", "command", "url", "query", "notebook_path"]) {
|
|
19174
|
+
const v = input[key];
|
|
19175
|
+
if (typeof v === "string" && v.length > 0)
|
|
19176
|
+
return v;
|
|
19177
|
+
}
|
|
19178
|
+
return void 0;
|
|
19179
|
+
}
|
|
19180
|
+
}
|
|
19181
|
+
});
|
|
19182
|
+
|
|
18883
19183
|
// ../../packages/shuttle/dist/executors/workspace.js
|
|
18884
19184
|
var require_workspace = __commonJS({
|
|
18885
19185
|
"../../packages/shuttle/dist/executors/workspace.js"(exports2) {
|
|
@@ -19111,7 +19411,7 @@ var require_overlay = __commonJS({
|
|
|
19111
19411
|
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
19112
19412
|
const allowed = new Set(options.allowedHosts.map(normalizeOrigin).filter((o) => !!o));
|
|
19113
19413
|
const rootReal = await fs_1.promises.realpath(runDir);
|
|
19114
|
-
const result = { written: 0, skipped: [] };
|
|
19414
|
+
const result = { written: 0, writtenPaths: [], skipped: [] };
|
|
19115
19415
|
const files = Array.isArray(overlay.files) ? overlay.files : [];
|
|
19116
19416
|
for (const file of files) {
|
|
19117
19417
|
const skip = (reason) => result.skipped.push({ path: String(file?.path ?? "?"), reason });
|
|
@@ -19145,6 +19445,7 @@ var require_overlay = __commonJS({
|
|
|
19145
19445
|
await fs_1.promises.mkdir(path_1.default.dirname(dest), { recursive: true });
|
|
19146
19446
|
await fs_1.promises.writeFile(dest, content);
|
|
19147
19447
|
result.written += 1;
|
|
19448
|
+
result.writtenPaths.push(file.path);
|
|
19148
19449
|
} catch (err) {
|
|
19149
19450
|
skip(err instanceof Error ? err.message : String(err));
|
|
19150
19451
|
}
|
|
@@ -19258,9 +19559,11 @@ var require_claude_code = __commonJS({
|
|
|
19258
19559
|
var fs_1 = require("fs");
|
|
19259
19560
|
var os_1 = __importDefault(require("os"));
|
|
19260
19561
|
var path_1 = __importDefault(require("path"));
|
|
19562
|
+
var string_decoder_1 = require("string_decoder");
|
|
19261
19563
|
var loom_1 = require_dist3();
|
|
19262
19564
|
var keep_1 = require_dist4();
|
|
19263
19565
|
var validate_output_1 = require_validate_output();
|
|
19566
|
+
var progress_1 = require_progress();
|
|
19264
19567
|
var workspace_1 = require_workspace();
|
|
19265
19568
|
var overlay_1 = require_overlay();
|
|
19266
19569
|
var env_ref_1 = require_env_ref();
|
|
@@ -19281,7 +19584,7 @@ var require_claude_code = __commonJS({
|
|
|
19281
19584
|
outputTokens: spec.costCapHint?.estimatedOutputTokens
|
|
19282
19585
|
};
|
|
19283
19586
|
}
|
|
19284
|
-
async execute(dispatch, signal) {
|
|
19587
|
+
async execute(dispatch, signal, ctx) {
|
|
19285
19588
|
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
19286
19589
|
let credentials = null;
|
|
19287
19590
|
if (this.config.resolveCredentials) {
|
|
@@ -19306,6 +19609,7 @@ var require_claude_code = __commonJS({
|
|
|
19306
19609
|
let workspace = null;
|
|
19307
19610
|
let promptOnlyScratch = null;
|
|
19308
19611
|
let overlayResult = null;
|
|
19612
|
+
const reporter = spec.progressChannel && ctx?.reportProgress ? new progress_1.ProgressReporter((frames) => ctx.reportProgress(spec.progressChannel, frames)) : null;
|
|
19309
19613
|
try {
|
|
19310
19614
|
if (spec.workspace) {
|
|
19311
19615
|
workspace = await (0, workspace_1.prepareWorkspace)(spec.workspace, {
|
|
@@ -19327,11 +19631,16 @@ var require_claude_code = __commonJS({
|
|
|
19327
19631
|
promptOnlyScratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-run-"));
|
|
19328
19632
|
}
|
|
19329
19633
|
const runCwd = workspace?.cwd ?? promptOnlyScratch;
|
|
19330
|
-
const { response, readPaths } = await this.runClaude(prompt, spec, runCwd, childEnv, signal, hintedModel);
|
|
19634
|
+
const { response, readPaths, toolText } = await this.runClaude(prompt, spec, runCwd, childEnv, signal, hintedModel, reporter);
|
|
19331
19635
|
if (workspace) {
|
|
19636
|
+
const reads = [...readPaths];
|
|
19637
|
+
for (const rel of overlayResult?.writtenPaths ?? []) {
|
|
19638
|
+
if (toolText.includes(rel))
|
|
19639
|
+
reads.push(path_1.default.join(runCwd, rel));
|
|
19640
|
+
}
|
|
19332
19641
|
response.outputs.push({
|
|
19333
19642
|
label: "workspace_reads",
|
|
19334
|
-
content: { paths: relativeWorkspaceReads(
|
|
19643
|
+
content: { paths: relativeWorkspaceReads(reads, runCwd) }
|
|
19335
19644
|
});
|
|
19336
19645
|
response.outputs.push({
|
|
19337
19646
|
label: "workspace",
|
|
@@ -19349,6 +19658,7 @@ var require_claude_code = __commonJS({
|
|
|
19349
19658
|
}
|
|
19350
19659
|
return response;
|
|
19351
19660
|
} finally {
|
|
19661
|
+
reporter?.close();
|
|
19352
19662
|
if (workspace)
|
|
19353
19663
|
await workspace.cleanup();
|
|
19354
19664
|
if (promptOnlyScratch)
|
|
@@ -19359,11 +19669,40 @@ var require_claude_code = __commonJS({
|
|
|
19359
19669
|
});
|
|
19360
19670
|
}
|
|
19361
19671
|
}
|
|
19362
|
-
|
|
19672
|
+
/**
|
|
19673
|
+
* In-session gate repair (ADR 0017): resume the session `execute()` returned a
|
|
19674
|
+
* handle for and correct its output. The repair turn works from the resumed
|
|
19675
|
+
* session's own context (no checkout needed), under the same `--json-schema`
|
|
19676
|
+
* constraint. A throwaway cwd hosts the run; the same handle rides back out so
|
|
19677
|
+
* a second repair round can resume again.
|
|
19678
|
+
*/
|
|
19679
|
+
async continue(continuation, followUp, signal, _ctx) {
|
|
19680
|
+
const handle = continuation;
|
|
19681
|
+
if (!handle || typeof handle.sessionId !== "string") {
|
|
19682
|
+
throw new Error("claude-code continue: missing session to resume");
|
|
19683
|
+
}
|
|
19684
|
+
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
19685
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-resume-"));
|
|
19686
|
+
try {
|
|
19687
|
+
const spec = {
|
|
19688
|
+
rendering: "producer",
|
|
19689
|
+
outputLabel: handle.outputLabel,
|
|
19690
|
+
outputSchema: handle.outputSchema
|
|
19691
|
+
};
|
|
19692
|
+
const { response } = await this.runClaude(followUp, spec, scratch, handle.childEnv, signal, handle.model, null, handle.sessionId);
|
|
19693
|
+
return response;
|
|
19694
|
+
} finally {
|
|
19695
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
19696
|
+
});
|
|
19697
|
+
}
|
|
19698
|
+
}
|
|
19699
|
+
runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId) {
|
|
19363
19700
|
const outputLabel = spec.outputLabel;
|
|
19364
19701
|
const model = modelOverride ?? this.config.model;
|
|
19365
19702
|
return new Promise((resolve, reject) => {
|
|
19366
19703
|
const args = ["--print", "--output-format", "stream-json", "--verbose"];
|
|
19704
|
+
if (resumeSessionId)
|
|
19705
|
+
args.push("--resume", resumeSessionId);
|
|
19367
19706
|
if (spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0) {
|
|
19368
19707
|
args.push("--json-schema", JSON.stringify(spec.outputSchema));
|
|
19369
19708
|
}
|
|
@@ -19395,7 +19734,17 @@ var require_claude_code = __commonJS({
|
|
|
19395
19734
|
settled = true;
|
|
19396
19735
|
fn();
|
|
19397
19736
|
};
|
|
19398
|
-
|
|
19737
|
+
const tap = reporter ? new progress_1.StreamJsonTap({
|
|
19738
|
+
onReasoning: (t) => reporter.reasoning(t),
|
|
19739
|
+
onTool: (name, target) => reporter.tool(name, target),
|
|
19740
|
+
onResult: () => reporter.flush()
|
|
19741
|
+
}) : null;
|
|
19742
|
+
const decoder = tap ? new string_decoder_1.StringDecoder("utf8") : null;
|
|
19743
|
+
child.stdout.on("data", (chunk) => {
|
|
19744
|
+
stdoutChunks.push(chunk);
|
|
19745
|
+
if (tap && decoder)
|
|
19746
|
+
tap.push(decoder.write(chunk));
|
|
19747
|
+
});
|
|
19399
19748
|
child.stdin.write(prompt);
|
|
19400
19749
|
child.stdin.end();
|
|
19401
19750
|
const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;
|
|
@@ -19422,6 +19771,10 @@ var require_claude_code = __commonJS({
|
|
|
19422
19771
|
child.on("close", (code) => {
|
|
19423
19772
|
clearTimeout(timer);
|
|
19424
19773
|
signal.removeEventListener("abort", onAbort);
|
|
19774
|
+
if (tap && decoder) {
|
|
19775
|
+
tap.push(decoder.end());
|
|
19776
|
+
tap.end();
|
|
19777
|
+
}
|
|
19425
19778
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
19426
19779
|
if (code === 0) {
|
|
19427
19780
|
try {
|
|
@@ -19444,9 +19797,21 @@ var require_claude_code = __commonJS({
|
|
|
19444
19797
|
aiProvider: "claude-code",
|
|
19445
19798
|
...model ? { aiModel: model } : {},
|
|
19446
19799
|
...stream.usage ?? {}
|
|
19447
|
-
}
|
|
19800
|
+
},
|
|
19801
|
+
// In-session repair handle (ADR 0017): present only when the run
|
|
19802
|
+
// reported a session id, so the runner can `--resume` it.
|
|
19803
|
+
...stream.sessionId ? {
|
|
19804
|
+
continuation: {
|
|
19805
|
+
sessionId: stream.sessionId,
|
|
19806
|
+
model,
|
|
19807
|
+
childEnv,
|
|
19808
|
+
outputLabel,
|
|
19809
|
+
outputSchema: spec.outputSchema
|
|
19810
|
+
}
|
|
19811
|
+
} : {}
|
|
19448
19812
|
},
|
|
19449
|
-
readPaths: stream.readPaths
|
|
19813
|
+
readPaths: stream.readPaths,
|
|
19814
|
+
toolText: stream.toolText
|
|
19450
19815
|
}));
|
|
19451
19816
|
} catch (err) {
|
|
19452
19817
|
settle(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
@@ -19543,6 +19908,8 @@ var require_claude_code = __commonJS({
|
|
|
19543
19908
|
let usage = null;
|
|
19544
19909
|
let resultMeta = null;
|
|
19545
19910
|
let structuredOutput = null;
|
|
19911
|
+
let sessionId = null;
|
|
19912
|
+
let toolText = "";
|
|
19546
19913
|
for (const line of stdout.split("\n")) {
|
|
19547
19914
|
const trimmed = line.trim();
|
|
19548
19915
|
if (!trimmed.startsWith("{"))
|
|
@@ -19553,15 +19920,22 @@ var require_claude_code = __commonJS({
|
|
|
19553
19920
|
} catch {
|
|
19554
19921
|
continue;
|
|
19555
19922
|
}
|
|
19923
|
+
if (typeof event.session_id === "string")
|
|
19924
|
+
sessionId = event.session_id;
|
|
19556
19925
|
if (event.type === "assistant") {
|
|
19557
19926
|
const content = event.message?.content;
|
|
19558
19927
|
if (!Array.isArray(content))
|
|
19559
19928
|
continue;
|
|
19560
19929
|
for (const block of content) {
|
|
19561
19930
|
const b = block;
|
|
19562
|
-
if (b?.type
|
|
19563
|
-
|
|
19931
|
+
if (b?.type !== "tool_use")
|
|
19932
|
+
continue;
|
|
19933
|
+
const fp = b.input?.file_path;
|
|
19934
|
+
if (b.name === "Read" && typeof fp === "string") {
|
|
19935
|
+
readPaths.push(fp);
|
|
19564
19936
|
}
|
|
19937
|
+
if (b.input && typeof b.input === "object")
|
|
19938
|
+
toolText += JSON.stringify(b.input);
|
|
19565
19939
|
}
|
|
19566
19940
|
} else if (event.type === "result") {
|
|
19567
19941
|
if (typeof event.result === "string" && event.result.trim().length > 0) {
|
|
@@ -19584,7 +19958,7 @@ var require_claude_code = __commonJS({
|
|
|
19584
19958
|
};
|
|
19585
19959
|
}
|
|
19586
19960
|
}
|
|
19587
|
-
return { finalText, readPaths, usage, resultMeta, structuredOutput };
|
|
19961
|
+
return { finalText, readPaths, usage, resultMeta, structuredOutput, sessionId, toolText };
|
|
19588
19962
|
}
|
|
19589
19963
|
var MAX_REPORTED_READS = 2e3;
|
|
19590
19964
|
function relativeWorkspaceReads(readPaths, cwd) {
|
|
@@ -19848,7 +20222,6 @@ var require_anthropic_api = __commonJS({
|
|
|
19848
20222
|
}
|
|
19849
20223
|
async execute(dispatch, signal) {
|
|
19850
20224
|
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
19851
|
-
const outputLabel = spec.outputLabel;
|
|
19852
20225
|
const providerHints = (0, loom_1.pickProviderHints)(spec, this.instance.capabilityId);
|
|
19853
20226
|
const apiKey = await this.instance.resolveKey(providerHints);
|
|
19854
20227
|
const hintedModel = providerHints.model;
|
|
@@ -19857,6 +20230,33 @@ var require_anthropic_api = __commonJS({
|
|
|
19857
20230
|
max_tokens: this.instance.maxTokens,
|
|
19858
20231
|
messages: [{ role: "user", content: prompt }]
|
|
19859
20232
|
};
|
|
20233
|
+
return this.callApi(apiKey, body, spec, spec.outputLabel, signal);
|
|
20234
|
+
}
|
|
20235
|
+
/**
|
|
20236
|
+
* In-session gate repair (ADR 0017): re-call the Messages API with the prior
|
|
20237
|
+
* conversation plus the model's own last turn plus the repair turn, under the
|
|
20238
|
+
* same output contract. A fresh conversation is not started — the appended
|
|
20239
|
+
* assistant turn gives the model its previous answer to correct.
|
|
20240
|
+
*/
|
|
20241
|
+
async continue(continuation, followUp, signal, _ctx) {
|
|
20242
|
+
const handle = continuation;
|
|
20243
|
+
if (!handle || !Array.isArray(handle.messages)) {
|
|
20244
|
+
throw new Error("anthropic-api continue: missing conversation to resume");
|
|
20245
|
+
}
|
|
20246
|
+
const spec = {
|
|
20247
|
+
rendering: "producer",
|
|
20248
|
+
outputLabel: handle.outputLabel,
|
|
20249
|
+
outputSchema: handle.outputSchema
|
|
20250
|
+
};
|
|
20251
|
+
const body = {
|
|
20252
|
+
model: handle.model,
|
|
20253
|
+
max_tokens: handle.maxTokens,
|
|
20254
|
+
messages: [...handle.messages, { role: "user", content: followUp }]
|
|
20255
|
+
};
|
|
20256
|
+
return this.callApi(handle.apiKey, body, spec, handle.outputLabel, signal);
|
|
20257
|
+
}
|
|
20258
|
+
/** Shared Messages-API call + parse + validate, used by execute and continue. */
|
|
20259
|
+
async callApi(apiKey, body, spec, outputLabel, signal) {
|
|
19860
20260
|
const timeoutController = new AbortController();
|
|
19861
20261
|
const timer = setTimeout(() => timeoutController.abort(), this.instance.timeoutMs);
|
|
19862
20262
|
function onAbort() {
|
|
@@ -19904,7 +20304,17 @@ var require_anthropic_api = __commonJS({
|
|
|
19904
20304
|
outputs: [{ label: outputLabel, content: parsed ?? { raw: rawText } }],
|
|
19905
20305
|
rawText,
|
|
19906
20306
|
parsed,
|
|
19907
|
-
...usage ? { usage } : {}
|
|
20307
|
+
...usage ? { usage } : {},
|
|
20308
|
+
// In-session repair handle (ADR 0017): the conversation with the model's
|
|
20309
|
+
// answer appended, ready for `continue()` to add the repair turn.
|
|
20310
|
+
continuation: {
|
|
20311
|
+
apiKey,
|
|
20312
|
+
model: body.model,
|
|
20313
|
+
maxTokens: body.max_tokens,
|
|
20314
|
+
messages: [...body.messages, { role: "assistant", content: rawText }],
|
|
20315
|
+
outputLabel,
|
|
20316
|
+
outputSchema: spec.outputSchema
|
|
20317
|
+
}
|
|
19908
20318
|
};
|
|
19909
20319
|
} finally {
|
|
19910
20320
|
clearTimeout(timer);
|
|
@@ -20737,6 +21147,7 @@ var require_shuttle = __commonJS({
|
|
|
20737
21147
|
var decommission_1 = require_decommission();
|
|
20738
21148
|
var loom_1 = require_dist3();
|
|
20739
21149
|
var chain_1 = require_chain();
|
|
21150
|
+
var output_gates_1 = require_output_gates();
|
|
20740
21151
|
var keep_1 = require_dist4();
|
|
20741
21152
|
var apply_1 = require_apply();
|
|
20742
21153
|
var version_check_1 = require_version_check();
|
|
@@ -20751,6 +21162,37 @@ var require_shuttle = __commonJS({
|
|
|
20751
21162
|
var audit_log_1 = require_audit_log();
|
|
20752
21163
|
var spend_tracker_1 = require_spend_tracker();
|
|
20753
21164
|
var store_1 = require_store();
|
|
21165
|
+
var GATE_REPAIR_BUDGET = 2;
|
|
21166
|
+
async function runOutputGates(dispatch, response, executor, signal, ctx) {
|
|
21167
|
+
let spec;
|
|
21168
|
+
try {
|
|
21169
|
+
spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
21170
|
+
} catch {
|
|
21171
|
+
return { ok: true, response };
|
|
21172
|
+
}
|
|
21173
|
+
const gates = spec.gates;
|
|
21174
|
+
if (!gates || gates.length === 0)
|
|
21175
|
+
return { ok: true, response };
|
|
21176
|
+
let current = response;
|
|
21177
|
+
let result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
|
|
21178
|
+
let attempts = 0;
|
|
21179
|
+
while (!result.ok && attempts < GATE_REPAIR_BUDGET && current.continuation !== void 0 && typeof executor.continue === "function") {
|
|
21180
|
+
try {
|
|
21181
|
+
current = await executor.continue(current.continuation, (0, output_gates_1.renderRepairPrompt)(result.findings), signal, ctx);
|
|
21182
|
+
} catch {
|
|
21183
|
+
break;
|
|
21184
|
+
}
|
|
21185
|
+
result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
|
|
21186
|
+
attempts += 1;
|
|
21187
|
+
}
|
|
21188
|
+
if (result.ok)
|
|
21189
|
+
return { ok: true, response: current };
|
|
21190
|
+
const detail = result.findings.map((f) => `[${f.gate}] ${f.message}`).join(" | ");
|
|
21191
|
+
return {
|
|
21192
|
+
ok: false,
|
|
21193
|
+
reason: `output gate(s) failed${attempts > 0 ? ` after ${attempts} repair attempt(s)` : ""}: ${detail}`
|
|
21194
|
+
};
|
|
21195
|
+
}
|
|
20754
21196
|
var Shuttle = class {
|
|
20755
21197
|
brand;
|
|
20756
21198
|
config;
|
|
@@ -20847,14 +21289,26 @@ var require_shuttle = __commonJS({
|
|
|
20847
21289
|
}
|
|
20848
21290
|
await observerChain.notify({ kind: "executor.dispatch.started", jobId, attendanceId, executor: executor.capability.id, strategy }, onObserverError);
|
|
20849
21291
|
const dispatchStartedAt = Date.now();
|
|
21292
|
+
const executionCtx = {
|
|
21293
|
+
reportProgress: (channel, frames) => {
|
|
21294
|
+
void jobsClient.postProgress(jobId, { channel, frames }).catch(() => {
|
|
21295
|
+
});
|
|
21296
|
+
}
|
|
21297
|
+
};
|
|
20850
21298
|
let response;
|
|
20851
21299
|
try {
|
|
20852
|
-
response = await executor.execute(dispatch, signal);
|
|
21300
|
+
response = await executor.execute(dispatch, signal, executionCtx);
|
|
20853
21301
|
} catch (err) {
|
|
20854
21302
|
const reason = `executor failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
20855
21303
|
await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
|
|
20856
21304
|
return { status: "failed", reason };
|
|
20857
21305
|
}
|
|
21306
|
+
const gated = await runOutputGates(dispatch, response, executor, signal, executionCtx);
|
|
21307
|
+
if (!gated.ok) {
|
|
21308
|
+
await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason: gated.reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
|
|
21309
|
+
return { status: "failed", reason: gated.reason };
|
|
21310
|
+
}
|
|
21311
|
+
response = gated.response;
|
|
20858
21312
|
await observerChain.notify({
|
|
20859
21313
|
kind: "executor.dispatch.completed",
|
|
20860
21314
|
jobId,
|
|
@@ -21527,58 +21981,14 @@ var import_path = require("path");
|
|
|
21527
21981
|
var import_promises = require("fs/promises");
|
|
21528
21982
|
var import_yaml = __toESM(require_dist());
|
|
21529
21983
|
var import_shuttle = __toESM(require_dist5());
|
|
21530
|
-
|
|
21531
|
-
// package.json
|
|
21532
|
-
var package_default = {
|
|
21533
|
-
name: "@whittlelabs/sifter",
|
|
21534
|
-
version: "0.15.0",
|
|
21535
|
-
description: "Whittle Sifter: paired AI reviewer for Whittle Sift job pools.",
|
|
21536
|
-
bin: {
|
|
21537
|
-
"whittle-sifter": "./dist/bin.js"
|
|
21538
|
-
},
|
|
21539
|
-
main: "dist/bin.js",
|
|
21540
|
-
engines: {
|
|
21541
|
-
node: ">=20"
|
|
21542
|
-
},
|
|
21543
|
-
scripts: {
|
|
21544
|
-
dev: "tsx watch src/bin.ts",
|
|
21545
|
-
build: "node scripts/bundle.mjs",
|
|
21546
|
-
typecheck: "tsc --noEmit",
|
|
21547
|
-
start: "node dist/bin.js",
|
|
21548
|
-
lint: "eslint src/"
|
|
21549
|
-
},
|
|
21550
|
-
publishConfig: {
|
|
21551
|
-
registry: "https://registry.npmjs.org/",
|
|
21552
|
-
access: "public"
|
|
21553
|
-
},
|
|
21554
|
-
repository: {
|
|
21555
|
-
type: "git",
|
|
21556
|
-
url: "https://github.com/whittlelabs/whittlelabs.git",
|
|
21557
|
-
directory: "apps/whittle-sifter"
|
|
21558
|
-
},
|
|
21559
|
-
author: "Whittle Labs",
|
|
21560
|
-
license: "UNLICENSED",
|
|
21561
|
-
private: false,
|
|
21562
|
-
dependencies: {
|
|
21563
|
-
"@whittlelabs/shuttle": "workspace:*",
|
|
21564
|
-
yaml: "^2.7.1"
|
|
21565
|
-
},
|
|
21566
|
-
devDependencies: {
|
|
21567
|
-
"@types/node": "^20.10.5",
|
|
21568
|
-
esbuild: "^0.25.0",
|
|
21569
|
-
tsx: "^4.21.0",
|
|
21570
|
-
typescript: "^5.3.3"
|
|
21571
|
-
}
|
|
21572
|
-
};
|
|
21573
|
-
|
|
21574
|
-
// src/brand.ts
|
|
21984
|
+
var buildVersion = true ? "0.17.0" : pkg.version;
|
|
21575
21985
|
var sifterBrand = {
|
|
21576
21986
|
product: {
|
|
21577
21987
|
id: "sifter",
|
|
21578
21988
|
title: "Whittle Sifter",
|
|
21579
21989
|
cliBinary: "whittle-sifter",
|
|
21580
21990
|
packageName: "@whittlelabs/sifter",
|
|
21581
|
-
version:
|
|
21991
|
+
version: buildVersion,
|
|
21582
21992
|
description: "Pairs with Whittle Sift to run AI code reviews on your hardware with your credentials."
|
|
21583
21993
|
},
|
|
21584
21994
|
paths: {
|
|
@@ -21723,7 +22133,7 @@ async function buildExecutorBlock(executor, ctx) {
|
|
|
21723
22133
|
console.error("Keep refused its credentials \u2014 it was decommissioned from the");
|
|
21724
22134
|
console.error("web app, or its secret was rotated by a newer pairing.");
|
|
21725
22135
|
console.error("");
|
|
21726
|
-
console.error(`Run \`${sifterBrand.product.
|
|
22136
|
+
console.error(`Run \`${sifterBrand.product.cliBinary} init\` to pair again.`);
|
|
21727
22137
|
console.error("");
|
|
21728
22138
|
process.exit(3);
|
|
21729
22139
|
}
|