@whittlelabs/sifter 0.7.1 → 0.8.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 +644 -54
- package/bin.js.map +4 -4
- package/package.json +1 -1
package/bin.js
CHANGED
|
@@ -15665,6 +15665,21 @@ var require_prompt_execution = __commonJS({
|
|
|
15665
15665
|
if (typeof spec.outputSchema !== "object" || spec.outputSchema === null) {
|
|
15666
15666
|
throw new Error("prompt-execution: spec.outputSchema must be a JSON Schema object");
|
|
15667
15667
|
}
|
|
15668
|
+
if (spec.workspace !== void 0) {
|
|
15669
|
+
const ws = spec.workspace;
|
|
15670
|
+
if (typeof ws !== "object" || ws === null || Array.isArray(ws)) {
|
|
15671
|
+
throw new Error("prompt-execution: spec.workspace must be an object when present");
|
|
15672
|
+
}
|
|
15673
|
+
const namespaces = Object.entries(ws);
|
|
15674
|
+
if (namespaces.length === 0) {
|
|
15675
|
+
throw new Error("prompt-execution: spec.workspace must carry at least one namespace");
|
|
15676
|
+
}
|
|
15677
|
+
for (const [name, value] of namespaces) {
|
|
15678
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
15679
|
+
throw new Error(`prompt-execution: spec.workspace.${name} must be an object`);
|
|
15680
|
+
}
|
|
15681
|
+
}
|
|
15682
|
+
}
|
|
15668
15683
|
return spec;
|
|
15669
15684
|
}
|
|
15670
15685
|
function renderPromptExecution(inputs) {
|
|
@@ -15876,6 +15891,8 @@ var require_prompt_execution2 = __commonJS({
|
|
|
15876
15891
|
outputLabel: options.output.label,
|
|
15877
15892
|
outputSchema: options.output.schema
|
|
15878
15893
|
};
|
|
15894
|
+
if (options.workspace)
|
|
15895
|
+
spec.workspace = options.workspace;
|
|
15879
15896
|
if (options.providerHints)
|
|
15880
15897
|
spec.providerHints = options.providerHints;
|
|
15881
15898
|
if (options.costCapHint)
|
|
@@ -18418,6 +18435,17 @@ var require_client2 = __commonJS({
|
|
|
18418
18435
|
async getManagedSifterStatus(userId) {
|
|
18419
18436
|
return this.request("GET", `/api/users/${encodeURIComponent(userId)}/managed-sifter-status`);
|
|
18420
18437
|
}
|
|
18438
|
+
// ── GitHub OAuth token ──────────────────────────────────────────
|
|
18439
|
+
/**
|
|
18440
|
+
* Reveal a user's decrypted GitHub OAuth token for a service-to-service
|
|
18441
|
+
* call on their behalf (e.g. the hosted Sifter cloning the repo under
|
|
18442
|
+
* review). Service-token only; requires `keep:oauth-tokens:read`. Treat
|
|
18443
|
+
* the returned token as a single-use, in-memory secret. Throws (404 →
|
|
18444
|
+
* NotFound) when the user has not connected GitHub.
|
|
18445
|
+
*/
|
|
18446
|
+
async getUserGithubToken(userId) {
|
|
18447
|
+
return this.request("GET", `/api/users/${encodeURIComponent(userId)}/github-token`);
|
|
18448
|
+
}
|
|
18421
18449
|
// ── HTTP ────────────────────────────────────────────────────────
|
|
18422
18450
|
async request(method, path, body) {
|
|
18423
18451
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -18516,7 +18544,15 @@ var require_agent_token = __commonJS({
|
|
|
18516
18544
|
"../../packages/keep/dist/agent-token.js"(exports2) {
|
|
18517
18545
|
"use strict";
|
|
18518
18546
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18519
|
-
exports2.AgentTokenManager = void 0;
|
|
18547
|
+
exports2.AgentTokenManager = exports2.AgentCredentialRevokedError = void 0;
|
|
18548
|
+
var AgentCredentialRevokedError2 = class extends Error {
|
|
18549
|
+
code = "PAIRING_REVOKED";
|
|
18550
|
+
constructor(message) {
|
|
18551
|
+
super(message);
|
|
18552
|
+
this.name = "AgentCredentialRevokedError";
|
|
18553
|
+
}
|
|
18554
|
+
};
|
|
18555
|
+
exports2.AgentCredentialRevokedError = AgentCredentialRevokedError2;
|
|
18520
18556
|
var AgentTokenManager = class {
|
|
18521
18557
|
token = null;
|
|
18522
18558
|
expiresAt = 0;
|
|
@@ -18563,6 +18599,9 @@ var require_agent_token = __commonJS({
|
|
|
18563
18599
|
const json = await response.json();
|
|
18564
18600
|
if (!response.ok || !json.success) {
|
|
18565
18601
|
const msg = json.error?.message ?? `Agent token exchange failed with status ${response.status}`;
|
|
18602
|
+
if (response.status === 401) {
|
|
18603
|
+
throw new AgentCredentialRevokedError2(`[AgentTokenManager] Keep refused this agent's credentials (401): the pairing was revoked or the secret rotated. Re-pair to continue.`);
|
|
18604
|
+
}
|
|
18566
18605
|
throw new Error(`[AgentTokenManager] ${msg}`);
|
|
18567
18606
|
}
|
|
18568
18607
|
this.token = json.data.token;
|
|
@@ -18579,7 +18618,7 @@ var require_dist4 = __commonJS({
|
|
|
18579
18618
|
"../../packages/keep/dist/index.js"(exports2) {
|
|
18580
18619
|
"use strict";
|
|
18581
18620
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18582
|
-
exports2.AgentTokenManager = exports2.ServiceTokenManager = exports2.KeepClient = void 0;
|
|
18621
|
+
exports2.AgentCredentialRevokedError = exports2.AgentTokenManager = exports2.ServiceTokenManager = exports2.KeepClient = void 0;
|
|
18583
18622
|
var client_1 = require_client2();
|
|
18584
18623
|
Object.defineProperty(exports2, "KeepClient", { enumerable: true, get: function() {
|
|
18585
18624
|
return client_1.KeepClient;
|
|
@@ -18592,6 +18631,9 @@ var require_dist4 = __commonJS({
|
|
|
18592
18631
|
Object.defineProperty(exports2, "AgentTokenManager", { enumerable: true, get: function() {
|
|
18593
18632
|
return agent_token_1.AgentTokenManager;
|
|
18594
18633
|
} });
|
|
18634
|
+
Object.defineProperty(exports2, "AgentCredentialRevokedError", { enumerable: true, get: function() {
|
|
18635
|
+
return agent_token_1.AgentCredentialRevokedError;
|
|
18636
|
+
} });
|
|
18595
18637
|
}
|
|
18596
18638
|
});
|
|
18597
18639
|
|
|
@@ -18728,15 +18770,415 @@ var require_validate_output = __commonJS({
|
|
|
18728
18770
|
}
|
|
18729
18771
|
});
|
|
18730
18772
|
|
|
18773
|
+
// ../../packages/shuttle/dist/executors/workspace.js
|
|
18774
|
+
var require_workspace = __commonJS({
|
|
18775
|
+
"../../packages/shuttle/dist/executors/workspace.js"(exports2) {
|
|
18776
|
+
"use strict";
|
|
18777
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
18778
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
18779
|
+
};
|
|
18780
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18781
|
+
exports2.prepareWorkspace = prepareWorkspace;
|
|
18782
|
+
exports2.buildGitAuthEnv = buildGitAuthEnv;
|
|
18783
|
+
var child_process_1 = require("child_process");
|
|
18784
|
+
var fs_1 = require("fs");
|
|
18785
|
+
var os_1 = __importDefault(require("os"));
|
|
18786
|
+
var path_1 = __importDefault(require("path"));
|
|
18787
|
+
var DEFAULT_GIT_TIMEOUT_MS = 12e4;
|
|
18788
|
+
var DEFAULT_GIT_PROTOCOLS = "https:ssh:git";
|
|
18789
|
+
async function prepareWorkspace(repoDir, workspace, options = {}) {
|
|
18790
|
+
const resolved = readGitWorkspace(workspace);
|
|
18791
|
+
if ("warning" in resolved) {
|
|
18792
|
+
return degradedWorkspace(resolved.warning);
|
|
18793
|
+
}
|
|
18794
|
+
const gitWorkspace = resolved.git;
|
|
18795
|
+
const timeoutMs = options.commandTimeoutMs ?? DEFAULT_GIT_TIMEOUT_MS;
|
|
18796
|
+
const allowedProtocols = options.allowedProtocols ?? DEFAULT_GIT_PROTOCOLS;
|
|
18797
|
+
const signal = options.signal;
|
|
18798
|
+
const authEnv = options.gitToken !== void 0 ? buildGitAuthEnv(gitWorkspace.remote, options.gitToken) : void 0;
|
|
18799
|
+
const git = (args, cwd) => runGit(args, cwd, { timeoutMs, signal, allowedProtocols, extraEnv: authEnv });
|
|
18800
|
+
const gitCleanup = (args, cwd) => runGit(args, cwd, { timeoutMs, allowedProtocols });
|
|
18801
|
+
const hasCommit = (dir) => git(["cat-file", "-e", `${gitWorkspace.commit}^{commit}`], dir).then((r) => r.code === 0);
|
|
18802
|
+
const fetchCommit = async (dir, opts) => {
|
|
18803
|
+
if (await hasCommit(dir))
|
|
18804
|
+
return [];
|
|
18805
|
+
const depth = opts.shallow ? ["--depth=1"] : [];
|
|
18806
|
+
const attempts = [];
|
|
18807
|
+
for (const ref of gitWorkspace.fetchRefs ?? []) {
|
|
18808
|
+
if (opts.includeOrigin) {
|
|
18809
|
+
attempts.push({ source: `origin ${ref}`, args: ["fetch", "--no-tags", ...depth, "origin", ref] });
|
|
18810
|
+
}
|
|
18811
|
+
attempts.push({
|
|
18812
|
+
source: `${gitWorkspace.remote} ${ref}`,
|
|
18813
|
+
args: ["fetch", "--no-tags", ...depth, gitWorkspace.remote, ref]
|
|
18814
|
+
});
|
|
18815
|
+
}
|
|
18816
|
+
attempts.push({
|
|
18817
|
+
source: `${gitWorkspace.remote} commit`,
|
|
18818
|
+
args: ["fetch", "--no-tags", ...depth, gitWorkspace.remote, gitWorkspace.commit]
|
|
18819
|
+
});
|
|
18820
|
+
const failures2 = [];
|
|
18821
|
+
for (const { source, args } of attempts) {
|
|
18822
|
+
const result = await git(args, dir);
|
|
18823
|
+
if (result.code !== 0) {
|
|
18824
|
+
failures2.push(`${source}: ${firstLine(result.stderr)}`);
|
|
18825
|
+
continue;
|
|
18826
|
+
}
|
|
18827
|
+
if (await hasCommit(dir))
|
|
18828
|
+
return [];
|
|
18829
|
+
failures2.push(`${source}: fetch succeeded but ${gitWorkspace.commit} is still absent`);
|
|
18830
|
+
}
|
|
18831
|
+
return failures2;
|
|
18832
|
+
};
|
|
18833
|
+
const notMaterialized = (failures2) => `could not materialize ${gitWorkspace.commit} from ${gitWorkspace.remote}` + (failures2.length > 0 ? ` (${failures2.join("; ")})` : "");
|
|
18834
|
+
const isRepo = (await git(["rev-parse", "--git-dir"], repoDir)).code === 0;
|
|
18835
|
+
if (isRepo) {
|
|
18836
|
+
const failures2 = await fetchCommit(repoDir, { includeOrigin: true, shallow: false });
|
|
18837
|
+
if (!await hasCommit(repoDir))
|
|
18838
|
+
return degradedWorkspace(notMaterialized(failures2));
|
|
18839
|
+
const tempRoot2 = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-ws-"));
|
|
18840
|
+
const worktreeDir = path_1.default.join(tempRoot2, "checkout");
|
|
18841
|
+
const add = await git(["worktree", "add", "--detach", worktreeDir, gitWorkspace.commit], repoDir);
|
|
18842
|
+
if (add.code !== 0) {
|
|
18843
|
+
await fs_1.promises.rm(tempRoot2, { recursive: true, force: true }).catch(() => {
|
|
18844
|
+
});
|
|
18845
|
+
return degradedWorkspace(`git worktree add failed: ${firstLine(add.stderr)}`);
|
|
18846
|
+
}
|
|
18847
|
+
return {
|
|
18848
|
+
cwd: worktreeDir,
|
|
18849
|
+
materialized: true,
|
|
18850
|
+
cleanup: async () => {
|
|
18851
|
+
await gitCleanup(["worktree", "remove", "--force", worktreeDir], repoDir);
|
|
18852
|
+
await gitCleanup(["worktree", "prune"], repoDir);
|
|
18853
|
+
await fs_1.promises.rm(tempRoot2, { recursive: true, force: true }).catch(() => {
|
|
18854
|
+
});
|
|
18855
|
+
}
|
|
18856
|
+
};
|
|
18857
|
+
}
|
|
18858
|
+
const tempRoot = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-ws-"));
|
|
18859
|
+
const checkoutDir = path_1.default.join(tempRoot, "checkout");
|
|
18860
|
+
const removeTempRoot = async () => {
|
|
18861
|
+
await fs_1.promises.rm(tempRoot, { recursive: true, force: true }).catch(() => {
|
|
18862
|
+
});
|
|
18863
|
+
};
|
|
18864
|
+
await fs_1.promises.mkdir(checkoutDir);
|
|
18865
|
+
const init = await git(["init", "-q"], checkoutDir);
|
|
18866
|
+
if (init.code !== 0) {
|
|
18867
|
+
await removeTempRoot();
|
|
18868
|
+
return degradedWorkspace(`git init failed: ${firstLine(init.stderr)}`);
|
|
18869
|
+
}
|
|
18870
|
+
const failures = await fetchCommit(checkoutDir, { includeOrigin: false, shallow: true });
|
|
18871
|
+
if (!await hasCommit(checkoutDir)) {
|
|
18872
|
+
await removeTempRoot();
|
|
18873
|
+
return degradedWorkspace(notMaterialized(failures));
|
|
18874
|
+
}
|
|
18875
|
+
const checkout = await git(["checkout", "--detach", gitWorkspace.commit], checkoutDir);
|
|
18876
|
+
if (checkout.code !== 0) {
|
|
18877
|
+
await removeTempRoot();
|
|
18878
|
+
return degradedWorkspace(`git checkout failed: ${firstLine(checkout.stderr)}`);
|
|
18879
|
+
}
|
|
18880
|
+
return { cwd: checkoutDir, materialized: true, cleanup: removeTempRoot };
|
|
18881
|
+
}
|
|
18882
|
+
function buildGitAuthEnv(remote, token) {
|
|
18883
|
+
let origin;
|
|
18884
|
+
try {
|
|
18885
|
+
const url = new URL(remote);
|
|
18886
|
+
if (url.protocol !== "https:")
|
|
18887
|
+
return void 0;
|
|
18888
|
+
origin = `${url.protocol}//${url.host}/`;
|
|
18889
|
+
} catch {
|
|
18890
|
+
return void 0;
|
|
18891
|
+
}
|
|
18892
|
+
const basic = Buffer.from(`x-access-token:${token}`).toString("base64");
|
|
18893
|
+
return {
|
|
18894
|
+
GIT_CONFIG_COUNT: "1",
|
|
18895
|
+
GIT_CONFIG_KEY_0: `http.${origin}.extraheader`,
|
|
18896
|
+
GIT_CONFIG_VALUE_0: `Authorization: Basic ${basic}`
|
|
18897
|
+
};
|
|
18898
|
+
}
|
|
18899
|
+
var COMMIT_PATTERN = /^[0-9a-f]{40}$/i;
|
|
18900
|
+
var REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9/_.@^~+-]*$/;
|
|
18901
|
+
function readGitWorkspace(workspace) {
|
|
18902
|
+
const git = workspace.git;
|
|
18903
|
+
if (git === void 0) {
|
|
18904
|
+
const offered = Object.keys(workspace).join(", ") || "(none)";
|
|
18905
|
+
return {
|
|
18906
|
+
warning: `workspace carries no namespace this executor understands (offered: ${offered}; supported: git)`
|
|
18907
|
+
};
|
|
18908
|
+
}
|
|
18909
|
+
if (typeof git.remote !== "string" || git.remote.length === 0 || git.remote.startsWith("-")) {
|
|
18910
|
+
return { warning: "workspace.git.remote must be a non-empty remote URL or path" };
|
|
18911
|
+
}
|
|
18912
|
+
if (typeof git.commit !== "string" || !COMMIT_PATTERN.test(git.commit)) {
|
|
18913
|
+
return { warning: "workspace.git.commit must be a full 40-char commit SHA" };
|
|
18914
|
+
}
|
|
18915
|
+
if (git.fetchRefs !== void 0) {
|
|
18916
|
+
if (!Array.isArray(git.fetchRefs) || !git.fetchRefs.every((r) => typeof r === "string")) {
|
|
18917
|
+
return { warning: "workspace.git.fetchRefs must be an array of ref strings" };
|
|
18918
|
+
}
|
|
18919
|
+
const bad = git.fetchRefs.find((r) => !REF_PATTERN.test(r));
|
|
18920
|
+
if (bad !== void 0) {
|
|
18921
|
+
return { warning: `workspace.git.fetchRefs contains an invalid ref: ${JSON.stringify(bad)}` };
|
|
18922
|
+
}
|
|
18923
|
+
}
|
|
18924
|
+
return { git };
|
|
18925
|
+
}
|
|
18926
|
+
async function degradedWorkspace(warning) {
|
|
18927
|
+
const scratchDir = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-ws-degraded-"));
|
|
18928
|
+
return {
|
|
18929
|
+
cwd: scratchDir,
|
|
18930
|
+
materialized: false,
|
|
18931
|
+
warning,
|
|
18932
|
+
cleanup: async () => {
|
|
18933
|
+
await fs_1.promises.rm(scratchDir, { recursive: true, force: true }).catch(() => {
|
|
18934
|
+
});
|
|
18935
|
+
}
|
|
18936
|
+
};
|
|
18937
|
+
}
|
|
18938
|
+
function runGit(args, cwd, opts) {
|
|
18939
|
+
return new Promise((resolve) => {
|
|
18940
|
+
const child = (0, child_process_1.spawn)("git", args, {
|
|
18941
|
+
cwd,
|
|
18942
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
18943
|
+
// `extraEnv` (e.g. the credential extraheader) goes first so the two
|
|
18944
|
+
// security-critical vars below always win and can't be overridden.
|
|
18945
|
+
env: {
|
|
18946
|
+
...process.env,
|
|
18947
|
+
...opts.extraEnv ?? {},
|
|
18948
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
18949
|
+
// Transport allowlist. `remote` passes readGitWorkspace as any
|
|
18950
|
+
// non-empty non-`-` string, which still admits `ext::sh -c …` and
|
|
18951
|
+
// `fd::` remotes that execute arbitrary commands on this host.
|
|
18952
|
+
// Restricting to the transports we actually use turns those into a
|
|
18953
|
+
// fast failure (→ degrade) rather than RCE across the boundary this
|
|
18954
|
+
// executor is meant to defend.
|
|
18955
|
+
GIT_ALLOW_PROTOCOL: opts.allowedProtocols
|
|
18956
|
+
}
|
|
18957
|
+
});
|
|
18958
|
+
const stdout = [];
|
|
18959
|
+
const stderr = [];
|
|
18960
|
+
let settled = false;
|
|
18961
|
+
const settle = (result) => {
|
|
18962
|
+
if (settled)
|
|
18963
|
+
return;
|
|
18964
|
+
settled = true;
|
|
18965
|
+
clearTimeout(timer);
|
|
18966
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
18967
|
+
resolve(result);
|
|
18968
|
+
};
|
|
18969
|
+
const killChild = () => {
|
|
18970
|
+
child.kill("SIGTERM");
|
|
18971
|
+
const escalate = setTimeout(() => child.kill("SIGKILL"), 5e3);
|
|
18972
|
+
escalate.unref();
|
|
18973
|
+
child.once("close", () => clearTimeout(escalate));
|
|
18974
|
+
};
|
|
18975
|
+
const timer = setTimeout(() => {
|
|
18976
|
+
killChild();
|
|
18977
|
+
settle({
|
|
18978
|
+
code: -1,
|
|
18979
|
+
stdout: Buffer.concat(stdout).toString("utf-8"),
|
|
18980
|
+
stderr: `git ${args[0]} timed out after ${opts.timeoutMs}ms`
|
|
18981
|
+
});
|
|
18982
|
+
}, opts.timeoutMs);
|
|
18983
|
+
const onAbort = () => {
|
|
18984
|
+
killChild();
|
|
18985
|
+
settle({
|
|
18986
|
+
code: -1,
|
|
18987
|
+
stdout: Buffer.concat(stdout).toString("utf-8"),
|
|
18988
|
+
stderr: "aborted"
|
|
18989
|
+
});
|
|
18990
|
+
};
|
|
18991
|
+
if (opts.signal?.aborted) {
|
|
18992
|
+
onAbort();
|
|
18993
|
+
return;
|
|
18994
|
+
}
|
|
18995
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
18996
|
+
child.stdout.on("data", (c) => stdout.push(c));
|
|
18997
|
+
child.stderr.on("data", (c) => stderr.push(c));
|
|
18998
|
+
child.on("error", (err) => settle({ code: -1, stdout: "", stderr: err.message }));
|
|
18999
|
+
child.on("close", (code) => settle({
|
|
19000
|
+
code: code ?? -1,
|
|
19001
|
+
stdout: Buffer.concat(stdout).toString("utf-8"),
|
|
19002
|
+
stderr: Buffer.concat(stderr).toString("utf-8")
|
|
19003
|
+
}));
|
|
19004
|
+
});
|
|
19005
|
+
}
|
|
19006
|
+
function firstLine(text) {
|
|
19007
|
+
const line = text.trim().split("\n", 1)[0];
|
|
19008
|
+
return line.length > 0 ? line : "(no output)";
|
|
19009
|
+
}
|
|
19010
|
+
}
|
|
19011
|
+
});
|
|
19012
|
+
|
|
19013
|
+
// ../../packages/shuttle/dist/executors/overlay.js
|
|
19014
|
+
var require_overlay = __commonJS({
|
|
19015
|
+
"../../packages/shuttle/dist/executors/overlay.js"(exports2) {
|
|
19016
|
+
"use strict";
|
|
19017
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
19018
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
19019
|
+
};
|
|
19020
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
19021
|
+
exports2.applyOverlay = applyOverlay;
|
|
19022
|
+
var fs_1 = require("fs");
|
|
19023
|
+
var path_1 = __importDefault(require("path"));
|
|
19024
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
19025
|
+
var DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
19026
|
+
async function applyOverlay(runDir, overlay, options) {
|
|
19027
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
19028
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
19029
|
+
const allowed = new Set(options.allowedHosts.map(normalizeOrigin).filter((o) => !!o));
|
|
19030
|
+
const rootReal = await fs_1.promises.realpath(runDir);
|
|
19031
|
+
const result = { written: 0, skipped: [] };
|
|
19032
|
+
const files = Array.isArray(overlay.files) ? overlay.files : [];
|
|
19033
|
+
for (const file of files) {
|
|
19034
|
+
const skip = (reason) => result.skipped.push({ path: String(file?.path ?? "?"), reason });
|
|
19035
|
+
if (typeof file?.path !== "string" || typeof file?.url !== "string") {
|
|
19036
|
+
skip("malformed overlay entry (path/url must be strings)");
|
|
19037
|
+
continue;
|
|
19038
|
+
}
|
|
19039
|
+
const dest = safeDestination(rootReal, file.path);
|
|
19040
|
+
if (!dest) {
|
|
19041
|
+
skip("path escapes the workspace root");
|
|
19042
|
+
continue;
|
|
19043
|
+
}
|
|
19044
|
+
let origin;
|
|
19045
|
+
try {
|
|
19046
|
+
const u = new URL(file.url);
|
|
19047
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") {
|
|
19048
|
+
skip(`unsupported url protocol ${u.protocol}`);
|
|
19049
|
+
continue;
|
|
19050
|
+
}
|
|
19051
|
+
origin = normalizeOrigin(u.origin);
|
|
19052
|
+
} catch {
|
|
19053
|
+
skip("invalid url");
|
|
19054
|
+
continue;
|
|
19055
|
+
}
|
|
19056
|
+
if (!origin || !allowed.has(origin)) {
|
|
19057
|
+
skip(`origin not in the overlay allowlist (${origin})`);
|
|
19058
|
+
continue;
|
|
19059
|
+
}
|
|
19060
|
+
try {
|
|
19061
|
+
const content = await fetchCapped(file.url, { timeoutMs, maxBytes, signal: options.signal });
|
|
19062
|
+
await fs_1.promises.mkdir(path_1.default.dirname(dest), { recursive: true });
|
|
19063
|
+
await fs_1.promises.writeFile(dest, content);
|
|
19064
|
+
result.written += 1;
|
|
19065
|
+
} catch (err) {
|
|
19066
|
+
skip(err instanceof Error ? err.message : String(err));
|
|
19067
|
+
}
|
|
19068
|
+
}
|
|
19069
|
+
return result;
|
|
19070
|
+
}
|
|
19071
|
+
function safeDestination(rootReal, relPath) {
|
|
19072
|
+
if (relPath.length === 0 || path_1.default.isAbsolute(relPath))
|
|
19073
|
+
return void 0;
|
|
19074
|
+
const resolved = path_1.default.resolve(rootReal, relPath);
|
|
19075
|
+
const rootWithSep = rootReal.endsWith(path_1.default.sep) ? rootReal : rootReal + path_1.default.sep;
|
|
19076
|
+
if (resolved !== rootReal && !resolved.startsWith(rootWithSep))
|
|
19077
|
+
return void 0;
|
|
19078
|
+
if (relPath.split(/[/\\]/).includes(".."))
|
|
19079
|
+
return void 0;
|
|
19080
|
+
return resolved;
|
|
19081
|
+
}
|
|
19082
|
+
function normalizeOrigin(value) {
|
|
19083
|
+
try {
|
|
19084
|
+
const u = new URL(value.includes("://") ? value : `https://${value}`);
|
|
19085
|
+
return u.origin;
|
|
19086
|
+
} catch {
|
|
19087
|
+
return void 0;
|
|
19088
|
+
}
|
|
19089
|
+
}
|
|
19090
|
+
async function fetchCapped(url, opts) {
|
|
19091
|
+
const controller = new AbortController();
|
|
19092
|
+
const onParentAbort = () => controller.abort();
|
|
19093
|
+
opts.signal?.addEventListener("abort", onParentAbort, { once: true });
|
|
19094
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs);
|
|
19095
|
+
try {
|
|
19096
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
19097
|
+
if (!response.ok) {
|
|
19098
|
+
throw new Error(`overlay fetch returned HTTP ${response.status}`);
|
|
19099
|
+
}
|
|
19100
|
+
if (!response.body) {
|
|
19101
|
+
const buf = Buffer.from(await response.arrayBuffer());
|
|
19102
|
+
if (buf.byteLength > opts.maxBytes)
|
|
19103
|
+
throw new Error("overlay file exceeds size cap");
|
|
19104
|
+
return buf;
|
|
19105
|
+
}
|
|
19106
|
+
const chunks = [];
|
|
19107
|
+
let total = 0;
|
|
19108
|
+
for await (const chunk of response.body) {
|
|
19109
|
+
const buf = Buffer.from(chunk);
|
|
19110
|
+
total += buf.byteLength;
|
|
19111
|
+
if (total > opts.maxBytes)
|
|
19112
|
+
throw new Error("overlay file exceeds size cap");
|
|
19113
|
+
chunks.push(buf);
|
|
19114
|
+
}
|
|
19115
|
+
return Buffer.concat(chunks);
|
|
19116
|
+
} finally {
|
|
19117
|
+
clearTimeout(timer);
|
|
19118
|
+
opts.signal?.removeEventListener("abort", onParentAbort);
|
|
19119
|
+
}
|
|
19120
|
+
}
|
|
19121
|
+
}
|
|
19122
|
+
});
|
|
19123
|
+
|
|
19124
|
+
// ../../packages/shuttle/dist/config/env-ref.js
|
|
19125
|
+
var require_env_ref = __commonJS({
|
|
19126
|
+
"../../packages/shuttle/dist/config/env-ref.js"(exports2) {
|
|
19127
|
+
"use strict";
|
|
19128
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
19129
|
+
exports2.resolveEnvRef = resolveEnvRef;
|
|
19130
|
+
exports2.isEnvRef = isEnvRef;
|
|
19131
|
+
exports2.resolveConfigStringOrEnv = resolveConfigStringOrEnv;
|
|
19132
|
+
var ENV_REF_PATTERN = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
|
|
19133
|
+
function resolveEnvRef(value, fieldName) {
|
|
19134
|
+
const match = ENV_REF_PATTERN.exec(value);
|
|
19135
|
+
if (!match)
|
|
19136
|
+
return value;
|
|
19137
|
+
const varName = match[1];
|
|
19138
|
+
const resolved = process.env[varName];
|
|
19139
|
+
if (resolved === void 0 || resolved === "") {
|
|
19140
|
+
const subject = fieldName ? `${fieldName} (${value})` : value;
|
|
19141
|
+
throw new Error(`Environment variable "${varName}" referenced by ${subject} is not set. Export it before starting the Sifter, or hard-code the value in the YAML.`);
|
|
19142
|
+
}
|
|
19143
|
+
return resolved;
|
|
19144
|
+
}
|
|
19145
|
+
function isEnvRef(value) {
|
|
19146
|
+
return ENV_REF_PATTERN.test(value);
|
|
19147
|
+
}
|
|
19148
|
+
function resolveConfigStringOrEnv(config, field, envFallback) {
|
|
19149
|
+
const raw = config[field];
|
|
19150
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
19151
|
+
return resolveEnvRef(raw, field);
|
|
19152
|
+
}
|
|
19153
|
+
const fromEnv = process.env[envFallback];
|
|
19154
|
+
if (fromEnv && fromEnv.length > 0) {
|
|
19155
|
+
return fromEnv;
|
|
19156
|
+
}
|
|
19157
|
+
throw new Error(`dynamic-key mode requires "${field}" in config or the ${envFallback} env var`);
|
|
19158
|
+
}
|
|
19159
|
+
}
|
|
19160
|
+
});
|
|
19161
|
+
|
|
18731
19162
|
// ../../packages/shuttle/dist/executors/claude-code.js
|
|
18732
19163
|
var require_claude_code = __commonJS({
|
|
18733
19164
|
"../../packages/shuttle/dist/executors/claude-code.js"(exports2) {
|
|
18734
19165
|
"use strict";
|
|
19166
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
19167
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
19168
|
+
};
|
|
18735
19169
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18736
19170
|
exports2.createClaudeCodeExecutor = createClaudeCodeExecutor;
|
|
19171
|
+
exports2.extractJsonObject = extractJsonObject;
|
|
18737
19172
|
var child_process_1 = require("child_process");
|
|
19173
|
+
var fs_1 = require("fs");
|
|
19174
|
+
var os_1 = __importDefault(require("os"));
|
|
19175
|
+
var path_1 = __importDefault(require("path"));
|
|
18738
19176
|
var loom_1 = require_dist3();
|
|
19177
|
+
var keep_1 = require_dist4();
|
|
18739
19178
|
var validate_output_1 = require_validate_output();
|
|
19179
|
+
var workspace_1 = require_workspace();
|
|
19180
|
+
var overlay_1 = require_overlay();
|
|
19181
|
+
var env_ref_1 = require_env_ref();
|
|
18740
19182
|
var apply_1 = require_apply();
|
|
18741
19183
|
var DEFAULT_TIMEOUT = 6e5;
|
|
18742
19184
|
var ClaudeCodeExecutor = class {
|
|
@@ -18754,13 +19196,77 @@ var require_claude_code = __commonJS({
|
|
|
18754
19196
|
outputTokens: spec.costCapHint?.estimatedOutputTokens
|
|
18755
19197
|
};
|
|
18756
19198
|
}
|
|
18757
|
-
execute(dispatch, signal) {
|
|
19199
|
+
async execute(dispatch, signal) {
|
|
18758
19200
|
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
19201
|
+
let credentials = null;
|
|
19202
|
+
if (this.config.resolveCredentials) {
|
|
19203
|
+
const hints = (0, loom_1.pickProviderHints)(spec, "anthropic-api");
|
|
19204
|
+
const customerId = typeof hints.customerId === "string" ? hints.customerId : void 0;
|
|
19205
|
+
if (!customerId) {
|
|
19206
|
+
throw new Error("claude-code dynamic-key mode requires providerHints.anthropicApi.customerId on every job");
|
|
19207
|
+
}
|
|
19208
|
+
credentials = await this.config.resolveCredentials({
|
|
19209
|
+
customerId,
|
|
19210
|
+
needGithubToken: !!spec.workspace
|
|
19211
|
+
});
|
|
19212
|
+
}
|
|
19213
|
+
let jobScratch = null;
|
|
19214
|
+
let configDir;
|
|
19215
|
+
if (credentials) {
|
|
19216
|
+
jobScratch = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-cc-"));
|
|
19217
|
+
configDir = path_1.default.join(jobScratch, "claude-config");
|
|
19218
|
+
await fs_1.promises.mkdir(configDir);
|
|
19219
|
+
}
|
|
19220
|
+
let workspace = null;
|
|
19221
|
+
let overlayResult = null;
|
|
19222
|
+
try {
|
|
19223
|
+
if (spec.workspace) {
|
|
19224
|
+
workspace = await (0, workspace_1.prepareWorkspace)(this.config.cwd, spec.workspace, {
|
|
19225
|
+
signal,
|
|
19226
|
+
...credentials?.githubToken ? { gitToken: credentials.githubToken } : {}
|
|
19227
|
+
});
|
|
19228
|
+
if (spec.workspace.overlay) {
|
|
19229
|
+
overlayResult = await (0, overlay_1.applyOverlay)(workspace.cwd, spec.workspace.overlay, {
|
|
19230
|
+
allowedHosts: this.config.overlayAllowedHosts ?? [],
|
|
19231
|
+
signal
|
|
19232
|
+
});
|
|
19233
|
+
}
|
|
19234
|
+
}
|
|
19235
|
+
const childEnv = buildChildEnv(credentials, configDir);
|
|
19236
|
+
const claudeHints = (0, loom_1.pickProviderHints)(spec, "claude-code");
|
|
19237
|
+
const hintedModel = typeof claudeHints.model === "string" && claudeHints.model.length > 0 ? claudeHints.model : void 0;
|
|
19238
|
+
const response = await this.runClaude(prompt, spec, workspace?.cwd ?? this.config.cwd, childEnv, signal, hintedModel);
|
|
19239
|
+
if (workspace) {
|
|
19240
|
+
response.outputs.push({
|
|
19241
|
+
label: "workspace",
|
|
19242
|
+
content: {
|
|
19243
|
+
materialized: workspace.materialized,
|
|
19244
|
+
...workspace.warning ? { warning: workspace.warning } : {},
|
|
19245
|
+
...overlayResult ? {
|
|
19246
|
+
overlay: {
|
|
19247
|
+
written: overlayResult.written,
|
|
19248
|
+
...overlayResult.skipped.length > 0 ? { skipped: overlayResult.skipped } : {}
|
|
19249
|
+
}
|
|
19250
|
+
} : {}
|
|
19251
|
+
}
|
|
19252
|
+
});
|
|
19253
|
+
}
|
|
19254
|
+
return response;
|
|
19255
|
+
} finally {
|
|
19256
|
+
if (workspace)
|
|
19257
|
+
await workspace.cleanup();
|
|
19258
|
+
if (jobScratch)
|
|
19259
|
+
await fs_1.promises.rm(jobScratch, { recursive: true, force: true }).catch(() => {
|
|
19260
|
+
});
|
|
19261
|
+
}
|
|
19262
|
+
}
|
|
19263
|
+
runClaude(prompt, spec, cwd, childEnv, signal, modelOverride) {
|
|
18759
19264
|
const outputLabel = spec.outputLabel;
|
|
18760
19265
|
return new Promise((resolve, reject) => {
|
|
18761
19266
|
const args = ["--print"];
|
|
18762
|
-
|
|
18763
|
-
|
|
19267
|
+
const model = modelOverride ?? this.config.model;
|
|
19268
|
+
if (model)
|
|
19269
|
+
args.push("--model", model);
|
|
18764
19270
|
if (this.config.maxTurns !== void 0)
|
|
18765
19271
|
args.push("--max-turns", String(this.config.maxTurns));
|
|
18766
19272
|
if (this.config.allowedTools && this.config.allowedTools.length > 0) {
|
|
@@ -18769,8 +19275,11 @@ var require_claude_code = __commonJS({
|
|
|
18769
19275
|
let child;
|
|
18770
19276
|
try {
|
|
18771
19277
|
child = (0, child_process_1.spawn)("claude", args, {
|
|
18772
|
-
cwd
|
|
18773
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
19278
|
+
cwd,
|
|
19279
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
19280
|
+
// Undefined in local mode → inherit the ambient env (and its
|
|
19281
|
+
// `~/.claude` login) unchanged.
|
|
19282
|
+
...childEnv ? { env: childEnv } : {}
|
|
18774
19283
|
});
|
|
18775
19284
|
} catch (err) {
|
|
18776
19285
|
reject(err);
|
|
@@ -18814,7 +19323,7 @@ var require_claude_code = __commonJS({
|
|
|
18814
19323
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
18815
19324
|
if (code === 0) {
|
|
18816
19325
|
try {
|
|
18817
|
-
const parsed =
|
|
19326
|
+
const parsed = extractJsonObject(stdout);
|
|
18818
19327
|
(0, validate_output_1.assertOutputValid)(spec, parsed);
|
|
18819
19328
|
settle(() => resolve({
|
|
18820
19329
|
outputs: [{ label: outputLabel, content: parsed ?? { raw: stdout } }],
|
|
@@ -18832,7 +19341,7 @@ var require_claude_code = __commonJS({
|
|
|
18832
19341
|
});
|
|
18833
19342
|
}
|
|
18834
19343
|
};
|
|
18835
|
-
function createClaudeCodeExecutor(config, capabilityId = "claude-code") {
|
|
19344
|
+
function createClaudeCodeExecutor(config, capabilityId = "claude-code", deps = {}) {
|
|
18836
19345
|
const cwdRaw = config.cwd;
|
|
18837
19346
|
if (typeof cwdRaw !== "string" || cwdRaw.length === 0) {
|
|
18838
19347
|
throw new Error('ClaudeCodeExecutor requires a non-empty "cwd" string in config');
|
|
@@ -18862,12 +19371,108 @@ var require_claude_code = __commonJS({
|
|
|
18862
19371
|
}
|
|
18863
19372
|
validated.timeout = config.timeout;
|
|
18864
19373
|
}
|
|
19374
|
+
if (config.apiKeyFrom !== void 0) {
|
|
19375
|
+
validated.resolveCredentials = buildCredentialResolver(config, deps);
|
|
19376
|
+
}
|
|
19377
|
+
if (config.overlayAllowedHosts !== void 0) {
|
|
19378
|
+
if (!Array.isArray(config.overlayAllowedHosts) || !config.overlayAllowedHosts.every((h) => typeof h === "string")) {
|
|
19379
|
+
throw new Error('"overlayAllowedHosts" must be an array of strings');
|
|
19380
|
+
}
|
|
19381
|
+
validated.overlayAllowedHosts = config.overlayAllowedHosts;
|
|
19382
|
+
}
|
|
18865
19383
|
return new ClaudeCodeExecutor(validated);
|
|
18866
19384
|
}
|
|
18867
|
-
function
|
|
19385
|
+
function buildChildEnv(credentials, configDir) {
|
|
19386
|
+
if (!credentials && !configDir)
|
|
19387
|
+
return void 0;
|
|
19388
|
+
return {
|
|
19389
|
+
...process.env,
|
|
19390
|
+
...credentials ? { ANTHROPIC_API_KEY: credentials.anthropicApiKey } : {},
|
|
19391
|
+
...configDir ? { CLAUDE_CONFIG_DIR: configDir } : {}
|
|
19392
|
+
};
|
|
19393
|
+
}
|
|
19394
|
+
function buildCredentialResolver(config, deps) {
|
|
19395
|
+
if (config.apiKeyFrom !== "job-metadata") {
|
|
19396
|
+
throw new Error(`Unsupported "apiKeyFrom" value: ${JSON.stringify(config.apiKeyFrom)}. Only "job-metadata" is supported.`);
|
|
19397
|
+
}
|
|
19398
|
+
const keepApiUrl = (0, env_ref_1.resolveConfigStringOrEnv)(config, "keepApiUrl", "KEEP_API_URL");
|
|
19399
|
+
const clientId = (0, env_ref_1.resolveConfigStringOrEnv)(config, "keepClientId", "KEEP_CLIENT_ID");
|
|
19400
|
+
const clientSecret = (0, env_ref_1.resolveConfigStringOrEnv)(config, "keepClientSecret", "KEEP_CLIENT_SECRET");
|
|
19401
|
+
const stmFactory = deps.buildServiceTokenManager ?? ((cfg) => new keep_1.ServiceTokenManager(cfg));
|
|
19402
|
+
const stm = stmFactory({ keepApiUrl, clientId, clientSecret });
|
|
19403
|
+
const keepClientFactory = deps.buildKeepClient ?? ((cfg) => new keep_1.KeepClient(cfg));
|
|
19404
|
+
return async ({ customerId, needGithubToken }) => {
|
|
19405
|
+
const serviceToken = await stm.getToken();
|
|
19406
|
+
if (!serviceToken) {
|
|
19407
|
+
throw new Error("claude-code dynamic-key mode failed to exchange a Keep service token. Check KEEP_CLIENT_ID / KEEP_CLIENT_SECRET.");
|
|
19408
|
+
}
|
|
19409
|
+
const keep = keepClientFactory({ baseUrl: keepApiUrl, serviceToken });
|
|
19410
|
+
const reveal = await keep.revealManagedSifterKey(customerId);
|
|
19411
|
+
const credentials = { anthropicApiKey: reveal.apiKey };
|
|
19412
|
+
if (needGithubToken) {
|
|
19413
|
+
try {
|
|
19414
|
+
const gh = await keep.getUserGithubToken(customerId);
|
|
19415
|
+
credentials.githubToken = gh.access_token;
|
|
19416
|
+
} catch {
|
|
19417
|
+
}
|
|
19418
|
+
}
|
|
19419
|
+
return credentials;
|
|
19420
|
+
};
|
|
19421
|
+
}
|
|
19422
|
+
function extractJsonObject(s) {
|
|
19423
|
+
const exact = tryParseObject(s);
|
|
19424
|
+
if (exact)
|
|
19425
|
+
return exact;
|
|
19426
|
+
let last;
|
|
19427
|
+
let i = 0;
|
|
19428
|
+
while (i < s.length) {
|
|
19429
|
+
if (s[i] !== "{") {
|
|
19430
|
+
i++;
|
|
19431
|
+
continue;
|
|
19432
|
+
}
|
|
19433
|
+
let depth = 0;
|
|
19434
|
+
let inString = false;
|
|
19435
|
+
let escaped = false;
|
|
19436
|
+
let end = -1;
|
|
19437
|
+
for (let j = i; j < s.length; j++) {
|
|
19438
|
+
const ch = s[j];
|
|
19439
|
+
if (escaped) {
|
|
19440
|
+
escaped = false;
|
|
19441
|
+
continue;
|
|
19442
|
+
}
|
|
19443
|
+
if (ch === "\\") {
|
|
19444
|
+
escaped = inString;
|
|
19445
|
+
continue;
|
|
19446
|
+
}
|
|
19447
|
+
if (ch === '"') {
|
|
19448
|
+
inString = !inString;
|
|
19449
|
+
continue;
|
|
19450
|
+
}
|
|
19451
|
+
if (inString)
|
|
19452
|
+
continue;
|
|
19453
|
+
if (ch === "{")
|
|
19454
|
+
depth++;
|
|
19455
|
+
else if (ch === "}") {
|
|
19456
|
+
depth--;
|
|
19457
|
+
if (depth === 0) {
|
|
19458
|
+
end = j;
|
|
19459
|
+
break;
|
|
19460
|
+
}
|
|
19461
|
+
}
|
|
19462
|
+
}
|
|
19463
|
+
if (end === -1)
|
|
19464
|
+
break;
|
|
19465
|
+
const candidate = tryParseObject(s.slice(i, end + 1));
|
|
19466
|
+
if (candidate)
|
|
19467
|
+
last = candidate;
|
|
19468
|
+
i = end + 1;
|
|
19469
|
+
}
|
|
19470
|
+
return last;
|
|
19471
|
+
}
|
|
19472
|
+
function tryParseObject(s) {
|
|
18868
19473
|
try {
|
|
18869
19474
|
const parsed = JSON.parse(s);
|
|
18870
|
-
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
19475
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : void 0;
|
|
18871
19476
|
} catch {
|
|
18872
19477
|
return void 0;
|
|
18873
19478
|
}
|
|
@@ -19028,32 +19633,6 @@ var require_http_api = __commonJS({
|
|
|
19028
19633
|
}
|
|
19029
19634
|
});
|
|
19030
19635
|
|
|
19031
|
-
// ../../packages/shuttle/dist/config/env-ref.js
|
|
19032
|
-
var require_env_ref = __commonJS({
|
|
19033
|
-
"../../packages/shuttle/dist/config/env-ref.js"(exports2) {
|
|
19034
|
-
"use strict";
|
|
19035
|
-
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
19036
|
-
exports2.resolveEnvRef = resolveEnvRef;
|
|
19037
|
-
exports2.isEnvRef = isEnvRef;
|
|
19038
|
-
var ENV_REF_PATTERN = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
|
|
19039
|
-
function resolveEnvRef(value, fieldName) {
|
|
19040
|
-
const match = ENV_REF_PATTERN.exec(value);
|
|
19041
|
-
if (!match)
|
|
19042
|
-
return value;
|
|
19043
|
-
const varName = match[1];
|
|
19044
|
-
const resolved = process.env[varName];
|
|
19045
|
-
if (resolved === void 0 || resolved === "") {
|
|
19046
|
-
const subject = fieldName ? `${fieldName} (${value})` : value;
|
|
19047
|
-
throw new Error(`Environment variable "${varName}" referenced by ${subject} is not set. Export it before starting the Sifter, or hard-code the value in the YAML.`);
|
|
19048
|
-
}
|
|
19049
|
-
return resolved;
|
|
19050
|
-
}
|
|
19051
|
-
function isEnvRef(value) {
|
|
19052
|
-
return ENV_REF_PATTERN.test(value);
|
|
19053
|
-
}
|
|
19054
|
-
}
|
|
19055
|
-
});
|
|
19056
|
-
|
|
19057
19636
|
// ../../packages/shuttle/dist/executors/anthropic-api.js
|
|
19058
19637
|
var require_anthropic_api = __commonJS({
|
|
19059
19638
|
"../../packages/shuttle/dist/executors/anthropic-api.js"(exports2) {
|
|
@@ -19089,8 +19668,9 @@ var require_anthropic_api = __commonJS({
|
|
|
19089
19668
|
const outputLabel = spec.outputLabel;
|
|
19090
19669
|
const providerHints = (0, loom_1.pickProviderHints)(spec, this.instance.capabilityId);
|
|
19091
19670
|
const apiKey = await this.instance.resolveKey(providerHints);
|
|
19671
|
+
const hintedModel = providerHints.model;
|
|
19092
19672
|
const body = {
|
|
19093
|
-
model: this.instance.model,
|
|
19673
|
+
model: typeof hintedModel === "string" && hintedModel.length > 0 ? hintedModel : this.instance.model,
|
|
19094
19674
|
max_tokens: this.instance.maxTokens,
|
|
19095
19675
|
messages: [{ role: "user", content: prompt }]
|
|
19096
19676
|
};
|
|
@@ -19117,6 +19697,12 @@ var require_anthropic_api = __commonJS({
|
|
|
19117
19697
|
bodyText = await response.text();
|
|
19118
19698
|
} catch {
|
|
19119
19699
|
}
|
|
19700
|
+
const envelope = bodyText ? safeJsonParse(bodyText) : void 0;
|
|
19701
|
+
const apiErr = envelope?.error;
|
|
19702
|
+
if (apiErr?.message) {
|
|
19703
|
+
const kind = apiErr.type ? ` (${apiErr.type})` : "";
|
|
19704
|
+
throw new Error(`Anthropic API error${kind}: ${apiErr.message} [HTTP ${response.status}]`);
|
|
19705
|
+
}
|
|
19120
19706
|
throw new Error(`HTTP ${response.status}${bodyText ? `: ${bodyText}` : ""}`);
|
|
19121
19707
|
}
|
|
19122
19708
|
const data = await response.json();
|
|
@@ -19198,9 +19784,9 @@ var require_anthropic_api = __commonJS({
|
|
|
19198
19784
|
if (config.apiKeyFrom !== "job-metadata") {
|
|
19199
19785
|
throw new Error(`Unsupported "apiKeyFrom" value: ${JSON.stringify(config.apiKeyFrom)}. Only "job-metadata" is supported in v0.2.`);
|
|
19200
19786
|
}
|
|
19201
|
-
const keepApiUrl =
|
|
19202
|
-
const clientId =
|
|
19203
|
-
const clientSecret =
|
|
19787
|
+
const keepApiUrl = (0, env_ref_1.resolveConfigStringOrEnv)(config, "keepApiUrl", "KEEP_API_URL");
|
|
19788
|
+
const clientId = (0, env_ref_1.resolveConfigStringOrEnv)(config, "keepClientId", "KEEP_CLIENT_ID");
|
|
19789
|
+
const clientSecret = (0, env_ref_1.resolveConfigStringOrEnv)(config, "keepClientSecret", "KEEP_CLIENT_SECRET");
|
|
19204
19790
|
const stmFactory = deps.buildServiceTokenManager ?? ((cfg) => new keep_1.ServiceTokenManager(cfg));
|
|
19205
19791
|
const stm = stmFactory({ keepApiUrl, clientId, clientSecret });
|
|
19206
19792
|
const keepClientFactory = deps.buildKeepClient ?? ((cfg) => new keep_1.KeepClient(cfg));
|
|
@@ -19219,17 +19805,6 @@ var require_anthropic_api = __commonJS({
|
|
|
19219
19805
|
return reveal.apiKey;
|
|
19220
19806
|
};
|
|
19221
19807
|
}
|
|
19222
|
-
function resolveStringConfig(config, field, envFallback) {
|
|
19223
|
-
const raw = config[field];
|
|
19224
|
-
if (typeof raw === "string" && raw.length > 0) {
|
|
19225
|
-
return (0, env_ref_1.resolveEnvRef)(raw, field);
|
|
19226
|
-
}
|
|
19227
|
-
const fromEnv = process.env[envFallback];
|
|
19228
|
-
if (fromEnv && fromEnv.length > 0) {
|
|
19229
|
-
return fromEnv;
|
|
19230
|
-
}
|
|
19231
|
-
throw new Error(`AnthropicApiExecutor dynamic-key mode requires "${field}" in config or the ${envFallback} env var`);
|
|
19232
|
-
}
|
|
19233
19808
|
function safeJsonParse(s) {
|
|
19234
19809
|
try {
|
|
19235
19810
|
const parsed = JSON.parse(s);
|
|
@@ -20594,7 +21169,7 @@ var require_dist5 = __commonJS({
|
|
|
20594
21169
|
"../../packages/shuttle/dist/index.js"(exports2) {
|
|
20595
21170
|
"use strict";
|
|
20596
21171
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
20597
|
-
exports2.revokePairing = exports2.listPairings = exports2.PairingConfigStore = exports2.pair = exports2.DEFAULT_REFUSAL_MESSAGE = exports2.DEFAULT_MONTHLY_PERIOD = exports2.DEFAULT_DAILY_PERIOD = exports2.SUBSTRATE_BRAND = exports2.renderTemplate = exports2.expandHome = exports2.resolvePaths = exports2.SpendStore = exports2.AuditLogStore = exports2.createSpendTrackerObserver = exports2.createAuditLogObserver = exports2.createLoggerObserver = exports2.SpendCapExceeded = exports2.ObserverChain = exports2.createCustomScriptExecutor = exports2.createWebhookExecutor = exports2.createHttpApiExecutor = exports2.createClaudeCodeExecutor = exports2.ExecutorRegistry = exports2.checkResponseMinVersion = exports2.compareSemver = exports2.UpgradeRequiredError = exports2.MIN_VERSION_HEADER = exports2.loadConfig = exports2.Shuttle = exports2.createCli = void 0;
|
|
21172
|
+
exports2.revokePairing = exports2.listPairings = exports2.PairingConfigStore = exports2.pair = exports2.DEFAULT_REFUSAL_MESSAGE = exports2.DEFAULT_MONTHLY_PERIOD = exports2.DEFAULT_DAILY_PERIOD = exports2.SUBSTRATE_BRAND = exports2.renderTemplate = exports2.expandHome = exports2.resolvePaths = exports2.SpendStore = exports2.AuditLogStore = exports2.createSpendTrackerObserver = exports2.createAuditLogObserver = exports2.createLoggerObserver = exports2.SpendCapExceeded = exports2.ObserverChain = exports2.createCustomScriptExecutor = exports2.createWebhookExecutor = exports2.createHttpApiExecutor = exports2.createClaudeCodeExecutor = exports2.ExecutorRegistry = exports2.AgentCredentialRevokedError = exports2.checkResponseMinVersion = exports2.compareSemver = exports2.UpgradeRequiredError = exports2.MIN_VERSION_HEADER = exports2.loadConfig = exports2.Shuttle = exports2.createCli = void 0;
|
|
20598
21173
|
var cli_1 = require_cli();
|
|
20599
21174
|
Object.defineProperty(exports2, "createCli", { enumerable: true, get: function() {
|
|
20600
21175
|
return cli_1.createCli;
|
|
@@ -20620,6 +21195,10 @@ var require_dist5 = __commonJS({
|
|
|
20620
21195
|
Object.defineProperty(exports2, "checkResponseMinVersion", { enumerable: true, get: function() {
|
|
20621
21196
|
return version_check_1.checkResponseMinVersion;
|
|
20622
21197
|
} });
|
|
21198
|
+
var keep_1 = require_dist4();
|
|
21199
|
+
Object.defineProperty(exports2, "AgentCredentialRevokedError", { enumerable: true, get: function() {
|
|
21200
|
+
return keep_1.AgentCredentialRevokedError;
|
|
21201
|
+
} });
|
|
20623
21202
|
var registry_1 = require_registry();
|
|
20624
21203
|
Object.defineProperty(exports2, "ExecutorRegistry", { enumerable: true, get: function() {
|
|
20625
21204
|
return registry_1.ExecutorRegistry;
|
|
@@ -20721,7 +21300,7 @@ var import_shuttle = __toESM(require_dist5());
|
|
|
20721
21300
|
// package.json
|
|
20722
21301
|
var package_default = {
|
|
20723
21302
|
name: "@whittlelabs/sifter",
|
|
20724
|
-
version: "0.
|
|
21303
|
+
version: "0.8.0",
|
|
20725
21304
|
description: "Whittle Sifter: paired AI reviewer for Whittle Sift job pools.",
|
|
20726
21305
|
bin: {
|
|
20727
21306
|
"whittle-sifter": "./dist/bin.js"
|
|
@@ -20905,6 +21484,17 @@ async function promptForCwd(prompt, fallback) {
|
|
|
20905
21484
|
console.error("");
|
|
20906
21485
|
process.exit(2);
|
|
20907
21486
|
}
|
|
21487
|
+
if (err instanceof import_shuttle2.AgentCredentialRevokedError) {
|
|
21488
|
+
console.error("");
|
|
21489
|
+
console.error(`This ${sifterBrand.product.title}'s pairing has been revoked.`);
|
|
21490
|
+
console.error("");
|
|
21491
|
+
console.error("Keep refused its credentials \u2014 it was decommissioned from the");
|
|
21492
|
+
console.error("web app, or its secret was rotated by a newer pairing.");
|
|
21493
|
+
console.error("");
|
|
21494
|
+
console.error(`Run \`${sifterBrand.product.command} init\` to pair again.`);
|
|
21495
|
+
console.error("");
|
|
21496
|
+
process.exit(3);
|
|
21497
|
+
}
|
|
20908
21498
|
const message = err instanceof Error ? err.message : String(err);
|
|
20909
21499
|
console.error(`Fatal: ${message}`);
|
|
20910
21500
|
process.exit(1);
|