@garuda-agent/garuda 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +188 -0
- package/README.md +56 -1074
- package/SECURITY.md +9 -2
- package/dist/{acpCommand-AJJBKAE5.js → acpCommand-7EY3NHPG.js} +18 -16
- package/dist/{anthropic-NQDBCHJU.js → anthropic-NQIZLD64.js} +12 -6
- package/dist/{chunk-WIJL72ZE.js → chunk-2DYMJMRQ.js} +1880 -982
- package/dist/{chunk-YQTGMCU6.js → chunk-3MRQJKUO.js} +29 -5
- package/dist/{chunk-WMOLXTBU.js → chunk-4LFJFDCF.js} +2 -1
- package/dist/{chunk-44QN5SWZ.js → chunk-55CFEEIN.js} +22 -2
- package/dist/{chunk-USCUXCAU.js → chunk-5B5HDTUR.js} +3 -3
- package/dist/{chunk-TUUEBHG2.js → chunk-6XHON3Q6.js} +2 -2
- package/dist/chunk-735BKABA.js +472 -0
- package/dist/{chunk-B6FA4I7Q.js → chunk-7TH3NHCQ.js} +2 -2
- package/dist/{chunk-OUCV3FCT.js → chunk-AOGFYUKG.js} +3 -3
- package/dist/{chunk-GXZACJUI.js → chunk-B4SJY7KT.js} +322 -39
- package/dist/{chunk-3EQU2GUX.js → chunk-BHZ6G65V.js} +5 -1
- package/dist/chunk-BSK5T2ZG.js +9 -0
- package/dist/chunk-J4346IDH.js +156 -0
- package/dist/{chunk-NP6RIC25.js → chunk-JRVWH27M.js} +7 -7
- package/dist/{chunk-HUIK3WXO.js → chunk-P6XWWIEK.js} +20 -3
- package/dist/{chunk-HE2K657U.js → chunk-TJIWD7R7.js} +47 -16
- package/dist/chunk-U65DQZDB.js +143 -0
- package/dist/chunk-UIRJOQQS.js +118 -0
- package/dist/{chunk-KKB5W47S.js → chunk-VTMD5IHD.js} +26 -2
- package/dist/{chunk-ENLDPN4S.js → chunk-Z66BWTMD.js} +81 -32
- package/dist/cli/index.js +99 -41
- package/dist/{create-VXIH2GUZ.js → create-W6BV75QX.js} +18 -12
- package/dist/{deadline-F5RJJ2QW.js → deadline-TP7ZGLT2.js} +4 -2
- package/dist/{evalCommand-OX2LQYEK.js → evalCommand-YVQAWUZE.js} +50 -29
- package/dist/{export-HN3CT7AV.js → export-SJ3DUP6W.js} +2 -2
- package/dist/{inkChat-YMMMGU7Y.js → inkChat-FNUBK43U.js} +14 -13
- package/dist/install-TKWN5AYH.js +19 -0
- package/dist/{job-3NYDMP2M.js → job-7JOI4YIQ.js} +6 -2
- package/dist/jobCommand-FE6XN3YD.js +33 -0
- package/dist/{launchd-SSX362VG.js → launchd-3A3ONEW5.js} +9 -8
- package/dist/{lspCommand-JRXKRSL7.js → lspCommand-ZZ6R4PA2.js} +7 -6
- package/dist/{manager-AWU4NHHK.js → manager-73MPYFTP.js} +7 -6
- package/dist/{nightCommand-65MUEV32.js → nightCommand-OODJNWDT.js} +26 -20
- package/dist/{openaiCompatible-CJWVJP5M.js → openaiCompatible-6TLDDW5U.js} +29 -15
- package/dist/{policy-EJPLEEXP.js → policy-ZJ7RMOFN.js} +10 -4
- package/dist/{remove-6GDSTAIO.js → remove-42WQNVPX.js} +8 -7
- package/dist/{server-KHHNHUYV.js → server-VEYIJHEU.js} +7 -3
- package/dist/{setupCommand-VA7D5MZK.js → setupCommand-GBQIVYJC.js} +45 -13
- package/dist/text-LS35WSRL.js +17 -0
- package/native/bin/garuda-reap +0 -0
- package/native/garuda-reap.c +113 -0
- package/package.json +39 -13
- package/dist/chunk-EIXUSVFX.js +0 -116
- package/dist/chunk-R7I2NBPX.js +0 -71
- package/dist/chunk-ULAJPMY7.js +0 -107
- package/dist/chunk-XOK5OZOC.js +0 -9
- package/dist/install-EWDSPFFZ.js +0 -18
- package/dist/jobCommand-ACSGSBQ4.js +0 -32
- package/dist/text-J2GD647J.js +0 -11
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
EFFORTS
|
|
4
|
+
} from "./chunk-BHZ6G65V.js";
|
|
5
|
+
|
|
6
|
+
// src/model/pricing.ts
|
|
7
|
+
var DEFAULT_CONTEXT_WINDOW = 2e5;
|
|
8
|
+
var M = 1e6;
|
|
9
|
+
var ALWAYS = { mode: "always", efforts: EFFORTS };
|
|
10
|
+
var OPTIONAL = { mode: "optional", efforts: EFFORTS };
|
|
11
|
+
var OPTIONAL_46 = {
|
|
12
|
+
mode: "optional",
|
|
13
|
+
efforts: EFFORTS.filter((e) => e !== "xhigh")
|
|
14
|
+
};
|
|
15
|
+
var KNOWN = [
|
|
16
|
+
// October 2026. Sonnet 5.5 turns thinking "off" with `between_tools`, Haiku 5.5 only at high
|
|
17
|
+
// effort or below: Garuda does not offer off for them yet, so they are "always" here.
|
|
18
|
+
["claude-sonnet-5-5", { price: p(2, 10, 0.1, 2.5), contextWindow: M, thinking: ALWAYS }],
|
|
19
|
+
[
|
|
20
|
+
"claude-haiku-5-5",
|
|
21
|
+
{
|
|
22
|
+
price: {
|
|
23
|
+
...p(0.1, 0.5, 0.01, 0.125),
|
|
24
|
+
over: { tokens: 1e5, price: p(0.5, 2.5, 0.05, 0.625) }
|
|
25
|
+
},
|
|
26
|
+
contextWindow: M,
|
|
27
|
+
thinking: ALWAYS
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
["claude-fable-5-1", { price: p(10, 50, 0.25, 12.5), contextWindow: M, thinking: ALWAYS }],
|
|
31
|
+
["claude-fable-5", { price: p(10, 50, 1, 12.5), contextWindow: M, thinking: ALWAYS }],
|
|
32
|
+
["claude-opus-5-5", { price: p(4, 20, 0.2, 5), contextWindow: M, thinking: ALWAYS }],
|
|
33
|
+
["claude-opus-5", { price: p(5, 25, 0.5, 6.25), contextWindow: M, thinking: ALWAYS }],
|
|
34
|
+
["claude-opus-4-8", { price: p(5, 25, 0.5, 6.25), contextWindow: M, thinking: OPTIONAL }],
|
|
35
|
+
["claude-opus-4-7", { price: p(5, 25, 0.5, 6.25), contextWindow: M, thinking: OPTIONAL }],
|
|
36
|
+
["claude-opus-4-6", { price: p(5, 25, 0.5, 6.25), contextWindow: M, thinking: OPTIONAL_46 }],
|
|
37
|
+
["claude-opus-4-5", { price: p(5, 25, 0.5, 6.25), contextWindow: 2e5 }],
|
|
38
|
+
["claude-sonnet-5", { price: p(2, 10, 0.2, 2.5), contextWindow: M, thinking: ALWAYS }],
|
|
39
|
+
["claude-sonnet-4-6", { price: p(3, 15, 0.3, 3.75), contextWindow: M, thinking: OPTIONAL_46 }],
|
|
40
|
+
["claude-sonnet-4-5", { price: p(3, 15, 0.3, 3.75), contextWindow: 2e5 }],
|
|
41
|
+
["claude-haiku-4-5", { price: p(1, 5, 0.1, 1.25), contextWindow: 2e5 }]
|
|
42
|
+
];
|
|
43
|
+
function p(input, output, cacheRead, cacheWrite) {
|
|
44
|
+
return { input, output, cacheRead, cacheWrite };
|
|
45
|
+
}
|
|
46
|
+
function aliasModel(alias) {
|
|
47
|
+
if (!/^(haiku|sonnet|opus|fable)$/.test(alias)) return void 0;
|
|
48
|
+
return KNOWN.find(([prefix]) => prefix.startsWith(`claude-${alias}-`))?.[0];
|
|
49
|
+
}
|
|
50
|
+
function knownModels() {
|
|
51
|
+
return KNOWN.map(([id, info]) => ({ id, info }));
|
|
52
|
+
}
|
|
53
|
+
function lookupModel(modelId) {
|
|
54
|
+
let best;
|
|
55
|
+
for (const [prefix, info] of KNOWN) {
|
|
56
|
+
if (modelId.startsWith(prefix) && prefix.length > (best?.prefix.length ?? 0)) {
|
|
57
|
+
best = { prefix, info };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return best?.info ?? { contextWindow: DEFAULT_CONTEXT_WINDOW };
|
|
61
|
+
}
|
|
62
|
+
var WEB_SEARCH_USD = 0.01;
|
|
63
|
+
function costOf(usage, price, factor = 1) {
|
|
64
|
+
const prompt = usage.inputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
65
|
+
const at = price.over !== void 0 && prompt > price.over.tokens ? price.over.price : price;
|
|
66
|
+
return (usage.inputTokens * at.input + usage.outputTokens * at.output + usage.cacheReadTokens * at.cacheRead + usage.cacheWriteTokens * at.cacheWrite) / M * factor + (usage.webSearches ?? 0) * WEB_SEARCH_USD;
|
|
67
|
+
}
|
|
68
|
+
function responseCost(response, price) {
|
|
69
|
+
if (response.usageReported === false) return void 0;
|
|
70
|
+
return costOf(response.usage, price, response.priceFactor ?? 1);
|
|
71
|
+
}
|
|
72
|
+
function totalTokens(usage) {
|
|
73
|
+
return usage.inputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
74
|
+
}
|
|
75
|
+
function contextSize(usage) {
|
|
76
|
+
return totalTokens(usage);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/session/redact.ts
|
|
80
|
+
var REDACTED = "[REDACTED]";
|
|
81
|
+
var PATTERNS = [
|
|
82
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g,
|
|
83
|
+
/sk-ant-[A-Za-z0-9_-]{16,}/g,
|
|
84
|
+
/\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}/g,
|
|
85
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
86
|
+
/\bgh[pousr]_[A-Za-z0-9]{30,}\b/g,
|
|
87
|
+
/\bgithub_pat_[A-Za-z0-9_]{30,}\b/g,
|
|
88
|
+
/\bxox[abprs]-[A-Za-z0-9-]{10,}/g,
|
|
89
|
+
/\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
90
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
|
|
91
|
+
];
|
|
92
|
+
var ASSIGNMENT = /\b([A-Za-z0-9_]*(?:api[_-]?key|secret|token|password|passwd)[A-Za-z0-9_]*)(["']?\s*[:=]\s*["']?)([^\s"'`,;]{8,})/gi;
|
|
93
|
+
var OPAQUE_KEYS = /* @__PURE__ */ new Set(["encrypted_content", "encrypted_index", "signature"]);
|
|
94
|
+
function looksLikeCode(name, value, next) {
|
|
95
|
+
if (/^[\d_.]+$/.test(value)) return !/pass|secret/i.test(name);
|
|
96
|
+
if (value.includes("(") || next === "(") return true;
|
|
97
|
+
return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value) && !/\d{4,}/.test(value);
|
|
98
|
+
}
|
|
99
|
+
var SECRET_ENV_NAME = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL)/i;
|
|
100
|
+
var movedSecrets = /* @__PURE__ */ new Map();
|
|
101
|
+
function keepSecretForRedaction(name, value) {
|
|
102
|
+
movedSecrets.set(name, value);
|
|
103
|
+
}
|
|
104
|
+
var Redactor = class {
|
|
105
|
+
literals;
|
|
106
|
+
/**
|
|
107
|
+
* `env`: variables whose names look secret have their values removed wherever they appear.
|
|
108
|
+
* Default: this process's environment and the secrets moved out of it.
|
|
109
|
+
*/
|
|
110
|
+
constructor(env = { ...process.env, ...Object.fromEntries(movedSecrets) }) {
|
|
111
|
+
this.literals = Object.entries(env).filter(
|
|
112
|
+
([name, value]) => (SECRET_ENV_NAME.test(name) || value !== void 0 && movedSecrets.get(name) === value) && (value?.length ?? 0) >= 8
|
|
113
|
+
).map(([, value]) => value).sort((a, b) => b.length - a.length);
|
|
114
|
+
}
|
|
115
|
+
text(input) {
|
|
116
|
+
let out = input;
|
|
117
|
+
for (const literal of this.literals) out = out.split(literal).join(REDACTED);
|
|
118
|
+
for (const pattern of PATTERNS) out = out.replace(pattern, REDACTED);
|
|
119
|
+
return out.replace(
|
|
120
|
+
ASSIGNMENT,
|
|
121
|
+
(all, name, sep, value, at) => looksLikeCode(name, value, out[at + all.length]) ? all : `${name}${sep}${REDACTED}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
/** A copy of `value` with every string redacted. */
|
|
125
|
+
value(value) {
|
|
126
|
+
return this.walk(value);
|
|
127
|
+
}
|
|
128
|
+
walk(value) {
|
|
129
|
+
if (typeof value === "string") return this.text(value);
|
|
130
|
+
if (Array.isArray(value)) return value.map((item) => this.walk(item));
|
|
131
|
+
if (value !== null && typeof value === "object") {
|
|
132
|
+
const redactedThinking = value.type === "redacted_thinking";
|
|
133
|
+
return Object.fromEntries(
|
|
134
|
+
Object.entries(value).map(([k, v]) => [
|
|
135
|
+
k,
|
|
136
|
+
(OPAQUE_KEYS.has(k) || redactedThinking && k === "data") && typeof v === "string" ? v : this.walk(v)
|
|
137
|
+
])
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export {
|
|
145
|
+
aliasModel,
|
|
146
|
+
knownModels,
|
|
147
|
+
lookupModel,
|
|
148
|
+
WEB_SEARCH_USD,
|
|
149
|
+
responseCost,
|
|
150
|
+
totalTokens,
|
|
151
|
+
contextSize,
|
|
152
|
+
REDACTED,
|
|
153
|
+
keepSecretForRedaction,
|
|
154
|
+
Redactor
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=chunk-J4346IDH.js.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
keepSecretForRedaction,
|
|
4
3
|
writeFileAtomic
|
|
5
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-U65DQZDB.js";
|
|
6
5
|
import {
|
|
6
|
+
keepSecretForRedaction,
|
|
7
7
|
lookupModel
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-J4346IDH.js";
|
|
9
9
|
import {
|
|
10
10
|
isLoopbackHost
|
|
11
11
|
} from "./chunk-ATW7BNIG.js";
|
|
@@ -269,7 +269,7 @@ function resolveModel(spec, config = { providers: {}, models: {} }) {
|
|
|
269
269
|
...def.type === "anthropic" ? {
|
|
270
270
|
async createBatch(env = process.env, options = {}) {
|
|
271
271
|
const apiKey = providerKey(env, def.apiKeyEnv ?? "ANTHROPIC_API_KEY");
|
|
272
|
-
const { AnthropicBatchClient } = await import("./anthropic-
|
|
272
|
+
const { AnthropicBatchClient } = await import("./anthropic-NQIZLD64.js");
|
|
273
273
|
return new AnthropicBatchClient({ model, ...apiKey ? { apiKey } : {}, ...options });
|
|
274
274
|
}
|
|
275
275
|
} : {},
|
|
@@ -282,10 +282,10 @@ function resolveModel(spec, config = { providers: {}, models: {} }) {
|
|
|
282
282
|
}
|
|
283
283
|
if (def.type === "anthropic") {
|
|
284
284
|
const key = providerKey(env, def.apiKeyEnv ?? "ANTHROPIC_API_KEY");
|
|
285
|
-
const { AnthropicClient } = await import("./anthropic-
|
|
285
|
+
const { AnthropicClient } = await import("./anthropic-NQIZLD64.js");
|
|
286
286
|
return new AnthropicClient({ model, ...key ? { apiKey: key } : {} });
|
|
287
287
|
}
|
|
288
|
-
const { OpenAICompatibleClient } = await import("./openaiCompatible-
|
|
288
|
+
const { OpenAICompatibleClient } = await import("./openaiCompatible-6TLDDW5U.js");
|
|
289
289
|
return new OpenAICompatibleClient({
|
|
290
290
|
provider,
|
|
291
291
|
baseUrl: def.baseUrl,
|
|
@@ -328,4 +328,4 @@ export {
|
|
|
328
328
|
keepProviderKeys,
|
|
329
329
|
resolveModel
|
|
330
330
|
};
|
|
331
|
-
//# sourceMappingURL=chunk-
|
|
331
|
+
//# sourceMappingURL=chunk-JRVWH27M.js.map
|
|
@@ -22,7 +22,9 @@ function ignoredProjectPolicy(root) {
|
|
|
22
22
|
const file = join(root, POLICY_FILE);
|
|
23
23
|
return existsSync(file) ? file : void 0;
|
|
24
24
|
}
|
|
25
|
+
var PROFILES = ["default", "strict"];
|
|
25
26
|
var policySchema = z.strictObject({
|
|
27
|
+
profile: z.enum(PROFILES).optional(),
|
|
26
28
|
disallowedCommands: z.array(z.string()).optional(),
|
|
27
29
|
requireSandbox: z.boolean().optional(),
|
|
28
30
|
denyPaths: z.array(z.string()).optional(),
|
|
@@ -37,7 +39,8 @@ var policySchema = z.strictObject({
|
|
|
37
39
|
}).optional(),
|
|
38
40
|
audit: z.strictObject({
|
|
39
41
|
enabled: z.boolean().optional(),
|
|
40
|
-
level: z.enum(["all", "mutations", "denials"]).optional()
|
|
42
|
+
level: z.enum(["all", "mutations", "denials"]).optional(),
|
|
43
|
+
checkpoint: z.boolean().optional()
|
|
41
44
|
}).optional()
|
|
42
45
|
});
|
|
43
46
|
function parsePolicy(json) {
|
|
@@ -47,6 +50,7 @@ function parsePolicy(json) {
|
|
|
47
50
|
}
|
|
48
51
|
const data = result.data;
|
|
49
52
|
return {
|
|
53
|
+
...data.profile === void 0 ? {} : { profile: data.profile },
|
|
50
54
|
...data.disallowedCommands === void 0 ? {} : { disallowedCommands: data.disallowedCommands },
|
|
51
55
|
...data.requireSandbox === void 0 ? {} : { requireSandbox: data.requireSandbox },
|
|
52
56
|
...data.denyPaths === void 0 ? {} : { denyPaths: data.denyPaths },
|
|
@@ -66,7 +70,8 @@ function parsePolicy(json) {
|
|
|
66
70
|
...data.audit === void 0 ? {} : {
|
|
67
71
|
audit: {
|
|
68
72
|
...data.audit.enabled === void 0 ? {} : { enabled: data.audit.enabled },
|
|
69
|
-
...data.audit.level === void 0 ? {} : { level: data.audit.level }
|
|
73
|
+
...data.audit.level === void 0 ? {} : { level: data.audit.level },
|
|
74
|
+
...data.audit.checkpoint === void 0 ? {} : { checkpoint: data.audit.checkpoint }
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
};
|
|
@@ -106,7 +111,9 @@ function mergePolicies(user, managed) {
|
|
|
106
111
|
const maxSteps = lower(user.limits?.maxSteps, managed.limits?.maxSteps);
|
|
107
112
|
const tokenBudget = lower(user.limits?.tokenBudget, managed.limits?.tokenBudget);
|
|
108
113
|
const audit = managed.audit ?? user.audit;
|
|
114
|
+
const profile = user.profile === void 0 && managed.profile === void 0 ? void 0 : user.profile === "strict" || managed.profile === "strict" ? "strict" : "default";
|
|
109
115
|
return {
|
|
116
|
+
...profile === void 0 ? {} : { profile },
|
|
110
117
|
...disallowedCommands === void 0 ? {} : { disallowedCommands },
|
|
111
118
|
...requireSandbox === void 0 ? {} : { requireSandbox },
|
|
112
119
|
...denyPaths === void 0 ? {} : { denyPaths },
|
|
@@ -156,6 +163,13 @@ function isCommandDisallowedByPolicy(policy, command) {
|
|
|
156
163
|
}
|
|
157
164
|
return { disallowed: false };
|
|
158
165
|
}
|
|
166
|
+
function isStrict(policy) {
|
|
167
|
+
return policy?.profile === "strict";
|
|
168
|
+
}
|
|
169
|
+
function withProfile(policy, projectProfile) {
|
|
170
|
+
if (!isStrict(policy) && projectProfile !== "strict") return policy;
|
|
171
|
+
return { ...policy, profile: "strict", requireSandbox: true };
|
|
172
|
+
}
|
|
159
173
|
function isSandboxRequiredByPolicy(policy, outsideSandbox) {
|
|
160
174
|
if (policy.requireSandbox === true && outsideSandbox) {
|
|
161
175
|
return {
|
|
@@ -221,14 +235,17 @@ export {
|
|
|
221
235
|
POLICY_FILE,
|
|
222
236
|
managedPolicyPath,
|
|
223
237
|
ignoredProjectPolicy,
|
|
238
|
+
PROFILES,
|
|
224
239
|
parsePolicy,
|
|
225
240
|
mergePolicies,
|
|
226
241
|
loadTeamPolicy,
|
|
227
242
|
isCommandDisallowedByPolicy,
|
|
243
|
+
isStrict,
|
|
244
|
+
withProfile,
|
|
228
245
|
isSandboxRequiredByPolicy,
|
|
229
246
|
isPathDeniedByPolicy,
|
|
230
247
|
isModelAllowedByPolicy,
|
|
231
248
|
assertModelAllowedByPolicy,
|
|
232
249
|
isHostBlockedByPolicy
|
|
233
250
|
};
|
|
234
|
-
//# sourceMappingURL=chunk-
|
|
251
|
+
//# sourceMappingURL=chunk-P6XWWIEK.js.map
|
|
@@ -4,11 +4,12 @@ import {
|
|
|
4
4
|
pickChannel
|
|
5
5
|
} from "./chunk-N4I6YS3E.js";
|
|
6
6
|
import {
|
|
7
|
-
gateProjectSettings
|
|
8
|
-
|
|
7
|
+
gateProjectSettings,
|
|
8
|
+
isCostIncomplete
|
|
9
|
+
} from "./chunk-735BKABA.js";
|
|
9
10
|
import {
|
|
10
11
|
totalTokens
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-J4346IDH.js";
|
|
12
13
|
import {
|
|
13
14
|
TrustStore
|
|
14
15
|
} from "./chunk-5DEWP3YX.js";
|
|
@@ -16,15 +17,16 @@ import {
|
|
|
16
17
|
cleanArgs,
|
|
17
18
|
commitJob,
|
|
18
19
|
jobChanges,
|
|
20
|
+
outsideScope,
|
|
19
21
|
prepareWorktree
|
|
20
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-55CFEEIN.js";
|
|
21
23
|
import {
|
|
22
24
|
defaultAgentEnv,
|
|
23
25
|
removeAgent
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-5B5HDTUR.js";
|
|
25
27
|
import {
|
|
26
28
|
jobReport
|
|
27
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-3MRQJKUO.js";
|
|
28
30
|
import {
|
|
29
31
|
jobVerdict,
|
|
30
32
|
parseReview,
|
|
@@ -33,21 +35,24 @@ import {
|
|
|
33
35
|
riskFlags,
|
|
34
36
|
stackOf,
|
|
35
37
|
testSummary
|
|
36
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-AOGFYUKG.js";
|
|
37
39
|
import {
|
|
38
40
|
JobGitError,
|
|
39
41
|
git,
|
|
40
42
|
hostCommand
|
|
41
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-6XHON3Q6.js";
|
|
42
44
|
import {
|
|
43
45
|
createExecutor,
|
|
44
46
|
loadSettings
|
|
45
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-B4SJY7KT.js";
|
|
46
48
|
import {
|
|
47
49
|
JOBS_DIR,
|
|
48
50
|
loadJob,
|
|
49
51
|
saveJob
|
|
50
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-VTMD5IHD.js";
|
|
53
|
+
import {
|
|
54
|
+
prepareWriteScope
|
|
55
|
+
} from "./chunk-UIRJOQQS.js";
|
|
51
56
|
import {
|
|
52
57
|
parseRule
|
|
53
58
|
} from "./chunk-3NJNVZTK.js";
|
|
@@ -93,6 +98,7 @@ async function proveJob(prepared, result, runtime, renderer, git2) {
|
|
|
93
98
|
};
|
|
94
99
|
if (job.review !== false && result.commit !== void 0) {
|
|
95
100
|
renderer.info("Reviewing the diff (a principal engineer's review)\u2026");
|
|
101
|
+
const unreportedBefore = runtime.session?.unreportedTokens ?? 0;
|
|
96
102
|
try {
|
|
97
103
|
const diff = (await git(git2, job.root, ["diff", "--no-renames", job.base, job.branch], {
|
|
98
104
|
check: false
|
|
@@ -118,8 +124,10 @@ async function proveJob(prepared, result, runtime, renderer, git2) {
|
|
|
118
124
|
} catch (error) {
|
|
119
125
|
proof.reviewError = error.message;
|
|
120
126
|
}
|
|
127
|
+
if ((runtime.session?.unreportedTokens ?? 0) > unreportedBefore)
|
|
128
|
+
proof.reviewCostIncomplete = true;
|
|
121
129
|
}
|
|
122
|
-
proof.verdict = jobVerdict(flags, proof.review?.verdict);
|
|
130
|
+
proof.verdict = jobVerdict(flags, proof.review?.verdict, proof.reviewError !== void 0);
|
|
123
131
|
return proof;
|
|
124
132
|
}
|
|
125
133
|
async function prepareJob(mainRoot, id, at, renderer, git2 = createExecutor("host").executor, launchd = {}, trustHome) {
|
|
@@ -159,6 +167,15 @@ async function prepare(mainRoot, id, at, renderer, git2, trustHome) {
|
|
|
159
167
|
renderer.error(`The job's worktree failed: ${error.message}`);
|
|
160
168
|
return 1;
|
|
161
169
|
}
|
|
170
|
+
if (job.writeScope !== void 0) {
|
|
171
|
+
const problem = prepareWriteScope(job.worktree, job.writeScope);
|
|
172
|
+
if (problem !== void 0) {
|
|
173
|
+
renderer.error(
|
|
174
|
+
`The job's write scope cannot be used: ${problem}. Fix "writeScope" in ${JOBS_DIR}/${id}.json.`
|
|
175
|
+
);
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
162
179
|
const gate = await gateProjectSettings({
|
|
163
180
|
root: job.root,
|
|
164
181
|
settings: await loadSettings(job.worktree),
|
|
@@ -172,6 +189,7 @@ async function prepare(mainRoot, id, at, renderer, git2, trustHome) {
|
|
|
172
189
|
...projectRest,
|
|
173
190
|
...job.network === void 0 ? {} : { network: { allow: job.network } },
|
|
174
191
|
allow: [...project.allow, ...job.allow.map((rule) => parseRule(rule))],
|
|
192
|
+
...job.writeScope === void 0 ? {} : { writeScope: job.writeScope },
|
|
175
193
|
maxSteps: job.maxSteps,
|
|
176
194
|
sandbox: {
|
|
177
195
|
...project.sandbox,
|
|
@@ -185,7 +203,8 @@ async function prepare(mainRoot, id, at, renderer, git2, trustHome) {
|
|
|
185
203
|
renderer.info(
|
|
186
204
|
`Job ${job.id}: ${job.title}
|
|
187
205
|
Branch ${job.branch} \xB7 worktree ${job.worktree}
|
|
188
|
-
Approved: ${job.allow.length === 0 ? "nothing beyond the sandbox" : job.allow.join(", ")}
|
|
206
|
+
Approved: ${job.allow.length === 0 ? "nothing beyond the sandbox" : job.allow.join(", ")}
|
|
207
|
+
${job.writeScope === void 0 ? "Write scope: none. Commands may write anywhere in the worktree." : `Write scope: ${job.writeScope.length === 0 ? "nothing in the worktree" : job.writeScope.join(", ")} (the OS sandbox enforces it for commands).`}`
|
|
189
208
|
);
|
|
190
209
|
return { job, root: job.worktree, settings, denied: [], startedAt: Date.now() };
|
|
191
210
|
}
|
|
@@ -224,8 +243,11 @@ function msUntil(hour, minute, now = /* @__PURE__ */ new Date()) {
|
|
|
224
243
|
if (next.getTime() <= now.getTime()) next.setDate(next.getDate() + 1);
|
|
225
244
|
return next.getTime() - now.getTime();
|
|
226
245
|
}
|
|
246
|
+
var ScopeViolation = class extends Error {
|
|
247
|
+
};
|
|
227
248
|
async function finishJob(prepared, outcome, runtime, renderer, git2 = createExecutor("host").executor, launchd = {}) {
|
|
228
249
|
const { job } = prepared;
|
|
250
|
+
let scopeFailure;
|
|
229
251
|
const hadAgent = job.launchd !== void 0 || launchd.fromLaunchd === true;
|
|
230
252
|
delete job.launchd;
|
|
231
253
|
const result = {
|
|
@@ -233,6 +255,7 @@ async function finishJob(prepared, outcome, runtime, renderer, git2 = createExec
|
|
|
233
255
|
steps: outcome.kind === "done" ? outcome.result.steps : 0,
|
|
234
256
|
tokens: runtime.session === void 0 ? 0 : totalTokens(runtime.session.usage),
|
|
235
257
|
...runtime.session?.costUsd === void 0 ? {} : { costUsd: runtime.session.costUsd },
|
|
258
|
+
...runtime.session !== void 0 && isCostIncomplete(runtime.session) ? { costIncomplete: true } : {},
|
|
236
259
|
durationMs: Date.now() - prepared.startedAt,
|
|
237
260
|
...runtime.session === void 0 ? {} : { sessionId: runtime.session.id },
|
|
238
261
|
files: [],
|
|
@@ -249,6 +272,12 @@ async function finishJob(prepared, outcome, runtime, renderer, git2 = createExec
|
|
|
249
272
|
...outcome.kind === "error" ? { error: outcome.message } : {}
|
|
250
273
|
};
|
|
251
274
|
try {
|
|
275
|
+
const outside = job.writeScope === void 0 ? [] : await outsideScope(git2, job);
|
|
276
|
+
if (outside.length > 0) {
|
|
277
|
+
scopeFailure = `changes outside the approved write scope, so nothing was committed: ${outside.slice(0, 20).join(", ")}${outside.length > 20 ? ", \u2026" : ""}`;
|
|
278
|
+
result.error = [result.error, scopeFailure].filter(Boolean).join("; ");
|
|
279
|
+
throw new ScopeViolation();
|
|
280
|
+
}
|
|
252
281
|
const commit = await commitJob(
|
|
253
282
|
git2,
|
|
254
283
|
job,
|
|
@@ -257,13 +286,15 @@ async function finishJob(prepared, outcome, runtime, renderer, git2 = createExec
|
|
|
257
286
|
if (commit !== void 0) result.commit = commit;
|
|
258
287
|
result.files = await jobChanges(git2, job);
|
|
259
288
|
} catch (error) {
|
|
260
|
-
if (!(error instanceof
|
|
261
|
-
|
|
289
|
+
if (!(error instanceof ScopeViolation)) {
|
|
290
|
+
if (!(error instanceof JobGitError)) throw error;
|
|
291
|
+
result.error = [result.error, `commit: ${error.message}`].filter(Boolean).join("; ");
|
|
292
|
+
}
|
|
262
293
|
}
|
|
263
294
|
if (outcome.kind !== "interrupted") {
|
|
264
295
|
result.proof = await proveJob(prepared, result, runtime, renderer, git2);
|
|
265
296
|
}
|
|
266
|
-
job.status = outcome.kind === "error" ? "failed" : outcome.kind === "interrupted" ? "stopped" : "done";
|
|
297
|
+
job.status = outcome.kind === "error" || scopeFailure !== void 0 ? "failed" : outcome.kind === "interrupted" ? "stopped" : "done";
|
|
267
298
|
job.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
268
299
|
job.result = result;
|
|
269
300
|
await saveJob(job);
|
|
@@ -319,4 +350,4 @@ export {
|
|
|
319
350
|
msUntil,
|
|
320
351
|
finishJob
|
|
321
352
|
};
|
|
322
|
-
//# sourceMappingURL=chunk-
|
|
353
|
+
//# sourceMappingURL=chunk-TJIWD7R7.js.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
knownModels
|
|
4
|
+
} from "./chunk-J4346IDH.js";
|
|
5
|
+
|
|
6
|
+
// src/model/catalog.ts
|
|
7
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { dirname, join } from "path";
|
|
10
|
+
var CATALOG_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
11
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
12
|
+
var MAX_PAGES = 10;
|
|
13
|
+
function catalogPath(home = homedir()) {
|
|
14
|
+
return join(home, ".garuda", "cache", "anthropic-models.json");
|
|
15
|
+
}
|
|
16
|
+
async function fetchCatalog(options) {
|
|
17
|
+
const fetcher = options.fetcher ?? fetch;
|
|
18
|
+
const base = (options.baseUrl ?? "https://api.anthropic.com").replace(/\/+$/, "");
|
|
19
|
+
const headers = { "anthropic-version": "2023-06-01" };
|
|
20
|
+
if (options.key !== void 0) headers["x-api-key"] = options.key;
|
|
21
|
+
const models = [];
|
|
22
|
+
let after;
|
|
23
|
+
try {
|
|
24
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
25
|
+
const url = `${base}/v1/models?limit=1000${after === void 0 ? "" : `&after_id=${encodeURIComponent(after)}`}`;
|
|
26
|
+
const response = await fetcher(url, {
|
|
27
|
+
headers,
|
|
28
|
+
redirect: "error",
|
|
29
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
30
|
+
});
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
await response.body?.cancel();
|
|
33
|
+
return { problem: `the Models API answered HTTP ${response.status}` };
|
|
34
|
+
}
|
|
35
|
+
const body = await response.json();
|
|
36
|
+
for (const m of body.data ?? []) {
|
|
37
|
+
if (typeof m.id !== "string" || !/^[A-Za-z0-9._:-]+$/.test(m.id)) continue;
|
|
38
|
+
models.push({
|
|
39
|
+
id: m.id,
|
|
40
|
+
...typeof m.display_name === "string" ? { displayName: m.display_name } : {},
|
|
41
|
+
...typeof m.max_input_tokens === "number" && m.max_input_tokens > 0 ? { contextWindow: m.max_input_tokens } : {}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (body.has_more !== true || typeof body.last_id !== "string") break;
|
|
45
|
+
after = body.last_id;
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return { problem: `Garuda could not read the Models API (${error.message})` };
|
|
49
|
+
}
|
|
50
|
+
return { catalog: { fetchedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(), models } };
|
|
51
|
+
}
|
|
52
|
+
async function saveCatalog(catalog, home = homedir()) {
|
|
53
|
+
const path = catalogPath(home);
|
|
54
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
55
|
+
await writeFile(path, `${JSON.stringify(catalog, null, 2)}
|
|
56
|
+
`, { mode: 384 });
|
|
57
|
+
}
|
|
58
|
+
async function loadCatalog(home = homedir()) {
|
|
59
|
+
try {
|
|
60
|
+
const data = JSON.parse(await readFile(catalogPath(home), "utf8"));
|
|
61
|
+
if (typeof data.fetchedAt !== "string" || !Array.isArray(data.models)) return void 0;
|
|
62
|
+
return {
|
|
63
|
+
fetchedAt: data.fetchedAt,
|
|
64
|
+
models: data.models.filter((m) => typeof m?.id === "string")
|
|
65
|
+
};
|
|
66
|
+
} catch {
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function mergedModels(catalog) {
|
|
71
|
+
const table = knownModels();
|
|
72
|
+
const exact = new Map(table.map((m) => [m.id, m.info]));
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const m of catalog?.models ?? []) {
|
|
75
|
+
const info = exact.get(m.id) ?? prefixInfo(m.id);
|
|
76
|
+
if (info !== void 0) out.push({ id: m.id, info, known: true });
|
|
77
|
+
else out.push({ id: m.id, info: { contextWindow: m.contextWindow ?? 2e5 }, known: false });
|
|
78
|
+
}
|
|
79
|
+
for (const m of table) {
|
|
80
|
+
if (!out.some((o) => o.id === m.id)) out.push({ id: m.id, info: m.info, known: true });
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
function prefixInfo(id) {
|
|
85
|
+
let best;
|
|
86
|
+
for (const m of knownModels()) {
|
|
87
|
+
if (id.startsWith(`${m.id}-`) && /^\d{8}$/.test(id.slice(m.id.length + 1))) {
|
|
88
|
+
if (m.id.length > (best?.id.length ?? 0)) best = m;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return best?.info;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/tools/atomicWrite.ts
|
|
95
|
+
import { randomBytes } from "crypto";
|
|
96
|
+
import { chmod, link, realpath, rename, stat, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
97
|
+
import { basename, dirname as dirname2, join as join2 } from "path";
|
|
98
|
+
async function writeFileAtomic(path, content, { createOnly }) {
|
|
99
|
+
const target = createOnly ? path : await realpath(path);
|
|
100
|
+
const temp = join2(
|
|
101
|
+
dirname2(target),
|
|
102
|
+
`.${basename(target)}.garuda-${randomBytes(4).toString("hex")}.tmp`
|
|
103
|
+
);
|
|
104
|
+
const mode = createOnly ? void 0 : (await stat(target)).mode & 4095;
|
|
105
|
+
await writeFile2(temp, content, { flag: "wx", ...mode === void 0 ? {} : { mode } });
|
|
106
|
+
try {
|
|
107
|
+
if (createOnly) {
|
|
108
|
+
try {
|
|
109
|
+
await link(temp, target);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const code = error.code;
|
|
112
|
+
if (code !== "EPERM" && code !== "ENOTSUP" && code !== "EXDEV") throw error;
|
|
113
|
+
if (await exists(target)) throw Object.assign(new Error("EEXIST"), { code: "EEXIST" });
|
|
114
|
+
await rename(temp, target);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
await chmod(temp, mode ?? 420);
|
|
119
|
+
await rename(temp, target);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
} finally {
|
|
123
|
+
await unlink(temp).catch(() => {
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function exists(path) {
|
|
128
|
+
try {
|
|
129
|
+
await stat(path);
|
|
130
|
+
return true;
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export {
|
|
137
|
+
writeFileAtomic,
|
|
138
|
+
fetchCatalog,
|
|
139
|
+
saveCatalog,
|
|
140
|
+
loadCatalog,
|
|
141
|
+
mergedModels
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=chunk-U65DQZDB.js.map
|