@desplega.ai/agent-swarm 1.131.0 → 1.131.1
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/dist/{actions-w53mbns9.js → actions-jgcy477k.js} +6 -6
- package/dist/{app-m9gwqsqv.js → app-5da78bd4.js} +3 -3
- package/dist/{assistant-bp65m6f3.js → assistant-f2vkk1hj.js} +8 -8
- package/dist/{boot-reembed-62p0at2k.js → boot-reembed-q7mp8hcr.js} +3 -3
- package/dist/{boot-reembed-xmfp6wdh.js → boot-reembed-xmw8wcx6.js} +4 -4
- package/dist/{boot-scrub-logs-rr8q2dms.js → boot-scrub-logs-dsqrv79n.js} +2 -2
- package/dist/{claude-adapter-2y47mrkd.js → claude-adapter-q6x7yspx.js} +4 -4
- package/dist/{claude-managed-adapter-gv19tdew.js → claude-managed-adapter-wsg37bt8.js} +1 -1
- package/dist/{cli-t1xze5qh.js → cli-114fvtmj.js} +1 -1
- package/dist/{cli-cdeg08wx.js → cli-2f07m0xr.js} +1 -1
- package/dist/{cli-70xqj0z3.js → cli-2rk0cx7s.js} +2 -2
- package/dist/{cli-nm5gtp1x.js → cli-3ty0t082.js} +1 -1
- package/dist/{cli-efxfnq6q.js → cli-4rkfg1wx.js} +3 -3
- package/dist/{cli-a4jzdpa3.js → cli-50vbt00w.js} +1 -1
- package/dist/{cli-pm6xbh84.js → cli-6dp3ydxa.js} +34 -8
- package/dist/{cli-d3x3x1c9.js → cli-6dpytdr2.js} +1 -1
- package/dist/{cli-e7ckcg4v.js → cli-6pcz9h15.js} +12 -12
- package/dist/{cli-5kx36evn.js → cli-793jvbsh.js} +1 -1
- package/dist/{cli-78xwvkhj.js → cli-7dm2zqy2.js} +2 -2
- package/dist/{cli-psh3c4va.js → cli-aca5pz9f.js} +1 -1
- package/dist/{cli-72q8m8gk.js → cli-ccf9vy7q.js} +2 -2
- package/dist/{cli-qv61hp37.js → cli-g1re8r05.js} +2 -2
- package/dist/{cli-d2tfkcga.js → cli-g74vpjp2.js} +3 -3
- package/dist/{cli-2q72qz2s.js → cli-gecy3z2s.js} +13 -4
- package/dist/{cli-yw8vghx2.js → cli-jbcva0xj.js} +14 -6
- package/dist/{cli-t76vw5db.js → cli-jevgrb4w.js} +19 -19
- package/dist/{cli-3kf51j15.js → cli-kd1ck4rx.js} +1 -1
- package/dist/{cli-g6b8x1h8.js → cli-nrkkknat.js} +1 -1
- package/dist/{cli-fywms69x.js → cli-pxfxkx5m.js} +1 -1
- package/dist/{cli-cvgjwnh1.js → cli-rgrfzg8z.js} +6 -6
- package/dist/{cli-rzbpmxdr.js → cli-se2p6rkb.js} +1 -1
- package/dist/{cli-5v4rw6yd.js → cli-sy7jk3tx.js} +1 -1
- package/dist/{cli-t55yw3a1.js → cli-t8d349cy.js} +2 -2
- package/dist/{cli-0rcyq6d9.js → cli-w2wxd56v.js} +2 -2
- package/dist/{cli-qmdnhpej.js → cli-wz9115x0.js} +17 -7
- package/dist/{cli-5ze9rv97.js → cli-ycrnynpw.js} +4 -4
- package/dist/{cli-a40rnf71.js → cli-ygrbmjja.js} +5 -5
- package/dist/cli.js +12 -12
- package/dist/{codex-adapter-d24q75wk.js → codex-adapter-htjkn22b.js} +2 -2
- package/dist/{codex-session-runner-g50vcsex.js → codex-session-runner-wjfc7smz.js} +2 -2
- package/dist/{commands-c3nrc5hg.js → commands-xhrfwqsk.js} +2 -2
- package/dist/{db-nt7fg3tw.js → db-anvwb22f.js} +2 -2
- package/dist/{handlers-fkr6swat.js → handlers-b9kbc621.js} +8 -8
- package/dist/{hook-g019yehy.js → hook-cgk4wxmx.js} +3 -3
- package/dist/{hook-77wpvnjc.js → hook-xkvn6c5h.js} +2 -2
- package/dist/{http-jewj0syk.js → http-9xh6ta4r.js} +156 -37
- package/dist/{index-hfvggd0z.js → index-25wjsdv2.js} +9 -9
- package/dist/{index-7n2xpbd4.js → index-eaxyqcmj.js} +8 -8
- package/dist/{index-hp1kdbjj.js → index-vg4snay4.js} +10 -10
- package/dist/{index-6gm5he8h.js → index-ywds4812.js} +8 -8
- package/dist/{keepalive-j0fjvqfd.js → keepalive-vc7s2aa6.js} +4 -4
- package/dist/{lead-1n8x30xe.js → lead-r2yqfmx7.js} +24 -24
- package/dist/{maintenance-gvev11sa.js → maintenance-mw70k468.js} +4 -4
- package/dist/{oauth-refresh-sweep-s9ecsvh0.js → oauth-refresh-sweep-yecgjpk4.js} +3 -3
- package/dist/{onboard-pvm5b38w.js → onboard-xj0f73f9.js} +2 -2
- package/dist/{otel-impl-p6jvgd9c.js → otel-impl-nxg0n0b3.js} +1 -1
- package/dist/{pi-mono-adapter-t091s27g.js → pi-mono-adapter-4g5kby31.js} +1 -1
- package/dist/{pricing-refresh-k49rcyed.js → pricing-refresh-2cn6effr.js} +4 -4
- package/dist/{rbac-roles-cr80g96t.js → rbac-roles-1hcxdtg7.js} +2 -2
- package/dist/{rbac-roles-30pspvy7.js → rbac-roles-vynday5g.js} +3 -3
- package/dist/{render-v2-xm457ng1.js → render-v2-tc0bh8kk.js} +3 -3
- package/dist/{seed-pricing-yhpv35fr.js → seed-pricing-8kceb43f.js} +3 -3
- package/dist/{setup-nytnmwwr.js → setup-46xtgq3c.js} +2 -2
- package/dist/{worker-msytr2cw.js → worker-qrbz6hmv.js} +24 -24
- package/openapi.json +1 -1
- package/package.json +2 -2
- package/src/be/migrations/130_scratch_script_retention_grace.sql +6 -0
- package/src/be/scripts/db.ts +43 -1
- package/src/be/scripts/retention.ts +164 -0
- package/src/be/scripts/run-saved.ts +16 -1
- package/src/http/index.ts +5 -0
- package/src/http/scripts.ts +21 -3
- package/src/http/x.ts +14 -0
- package/src/providers/claude-managed-models.ts +34 -10
- package/src/tests/claude-managed-adapter.test.ts +38 -7
- package/src/tests/sandboxed-process.test.ts +45 -0
- package/src/tests/script-connections.test.ts +3 -2
- package/src/tests/scripts-external-api.test.ts +8 -3
- package/src/tests/scripts-http.test.ts +12 -0
- package/src/tests/scripts-retention.test.ts +384 -0
- package/src/tests/workflow-swarm-script.test.ts +14 -5
- package/src/utils/sandboxed-process.ts +40 -14
- package/src/workflows/executors/swarm-script.ts +24 -1
- package/templates/schedules/daily-compounding-reflection/content.md +12 -3
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
getScriptMcpConnectionDescriptors,
|
|
6
6
|
} from "../script-connections";
|
|
7
7
|
import { buildScriptCredentialBindingsWithFailures } from "../script-credential-broker";
|
|
8
|
+
import { restoreScratchScriptLastUsedIfUnchanged, touchScratchScriptLastUsed } from "./db";
|
|
8
9
|
|
|
9
10
|
export function getSavedScriptOwnerAgentId(script: ScriptRecord): string | null {
|
|
10
11
|
return script.scopeId ?? script.createdByAgentId;
|
|
@@ -16,10 +17,15 @@ export async function runSavedScriptAsAgent(args: {
|
|
|
16
17
|
input: unknown;
|
|
17
18
|
agentId: string;
|
|
18
19
|
}) {
|
|
20
|
+
// Touch before executing (not just after) so a scratch script that's already
|
|
21
|
+
// stale when a run starts can't be reaped by the retention sweep while the
|
|
22
|
+
// run — which may take up to the runtime's wall-clock ceiling — is in flight.
|
|
23
|
+
const runStartTouch = args.script.isScratch ? touchScratchScriptLastUsed(args.script.id) : null;
|
|
24
|
+
|
|
19
25
|
const credentials = await buildScriptCredentialBindingsWithFailures({
|
|
20
26
|
agentId: args.agentId,
|
|
21
27
|
});
|
|
22
|
-
|
|
28
|
+
const output = await runScript({
|
|
23
29
|
source: args.script.source,
|
|
24
30
|
args: args.input,
|
|
25
31
|
fsMode: args.script.fsMode,
|
|
@@ -29,4 +35,13 @@ export async function runSavedScriptAsAgent(args: {
|
|
|
29
35
|
apiConnections: getScriptApiConnectionDescriptors({ agentId: args.agentId }),
|
|
30
36
|
mcpConnections: getScriptMcpConnectionDescriptors({ agentId: args.agentId }),
|
|
31
37
|
});
|
|
38
|
+
if (output.exitCode === 0 && !output.error && !output.runtimeError) {
|
|
39
|
+
touchScratchScriptLastUsed(args.script.id);
|
|
40
|
+
} else if (runStartTouch) {
|
|
41
|
+
// The run-start touch didn't pan out — restore the pre-run timestamp so a
|
|
42
|
+
// failed invocation doesn't buy the script another retention window, as
|
|
43
|
+
// long as no concurrent run has touched it since.
|
|
44
|
+
restoreScratchScriptLastUsedIfUnchanged(args.script.id, args.script.updatedAt, runStartTouch);
|
|
45
|
+
}
|
|
46
|
+
return output;
|
|
32
47
|
}
|
package/src/http/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
stopAuditGc,
|
|
18
18
|
stopAuditWriter,
|
|
19
19
|
} from "../be/rbac-audit";
|
|
20
|
+
import { startScratchScriptGc, stopScratchScriptGc } from "../be/scripts/retention";
|
|
20
21
|
import { seedLegacyCapabilitiesConfig } from "../be/seed-capabilities";
|
|
21
22
|
import { initGitHub } from "../github";
|
|
22
23
|
import { initGitLab } from "../gitlab";
|
|
@@ -441,6 +442,9 @@ async function shutdown() {
|
|
|
441
442
|
// Stop memory expired-row garbage collector
|
|
442
443
|
stopMemoryGc();
|
|
443
444
|
|
|
445
|
+
// Stop scratch-script retention garbage collector
|
|
446
|
+
stopScratchScriptGc();
|
|
447
|
+
|
|
444
448
|
// Stop RBAC audit: retention GC, flush interval, final drain, detach sink
|
|
445
449
|
stopAuditGc();
|
|
446
450
|
stopAuditWriter();
|
|
@@ -561,6 +565,7 @@ try {
|
|
|
561
565
|
setAuditSink(enqueueAuditRow);
|
|
562
566
|
startAuditWriter();
|
|
563
567
|
startAuditGc();
|
|
568
|
+
startScratchScriptGc();
|
|
564
569
|
|
|
565
570
|
// business-use initialization (no-op if envs not set)
|
|
566
571
|
initialize();
|
package/src/http/scripts.ts
CHANGED
|
@@ -24,7 +24,9 @@ import {
|
|
|
24
24
|
listScriptApisForScript,
|
|
25
25
|
listScripts,
|
|
26
26
|
listScriptVersions,
|
|
27
|
+
restoreScratchScriptLastUsedIfUnchanged,
|
|
27
28
|
rotateScriptApiSecret,
|
|
29
|
+
touchScratchScriptLastUsed,
|
|
28
30
|
updateScriptApi,
|
|
29
31
|
upsertScriptByName,
|
|
30
32
|
} from "../be/scripts/db";
|
|
@@ -695,12 +697,14 @@ export async function handleScripts(
|
|
|
695
697
|
|
|
696
698
|
let source = parsed.body.source;
|
|
697
699
|
let fsMode = parsed.body.fsMode;
|
|
700
|
+
let namedScript: ScriptRecord | null = null;
|
|
698
701
|
if (parsed.body.name) {
|
|
699
702
|
const script = resolveScript(parsed.body.name, agent.id, parsed.body.scope);
|
|
700
703
|
if (!script) {
|
|
701
704
|
jsonError(res, "Script not found", 404);
|
|
702
705
|
return true;
|
|
703
706
|
}
|
|
707
|
+
namedScript = script;
|
|
704
708
|
source = script.source;
|
|
705
709
|
fsMode = script.fsMode;
|
|
706
710
|
}
|
|
@@ -711,6 +715,11 @@ export async function handleScripts(
|
|
|
711
715
|
}
|
|
712
716
|
|
|
713
717
|
const startedAt = new Date().toISOString();
|
|
718
|
+
// Touch before executing so an already-stale scratch script isn't reaped
|
|
719
|
+
// by the retention sweep while this run is still in flight.
|
|
720
|
+
const runStartTouch = namedScript?.isScratch
|
|
721
|
+
? touchScratchScriptLastUsed(namedScript.id)
|
|
722
|
+
: null;
|
|
714
723
|
const credentials = await buildScriptCredentialBindingsWithFailures({ agentId: agent.id });
|
|
715
724
|
const output = await runScript({
|
|
716
725
|
source: source as string,
|
|
@@ -722,10 +731,20 @@ export async function handleScripts(
|
|
|
722
731
|
apiConnections: getScriptApiConnectionDescriptors({ agentId: agent.id }),
|
|
723
732
|
mcpConnections: getScriptMcpConnectionDescriptors({ agentId: agent.id }),
|
|
724
733
|
});
|
|
734
|
+
const ok = output.exitCode === 0 && !output.error && !output.runtimeError;
|
|
735
|
+
|
|
736
|
+
if (namedScript?.isScratch && ok) {
|
|
737
|
+
touchScratchScriptLastUsed(namedScript.id);
|
|
738
|
+
} else if (namedScript?.isScratch && runStartTouch) {
|
|
739
|
+
// Failed run — restore the pre-run timestamp so it doesn't buy the
|
|
740
|
+
// script another retention window, unless a concurrent run already
|
|
741
|
+
// touched it since.
|
|
742
|
+
restoreScratchScriptLastUsedIfUnchanged(namedScript.id, namedScript.updatedAt, runStartTouch);
|
|
743
|
+
}
|
|
725
744
|
|
|
726
745
|
// Persist output to KV when idempotencyKey is provided and run succeeded
|
|
727
746
|
let kvSaved: { namespace: string; key: string } | undefined;
|
|
728
|
-
if (parsed.body.idempotencyKey &&
|
|
747
|
+
if (parsed.body.idempotencyKey && ok) {
|
|
729
748
|
const kvNamespace = `script:executions`;
|
|
730
749
|
const kvKey = parsed.body.idempotencyKey;
|
|
731
750
|
const kvValue = {
|
|
@@ -745,7 +764,7 @@ export async function handleScripts(
|
|
|
745
764
|
}
|
|
746
765
|
|
|
747
766
|
let autoSaved: { slug: string; reason: string } | undefined;
|
|
748
|
-
if (parsed.body.source &&
|
|
767
|
+
if (parsed.body.source && ok) {
|
|
749
768
|
const slug = scratchSlug(parsed.body.intent, parsed.body.source);
|
|
750
769
|
await upsertScriptByName({
|
|
751
770
|
name: slug,
|
|
@@ -767,7 +786,6 @@ export async function handleScripts(
|
|
|
767
786
|
// Persist the inline run (no journal) so one-off executions show up alongside
|
|
768
787
|
// durable workflow runs in the Script Runs dashboard. Best-effort: recording
|
|
769
788
|
// must never fail the actual execution.
|
|
770
|
-
const ok = output.exitCode === 0 && !output.error && !output.runtimeError;
|
|
771
789
|
const runError = ok
|
|
772
790
|
? undefined
|
|
773
791
|
: scrubSecrets(
|
package/src/http/x.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
getScriptApiSecret,
|
|
13
13
|
getScriptById,
|
|
14
14
|
recordScriptApiUsage,
|
|
15
|
+
restoreScratchScriptLastUsedIfUnchanged,
|
|
16
|
+
touchScratchScriptLastUsed,
|
|
15
17
|
} from "../be/scripts/db";
|
|
16
18
|
import {
|
|
17
19
|
MAX_SCRIPT_WALL_CLOCK_MS,
|
|
@@ -228,6 +230,10 @@ export async function handleX(
|
|
|
228
230
|
const timeoutMs = resolveTimeoutMs(req);
|
|
229
231
|
const startedAt = new Date().toISOString();
|
|
230
232
|
|
|
233
|
+
// Touch before executing so an already-stale scratch script isn't reaped by
|
|
234
|
+
// the retention sweep while this run is still in flight.
|
|
235
|
+
const runStartTouch = script.isScratch ? touchScratchScriptLastUsed(script.id) : null;
|
|
236
|
+
|
|
231
237
|
const credentials = await buildScriptCredentialBindingsWithFailures({
|
|
232
238
|
agentId: endpoint.agentId,
|
|
233
239
|
});
|
|
@@ -250,6 +256,14 @@ export async function handleX(
|
|
|
250
256
|
|
|
251
257
|
const ok = output.exitCode === 0 && !output.error && !output.runtimeError;
|
|
252
258
|
const error = ok ? null : buildExecutionError(output);
|
|
259
|
+
if (ok) {
|
|
260
|
+
touchScratchScriptLastUsed(script.id);
|
|
261
|
+
} else if (script.isScratch && runStartTouch) {
|
|
262
|
+
// Failed run — restore the pre-run timestamp so it doesn't buy the
|
|
263
|
+
// script another retention window, unless a concurrent run already
|
|
264
|
+
// touched it since.
|
|
265
|
+
restoreScratchScriptLastUsedIfUnchanged(script.id, script.updatedAt, runStartTouch);
|
|
266
|
+
}
|
|
253
267
|
|
|
254
268
|
// Usage + observability — best-effort, must never fail the response.
|
|
255
269
|
try {
|
|
@@ -38,8 +38,8 @@ export const CLAUDE_MANAGED_MODELS = [
|
|
|
38
38
|
|
|
39
39
|
export type ClaudeManagedModel = (typeof CLAUDE_MANAGED_MODELS)[number];
|
|
40
40
|
|
|
41
|
-
/**
|
|
42
|
-
export interface
|
|
41
|
+
/** Token rates per million tokens (USD). */
|
|
42
|
+
export interface ClaudeManagedTokenPricing {
|
|
43
43
|
/** USD per million uncached input tokens. */
|
|
44
44
|
inputPerMillion: number;
|
|
45
45
|
/** USD per million output tokens. */
|
|
@@ -50,6 +50,15 @@ export interface ClaudeManagedModelPricing {
|
|
|
50
50
|
cacheWritePerMillion: number;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/** Pricing per million tokens (USD), including an optional scheduled rate change. */
|
|
54
|
+
export interface ClaudeManagedModelPricing extends ClaudeManagedTokenPricing {
|
|
55
|
+
scheduledChange?: {
|
|
56
|
+
/** ISO timestamp at which the replacement rates take effect. */
|
|
57
|
+
effectiveAt: string;
|
|
58
|
+
pricing: ClaudeManagedTokenPricing;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
/**
|
|
54
63
|
* Anthropic public list pricing. Source:
|
|
55
64
|
* https://platform.claude.com/docs/en/about-claude/pricing
|
|
@@ -57,7 +66,7 @@ export interface ClaudeManagedModelPricing {
|
|
|
57
66
|
* - claude-fable-5: $10 / $50 / $1.00 / $12.50 (verified 2026-06-10)
|
|
58
67
|
* - claude-mythos-5: $10 / $50 / $1.00 / $12.50 (limited availability, verified 2026-06-10)
|
|
59
68
|
* - claude-opus-5: $5 / $25 / $0.50 / $6.25 (verified 2026-07-25)
|
|
60
|
-
* - claude-sonnet-5: $
|
|
69
|
+
* - claude-sonnet-5: $2 / $10 / $0.20 / $2.50 (introductory rate, verified 2026-08-11; $3/$15 from 2026-09-01)
|
|
61
70
|
* - claude-sonnet-4-6: $3 / $15 / $0.30 / $3.75 (in / out / cache-read / cache-write)
|
|
62
71
|
* - claude-opus-4-8: $5 / $25 / $0.50 / $6.25 (verified 2026-05-28)
|
|
63
72
|
* - claude-opus-4-7: $15 / $75 / $1.50 / $18.75 (STALE — was correct at launch, Anthropic has since dropped Opus to $5/$25)
|
|
@@ -83,12 +92,21 @@ export const CLAUDE_MANAGED_MODEL_PRICING: Record<ClaudeManagedModel, ClaudeMana
|
|
|
83
92
|
cacheReadPerMillion: 1.0,
|
|
84
93
|
cacheWritePerMillion: 12.5,
|
|
85
94
|
},
|
|
86
|
-
//
|
|
95
|
+
// Introductory rate through 2026-08-31; changes to $3/$15 from 2026-09-01.
|
|
87
96
|
"claude-sonnet-5": {
|
|
88
|
-
inputPerMillion:
|
|
89
|
-
outputPerMillion:
|
|
90
|
-
cacheReadPerMillion: 0.
|
|
91
|
-
cacheWritePerMillion:
|
|
97
|
+
inputPerMillion: 2.0,
|
|
98
|
+
outputPerMillion: 10.0,
|
|
99
|
+
cacheReadPerMillion: 0.2,
|
|
100
|
+
cacheWritePerMillion: 2.5,
|
|
101
|
+
scheduledChange: {
|
|
102
|
+
effectiveAt: "2026-09-01T00:00:00.000Z",
|
|
103
|
+
pricing: {
|
|
104
|
+
inputPerMillion: 3.0,
|
|
105
|
+
outputPerMillion: 15.0,
|
|
106
|
+
cacheReadPerMillion: 0.3,
|
|
107
|
+
cacheWritePerMillion: 3.75,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
92
110
|
},
|
|
93
111
|
"claude-sonnet-4-6": {
|
|
94
112
|
inputPerMillion: 3.0,
|
|
@@ -146,9 +164,10 @@ export function computeClaudeManagedCostUsd(
|
|
|
146
164
|
outputTokens: number,
|
|
147
165
|
cacheReadTokens: number,
|
|
148
166
|
cacheWriteTokens: number,
|
|
167
|
+
pricedAtMs: number = Date.now(),
|
|
149
168
|
): number {
|
|
150
|
-
const
|
|
151
|
-
if (!
|
|
169
|
+
const configuredPricing = CLAUDE_MANAGED_MODEL_PRICING[model as ClaudeManagedModel];
|
|
170
|
+
if (!configuredPricing) {
|
|
152
171
|
if (!warnedUnknownModels.has(model)) {
|
|
153
172
|
warnedUnknownModels.add(model);
|
|
154
173
|
console.warn(
|
|
@@ -158,6 +177,11 @@ export function computeClaudeManagedCostUsd(
|
|
|
158
177
|
}
|
|
159
178
|
return 0;
|
|
160
179
|
}
|
|
180
|
+
const pricing =
|
|
181
|
+
configuredPricing.scheduledChange &&
|
|
182
|
+
pricedAtMs >= Date.parse(configuredPricing.scheduledChange.effectiveAt)
|
|
183
|
+
? configuredPricing.scheduledChange.pricing
|
|
184
|
+
: configuredPricing;
|
|
161
185
|
const inputCost = (inputTokens / 1_000_000) * pricing.inputPerMillion;
|
|
162
186
|
const outputCost = (outputTokens / 1_000_000) * pricing.outputPerMillion;
|
|
163
187
|
const cacheReadCost = (cacheReadTokens / 1_000_000) * pricing.cacheReadPerMillion;
|
|
@@ -831,11 +831,33 @@ describe("ClaudeManagedAdapter (Phase 4) — repo provisioning + cost data", ()
|
|
|
831
831
|
expect(cost).toBeCloseTo(4.5, 10);
|
|
832
832
|
});
|
|
833
833
|
|
|
834
|
-
test("computeClaudeManagedCostUsd
|
|
834
|
+
test("computeClaudeManagedCostUsd uses sonnet-5 introductory rate through 2026-08-31", () => {
|
|
835
|
+
// 1M input tokens × $2.00/Mtok = $2.00
|
|
836
|
+
// 100k output tokens × $10.00/Mtok = $1.00
|
|
837
|
+
// total = $3.00. The introductory rate applies through 2026-08-31.
|
|
838
|
+
const cost = computeClaudeManagedCostUsd(
|
|
839
|
+
"claude-sonnet-5",
|
|
840
|
+
1_000_000,
|
|
841
|
+
100_000,
|
|
842
|
+
0,
|
|
843
|
+
0,
|
|
844
|
+
Date.parse("2026-08-31T23:59:59.999Z"),
|
|
845
|
+
);
|
|
846
|
+
expect(cost).toBeCloseTo(3.0, 10);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
test("computeClaudeManagedCostUsd uses sonnet-5 standard rate from 2026-09-01", () => {
|
|
835
850
|
// 1M input tokens × $3.00/Mtok = $3.00
|
|
836
851
|
// 100k output tokens × $15.00/Mtok = $1.50
|
|
837
|
-
// total = $4.50
|
|
838
|
-
const cost = computeClaudeManagedCostUsd(
|
|
852
|
+
// total = $4.50 from the first instant of 2026-09-01 UTC.
|
|
853
|
+
const cost = computeClaudeManagedCostUsd(
|
|
854
|
+
"claude-sonnet-5",
|
|
855
|
+
1_000_000,
|
|
856
|
+
100_000,
|
|
857
|
+
0,
|
|
858
|
+
0,
|
|
859
|
+
Date.parse("2026-09-01T00:00:00.000Z"),
|
|
860
|
+
);
|
|
839
861
|
expect(cost).toBeCloseTo(4.5, 10);
|
|
840
862
|
});
|
|
841
863
|
|
|
@@ -882,10 +904,19 @@ describe("ClaudeManagedAdapter (Phase 4) — repo provisioning + cost data", ()
|
|
|
882
904
|
cacheWritePerMillion: 12.5,
|
|
883
905
|
});
|
|
884
906
|
expect(CLAUDE_MANAGED_MODEL_PRICING["claude-sonnet-5"]).toEqual({
|
|
885
|
-
inputPerMillion:
|
|
886
|
-
outputPerMillion:
|
|
887
|
-
cacheReadPerMillion: 0.
|
|
888
|
-
cacheWritePerMillion:
|
|
907
|
+
inputPerMillion: 2.0,
|
|
908
|
+
outputPerMillion: 10.0,
|
|
909
|
+
cacheReadPerMillion: 0.2,
|
|
910
|
+
cacheWritePerMillion: 2.5,
|
|
911
|
+
scheduledChange: {
|
|
912
|
+
effectiveAt: "2026-09-01T00:00:00.000Z",
|
|
913
|
+
pricing: {
|
|
914
|
+
inputPerMillion: 3.0,
|
|
915
|
+
outputPerMillion: 15.0,
|
|
916
|
+
cacheReadPerMillion: 0.3,
|
|
917
|
+
cacheWritePerMillion: 3.75,
|
|
918
|
+
},
|
|
919
|
+
},
|
|
889
920
|
});
|
|
890
921
|
expect(CLAUDE_MANAGED_MODEL_PRICING["claude-sonnet-4-6"]).toBeDefined();
|
|
891
922
|
expect(CLAUDE_MANAGED_MODEL_PRICING["claude-opus-4-7"]).toBeDefined();
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
|
+
BUN_SANDBOX_VIRTUAL_MEMORY_MB,
|
|
3
4
|
buildSandboxedCommand,
|
|
4
5
|
createCappedStreamState,
|
|
6
|
+
DEFAULT_SANDBOX_LIMITS,
|
|
7
|
+
JAVASCRIPT_RUNTIME_SANDBOX_MAX_PROCS,
|
|
5
8
|
readStreamCapped,
|
|
6
9
|
sandboxSpawnEnv,
|
|
7
10
|
snapshotCapped,
|
|
8
11
|
} from "../utils/sandboxed-process";
|
|
9
12
|
|
|
13
|
+
const TEST_ENV = { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: "/tmp" };
|
|
14
|
+
|
|
15
|
+
function sandboxPrelude(command: readonly string[]): string {
|
|
16
|
+
return buildSandboxedCommand(command, TEST_ENV)[2] ?? "";
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
/**
|
|
11
20
|
* Temporarily override `process.platform` for the duration of `fn`, then
|
|
12
21
|
* restore it. Used to exercise the win32 branches of `buildSandboxedCommand`
|
|
@@ -56,6 +65,42 @@ describe("sandboxSpawnEnv", () => {
|
|
|
56
65
|
});
|
|
57
66
|
});
|
|
58
67
|
|
|
68
|
+
describe("buildSandboxedCommand runtime-aware limits", () => {
|
|
69
|
+
test.each(["bun", "node", "npx"])("raises AS and nproc for direct %s commands", (runtime) => {
|
|
70
|
+
const command = buildSandboxedCommand([runtime, "--version"], TEST_ENV);
|
|
71
|
+
expect(command[0]).toBe("bash");
|
|
72
|
+
expect(command[2]).toContain(`ulimit -v ${BUN_SANDBOX_VIRTUAL_MEMORY_MB * 1024}`);
|
|
73
|
+
expect(command[2]).toContain(`ulimit -u ${JAVASCRIPT_RUNTIME_SANDBOX_MAX_PROCS}`);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test.each(["bash", "sh", "dash"])("propagates the runtime profile through %s -c", (shell) => {
|
|
77
|
+
const script = "bun /opt/meme-post.bundle.js";
|
|
78
|
+
const prelude = sandboxPrelude([shell, "-c", script]);
|
|
79
|
+
expect(prelude).toContain(`ulimit -v ${BUN_SANDBOX_VIRTUAL_MEMORY_MB * 1024}`);
|
|
80
|
+
expect(prelude).toContain(`ulimit -u ${JAVASCRIPT_RUNTIME_SANDBOX_MAX_PROCS}`);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("keeps strict defaults for direct non-interpreter commands", () => {
|
|
84
|
+
const command = buildSandboxedCommand(["git", "status", "--short"], TEST_ENV);
|
|
85
|
+
expect(command[0]).toBe("sh");
|
|
86
|
+
const prelude = command[2] ?? "";
|
|
87
|
+
expect(prelude).toContain(`ulimit -v ${DEFAULT_SANDBOX_LIMITS.virtualMemoryMb * 1024}`);
|
|
88
|
+
expect(prelude).toContain(`ulimit -u ${DEFAULT_SANDBOX_LIMITS.maxProcs}`);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("the raised profile starts a shell-wrapped Bun process with real nproc enforcement", () => {
|
|
92
|
+
const result = Bun.spawnSync(
|
|
93
|
+
buildSandboxedCommand(
|
|
94
|
+
["bash", "-c", "bun -e 'console.log(JSON.stringify({ started: true }))'"],
|
|
95
|
+
TEST_ENV,
|
|
96
|
+
),
|
|
97
|
+
{ env: sandboxSpawnEnv(TEST_ENV) },
|
|
98
|
+
);
|
|
99
|
+
expect(result.exitCode).toBe(0);
|
|
100
|
+
expect(JSON.parse(result.stdout.toString())).toEqual({ started: true });
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
59
104
|
// ─── readStreamCapped / snapshotCapped (Codex PRRT_kwDOQr3Tmc6XCRuy — deadline snapshot) ──
|
|
60
105
|
|
|
61
106
|
describe("readStreamCapped with an external CappedStreamState", () => {
|
|
@@ -535,9 +535,10 @@ describe("script connections", () => {
|
|
|
535
535
|
markMigrationApplied(database, "127_backfill_user_attribution.sql");
|
|
536
536
|
// 128 alters session_costs, which this partial fixture does not create.
|
|
537
537
|
markMigrationApplied(database, "128_session_costs_accuracy.sql");
|
|
538
|
-
// 129
|
|
539
|
-
// migration-112-only fixture creates.
|
|
538
|
+
// 129 and 130 alter scripts (and 129 rebuilds asset_key_history), none of
|
|
539
|
+
// which this migration-112-only fixture creates.
|
|
540
540
|
markMigrationApplied(database, "129_asset_keys_apps_scripts.sql");
|
|
541
|
+
markMigrationApplied(database, "130_scratch_script_retention_grace.sql");
|
|
541
542
|
|
|
542
543
|
const id = crypto.randomUUID();
|
|
543
544
|
const now = new Date().toISOString();
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
createScriptApi,
|
|
9
9
|
getScriptApiById,
|
|
10
10
|
getScriptApiSecret,
|
|
11
|
+
getScriptById,
|
|
11
12
|
insertScript,
|
|
12
13
|
listScriptApisForScript,
|
|
13
14
|
} from "../be/scripts/db";
|
|
@@ -49,9 +50,9 @@ const DOUBLER_SOURCE =
|
|
|
49
50
|
let workerId: string;
|
|
50
51
|
let savedEnv: NodeJS.ProcessEnv;
|
|
51
52
|
|
|
52
|
-
function insertDoubler(opts: { argsJsonSchema?: string | null } = {}) {
|
|
53
|
+
function insertDoubler(opts: { argsJsonSchema?: string | null; isScratch?: boolean } = {}) {
|
|
53
54
|
return insertScript({
|
|
54
|
-
name:
|
|
55
|
+
name: `${opts.isScratch ? "scratch-" : ""}doubler-${crypto.randomUUID().slice(0, 8)}`,
|
|
55
56
|
scope: "agent",
|
|
56
57
|
scopeId: workerId,
|
|
57
58
|
source: DOUBLER_SOURCE,
|
|
@@ -61,6 +62,7 @@ function insertDoubler(opts: { argsJsonSchema?: string | null } = {}) {
|
|
|
61
62
|
argsJsonSchema: opts.argsJsonSchema ?? null,
|
|
62
63
|
agentId: workerId,
|
|
63
64
|
typeChecked: true,
|
|
65
|
+
isScratch: opts.isScratch,
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -262,7 +264,9 @@ describe("management routes", () => {
|
|
|
262
264
|
|
|
263
265
|
describe("public execution route", () => {
|
|
264
266
|
test("none-mode endpoint runs and returns the wrapped envelope", async () => {
|
|
265
|
-
const script = insertDoubler();
|
|
267
|
+
const script = insertDoubler({ isScratch: true });
|
|
268
|
+
const old = "2026-07-01T00:00:00.000Z";
|
|
269
|
+
getDb().prepare("UPDATE scripts SET updatedAt = ? WHERE id = ?").run(old, script.id);
|
|
266
270
|
const endpoint = createScriptApi({
|
|
267
271
|
scriptId: script.id,
|
|
268
272
|
agentId: workerId,
|
|
@@ -285,6 +289,7 @@ describe("public execution route", () => {
|
|
|
285
289
|
expect(body.result).toEqual({ doubled: 42 });
|
|
286
290
|
expect(body.error).toBeNull();
|
|
287
291
|
expect(typeof body.durationMs).toBe("number");
|
|
292
|
+
expect(getScriptById(script.id)?.updatedAt).not.toBe(old);
|
|
288
293
|
});
|
|
289
294
|
|
|
290
295
|
test("external endpoint runs receive ctx.mcp connections (parity with /api/scripts/run)", async () => {
|
|
@@ -501,6 +501,18 @@ describe("/api/scripts HTTP", () => {
|
|
|
501
501
|
const inlineBody = await inline.json();
|
|
502
502
|
expect(inlineBody.result).toEqual({ ok: "not typechecked" });
|
|
503
503
|
expect(inlineBody.autoSaved.slug).toContain("scratch-inline-type-error-still-runs");
|
|
504
|
+
getDb()
|
|
505
|
+
.prepare("UPDATE scripts SET updatedAt = ? WHERE name = ? AND scopeId = ?")
|
|
506
|
+
.run("2026-07-01T00:00:00.000Z", inlineBody.autoSaved.slug, workerId);
|
|
507
|
+
const reusedScratch = await dispatch("/api/scripts/run", {
|
|
508
|
+
method: "POST",
|
|
509
|
+
agentId: workerId,
|
|
510
|
+
body: JSON.stringify({ name: inlineBody.autoSaved.slug }),
|
|
511
|
+
});
|
|
512
|
+
expect(reusedScratch.status).toBe(200);
|
|
513
|
+
expect(
|
|
514
|
+
getScript({ name: inlineBody.autoSaved.slug, scope: "agent", scopeId: workerId })?.updatedAt,
|
|
515
|
+
).not.toBe("2026-07-01T00:00:00.000Z");
|
|
504
516
|
|
|
505
517
|
const beforeFailed = listScripts({
|
|
506
518
|
scope: "agent",
|