@whittlelabs/sifter 0.7.0 → 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 +655 -61
- 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);
|
|
@@ -18813,14 +19322,18 @@ var require_claude_code = __commonJS({
|
|
|
18813
19322
|
signal.removeEventListener("abort", onAbort);
|
|
18814
19323
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
18815
19324
|
if (code === 0) {
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
18819
|
-
|
|
18820
|
-
|
|
18821
|
-
|
|
18822
|
-
|
|
18823
|
-
|
|
19325
|
+
try {
|
|
19326
|
+
const parsed = extractJsonObject(stdout);
|
|
19327
|
+
(0, validate_output_1.assertOutputValid)(spec, parsed);
|
|
19328
|
+
settle(() => resolve({
|
|
19329
|
+
outputs: [{ label: outputLabel, content: parsed ?? { raw: stdout } }],
|
|
19330
|
+
rawText: stdout,
|
|
19331
|
+
parsed,
|
|
19332
|
+
usage: this.config.model ? { aiProvider: "claude-code", aiModel: this.config.model } : void 0
|
|
19333
|
+
}));
|
|
19334
|
+
} catch (err) {
|
|
19335
|
+
settle(() => reject(err instanceof Error ? err : new Error(String(err))));
|
|
19336
|
+
}
|
|
18824
19337
|
} else {
|
|
18825
19338
|
settle(() => reject(new Error(`Claude Code exited with code ${code}`)));
|
|
18826
19339
|
}
|
|
@@ -18828,7 +19341,7 @@ var require_claude_code = __commonJS({
|
|
|
18828
19341
|
});
|
|
18829
19342
|
}
|
|
18830
19343
|
};
|
|
18831
|
-
function createClaudeCodeExecutor(config, capabilityId = "claude-code") {
|
|
19344
|
+
function createClaudeCodeExecutor(config, capabilityId = "claude-code", deps = {}) {
|
|
18832
19345
|
const cwdRaw = config.cwd;
|
|
18833
19346
|
if (typeof cwdRaw !== "string" || cwdRaw.length === 0) {
|
|
18834
19347
|
throw new Error('ClaudeCodeExecutor requires a non-empty "cwd" string in config');
|
|
@@ -18858,12 +19371,108 @@ var require_claude_code = __commonJS({
|
|
|
18858
19371
|
}
|
|
18859
19372
|
validated.timeout = config.timeout;
|
|
18860
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
|
+
}
|
|
18861
19383
|
return new ClaudeCodeExecutor(validated);
|
|
18862
19384
|
}
|
|
18863
|
-
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) {
|
|
18864
19473
|
try {
|
|
18865
19474
|
const parsed = JSON.parse(s);
|
|
18866
|
-
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
19475
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : void 0;
|
|
18867
19476
|
} catch {
|
|
18868
19477
|
return void 0;
|
|
18869
19478
|
}
|
|
@@ -19024,32 +19633,6 @@ var require_http_api = __commonJS({
|
|
|
19024
19633
|
}
|
|
19025
19634
|
});
|
|
19026
19635
|
|
|
19027
|
-
// ../../packages/shuttle/dist/config/env-ref.js
|
|
19028
|
-
var require_env_ref = __commonJS({
|
|
19029
|
-
"../../packages/shuttle/dist/config/env-ref.js"(exports2) {
|
|
19030
|
-
"use strict";
|
|
19031
|
-
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
19032
|
-
exports2.resolveEnvRef = resolveEnvRef;
|
|
19033
|
-
exports2.isEnvRef = isEnvRef;
|
|
19034
|
-
var ENV_REF_PATTERN = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
|
|
19035
|
-
function resolveEnvRef(value, fieldName) {
|
|
19036
|
-
const match = ENV_REF_PATTERN.exec(value);
|
|
19037
|
-
if (!match)
|
|
19038
|
-
return value;
|
|
19039
|
-
const varName = match[1];
|
|
19040
|
-
const resolved = process.env[varName];
|
|
19041
|
-
if (resolved === void 0 || resolved === "") {
|
|
19042
|
-
const subject = fieldName ? `${fieldName} (${value})` : value;
|
|
19043
|
-
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.`);
|
|
19044
|
-
}
|
|
19045
|
-
return resolved;
|
|
19046
|
-
}
|
|
19047
|
-
function isEnvRef(value) {
|
|
19048
|
-
return ENV_REF_PATTERN.test(value);
|
|
19049
|
-
}
|
|
19050
|
-
}
|
|
19051
|
-
});
|
|
19052
|
-
|
|
19053
19636
|
// ../../packages/shuttle/dist/executors/anthropic-api.js
|
|
19054
19637
|
var require_anthropic_api = __commonJS({
|
|
19055
19638
|
"../../packages/shuttle/dist/executors/anthropic-api.js"(exports2) {
|
|
@@ -19085,8 +19668,9 @@ var require_anthropic_api = __commonJS({
|
|
|
19085
19668
|
const outputLabel = spec.outputLabel;
|
|
19086
19669
|
const providerHints = (0, loom_1.pickProviderHints)(spec, this.instance.capabilityId);
|
|
19087
19670
|
const apiKey = await this.instance.resolveKey(providerHints);
|
|
19671
|
+
const hintedModel = providerHints.model;
|
|
19088
19672
|
const body = {
|
|
19089
|
-
model: this.instance.model,
|
|
19673
|
+
model: typeof hintedModel === "string" && hintedModel.length > 0 ? hintedModel : this.instance.model,
|
|
19090
19674
|
max_tokens: this.instance.maxTokens,
|
|
19091
19675
|
messages: [{ role: "user", content: prompt }]
|
|
19092
19676
|
};
|
|
@@ -19113,6 +19697,12 @@ var require_anthropic_api = __commonJS({
|
|
|
19113
19697
|
bodyText = await response.text();
|
|
19114
19698
|
} catch {
|
|
19115
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
|
+
}
|
|
19116
19706
|
throw new Error(`HTTP ${response.status}${bodyText ? `: ${bodyText}` : ""}`);
|
|
19117
19707
|
}
|
|
19118
19708
|
const data = await response.json();
|
|
@@ -19194,9 +19784,9 @@ var require_anthropic_api = __commonJS({
|
|
|
19194
19784
|
if (config.apiKeyFrom !== "job-metadata") {
|
|
19195
19785
|
throw new Error(`Unsupported "apiKeyFrom" value: ${JSON.stringify(config.apiKeyFrom)}. Only "job-metadata" is supported in v0.2.`);
|
|
19196
19786
|
}
|
|
19197
|
-
const keepApiUrl =
|
|
19198
|
-
const clientId =
|
|
19199
|
-
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");
|
|
19200
19790
|
const stmFactory = deps.buildServiceTokenManager ?? ((cfg) => new keep_1.ServiceTokenManager(cfg));
|
|
19201
19791
|
const stm = stmFactory({ keepApiUrl, clientId, clientSecret });
|
|
19202
19792
|
const keepClientFactory = deps.buildKeepClient ?? ((cfg) => new keep_1.KeepClient(cfg));
|
|
@@ -19215,17 +19805,6 @@ var require_anthropic_api = __commonJS({
|
|
|
19215
19805
|
return reveal.apiKey;
|
|
19216
19806
|
};
|
|
19217
19807
|
}
|
|
19218
|
-
function resolveStringConfig(config, field, envFallback) {
|
|
19219
|
-
const raw = config[field];
|
|
19220
|
-
if (typeof raw === "string" && raw.length > 0) {
|
|
19221
|
-
return (0, env_ref_1.resolveEnvRef)(raw, field);
|
|
19222
|
-
}
|
|
19223
|
-
const fromEnv = process.env[envFallback];
|
|
19224
|
-
if (fromEnv && fromEnv.length > 0) {
|
|
19225
|
-
return fromEnv;
|
|
19226
|
-
}
|
|
19227
|
-
throw new Error(`AnthropicApiExecutor dynamic-key mode requires "${field}" in config or the ${envFallback} env var`);
|
|
19228
|
-
}
|
|
19229
19808
|
function safeJsonParse(s) {
|
|
19230
19809
|
try {
|
|
19231
19810
|
const parsed = JSON.parse(s);
|
|
@@ -20590,7 +21169,7 @@ var require_dist5 = __commonJS({
|
|
|
20590
21169
|
"../../packages/shuttle/dist/index.js"(exports2) {
|
|
20591
21170
|
"use strict";
|
|
20592
21171
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
20593
|
-
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;
|
|
20594
21173
|
var cli_1 = require_cli();
|
|
20595
21174
|
Object.defineProperty(exports2, "createCli", { enumerable: true, get: function() {
|
|
20596
21175
|
return cli_1.createCli;
|
|
@@ -20616,6 +21195,10 @@ var require_dist5 = __commonJS({
|
|
|
20616
21195
|
Object.defineProperty(exports2, "checkResponseMinVersion", { enumerable: true, get: function() {
|
|
20617
21196
|
return version_check_1.checkResponseMinVersion;
|
|
20618
21197
|
} });
|
|
21198
|
+
var keep_1 = require_dist4();
|
|
21199
|
+
Object.defineProperty(exports2, "AgentCredentialRevokedError", { enumerable: true, get: function() {
|
|
21200
|
+
return keep_1.AgentCredentialRevokedError;
|
|
21201
|
+
} });
|
|
20619
21202
|
var registry_1 = require_registry();
|
|
20620
21203
|
Object.defineProperty(exports2, "ExecutorRegistry", { enumerable: true, get: function() {
|
|
20621
21204
|
return registry_1.ExecutorRegistry;
|
|
@@ -20717,7 +21300,7 @@ var import_shuttle = __toESM(require_dist5());
|
|
|
20717
21300
|
// package.json
|
|
20718
21301
|
var package_default = {
|
|
20719
21302
|
name: "@whittlelabs/sifter",
|
|
20720
|
-
version: "0.
|
|
21303
|
+
version: "0.8.0",
|
|
20721
21304
|
description: "Whittle Sifter: paired AI reviewer for Whittle Sift job pools.",
|
|
20722
21305
|
bin: {
|
|
20723
21306
|
"whittle-sifter": "./dist/bin.js"
|
|
@@ -20901,6 +21484,17 @@ async function promptForCwd(prompt, fallback) {
|
|
|
20901
21484
|
console.error("");
|
|
20902
21485
|
process.exit(2);
|
|
20903
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
|
+
}
|
|
20904
21498
|
const message = err instanceof Error ? err.message : String(err);
|
|
20905
21499
|
console.error(`Fatal: ${message}`);
|
|
20906
21500
|
process.exit(1);
|