totemlayer 0.1.15 → 0.1.17
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/README.md +29 -39
- package/bin/agent-runner.mjs +5 -1
- package/bin/agent-setup.mjs +45 -11
- package/bin/connected-agent-service.mjs +8 -2
- package/bin/executor-readiness.mjs +67 -0
- package/bin/inbox-queue.mjs +23 -10
- package/bin/installation.mjs +57 -5
- package/bin/mission-infrastructure.mjs +0 -49
- package/bin/mission-worker.mjs +11 -129
- package/bin/model-adapter.mjs +35 -2
- package/bin/network-retry.mjs +26 -0
- package/bin/network-runtime.mjs +89 -19
- package/bin/platform-client.mjs +30 -2
- package/bin/runtime-diagnostics.mjs +38 -0
- package/bin/sdk/index.js +11 -40
- package/bin/totem.mjs +1 -1
- package/bin/worker-core/adapters.mjs +25 -3
- package/bin/worker-core/errors.mjs +5 -4
- package/package.json +1 -1
- package/scripts/prepare.mjs +20 -0
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
readPrivateRuntimeJson,
|
|
11
11
|
} from "./mission-agent-session.mjs";
|
|
12
12
|
import { inspectLocalRepository } from "./repository.mjs";
|
|
13
|
-
import { customerInspector } from "./local-inspector.mjs";
|
|
14
13
|
|
|
15
14
|
const execute = promisify(execFile);
|
|
16
15
|
const sha = (value) => createHash("sha256").update(value).digest("hex");
|
|
@@ -252,13 +251,10 @@ export async function runInfrastructureWorkerCycle({
|
|
|
252
251
|
infrastructureProvider,
|
|
253
252
|
signal,
|
|
254
253
|
emit = () => {},
|
|
255
|
-
inspector,
|
|
256
|
-
inspectorOptions,
|
|
257
254
|
}) {
|
|
258
255
|
const kind = candidateWorkItem?.metadata?.kind;
|
|
259
256
|
if (!recoveryOnly && !KINDS.has(kind)) return false;
|
|
260
257
|
if (recoveryOnly && !infrastructureProvider) return false;
|
|
261
|
-
inspector = customerInspector({ inspector, inspectorOptions });
|
|
262
258
|
if (!_session || !infrastructureProvider)
|
|
263
259
|
throw new Error("Infrastructure work requires the connected agent service.");
|
|
264
260
|
assertMissionAgentSession(_session, baseUrl, expectedAgentId);
|
|
@@ -305,18 +301,6 @@ export async function runInfrastructureWorkerCycle({
|
|
|
305
301
|
};
|
|
306
302
|
await persist();
|
|
307
303
|
const mission = await client.missions.get(missionId, { signal: activeSignal });
|
|
308
|
-
await inspector.assertAllowed(
|
|
309
|
-
"input",
|
|
310
|
-
{
|
|
311
|
-
goal: mission.goal,
|
|
312
|
-
workItem: item,
|
|
313
|
-
infrastructure: mission.infrastructure,
|
|
314
|
-
workItems: mission.workItems,
|
|
315
|
-
repository: mission.repository,
|
|
316
|
-
},
|
|
317
|
-
{ signal: activeSignal },
|
|
318
|
-
);
|
|
319
|
-
record.originalTask = String(mission.goal ?? "");
|
|
320
304
|
if (mission.infrastructure?.provider !== "local-static")
|
|
321
305
|
throw new Error("Unsupported infrastructure provider.");
|
|
322
306
|
const repository = await inspectLocalRepository({ TOTEM_REPOSITORY_ROOT: repositoryRoot });
|
|
@@ -382,11 +366,6 @@ export async function runInfrastructureWorkerCycle({
|
|
|
382
366
|
emit({ type: "work_started", missionId, workItemId: record.workItemId, stage: record.kind });
|
|
383
367
|
const action = record.kind === "infrastructure_deploy" ? "app.deploy" : "app.verify";
|
|
384
368
|
if (record.state === "infra_source") {
|
|
385
|
-
await inspector.assertAllowed(
|
|
386
|
-
"action",
|
|
387
|
-
{ action: "repository.read", resource: record.repositoryResource, source: record.source },
|
|
388
|
-
{ task: record.originalTask, signal: activeSignal },
|
|
389
|
-
);
|
|
390
369
|
const effect = await client.missions.reserveEffect(
|
|
391
370
|
missionId,
|
|
392
371
|
{
|
|
@@ -415,20 +394,6 @@ export async function runInfrastructureWorkerCycle({
|
|
|
415
394
|
record.source.sourceDirectory,
|
|
416
395
|
activeSignal,
|
|
417
396
|
);
|
|
418
|
-
for (const file of bundle.files) {
|
|
419
|
-
const mediaType = MIME[extname(file.path).toLowerCase()];
|
|
420
|
-
if (
|
|
421
|
-
mediaType?.startsWith("text/") ||
|
|
422
|
-
mediaType === "application/json" ||
|
|
423
|
-
mediaType === "image/svg+xml"
|
|
424
|
-
)
|
|
425
|
-
await inspector.inspectFile(
|
|
426
|
-
file.path,
|
|
427
|
-
mediaType === "image/svg+xml" ? "application/xml" : mediaType,
|
|
428
|
-
Buffer.from(file.content, "base64"),
|
|
429
|
-
{ task: record.originalTask, signal: activeSignal },
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
397
|
if (
|
|
433
398
|
record.kind === "infrastructure_verify" &&
|
|
434
399
|
(bundle.bundleSha256 !== record.source.bundleSha256 ||
|
|
@@ -472,16 +437,6 @@ export async function runInfrastructureWorkerCycle({
|
|
|
472
437
|
await persist();
|
|
473
438
|
}
|
|
474
439
|
if (record.state === "infra_reserved") {
|
|
475
|
-
await inspector.assertAllowed(
|
|
476
|
-
"action",
|
|
477
|
-
{
|
|
478
|
-
action,
|
|
479
|
-
resource: `totem:mission:${missionId}:app`,
|
|
480
|
-
parameters: record.parameters,
|
|
481
|
-
url: record.deploymentUrl,
|
|
482
|
-
},
|
|
483
|
-
{ task: record.originalTask, signal: activeSignal },
|
|
484
|
-
);
|
|
485
440
|
const effect = await client.missions.reserveEffect(
|
|
486
441
|
missionId,
|
|
487
442
|
{
|
|
@@ -548,10 +503,6 @@ export async function runInfrastructureWorkerCycle({
|
|
|
548
503
|
await persist();
|
|
549
504
|
}
|
|
550
505
|
const summary = `${action === "app.deploy" ? "Published" : "Verified"} the reviewed static app locally. The preview is available while its deployment agent is connected.`;
|
|
551
|
-
await inspector.assertAllowed("output", record.receipt, {
|
|
552
|
-
task: record.originalTask,
|
|
553
|
-
signal: activeSignal,
|
|
554
|
-
});
|
|
555
506
|
await client.missions.complete(
|
|
556
507
|
missionId,
|
|
557
508
|
record.workItemId,
|
package/bin/mission-worker.mjs
CHANGED
|
@@ -21,7 +21,6 @@ import { setTimeout as sleep } from "node:timers/promises";
|
|
|
21
21
|
import { inspectLocalRepository } from "./repository.mjs";
|
|
22
22
|
import { localClientEnvironment } from "./platform-client.mjs";
|
|
23
23
|
import { localModelEndpoint } from "./local-model-provider.mjs";
|
|
24
|
-
import { customerInspector } from "./local-inspector.mjs";
|
|
25
24
|
import { externalModelReply, isExternalModelProvider } from "./model-adapter.mjs";
|
|
26
25
|
import { runDockerNodeTests } from "./docker-test-sandbox.mjs";
|
|
27
26
|
import { acquireMissionAgentSession, assertMissionAgentSession } from "./mission-agent-session.mjs";
|
|
@@ -753,10 +752,7 @@ export async function runCodexMissionAgent({
|
|
|
753
752
|
stage,
|
|
754
753
|
agentName,
|
|
755
754
|
agentAddress,
|
|
756
|
-
inspector = customerInspector(),
|
|
757
|
-
task,
|
|
758
755
|
}) {
|
|
759
|
-
await inspector.assertAllowed("input", prompt, { task, signal });
|
|
760
756
|
if (provider !== "ollama-local")
|
|
761
757
|
throw new Error(
|
|
762
758
|
"The legacy Codex adapter requires explicit provider: ollama-local. Use runLocalMissionAgent for native Qwen by default.",
|
|
@@ -842,7 +838,6 @@ export async function runCodexMissionAgent({
|
|
|
842
838
|
});
|
|
843
839
|
}
|
|
844
840
|
if (!final.trim()) throw new Error("The connected agent returned no final result.");
|
|
845
|
-
await inspector.assertAllowed("output", { text: final, commands }, { task, signal });
|
|
846
841
|
return { text: final.slice(0, 32000), commands: commands.slice(-30) };
|
|
847
842
|
}
|
|
848
843
|
|
|
@@ -909,14 +904,7 @@ export function redactRepositorySource(source) {
|
|
|
909
904
|
return output;
|
|
910
905
|
}
|
|
911
906
|
|
|
912
|
-
export async function repositoryEvidence(
|
|
913
|
-
checkout,
|
|
914
|
-
stage,
|
|
915
|
-
checkoutInfo,
|
|
916
|
-
inspector = customerInspector(),
|
|
917
|
-
task,
|
|
918
|
-
signal,
|
|
919
|
-
) {
|
|
907
|
+
export async function repositoryEvidence(checkout, stage, checkoutInfo) {
|
|
920
908
|
if (!["engineering", "review"].includes(stage)) return "";
|
|
921
909
|
const tracked = (await git(checkout, "ls-files"))
|
|
922
910
|
.split("\n")
|
|
@@ -947,9 +935,7 @@ export async function repositoryEvidence(
|
|
|
947
935
|
throw new Error(`The reviewer cannot inspect the complete changed file: ${path}`);
|
|
948
936
|
continue;
|
|
949
937
|
}
|
|
950
|
-
const
|
|
951
|
-
await inspector.assertAllowed("tool-result", { path, source: original }, { task, signal });
|
|
952
|
-
const source = redactRepositorySource(original);
|
|
938
|
+
const source = redactRepositorySource(await readFile(absolute, "utf8"));
|
|
953
939
|
const numbered = source
|
|
954
940
|
.split("\n")
|
|
955
941
|
.map((line, index) => `${index + 1}: ${line}`)
|
|
@@ -964,10 +950,7 @@ export async function repositoryEvidence(
|
|
|
964
950
|
return JSON.stringify({ trackedFiles: paths, changedFiles: changed, files });
|
|
965
951
|
}
|
|
966
952
|
|
|
967
|
-
async function codeRepositoryEvidence(
|
|
968
|
-
checkout,
|
|
969
|
-
{ repair = false, inspector = customerInspector(), task, signal } = {},
|
|
970
|
-
) {
|
|
953
|
+
async function codeRepositoryEvidence(checkout, { repair = false } = {}) {
|
|
971
954
|
const checkoutPath = await realpath(checkout);
|
|
972
955
|
const changed = repair
|
|
973
956
|
? [
|
|
@@ -1005,7 +988,6 @@ async function codeRepositoryEvidence(
|
|
|
1005
988
|
continue;
|
|
1006
989
|
}
|
|
1007
990
|
const content = await readFile(absolute, "utf8");
|
|
1008
|
-
await inspector.assertAllowed("tool-result", { path, content }, { task, signal });
|
|
1009
991
|
if (
|
|
1010
992
|
/-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk-[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16})\b/.test(
|
|
1011
993
|
content,
|
|
@@ -1587,10 +1569,7 @@ async function runStructuredMissionAgent({
|
|
|
1587
1569
|
reasoning = false,
|
|
1588
1570
|
rejectionEvidenceDirectory,
|
|
1589
1571
|
adapterSettings = {},
|
|
1590
|
-
inspector = customerInspector(),
|
|
1591
|
-
task,
|
|
1592
1572
|
}) {
|
|
1593
|
-
await inspector.assertAllowed("input", prompt, { task, signal });
|
|
1594
1573
|
const regressionFile =
|
|
1595
1574
|
stage === "code" && !repair ? await selectMissionRegressionTestFile(checkout) : null;
|
|
1596
1575
|
const properties =
|
|
@@ -1651,9 +1630,8 @@ async function runStructuredMissionAgent({
|
|
|
1651
1630
|
throw new Error("Specify the installed model for this provider.");
|
|
1652
1631
|
const evidence =
|
|
1653
1632
|
stage === "code"
|
|
1654
|
-
? await codeRepositoryEvidence(checkout, { repair
|
|
1655
|
-
: await repositoryEvidence(checkout, stage, checkoutInfo
|
|
1656
|
-
await inspector.assertAllowed("tool-result", evidence, { task, signal });
|
|
1633
|
+
? await codeRepositoryEvidence(checkout, { repair })
|
|
1634
|
+
: await repositoryEvidence(checkout, stage, checkoutInfo);
|
|
1657
1635
|
const regressionInstruction = regressionFile
|
|
1658
1636
|
? `\n\nWorker-selected regression file: ${JSON.stringify(regressionFile.path)}. Return exactly this one path in testChanges, as a new file. Do not edit, replace, or rename any existing test file in initial generation. The new .test.js file uses ${regressionFile.moduleType === "module" ? "ES modules: import test from 'node:test' and assert from 'node:assert/strict'" : "CommonJS: require('node:test') and require('node:assert/strict'); use an async test with dynamic import() if the source API is an ES module"}. Import the actual source API with paths relative to this file. Node --test discovers this filename. Baseline expectation changes for accepted new fields are reserved for exact-edit repair after actual test failure.`
|
|
1659
1637
|
: "";
|
|
@@ -1662,7 +1640,6 @@ async function runStructuredMissionAgent({
|
|
|
1662
1640
|
let correction = "";
|
|
1663
1641
|
const useReasoning = reasoning === true && ["code", "review"].includes(stage);
|
|
1664
1642
|
for (let attempt = 0; attempt < (stage === "code" ? 2 : 1); attempt += 1) {
|
|
1665
|
-
await inspector.assertAllowed("input", correction, { task, signal });
|
|
1666
1643
|
const generationLimit = useReasoning ? 8192 : stage === "code" ? 4096 : 2048;
|
|
1667
1644
|
const data = await requestModel(endpoint, {
|
|
1668
1645
|
method: "POST",
|
|
@@ -1709,25 +1686,15 @@ async function runStructuredMissionAgent({
|
|
|
1709
1686
|
provider !== "ollama-local" ? data?.choices?.[0]?.message?.content : data?.message?.content;
|
|
1710
1687
|
if (typeof result !== "string" || !result.trim())
|
|
1711
1688
|
throw new Error("The local model returned no structured result.");
|
|
1712
|
-
await inspector.assertAllowed("output", result, { task, signal: combined });
|
|
1713
1689
|
if (stage !== "code") return { text: result.slice(0, 32000), commands: [] };
|
|
1714
1690
|
try {
|
|
1715
1691
|
return repair
|
|
1716
|
-
? await
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
{ checkout, result },
|
|
1723
|
-
(input) =>
|
|
1724
|
-
applyModelFileChanges(input.checkout, input.result, {
|
|
1725
|
-
requireRegressionTests: true,
|
|
1726
|
-
requireSeparateTestChanges: true,
|
|
1727
|
-
newRegressionTestPath: regressionFile.path,
|
|
1728
|
-
}),
|
|
1729
|
-
{ task, signal: combined },
|
|
1730
|
-
);
|
|
1692
|
+
? await applyModelFileEdits(checkout, result)
|
|
1693
|
+
: await applyModelFileChanges(checkout, result, {
|
|
1694
|
+
requireRegressionTests: true,
|
|
1695
|
+
requireSeparateTestChanges: true,
|
|
1696
|
+
newRegressionTestPath: regressionFile.path,
|
|
1697
|
+
});
|
|
1731
1698
|
} catch (error) {
|
|
1732
1699
|
if (rejectionEvidenceDirectory) {
|
|
1733
1700
|
await mkdir(rejectionEvidenceDirectory, {
|
|
@@ -2419,7 +2386,6 @@ async function settleCompletedResult(client, missionId, workItemId, path, record
|
|
|
2419
2386
|
stage: record.stage,
|
|
2420
2387
|
result: record.result,
|
|
2421
2388
|
leaseToken: record.leaseToken,
|
|
2422
|
-
originalTask: record.originalTask,
|
|
2423
2389
|
});
|
|
2424
2390
|
}
|
|
2425
2391
|
|
|
@@ -2431,7 +2397,6 @@ async function recoverReadyCompletions(
|
|
|
2431
2397
|
signal,
|
|
2432
2398
|
emit,
|
|
2433
2399
|
limit = Infinity,
|
|
2434
|
-
inspector = customerInspector(),
|
|
2435
2400
|
) {
|
|
2436
2401
|
let handled = 0;
|
|
2437
2402
|
for (const name of await readdir(directory)) {
|
|
@@ -2479,7 +2444,6 @@ async function recoverReadyCompletions(
|
|
|
2479
2444
|
await reportBlockedCompletion(record);
|
|
2480
2445
|
continue;
|
|
2481
2446
|
}
|
|
2482
|
-
await inspector.assertAllowed("output", record.result, { task: record.originalTask, signal });
|
|
2483
2447
|
if (record.state === "result_settling")
|
|
2484
2448
|
await settleCompletedResult(client, missionId, workItemId, path, record, signal);
|
|
2485
2449
|
await client.missions.complete(
|
|
@@ -2557,26 +2521,7 @@ export async function runMissionWorker({
|
|
|
2557
2521
|
emit = () => {},
|
|
2558
2522
|
runAgent = runLocalMissionAgent,
|
|
2559
2523
|
adapterSettings = {},
|
|
2560
|
-
inspector,
|
|
2561
|
-
inspectorOptions,
|
|
2562
2524
|
}) {
|
|
2563
|
-
inspector = customerInspector({ inspector, inspectorOptions });
|
|
2564
|
-
const uncheckedRunAgent = runAgent;
|
|
2565
|
-
let originalTask;
|
|
2566
|
-
runAgent = async (input) => {
|
|
2567
|
-
const result = await inspector.runAction(
|
|
2568
|
-
{
|
|
2569
|
-
prompt: input.prompt,
|
|
2570
|
-
checkout: input.checkout,
|
|
2571
|
-
write: input.write ?? false,
|
|
2572
|
-
stage: input.stage,
|
|
2573
|
-
},
|
|
2574
|
-
(snapshot) => uncheckedRunAgent({ ...input, ...snapshot, inspector, task: originalTask }),
|
|
2575
|
-
{ task: originalTask, signal: input.signal },
|
|
2576
|
-
);
|
|
2577
|
-
await inspector.assertAllowed("output", result, { task: originalTask, signal: input.signal });
|
|
2578
|
-
return result;
|
|
2579
|
-
};
|
|
2580
2525
|
if (!UUID.test(missionId)) throw new Error("Provide a valid mission ID.");
|
|
2581
2526
|
missionModelEndpoint(provider, { ollamaUrl, qwenUrl, nimUrl });
|
|
2582
2527
|
model ??=
|
|
@@ -2655,7 +2600,6 @@ export async function runMissionWorker({
|
|
|
2655
2600
|
signal,
|
|
2656
2601
|
emit,
|
|
2657
2602
|
_singleCycle ? 1 : Infinity,
|
|
2658
|
-
inspector,
|
|
2659
2603
|
);
|
|
2660
2604
|
if (_singleCycle && recovered) return { activity: "recovery", handled: recovered };
|
|
2661
2605
|
const reconciled = await recoverKnownLocalTestFailures(
|
|
@@ -2668,7 +2612,6 @@ export async function runMissionWorker({
|
|
|
2668
2612
|
);
|
|
2669
2613
|
if (_singleCycle && reconciled) return { activity: "recovery", handled: reconciled };
|
|
2670
2614
|
const mission = await client.missions.get(missionId, { signal });
|
|
2671
|
-
originalTask = String(mission.goal ?? "");
|
|
2672
2615
|
if (
|
|
2673
2616
|
mission.status === "canceled" ||
|
|
2674
2617
|
mission.status === "completed" ||
|
|
@@ -2713,32 +2656,6 @@ export async function runMissionWorker({
|
|
|
2713
2656
|
)
|
|
2714
2657
|
continue;
|
|
2715
2658
|
processed += 1;
|
|
2716
|
-
try {
|
|
2717
|
-
await inspector.assertAllowed(
|
|
2718
|
-
"input",
|
|
2719
|
-
{
|
|
2720
|
-
goal: mission.goal,
|
|
2721
|
-
desiredOutput: mission.desiredOutput,
|
|
2722
|
-
repository: mission.repository,
|
|
2723
|
-
workItem,
|
|
2724
|
-
events: mission.events ?? [],
|
|
2725
|
-
artifacts: mission.artifacts ?? [],
|
|
2726
|
-
},
|
|
2727
|
-
{ task: originalTask, signal },
|
|
2728
|
-
);
|
|
2729
|
-
} catch (error) {
|
|
2730
|
-
if (!error.code?.startsWith("INSPECTION_")) throw error;
|
|
2731
|
-
if (!reportedAttention.has(workItem.id)) {
|
|
2732
|
-
await recordRecoveryBlocker(workItem, error.code);
|
|
2733
|
-
emit({
|
|
2734
|
-
type: "needs_attention",
|
|
2735
|
-
workItemId: workItem.id,
|
|
2736
|
-
code: error.code,
|
|
2737
|
-
message: "The customer-side inspector blocked this assignment before launch.",
|
|
2738
|
-
});
|
|
2739
|
-
}
|
|
2740
|
-
continue;
|
|
2741
|
-
}
|
|
2742
2659
|
let stage;
|
|
2743
2660
|
try {
|
|
2744
2661
|
stage = stageOf(workItem);
|
|
@@ -3091,16 +3008,6 @@ export async function runMissionWorker({
|
|
|
3091
3008
|
? ["repository.read"]
|
|
3092
3009
|
: [];
|
|
3093
3010
|
const authorizeLocalLaunch = async () => {
|
|
3094
|
-
await inspector.assertAllowed(
|
|
3095
|
-
"action",
|
|
3096
|
-
{
|
|
3097
|
-
stage,
|
|
3098
|
-
actions: repositoryActions,
|
|
3099
|
-
resource: mission.repository?.url ?? null,
|
|
3100
|
-
objective: workItem.objective,
|
|
3101
|
-
},
|
|
3102
|
-
{ task: originalTask, signal: combined },
|
|
3103
|
-
);
|
|
3104
3011
|
// Renewal rechecks mission state, current credential, membership, and the lease.
|
|
3105
3012
|
await client.missions.renew(missionId, workItem.id, claim.leaseToken, {
|
|
3106
3013
|
signal: combined,
|
|
@@ -3149,15 +3056,6 @@ export async function runMissionWorker({
|
|
|
3149
3056
|
for (const repositoryAction of repositoryActions) {
|
|
3150
3057
|
if (!mission.repository)
|
|
3151
3058
|
throw new Error("This assignment needs a selected repository.");
|
|
3152
|
-
await inspector.assertAllowed(
|
|
3153
|
-
"action",
|
|
3154
|
-
{
|
|
3155
|
-
action: repositoryAction,
|
|
3156
|
-
resource: mission.repository.url,
|
|
3157
|
-
objective: workItem.objective,
|
|
3158
|
-
},
|
|
3159
|
-
{ task: originalTask, signal: combined },
|
|
3160
|
-
);
|
|
3161
3059
|
const reservation =
|
|
3162
3060
|
(prior?.reservedEffects ?? []).find(
|
|
3163
3061
|
(effect) =>
|
|
@@ -3199,16 +3097,6 @@ export async function runMissionWorker({
|
|
|
3199
3097
|
effectIds,
|
|
3200
3098
|
});
|
|
3201
3099
|
for (const effectId of effectIds) {
|
|
3202
|
-
await inspector.assertAllowed(
|
|
3203
|
-
"action",
|
|
3204
|
-
{
|
|
3205
|
-
effectId,
|
|
3206
|
-
actions: repositoryActions,
|
|
3207
|
-
resource: mission.repository.url,
|
|
3208
|
-
objective: workItem.objective,
|
|
3209
|
-
},
|
|
3210
|
-
{ task: originalTask, signal: combined },
|
|
3211
|
-
);
|
|
3212
3100
|
const dispatch = await client.missions.dispatchEffect(
|
|
3213
3101
|
missionId,
|
|
3214
3102
|
effectId,
|
|
@@ -3548,7 +3436,6 @@ export async function runMissionWorker({
|
|
|
3548
3436
|
state: "result_settling",
|
|
3549
3437
|
stage,
|
|
3550
3438
|
result,
|
|
3551
|
-
originalTask,
|
|
3552
3439
|
leaseToken: claim.leaseToken,
|
|
3553
3440
|
effectIds: [...effectIds],
|
|
3554
3441
|
repositoryUrl: mission.repository.url,
|
|
@@ -3569,15 +3456,10 @@ export async function runMissionWorker({
|
|
|
3569
3456
|
state: "ready",
|
|
3570
3457
|
stage,
|
|
3571
3458
|
result,
|
|
3572
|
-
originalTask,
|
|
3573
3459
|
leaseToken: claim.leaseToken,
|
|
3574
3460
|
});
|
|
3575
3461
|
}
|
|
3576
3462
|
}
|
|
3577
|
-
await inspector.assertAllowed("output", result, {
|
|
3578
|
-
task: originalTask,
|
|
3579
|
-
signal: combined,
|
|
3580
|
-
});
|
|
3581
3463
|
await client.missions.complete(
|
|
3582
3464
|
missionId,
|
|
3583
3465
|
workItem.id,
|
package/bin/model-adapter.mjs
CHANGED
|
@@ -4,6 +4,37 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { isAbsolute, join } from "node:path";
|
|
5
5
|
import { createCommandAdapter, createHttpAdapter } from "./worker-core/adapters.mjs";
|
|
6
6
|
import { installedClientInvocation, localClientEnvironment } from "./platform-client.mjs";
|
|
7
|
+
import { WorkerError } from "./worker-core/errors.mjs";
|
|
8
|
+
|
|
9
|
+
export function providerFailureCode(text) {
|
|
10
|
+
if (
|
|
11
|
+
/unexpected argument|unknown option|unrecognized option|no such option|unknown flag/i.test(text)
|
|
12
|
+
)
|
|
13
|
+
return "AGENT_CLI_INCOMPATIBLE";
|
|
14
|
+
if (
|
|
15
|
+
/not logged in|authentication required|invalid[_ ]api[_ ]key|unauthorized|refresh token.*(?:expired|reused)|sign in.*required/i.test(
|
|
16
|
+
text,
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
return "AGENT_AUTH_REQUIRED";
|
|
20
|
+
if (
|
|
21
|
+
/usage limit|rate limit|quota.*(?:exceeded|insufficient)|insufficient_quota|credit balance/i.test(
|
|
22
|
+
text,
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
return "AGENT_QUOTA_EXCEEDED";
|
|
26
|
+
if (
|
|
27
|
+
/sandbox.*(?:setup failed|not available|not configured|unsupported|error)|CreateProcessAsUser|error 1385/i.test(
|
|
28
|
+
text,
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
return "AGENT_SANDBOX_UNAVAILABLE";
|
|
32
|
+
if (
|
|
33
|
+
/model.*(?:not found|not supported|does not exist|not have access)|model_not_found/i.test(text)
|
|
34
|
+
)
|
|
35
|
+
return "AGENT_MODEL_UNAVAILABLE";
|
|
36
|
+
return "AGENT_EXIT_FAILED";
|
|
37
|
+
}
|
|
7
38
|
|
|
8
39
|
export const EXTERNAL_PROVIDERS = ["codex", "claude", "command", "http"];
|
|
9
40
|
export const isExternalModelProvider = (provider) => EXTERNAL_PROVIDERS.includes(provider);
|
|
@@ -88,7 +119,7 @@ export async function externalModelReply({
|
|
|
88
119
|
continue;
|
|
89
120
|
}
|
|
90
121
|
if (event.type === "turn.failed" || event.type === "error")
|
|
91
|
-
throw new
|
|
122
|
+
throw new WorkerError(providerFailureCode(event.error?.message ?? event.message ?? ""));
|
|
92
123
|
if (event.type === "item.completed" && event.item?.type === "agent_message")
|
|
93
124
|
final = event.item.text;
|
|
94
125
|
}
|
|
@@ -113,7 +144,8 @@ export async function externalModelReply({
|
|
|
113
144
|
];
|
|
114
145
|
parseResult = (output) => {
|
|
115
146
|
const result = JSON.parse(output);
|
|
116
|
-
if (result.is_error || result.subtype !== "success")
|
|
147
|
+
if (result.is_error || result.subtype !== "success")
|
|
148
|
+
throw new WorkerError(providerFailureCode(String(result.result ?? "")));
|
|
117
149
|
return {
|
|
118
150
|
status: "completed",
|
|
119
151
|
output: result.structured_output
|
|
@@ -153,6 +185,7 @@ export async function externalModelReply({
|
|
|
153
185
|
env,
|
|
154
186
|
timeoutMs,
|
|
155
187
|
parseResult,
|
|
188
|
+
...(["codex", "claude"].includes(provider) ? { classifyFailure: providerFailureCode } : {}),
|
|
156
189
|
});
|
|
157
190
|
}
|
|
158
191
|
const result = await adapter.run({ prompt, signal, runId: randomUUID() });
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { pause } from "./network.mjs";
|
|
2
|
+
|
|
3
|
+
export function transientNetworkError(error) {
|
|
4
|
+
return (
|
|
5
|
+
error?.status === 429 ||
|
|
6
|
+
error?.status >= 500 ||
|
|
7
|
+
["NETWORK_BUSY", "NETWORK_ERROR", "TIMEOUT"].includes(error?.code)
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Only use for reads or mutations protected by a durable idempotency key.
|
|
12
|
+
export async function retryNetwork(
|
|
13
|
+
operation,
|
|
14
|
+
{ signal, onRetry = () => {}, delayMs = 1000, attempts = 4 } = {},
|
|
15
|
+
) {
|
|
16
|
+
for (let attempt = 0; ; attempt++) {
|
|
17
|
+
signal?.throwIfAborted();
|
|
18
|
+
try {
|
|
19
|
+
return await operation();
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (signal?.aborted || !transientNetworkError(error) || attempt + 1 >= attempts) throw error;
|
|
22
|
+
onRetry(error);
|
|
23
|
+
await pause(Math.min(delayMs * 2 ** attempt, 10000), signal);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|