@whittlelabs/sifter 0.14.0 → 0.16.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 +256 -129
- package/bin.js.map +4 -4
- package/package.json +1 -1
package/bin.js
CHANGED
|
@@ -3538,7 +3538,7 @@ var require_init = __commonJS({
|
|
|
3538
3538
|
var store_1 = require_store();
|
|
3539
3539
|
var spend_store_1 = require_spend_store();
|
|
3540
3540
|
function buildInitCommand(brand) {
|
|
3541
|
-
return new commander_1.Command("init").description(`Pair this ${brand.product.title} with Keep and seed local state`).option("--pairing-code <code>", "Pairing code from the product UI").option("--keep-url <url>", "Override the Keep API URL from BrandConfig").option("--jobs-url <url>", "Override the Jobs API URL from BrandConfig").option("--executor <type>", `Executor to write into ${brand.product.id}.yaml (e.g. claude-code). Must be in the brand's executor allowlist.`).option("--poll-interval <ms>", "Run-loop poll interval in milliseconds").option("--daily-cap <tokens>", "Daily token cap; overrides the brand default").option("--monthly-cap <tokens>", "Monthly token cap; overrides the brand default").option("--
|
|
3541
|
+
return new commander_1.Command("init").description(`Pair this ${brand.product.title} with Keep and seed local state`).option("--pairing-code <code>", "Pairing code from the product UI").option("--keep-url <url>", "Override the Keep API URL from BrandConfig").option("--jobs-url <url>", "Override the Jobs API URL from BrandConfig").option("--executor <type>", `Executor to write into ${brand.product.id}.yaml (e.g. claude-code). Must be in the brand's executor allowlist.`).option("--poll-interval <ms>", "Run-loop poll interval in milliseconds").option("--daily-cap <tokens>", "Daily token cap; overrides the brand default").option("--monthly-cap <tokens>", "Monthly token cap; overrides the brand default").option("--tmp-dir <path>", "Base directory for each job's throwaway checkout (defaults to the OS temp dir; set only when that is unsuitable)").option("-p, --profile <name>", 'Config profile to pair into (isolates this pairing under its own dir; default "default")').action(async (options) => {
|
|
3542
3542
|
try {
|
|
3543
3543
|
const profile = (0, apply_1.resolveProfile)(brand, options.profile);
|
|
3544
3544
|
const setupOptions = parseSetupOptions(brand, options);
|
|
@@ -3621,8 +3621,8 @@ var require_init = __commonJS({
|
|
|
3621
3621
|
if (raw.monthlyCap !== void 0) {
|
|
3622
3622
|
out.monthlyCap = requireNonNegativeInt("--monthly-cap", raw.monthlyCap);
|
|
3623
3623
|
}
|
|
3624
|
-
if (raw.
|
|
3625
|
-
out.
|
|
3624
|
+
if (raw.tmpDir !== void 0) {
|
|
3625
|
+
out.tmpDir = raw.tmpDir;
|
|
3626
3626
|
}
|
|
3627
3627
|
return out;
|
|
3628
3628
|
}
|
|
@@ -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
|
});
|
|
@@ -16007,6 +16020,8 @@ var require_prompt_execution2 = __commonJS({
|
|
|
16007
16020
|
spec.providerHints = options.providerHints;
|
|
16008
16021
|
if (options.costCapHint)
|
|
16009
16022
|
spec.costCapHint = options.costCapHint;
|
|
16023
|
+
if (options.progressChannel !== void 0)
|
|
16024
|
+
spec.progressChannel = options.progressChannel;
|
|
16010
16025
|
if (options.rendering === "producer" && !options.prompt) {
|
|
16011
16026
|
throw new Error("rendering='producer' requires `prompt`");
|
|
16012
16027
|
}
|
|
@@ -18880,6 +18895,155 @@ var require_validate_output = __commonJS({
|
|
|
18880
18895
|
}
|
|
18881
18896
|
});
|
|
18882
18897
|
|
|
18898
|
+
// ../../packages/shuttle/dist/executors/progress.js
|
|
18899
|
+
var require_progress = __commonJS({
|
|
18900
|
+
"../../packages/shuttle/dist/executors/progress.js"(exports2) {
|
|
18901
|
+
"use strict";
|
|
18902
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18903
|
+
exports2.StreamJsonTap = exports2.ProgressReporter = void 0;
|
|
18904
|
+
var ProgressReporter = class {
|
|
18905
|
+
sink;
|
|
18906
|
+
seq = 0;
|
|
18907
|
+
pending = [];
|
|
18908
|
+
textBuf = "";
|
|
18909
|
+
timer = null;
|
|
18910
|
+
closed = false;
|
|
18911
|
+
flushMs;
|
|
18912
|
+
maxChars;
|
|
18913
|
+
constructor(sink, options = {}) {
|
|
18914
|
+
this.sink = sink;
|
|
18915
|
+
this.flushMs = options.flushMs ?? 400;
|
|
18916
|
+
this.maxChars = options.maxChars ?? 1200;
|
|
18917
|
+
}
|
|
18918
|
+
reasoning(text) {
|
|
18919
|
+
if (this.closed || !text)
|
|
18920
|
+
return;
|
|
18921
|
+
this.textBuf += text;
|
|
18922
|
+
if (this.textBuf.length >= this.maxChars) {
|
|
18923
|
+
this.flush();
|
|
18924
|
+
} else {
|
|
18925
|
+
this.arm();
|
|
18926
|
+
}
|
|
18927
|
+
}
|
|
18928
|
+
tool(name, target) {
|
|
18929
|
+
if (this.closed || !name)
|
|
18930
|
+
return;
|
|
18931
|
+
this.drainText();
|
|
18932
|
+
this.push({ kind: "tool", tool: target ? { name, target } : { name } });
|
|
18933
|
+
this.flush();
|
|
18934
|
+
}
|
|
18935
|
+
/** Flush pending text + frames now. */
|
|
18936
|
+
flush() {
|
|
18937
|
+
if (this.closed)
|
|
18938
|
+
return;
|
|
18939
|
+
this.drainText();
|
|
18940
|
+
this.clearTimer();
|
|
18941
|
+
if (this.pending.length === 0)
|
|
18942
|
+
return;
|
|
18943
|
+
const batch = this.pending;
|
|
18944
|
+
this.pending = [];
|
|
18945
|
+
try {
|
|
18946
|
+
this.sink(batch);
|
|
18947
|
+
} catch {
|
|
18948
|
+
}
|
|
18949
|
+
}
|
|
18950
|
+
/** Final flush; no further frames are emitted after this. */
|
|
18951
|
+
close() {
|
|
18952
|
+
this.flush();
|
|
18953
|
+
this.closed = true;
|
|
18954
|
+
this.clearTimer();
|
|
18955
|
+
}
|
|
18956
|
+
drainText() {
|
|
18957
|
+
if (this.textBuf.length === 0)
|
|
18958
|
+
return;
|
|
18959
|
+
this.push({ kind: "reasoning", text: this.textBuf });
|
|
18960
|
+
this.textBuf = "";
|
|
18961
|
+
}
|
|
18962
|
+
push(frame) {
|
|
18963
|
+
this.pending.push({ seq: this.seq++, at: (/* @__PURE__ */ new Date()).toISOString(), ...frame });
|
|
18964
|
+
}
|
|
18965
|
+
arm() {
|
|
18966
|
+
if (this.timer)
|
|
18967
|
+
return;
|
|
18968
|
+
this.timer = setTimeout(() => {
|
|
18969
|
+
this.timer = null;
|
|
18970
|
+
this.flush();
|
|
18971
|
+
}, this.flushMs);
|
|
18972
|
+
this.timer.unref?.();
|
|
18973
|
+
}
|
|
18974
|
+
clearTimer() {
|
|
18975
|
+
if (this.timer) {
|
|
18976
|
+
clearTimeout(this.timer);
|
|
18977
|
+
this.timer = null;
|
|
18978
|
+
}
|
|
18979
|
+
}
|
|
18980
|
+
};
|
|
18981
|
+
exports2.ProgressReporter = ProgressReporter;
|
|
18982
|
+
var StreamJsonTap = class {
|
|
18983
|
+
handlers;
|
|
18984
|
+
buf = "";
|
|
18985
|
+
constructor(handlers) {
|
|
18986
|
+
this.handlers = handlers;
|
|
18987
|
+
}
|
|
18988
|
+
/** Feed a raw stdout chunk. Emits handlers for every complete line parsed. */
|
|
18989
|
+
push(chunk) {
|
|
18990
|
+
this.buf += chunk;
|
|
18991
|
+
let nl;
|
|
18992
|
+
while ((nl = this.buf.indexOf("\n")) !== -1) {
|
|
18993
|
+
const line = this.buf.slice(0, nl);
|
|
18994
|
+
this.buf = this.buf.slice(nl + 1);
|
|
18995
|
+
this.consumeLine(line);
|
|
18996
|
+
}
|
|
18997
|
+
}
|
|
18998
|
+
/** Flush a trailing partial line (best-effort; usually empty at process close). */
|
|
18999
|
+
end() {
|
|
19000
|
+
if (this.buf.trim().length > 0)
|
|
19001
|
+
this.consumeLine(this.buf);
|
|
19002
|
+
this.buf = "";
|
|
19003
|
+
}
|
|
19004
|
+
consumeLine(line) {
|
|
19005
|
+
const trimmed = line.trim();
|
|
19006
|
+
if (!trimmed.startsWith("{"))
|
|
19007
|
+
return;
|
|
19008
|
+
let event;
|
|
19009
|
+
try {
|
|
19010
|
+
event = JSON.parse(trimmed);
|
|
19011
|
+
} catch {
|
|
19012
|
+
return;
|
|
19013
|
+
}
|
|
19014
|
+
if (event.type === "assistant") {
|
|
19015
|
+
const content = event.message?.content;
|
|
19016
|
+
if (!Array.isArray(content))
|
|
19017
|
+
return;
|
|
19018
|
+
for (const block of content) {
|
|
19019
|
+
const b = block;
|
|
19020
|
+
if (b?.type === "text" && typeof b.text === "string" && b.text.length > 0) {
|
|
19021
|
+
this.handlers.onReasoning?.(b.text);
|
|
19022
|
+
} else if (b?.type === "thinking" && typeof b.thinking === "string" && b.thinking.length > 0) {
|
|
19023
|
+
this.handlers.onReasoning?.(b.thinking);
|
|
19024
|
+
} else if (b?.type === "tool_use" && typeof b.name === "string") {
|
|
19025
|
+
this.handlers.onTool?.(b.name, toolTarget(b.input));
|
|
19026
|
+
}
|
|
19027
|
+
}
|
|
19028
|
+
} else if (event.type === "result") {
|
|
19029
|
+
this.handlers.onResult?.();
|
|
19030
|
+
}
|
|
19031
|
+
}
|
|
19032
|
+
};
|
|
19033
|
+
exports2.StreamJsonTap = StreamJsonTap;
|
|
19034
|
+
function toolTarget(input) {
|
|
19035
|
+
if (!input)
|
|
19036
|
+
return void 0;
|
|
19037
|
+
for (const key of ["file_path", "path", "pattern", "command", "url", "query", "notebook_path"]) {
|
|
19038
|
+
const v = input[key];
|
|
19039
|
+
if (typeof v === "string" && v.length > 0)
|
|
19040
|
+
return v;
|
|
19041
|
+
}
|
|
19042
|
+
return void 0;
|
|
19043
|
+
}
|
|
19044
|
+
}
|
|
19045
|
+
});
|
|
19046
|
+
|
|
18883
19047
|
// ../../packages/shuttle/dist/executors/workspace.js
|
|
18884
19048
|
var require_workspace = __commonJS({
|
|
18885
19049
|
"../../packages/shuttle/dist/executors/workspace.js"(exports2) {
|
|
@@ -18896,10 +19060,11 @@ var require_workspace = __commonJS({
|
|
|
18896
19060
|
var path_1 = __importDefault(require("path"));
|
|
18897
19061
|
var DEFAULT_GIT_TIMEOUT_MS = 12e4;
|
|
18898
19062
|
var DEFAULT_GIT_PROTOCOLS = "https:ssh:git";
|
|
18899
|
-
async function prepareWorkspace(
|
|
19063
|
+
async function prepareWorkspace(workspace, options = {}) {
|
|
19064
|
+
const tmpBase = options.tmpDir ?? os_1.default.tmpdir();
|
|
18900
19065
|
const resolved = readGitWorkspace(workspace);
|
|
18901
19066
|
if ("warning" in resolved) {
|
|
18902
|
-
return degradedWorkspace(resolved.warning);
|
|
19067
|
+
return degradedWorkspace(resolved.warning, tmpBase);
|
|
18903
19068
|
}
|
|
18904
19069
|
const gitWorkspace = resolved.git;
|
|
18905
19070
|
const timeoutMs = options.commandTimeoutMs ?? DEFAULT_GIT_TIMEOUT_MS;
|
|
@@ -18907,7 +19072,6 @@ var require_workspace = __commonJS({
|
|
|
18907
19072
|
const signal = options.signal;
|
|
18908
19073
|
const authEnv = options.gitToken !== void 0 ? buildGitAuthEnv(gitWorkspace.remote, options.gitToken) : void 0;
|
|
18909
19074
|
const git = (args, cwd) => runGit(args, cwd, { timeoutMs, signal, allowedProtocols, extraEnv: authEnv });
|
|
18910
|
-
const gitCleanup = (args, cwd) => runGit(args, cwd, { timeoutMs, allowedProtocols });
|
|
18911
19075
|
const hasCommit = (dir) => git(["cat-file", "-e", `${gitWorkspace.commit}^{commit}`], dir).then((r) => r.code === 0);
|
|
18912
19076
|
const fetchCommit = async (dir, opts) => {
|
|
18913
19077
|
if (await hasCommit(dir))
|
|
@@ -18915,9 +19079,6 @@ var require_workspace = __commonJS({
|
|
|
18915
19079
|
const depth = opts.shallow ? ["--depth=1"] : [];
|
|
18916
19080
|
const attempts = [];
|
|
18917
19081
|
for (const ref of gitWorkspace.fetchRefs ?? []) {
|
|
18918
|
-
if (opts.includeOrigin) {
|
|
18919
|
-
attempts.push({ source: `origin ${ref}`, args: ["fetch", "--no-tags", ...depth, "origin", ref] });
|
|
18920
|
-
}
|
|
18921
19082
|
attempts.push({
|
|
18922
19083
|
source: `${gitWorkspace.remote} ${ref}`,
|
|
18923
19084
|
args: ["fetch", "--no-tags", ...depth, gitWorkspace.remote, ref]
|
|
@@ -18941,31 +19102,7 @@ var require_workspace = __commonJS({
|
|
|
18941
19102
|
return failures2;
|
|
18942
19103
|
};
|
|
18943
19104
|
const notMaterialized = (failures2) => `could not materialize ${gitWorkspace.commit} from ${gitWorkspace.remote}` + (failures2.length > 0 ? ` (${failures2.join("; ")})` : "");
|
|
18944
|
-
const
|
|
18945
|
-
if (isRepo) {
|
|
18946
|
-
const failures2 = await fetchCommit(repoDir, { includeOrigin: true, shallow: false });
|
|
18947
|
-
if (!await hasCommit(repoDir))
|
|
18948
|
-
return degradedWorkspace(notMaterialized(failures2));
|
|
18949
|
-
const tempRoot2 = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-ws-"));
|
|
18950
|
-
const worktreeDir = path_1.default.join(tempRoot2, "checkout");
|
|
18951
|
-
const add = await git(["worktree", "add", "--detach", worktreeDir, gitWorkspace.commit], repoDir);
|
|
18952
|
-
if (add.code !== 0) {
|
|
18953
|
-
await fs_1.promises.rm(tempRoot2, { recursive: true, force: true }).catch(() => {
|
|
18954
|
-
});
|
|
18955
|
-
return degradedWorkspace(`git worktree add failed: ${firstLine(add.stderr)}`);
|
|
18956
|
-
}
|
|
18957
|
-
return {
|
|
18958
|
-
cwd: worktreeDir,
|
|
18959
|
-
materialized: true,
|
|
18960
|
-
cleanup: async () => {
|
|
18961
|
-
await gitCleanup(["worktree", "remove", "--force", worktreeDir], repoDir);
|
|
18962
|
-
await gitCleanup(["worktree", "prune"], repoDir);
|
|
18963
|
-
await fs_1.promises.rm(tempRoot2, { recursive: true, force: true }).catch(() => {
|
|
18964
|
-
});
|
|
18965
|
-
}
|
|
18966
|
-
};
|
|
18967
|
-
}
|
|
18968
|
-
const tempRoot = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-ws-"));
|
|
19105
|
+
const tempRoot = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-ws-"));
|
|
18969
19106
|
const checkoutDir = path_1.default.join(tempRoot, "checkout");
|
|
18970
19107
|
const removeTempRoot = async () => {
|
|
18971
19108
|
await fs_1.promises.rm(tempRoot, { recursive: true, force: true }).catch(() => {
|
|
@@ -18975,17 +19112,17 @@ var require_workspace = __commonJS({
|
|
|
18975
19112
|
const init = await git(["init", "-q"], checkoutDir);
|
|
18976
19113
|
if (init.code !== 0) {
|
|
18977
19114
|
await removeTempRoot();
|
|
18978
|
-
return degradedWorkspace(`git init failed: ${firstLine(init.stderr)}
|
|
19115
|
+
return degradedWorkspace(`git init failed: ${firstLine(init.stderr)}`, tmpBase);
|
|
18979
19116
|
}
|
|
18980
|
-
const failures = await fetchCommit(checkoutDir, {
|
|
19117
|
+
const failures = await fetchCommit(checkoutDir, { shallow: true });
|
|
18981
19118
|
if (!await hasCommit(checkoutDir)) {
|
|
18982
19119
|
await removeTempRoot();
|
|
18983
|
-
return degradedWorkspace(notMaterialized(failures));
|
|
19120
|
+
return degradedWorkspace(notMaterialized(failures), tmpBase);
|
|
18984
19121
|
}
|
|
18985
19122
|
const checkout = await git(["checkout", "--detach", gitWorkspace.commit], checkoutDir);
|
|
18986
19123
|
if (checkout.code !== 0) {
|
|
18987
19124
|
await removeTempRoot();
|
|
18988
|
-
return degradedWorkspace(`git checkout failed: ${firstLine(checkout.stderr)}
|
|
19125
|
+
return degradedWorkspace(`git checkout failed: ${firstLine(checkout.stderr)}`, tmpBase);
|
|
18989
19126
|
}
|
|
18990
19127
|
return { cwd: checkoutDir, materialized: true, cleanup: removeTempRoot };
|
|
18991
19128
|
}
|
|
@@ -19033,8 +19170,8 @@ var require_workspace = __commonJS({
|
|
|
19033
19170
|
}
|
|
19034
19171
|
return { git };
|
|
19035
19172
|
}
|
|
19036
|
-
async function degradedWorkspace(warning) {
|
|
19037
|
-
const scratchDir = await fs_1.promises.mkdtemp(path_1.default.join(
|
|
19173
|
+
async function degradedWorkspace(warning, tmpBase = os_1.default.tmpdir()) {
|
|
19174
|
+
const scratchDir = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-ws-degraded-"));
|
|
19038
19175
|
return {
|
|
19039
19176
|
cwd: scratchDir,
|
|
19040
19177
|
materialized: false,
|
|
@@ -19138,7 +19275,7 @@ var require_overlay = __commonJS({
|
|
|
19138
19275
|
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
19139
19276
|
const allowed = new Set(options.allowedHosts.map(normalizeOrigin).filter((o) => !!o));
|
|
19140
19277
|
const rootReal = await fs_1.promises.realpath(runDir);
|
|
19141
|
-
const result = { written: 0, skipped: [] };
|
|
19278
|
+
const result = { written: 0, writtenPaths: [], skipped: [] };
|
|
19142
19279
|
const files = Array.isArray(overlay.files) ? overlay.files : [];
|
|
19143
19280
|
for (const file of files) {
|
|
19144
19281
|
const skip = (reason) => result.skipped.push({ path: String(file?.path ?? "?"), reason });
|
|
@@ -19172,6 +19309,7 @@ var require_overlay = __commonJS({
|
|
|
19172
19309
|
await fs_1.promises.mkdir(path_1.default.dirname(dest), { recursive: true });
|
|
19173
19310
|
await fs_1.promises.writeFile(dest, content);
|
|
19174
19311
|
result.written += 1;
|
|
19312
|
+
result.writtenPaths.push(file.path);
|
|
19175
19313
|
} catch (err) {
|
|
19176
19314
|
skip(err instanceof Error ? err.message : String(err));
|
|
19177
19315
|
}
|
|
@@ -19285,9 +19423,11 @@ var require_claude_code = __commonJS({
|
|
|
19285
19423
|
var fs_1 = require("fs");
|
|
19286
19424
|
var os_1 = __importDefault(require("os"));
|
|
19287
19425
|
var path_1 = __importDefault(require("path"));
|
|
19426
|
+
var string_decoder_1 = require("string_decoder");
|
|
19288
19427
|
var loom_1 = require_dist3();
|
|
19289
19428
|
var keep_1 = require_dist4();
|
|
19290
19429
|
var validate_output_1 = require_validate_output();
|
|
19430
|
+
var progress_1 = require_progress();
|
|
19291
19431
|
var workspace_1 = require_workspace();
|
|
19292
19432
|
var overlay_1 = require_overlay();
|
|
19293
19433
|
var env_ref_1 = require_env_ref();
|
|
@@ -19308,7 +19448,7 @@ var require_claude_code = __commonJS({
|
|
|
19308
19448
|
outputTokens: spec.costCapHint?.estimatedOutputTokens
|
|
19309
19449
|
};
|
|
19310
19450
|
}
|
|
19311
|
-
async execute(dispatch, signal) {
|
|
19451
|
+
async execute(dispatch, signal, ctx) {
|
|
19312
19452
|
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
19313
19453
|
let credentials = null;
|
|
19314
19454
|
if (this.config.resolveCredentials) {
|
|
@@ -19329,12 +19469,16 @@ var require_claude_code = __commonJS({
|
|
|
19329
19469
|
configDir = path_1.default.join(jobScratch, "claude-config");
|
|
19330
19470
|
await fs_1.promises.mkdir(configDir);
|
|
19331
19471
|
}
|
|
19472
|
+
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
19332
19473
|
let workspace = null;
|
|
19474
|
+
let promptOnlyScratch = null;
|
|
19333
19475
|
let overlayResult = null;
|
|
19476
|
+
const reporter = spec.progressChannel && ctx?.reportProgress ? new progress_1.ProgressReporter((frames) => ctx.reportProgress(spec.progressChannel, frames)) : null;
|
|
19334
19477
|
try {
|
|
19335
19478
|
if (spec.workspace) {
|
|
19336
|
-
workspace = await (0, workspace_1.prepareWorkspace)(
|
|
19479
|
+
workspace = await (0, workspace_1.prepareWorkspace)(spec.workspace, {
|
|
19337
19480
|
signal,
|
|
19481
|
+
tmpDir: tmpBase,
|
|
19338
19482
|
...credentials?.githubToken ? { gitToken: credentials.githubToken } : {}
|
|
19339
19483
|
});
|
|
19340
19484
|
if (spec.workspace.overlay) {
|
|
@@ -19347,12 +19491,20 @@ var require_claude_code = __commonJS({
|
|
|
19347
19491
|
const childEnv = buildChildEnv(credentials, configDir);
|
|
19348
19492
|
const claudeHints = (0, loom_1.pickProviderHints)(spec, "claude-code");
|
|
19349
19493
|
const hintedModel = typeof claudeHints.model === "string" && claudeHints.model.length > 0 ? claudeHints.model : void 0;
|
|
19350
|
-
|
|
19351
|
-
|
|
19494
|
+
if (!workspace) {
|
|
19495
|
+
promptOnlyScratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-run-"));
|
|
19496
|
+
}
|
|
19497
|
+
const runCwd = workspace?.cwd ?? promptOnlyScratch;
|
|
19498
|
+
const { response, readPaths, toolText } = await this.runClaude(prompt, spec, runCwd, childEnv, signal, hintedModel, reporter);
|
|
19352
19499
|
if (workspace) {
|
|
19500
|
+
const reads = [...readPaths];
|
|
19501
|
+
for (const rel of overlayResult?.writtenPaths ?? []) {
|
|
19502
|
+
if (toolText.includes(rel))
|
|
19503
|
+
reads.push(path_1.default.join(runCwd, rel));
|
|
19504
|
+
}
|
|
19353
19505
|
response.outputs.push({
|
|
19354
19506
|
label: "workspace_reads",
|
|
19355
|
-
content: { paths: relativeWorkspaceReads(
|
|
19507
|
+
content: { paths: relativeWorkspaceReads(reads, runCwd) }
|
|
19356
19508
|
});
|
|
19357
19509
|
response.outputs.push({
|
|
19358
19510
|
label: "workspace",
|
|
@@ -19370,14 +19522,18 @@ var require_claude_code = __commonJS({
|
|
|
19370
19522
|
}
|
|
19371
19523
|
return response;
|
|
19372
19524
|
} finally {
|
|
19525
|
+
reporter?.close();
|
|
19373
19526
|
if (workspace)
|
|
19374
19527
|
await workspace.cleanup();
|
|
19528
|
+
if (promptOnlyScratch)
|
|
19529
|
+
await fs_1.promises.rm(promptOnlyScratch, { recursive: true, force: true }).catch(() => {
|
|
19530
|
+
});
|
|
19375
19531
|
if (jobScratch)
|
|
19376
19532
|
await fs_1.promises.rm(jobScratch, { recursive: true, force: true }).catch(() => {
|
|
19377
19533
|
});
|
|
19378
19534
|
}
|
|
19379
19535
|
}
|
|
19380
|
-
runClaude(prompt, spec, cwd, childEnv, signal, modelOverride) {
|
|
19536
|
+
runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter) {
|
|
19381
19537
|
const outputLabel = spec.outputLabel;
|
|
19382
19538
|
const model = modelOverride ?? this.config.model;
|
|
19383
19539
|
return new Promise((resolve, reject) => {
|
|
@@ -19413,7 +19569,17 @@ var require_claude_code = __commonJS({
|
|
|
19413
19569
|
settled = true;
|
|
19414
19570
|
fn();
|
|
19415
19571
|
};
|
|
19416
|
-
|
|
19572
|
+
const tap = reporter ? new progress_1.StreamJsonTap({
|
|
19573
|
+
onReasoning: (t) => reporter.reasoning(t),
|
|
19574
|
+
onTool: (name, target) => reporter.tool(name, target),
|
|
19575
|
+
onResult: () => reporter.flush()
|
|
19576
|
+
}) : null;
|
|
19577
|
+
const decoder = tap ? new string_decoder_1.StringDecoder("utf8") : null;
|
|
19578
|
+
child.stdout.on("data", (chunk) => {
|
|
19579
|
+
stdoutChunks.push(chunk);
|
|
19580
|
+
if (tap && decoder)
|
|
19581
|
+
tap.push(decoder.write(chunk));
|
|
19582
|
+
});
|
|
19417
19583
|
child.stdin.write(prompt);
|
|
19418
19584
|
child.stdin.end();
|
|
19419
19585
|
const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;
|
|
@@ -19440,6 +19606,10 @@ var require_claude_code = __commonJS({
|
|
|
19440
19606
|
child.on("close", (code) => {
|
|
19441
19607
|
clearTimeout(timer);
|
|
19442
19608
|
signal.removeEventListener("abort", onAbort);
|
|
19609
|
+
if (tap && decoder) {
|
|
19610
|
+
tap.push(decoder.end());
|
|
19611
|
+
tap.end();
|
|
19612
|
+
}
|
|
19443
19613
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
19444
19614
|
if (code === 0) {
|
|
19445
19615
|
try {
|
|
@@ -19464,7 +19634,8 @@ var require_claude_code = __commonJS({
|
|
|
19464
19634
|
...stream.usage ?? {}
|
|
19465
19635
|
}
|
|
19466
19636
|
},
|
|
19467
|
-
readPaths: stream.readPaths
|
|
19637
|
+
readPaths: stream.readPaths,
|
|
19638
|
+
toolText: stream.toolText
|
|
19468
19639
|
}));
|
|
19469
19640
|
} catch (err) {
|
|
19470
19641
|
settle(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
@@ -19477,12 +19648,13 @@ var require_claude_code = __commonJS({
|
|
|
19477
19648
|
}
|
|
19478
19649
|
};
|
|
19479
19650
|
function createClaudeCodeExecutor(config, capabilityId = "claude-code", deps = {}) {
|
|
19480
|
-
const
|
|
19481
|
-
if (
|
|
19482
|
-
|
|
19651
|
+
const validated = { capabilityId };
|
|
19652
|
+
if (config.tmpDir !== void 0) {
|
|
19653
|
+
if (typeof config.tmpDir !== "string" || config.tmpDir.length === 0) {
|
|
19654
|
+
throw new Error('"tmpDir" must be a non-empty string');
|
|
19655
|
+
}
|
|
19656
|
+
validated.tmpDir = config.tmpDir;
|
|
19483
19657
|
}
|
|
19484
|
-
const cwd = (0, apply_1.expandHome)(cwdRaw);
|
|
19485
|
-
const validated = { cwd, capabilityId };
|
|
19486
19658
|
if (config.model !== void 0) {
|
|
19487
19659
|
if (typeof config.model !== "string")
|
|
19488
19660
|
throw new Error('"model" must be a string');
|
|
@@ -19560,6 +19732,7 @@ var require_claude_code = __commonJS({
|
|
|
19560
19732
|
let usage = null;
|
|
19561
19733
|
let resultMeta = null;
|
|
19562
19734
|
let structuredOutput = null;
|
|
19735
|
+
let toolText = "";
|
|
19563
19736
|
for (const line of stdout.split("\n")) {
|
|
19564
19737
|
const trimmed = line.trim();
|
|
19565
19738
|
if (!trimmed.startsWith("{"))
|
|
@@ -19576,9 +19749,14 @@ var require_claude_code = __commonJS({
|
|
|
19576
19749
|
continue;
|
|
19577
19750
|
for (const block of content) {
|
|
19578
19751
|
const b = block;
|
|
19579
|
-
if (b?.type
|
|
19580
|
-
|
|
19752
|
+
if (b?.type !== "tool_use")
|
|
19753
|
+
continue;
|
|
19754
|
+
const fp = b.input?.file_path;
|
|
19755
|
+
if (b.name === "Read" && typeof fp === "string") {
|
|
19756
|
+
readPaths.push(fp);
|
|
19581
19757
|
}
|
|
19758
|
+
if (b.input && typeof b.input === "object")
|
|
19759
|
+
toolText += JSON.stringify(b.input);
|
|
19582
19760
|
}
|
|
19583
19761
|
} else if (event.type === "result") {
|
|
19584
19762
|
if (typeof event.result === "string" && event.result.trim().length > 0) {
|
|
@@ -19601,7 +19779,7 @@ var require_claude_code = __commonJS({
|
|
|
19601
19779
|
};
|
|
19602
19780
|
}
|
|
19603
19781
|
}
|
|
19604
|
-
return { finalText, readPaths, usage, resultMeta, structuredOutput };
|
|
19782
|
+
return { finalText, readPaths, usage, resultMeta, structuredOutput, toolText };
|
|
19605
19783
|
}
|
|
19606
19784
|
var MAX_REPORTED_READS = 2e3;
|
|
19607
19785
|
function relativeWorkspaceReads(readPaths, cwd) {
|
|
@@ -20864,9 +21042,15 @@ var require_shuttle = __commonJS({
|
|
|
20864
21042
|
}
|
|
20865
21043
|
await observerChain.notify({ kind: "executor.dispatch.started", jobId, attendanceId, executor: executor.capability.id, strategy }, onObserverError);
|
|
20866
21044
|
const dispatchStartedAt = Date.now();
|
|
21045
|
+
const executionCtx = {
|
|
21046
|
+
reportProgress: (channel, frames) => {
|
|
21047
|
+
void jobsClient.postProgress(jobId, { channel, frames }).catch(() => {
|
|
21048
|
+
});
|
|
21049
|
+
}
|
|
21050
|
+
};
|
|
20867
21051
|
let response;
|
|
20868
21052
|
try {
|
|
20869
|
-
response = await executor.execute(dispatch, signal);
|
|
21053
|
+
response = await executor.execute(dispatch, signal, executionCtx);
|
|
20870
21054
|
} catch (err) {
|
|
20871
21055
|
const reason = `executor failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
20872
21056
|
await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
|
|
@@ -21542,61 +21726,16 @@ var import_shuttle2 = __toESM(require_dist5());
|
|
|
21542
21726
|
// src/brand.ts
|
|
21543
21727
|
var import_path = require("path");
|
|
21544
21728
|
var import_promises = require("fs/promises");
|
|
21545
|
-
var import_readline = require("readline");
|
|
21546
21729
|
var import_yaml = __toESM(require_dist());
|
|
21547
21730
|
var import_shuttle = __toESM(require_dist5());
|
|
21548
|
-
|
|
21549
|
-
// package.json
|
|
21550
|
-
var package_default = {
|
|
21551
|
-
name: "@whittlelabs/sifter",
|
|
21552
|
-
version: "0.14.0",
|
|
21553
|
-
description: "Whittle Sifter: paired AI reviewer for Whittle Sift job pools.",
|
|
21554
|
-
bin: {
|
|
21555
|
-
"whittle-sifter": "./dist/bin.js"
|
|
21556
|
-
},
|
|
21557
|
-
main: "dist/bin.js",
|
|
21558
|
-
engines: {
|
|
21559
|
-
node: ">=20"
|
|
21560
|
-
},
|
|
21561
|
-
scripts: {
|
|
21562
|
-
dev: "tsx watch src/bin.ts",
|
|
21563
|
-
build: "node scripts/bundle.mjs",
|
|
21564
|
-
typecheck: "tsc --noEmit",
|
|
21565
|
-
start: "node dist/bin.js",
|
|
21566
|
-
lint: "eslint src/"
|
|
21567
|
-
},
|
|
21568
|
-
publishConfig: {
|
|
21569
|
-
registry: "https://registry.npmjs.org/",
|
|
21570
|
-
access: "public"
|
|
21571
|
-
},
|
|
21572
|
-
repository: {
|
|
21573
|
-
type: "git",
|
|
21574
|
-
url: "https://github.com/whittlelabs/whittlelabs.git",
|
|
21575
|
-
directory: "apps/whittle-sifter"
|
|
21576
|
-
},
|
|
21577
|
-
author: "Whittle Labs",
|
|
21578
|
-
license: "UNLICENSED",
|
|
21579
|
-
private: false,
|
|
21580
|
-
dependencies: {
|
|
21581
|
-
"@whittlelabs/shuttle": "workspace:*",
|
|
21582
|
-
yaml: "^2.7.1"
|
|
21583
|
-
},
|
|
21584
|
-
devDependencies: {
|
|
21585
|
-
"@types/node": "^20.10.5",
|
|
21586
|
-
esbuild: "^0.25.0",
|
|
21587
|
-
tsx: "^4.21.0",
|
|
21588
|
-
typescript: "^5.3.3"
|
|
21589
|
-
}
|
|
21590
|
-
};
|
|
21591
|
-
|
|
21592
|
-
// src/brand.ts
|
|
21731
|
+
var buildVersion = true ? "0.16.0" : pkg.version;
|
|
21593
21732
|
var sifterBrand = {
|
|
21594
21733
|
product: {
|
|
21595
21734
|
id: "sifter",
|
|
21596
21735
|
title: "Whittle Sifter",
|
|
21597
21736
|
cliBinary: "whittle-sifter",
|
|
21598
21737
|
packageName: "@whittlelabs/sifter",
|
|
21599
|
-
version:
|
|
21738
|
+
version: buildVersion,
|
|
21600
21739
|
description: "Pairs with Whittle Sift to run AI code reviews on your hardware with your credentials."
|
|
21601
21740
|
},
|
|
21602
21741
|
paths: {
|
|
@@ -21646,12 +21785,11 @@ var sifterBrand = {
|
|
|
21646
21785
|
// A re-pair must refresh the pairing-derived fields (Jobs URL, service
|
|
21647
21786
|
// identity, pools) even when sifter.yaml already exists — otherwise a
|
|
21648
21787
|
// stale file silently shadows the new pairing (exactly how a leftover
|
|
21649
|
-
// `localhost:3005`
|
|
21650
|
-
//
|
|
21651
|
-
//
|
|
21652
|
-
//
|
|
21653
|
-
//
|
|
21654
|
-
// isn't one to preserve.
|
|
21788
|
+
// `localhost:3005` once survived a fresh pair). So we merge rather than
|
|
21789
|
+
// skip: rebuild the pairing fields from `pairing`, carry over the user's
|
|
21790
|
+
// executor block (e.g. a claude-code `tmpDir`) and polling unless a fresh
|
|
21791
|
+
// flag overrides them, and log what happened. We only (re)scaffold an
|
|
21792
|
+
// executor block when there isn't one to preserve.
|
|
21655
21793
|
run: async (ctx) => {
|
|
21656
21794
|
const yamlPath = (0, import_path.join)(ctx.configDir, "sifter.yaml");
|
|
21657
21795
|
const pairing = new import_shuttle.PairingConfigStore((0, import_path.join)(ctx.configDir, "config.json")).read();
|
|
@@ -21706,8 +21844,10 @@ var sifterBrand = {
|
|
|
21706
21844
|
async function buildExecutorBlock(executor, ctx) {
|
|
21707
21845
|
switch (executor) {
|
|
21708
21846
|
case "claude-code": {
|
|
21709
|
-
|
|
21710
|
-
|
|
21847
|
+
return {
|
|
21848
|
+
type: "claude-code",
|
|
21849
|
+
...ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {}
|
|
21850
|
+
};
|
|
21711
21851
|
}
|
|
21712
21852
|
case "anthropic-api": {
|
|
21713
21853
|
return {
|
|
@@ -21719,19 +21859,6 @@ async function buildExecutorBlock(executor, ctx) {
|
|
|
21719
21859
|
return { type: executor };
|
|
21720
21860
|
}
|
|
21721
21861
|
}
|
|
21722
|
-
async function promptForCwd(prompt, fallback) {
|
|
21723
|
-
if (!process.stdin.isTTY) return fallback;
|
|
21724
|
-
const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
21725
|
-
try {
|
|
21726
|
-
const answer = await new Promise((resolve) => {
|
|
21727
|
-
rl.question(prompt, (input) => resolve(input));
|
|
21728
|
-
});
|
|
21729
|
-
const trimmed = answer.trim();
|
|
21730
|
-
return trimmed.length === 0 ? fallback : trimmed;
|
|
21731
|
-
} finally {
|
|
21732
|
-
rl.close();
|
|
21733
|
-
}
|
|
21734
|
-
}
|
|
21735
21862
|
|
|
21736
21863
|
// src/bin.ts
|
|
21737
21864
|
(0, import_shuttle2.createCli)(sifterBrand).parseAsync(process.argv).catch((err) => {
|
|
@@ -21753,7 +21880,7 @@ async function promptForCwd(prompt, fallback) {
|
|
|
21753
21880
|
console.error("Keep refused its credentials \u2014 it was decommissioned from the");
|
|
21754
21881
|
console.error("web app, or its secret was rotated by a newer pairing.");
|
|
21755
21882
|
console.error("");
|
|
21756
|
-
console.error(`Run \`${sifterBrand.product.
|
|
21883
|
+
console.error(`Run \`${sifterBrand.product.cliBinary} init\` to pair again.`);
|
|
21757
21884
|
console.error("");
|
|
21758
21885
|
process.exit(3);
|
|
21759
21886
|
}
|