@lazyingart/agintiflow 0.20.250 → 0.20.252
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/docs/model-selection.md +2 -0
- package/docs/supervision-campaign-ledger.md +29 -0
- package/package.json +3 -2
- package/scripts/smoke-dynamic-step-budget.js +146 -0
- package/src/agent-runner.js +187 -13
- package/src/model-client.js +32 -8
- package/src/provider-handoff.js +126 -0
- package/src/scs-evidence.js +14 -4
package/docs/model-selection.md
CHANGED
|
@@ -26,6 +26,8 @@ The local tier policy uses the workstation's installed aliases without treating
|
|
|
26
26
|
|
|
27
27
|
Installed aliases are not loaded during routing. A genuine coding/implementation request starts on Deep, checks the authenticated model inventory, and selects the exact `AGINTI_LOCALLLM_CODE_MODEL` value only when present. Missing or unverified capability stays on Deep; the decision and effective model are recorded so fallback sessions can re-evaluate and selected sessions resume on the same model. Explanation-only code questions and non-code writing/research/documentation/design keep their existing route. Automatic Max is off by default; set `AGINTI_LOCALLLM_ALLOW_AUTO_MAX=true` to opt in. An opted-in high-complexity non-code run starts on Deep, confirms the Max alias through authenticated `/v1/models`, and only then samples current resources. Unknown or pressured resource state stays on Deep. Explicit Max also cannot bypass the live gate: immediately before client creation, each new or resumed run rechecks at least 24 GiB available RAM, swap use at or below 75%, and 40 GiB aggregate free NVIDIA memory. A blocked explicit gate creates no model client and performs no inference.
|
|
28
28
|
|
|
29
|
+
Smart hosted routes fail over only toward the local trust boundary. If a hosted provider returns an authentication, quota, rate-limit, model-availability, capacity, or network failure, AgInTiFlow records `provider.handoff_requested`, patches the same durable session to `localllm-deep`, verifies LocalLLM readiness, records `provider.handoff_activated`, and continues without replaying the user request or prior side effects. Manual routing remains exact and never changes provider automatically. Retained integration profiles also keep their pinned provider identity. Set `AGINTI_PROVIDER_HANDOFF=false` to disable the smart hosted-to-local handoff or `AGINTI_PROVIDER_HANDOFF_MODEL` to select another verified local fallback model.
|
|
30
|
+
|
|
29
31
|
Long writing tasks use an additional tool boundary: `writing_specialist`. The main model still plans the run, manages files, formats Markdown/LaTeX/Final Draft output, compiles/checks artifacts, and finishes. The specialist gets only the writing brief, canon, style guide, prior draft, target, audience, constraints, and format intent, then returns prose plus a formatter handoff. It follows the active LocalLLM provider by default even when hosted keys exist. Cross-provider writing requires both an explicit target (`AGINTI_WRITING_PROVIDER` or a per-run provider override) and `AGINTI_ALLOW_HOSTED_WRITING_SPECIALIST=true` (or the equivalent per-run permission flag); language detection and ambient credentials never grant that permission.
|
|
30
32
|
|
|
31
33
|
## CLI Commands
|
|
@@ -379,3 +379,32 @@ local report paths and existing-document revision language now count as local
|
|
|
379
379
|
workspace intent, so surrounding research policy does not force a new deep
|
|
380
380
|
research route. The phrase `page-safe` no longer creates a browser-evidence
|
|
381
381
|
requirement. Focused routing/evidence smokes and the full npm suite pass.
|
|
382
|
+
|
|
383
|
+
### Fresh current-turn command evidence on retained sessions
|
|
384
|
+
|
|
385
|
+
`security-labshare-035` exercised a long-lived LocalLLM security-repair
|
|
386
|
+
session after the target repository was already fixed, committed, and clean.
|
|
387
|
+
The follow-up prompt deliberately asked the agent not to edit or recommit. It
|
|
388
|
+
required only the exact hidden verifier and `git status --short`, while allowing
|
|
389
|
+
all other passing implementation and unit-test evidence to remain reusable.
|
|
390
|
+
|
|
391
|
+
Installed AgInTiFlow `0.20.250` incorrectly finished after `inspect_project`.
|
|
392
|
+
It reused the old verifier/status evidence and never executed either command in
|
|
393
|
+
the current turn. The defect was not model quality: the runtime had failed to
|
|
394
|
+
turn explicit commands in a concrete same-task interruption into a fresh,
|
|
395
|
+
revision-bound evidence obligation.
|
|
396
|
+
|
|
397
|
+
The runtime now records explicit current-turn commands in the active execution
|
|
398
|
+
contract and creates a fresh command batch for only that subset. Unrelated
|
|
399
|
+
passing evidence from the unchanged mutation revision remains valid, while old
|
|
400
|
+
runs of the newly requested commands cannot pre-complete the batch. Inline
|
|
401
|
+
command extraction also recognizes natural `verify`, `validate`, `check`,
|
|
402
|
+
`confirm`, and rerun wording in English and Chinese.
|
|
403
|
+
|
|
404
|
+
The same real session was replayed against the patched source at goal revision
|
|
405
|
+
`34`. Its event ledger records both commands under
|
|
406
|
+
`required-command-batch-3`: the exact external security contract returned
|
|
407
|
+
`security_labshare_contract: PASS`, and `git status --short` returned empty.
|
|
408
|
+
The target stayed at commit `2b35928` with no mutation. Focused planning,
|
|
409
|
+
truthful-completion, and dynamic-budget regressions pass, as does the complete
|
|
410
|
+
AgInTiFlow npm suite. The fix is released in AgInTiFlow `0.20.251`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.252",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -174,6 +174,7 @@
|
|
|
174
174
|
"smoke:localllm-code-routing": "node scripts/smoke-localllm-code-routing.js",
|
|
175
175
|
"smoke:local-failure-recovery": "node scripts/smoke-local-failure-recovery.js",
|
|
176
176
|
"smoke:planning-timeout-recovery": "node scripts/smoke-planning-timeout-recovery.js",
|
|
177
|
+
"smoke:provider-handoff": "node scripts/smoke-provider-handoff.js",
|
|
177
178
|
"smoke:local-resource-policy": "node scripts/smoke-local-resource-policy.js",
|
|
178
179
|
"smoke:context-budget-recovery": "node scripts/smoke-context-budget-recovery.js",
|
|
179
180
|
"smoke:localllm-model-tiers": "node scripts/smoke-localllm-model-tiers.js",
|
|
@@ -228,7 +229,7 @@
|
|
|
228
229
|
"publish:env": "node scripts/npm-publish-from-env.js publish --access public",
|
|
229
230
|
"publish:env:whoami": "node scripts/npm-publish-from-env.js whoami",
|
|
230
231
|
"pretest": "npm run smoke:planning-timeout-recovery",
|
|
231
|
-
"test": "npm run check && npm run smoke:integration-production-mount && npm run smoke:integration-production-runtime-bundle && npm run smoke:integration-retained-idempotency-store && npm run smoke:integration-storage-authority && npm run smoke:integration-retained-durable-common && npm run smoke:integration-retained-file-lock && npm run smoke:integration-retained-event-ledger && npm run smoke:integration-retained-event-ledger-bundle && npm run smoke:integration-retained-repository-kernel && npm run smoke:integration-retained-session-state-store && npm run smoke:integration-retained-native-session-repository-state && npm run smoke:integration-retained-native-execution-evidence && npm run smoke:integration-retained-runtime-repository-surface && npm run smoke:integration-retained-runtime-repository-phase-b && npm run smoke:integration-runtime-repository-contract && npm run smoke:integration-runtime-authority && npm run smoke:localllm-provider && npm run smoke:localllm-model-tiers && npm run smoke:localllm-code-routing && npm run smoke:local-failure-recovery && npm run smoke:localllm-auto-max && npm run smoke:local-resource-policy && npm run smoke:context-budget-recovery && npm run smoke:session-runtime && npm run smoke:runtime-core && npm run smoke:progressive-tools && npm run smoke:truthful-completion && npm run smoke:document-artifact-quality && npm run smoke:writing-specialist-routing && npm run smoke:workspace-secret-false-positive && npm run eval:local-first-agent && npm run eval:provider-attribution && npm run smoke:runtime-compat && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:math-rendering && npm run smoke:web-ui && npm run smoke:web-autostart && npm run smoke:webapp-command && npm run smoke:web-port-fallback && npm run smoke:docker-command && npm run smoke:coding-tools && npm run smoke:dynamic-step-budget && npm run smoke:execution-policy && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && npm run smoke:perception-research && npm run smoke:deep-research && npm run smoke:public-research && npm run smoke:safe-chat && npm run smoke:auth && npm run smoke:agentlink && npm run smoke:canvas-artifacts && npm run smoke:capabilities && npm run smoke:mcp && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:permission-modes && npm run smoke:skills && npm run smoke:skillmesh && npm run smoke:supervision-ledger && npm run smoke:tmux-tools && npm run smoke:long-jobs && npm run smoke:run-stdin && npm run smoke:cli-chat && npm run smoke:inbox",
|
|
232
|
+
"test": "npm run check && npm run smoke:integration-production-mount && npm run smoke:integration-production-runtime-bundle && npm run smoke:integration-retained-idempotency-store && npm run smoke:integration-storage-authority && npm run smoke:integration-retained-durable-common && npm run smoke:integration-retained-file-lock && npm run smoke:integration-retained-event-ledger && npm run smoke:integration-retained-event-ledger-bundle && npm run smoke:integration-retained-repository-kernel && npm run smoke:integration-retained-session-state-store && npm run smoke:integration-retained-native-session-repository-state && npm run smoke:integration-retained-native-execution-evidence && npm run smoke:integration-retained-runtime-repository-surface && npm run smoke:integration-retained-runtime-repository-phase-b && npm run smoke:integration-runtime-repository-contract && npm run smoke:integration-runtime-authority && npm run smoke:localllm-provider && npm run smoke:localllm-model-tiers && npm run smoke:localllm-code-routing && npm run smoke:local-failure-recovery && npm run smoke:localllm-auto-max && npm run smoke:local-resource-policy && npm run smoke:context-budget-recovery && npm run smoke:session-runtime && npm run smoke:runtime-core && npm run smoke:progressive-tools && npm run smoke:truthful-completion && npm run smoke:document-artifact-quality && npm run smoke:writing-specialist-routing && npm run smoke:workspace-secret-false-positive && npm run smoke:provider-handoff && npm run eval:local-first-agent && npm run eval:provider-attribution && npm run smoke:runtime-compat && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:math-rendering && npm run smoke:web-ui && npm run smoke:web-autostart && npm run smoke:webapp-command && npm run smoke:web-port-fallback && npm run smoke:docker-command && npm run smoke:coding-tools && npm run smoke:dynamic-step-budget && npm run smoke:execution-policy && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && npm run smoke:perception-research && npm run smoke:deep-research && npm run smoke:public-research && npm run smoke:safe-chat && npm run smoke:auth && npm run smoke:agentlink && npm run smoke:canvas-artifacts && npm run smoke:capabilities && npm run smoke:mcp && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:permission-modes && npm run smoke:skills && npm run smoke:skillmesh && npm run smoke:supervision-ledger && npm run smoke:tmux-tools && npm run smoke:long-jobs && npm run smoke:run-stdin && npm run smoke:cli-chat && npm run smoke:inbox",
|
|
232
233
|
"pack:dry-run": "npm pack --dry-run",
|
|
233
234
|
"smoke:capabilities": "node scripts/smoke-capabilities.js"
|
|
234
235
|
},
|
|
@@ -2885,6 +2885,152 @@ try {
|
|
|
2885
2885
|
"the goal contract did not separate durable task lineage from the active interruption"
|
|
2886
2886
|
);
|
|
2887
2887
|
|
|
2888
|
+
const exactVerifier = "python3 /tmp/security_labshare_contract.py";
|
|
2889
|
+
const exactStatus = "git status --short";
|
|
2890
|
+
const retainedUnitTest = "python3 -m unittest discover -s tests -v";
|
|
2891
|
+
const freshCommandState = {
|
|
2892
|
+
goal: "Repair and verify the current security task.",
|
|
2893
|
+
plan: "The prior implementation and validation are complete.",
|
|
2894
|
+
messages: [
|
|
2895
|
+
toolMessage({
|
|
2896
|
+
ok: true,
|
|
2897
|
+
toolName: "run_command",
|
|
2898
|
+
args: { command: retainedUnitTest },
|
|
2899
|
+
exitCode: 0,
|
|
2900
|
+
stdout: "OK",
|
|
2901
|
+
projectMutationRevision: 7,
|
|
2902
|
+
goalRevision: 5,
|
|
2903
|
+
}),
|
|
2904
|
+
toolMessage({
|
|
2905
|
+
ok: true,
|
|
2906
|
+
toolName: "run_command",
|
|
2907
|
+
args: { command: exactVerifier },
|
|
2908
|
+
exitCode: 0,
|
|
2909
|
+
stdout: "security_labshare_contract: PASS",
|
|
2910
|
+
projectMutationRevision: 7,
|
|
2911
|
+
requiredCommandBatchId: "required-command-batch-2",
|
|
2912
|
+
requiredProjectCommand: exactVerifier,
|
|
2913
|
+
goalRevision: 5,
|
|
2914
|
+
}),
|
|
2915
|
+
toolMessage({
|
|
2916
|
+
ok: true,
|
|
2917
|
+
toolName: "run_command",
|
|
2918
|
+
args: { command: exactStatus },
|
|
2919
|
+
exitCode: 0,
|
|
2920
|
+
stdout: "",
|
|
2921
|
+
projectMutationRevision: 7,
|
|
2922
|
+
requiredCommandBatchId: "required-command-batch-2",
|
|
2923
|
+
requiredProjectCommand: exactStatus,
|
|
2924
|
+
goalRevision: 5,
|
|
2925
|
+
}),
|
|
2926
|
+
],
|
|
2927
|
+
meta: {
|
|
2928
|
+
taskProfile: "security",
|
|
2929
|
+
goalContract: {
|
|
2930
|
+
version: 3,
|
|
2931
|
+
revision: 5,
|
|
2932
|
+
status: "completed",
|
|
2933
|
+
taskGoal: "Repair and verify the current security task.",
|
|
2934
|
+
activeGoal: "Repair and verify the current security task.",
|
|
2935
|
+
currentRequest: "Repair and verify the current security task.",
|
|
2936
|
+
history: [{ revision: 5, taskHash: "same-security-task" }],
|
|
2937
|
+
},
|
|
2938
|
+
projectVerification: {
|
|
2939
|
+
mutationRevision: 7,
|
|
2940
|
+
requiredCommands: [retainedUnitTest],
|
|
2941
|
+
contractRequiredCommands: [exactVerifier, exactStatus],
|
|
2942
|
+
requiredCommandBatchSequence: 2,
|
|
2943
|
+
requiredCommandBatch: {
|
|
2944
|
+
id: "required-command-batch-2",
|
|
2945
|
+
key: JSON.stringify([exactVerifier, exactStatus]),
|
|
2946
|
+
requiredCommands: [exactVerifier, exactStatus],
|
|
2947
|
+
goalRevision: 5,
|
|
2948
|
+
completedCommands: [exactVerifier, exactStatus],
|
|
2949
|
+
completedRuns: [
|
|
2950
|
+
{ command: exactVerifier, mutationRevision: 7 },
|
|
2951
|
+
{ command: exactStatus, mutationRevision: 7 },
|
|
2952
|
+
],
|
|
2953
|
+
startedMutationRevision: 7,
|
|
2954
|
+
lastMutationRevision: 7,
|
|
2955
|
+
complete: true,
|
|
2956
|
+
},
|
|
2957
|
+
commandRuns: [{
|
|
2958
|
+
command: retainedUnitTest,
|
|
2959
|
+
mutationRevision: 7,
|
|
2960
|
+
ok: true,
|
|
2961
|
+
}],
|
|
2962
|
+
testRuns: [],
|
|
2963
|
+
},
|
|
2964
|
+
},
|
|
2965
|
+
};
|
|
2966
|
+
const freshCommandRequest =
|
|
2967
|
+
"Continue the same corrective task. Run `python3 /tmp/security_labshare_contract.py`, verify `git status --short`, and finish without editing or recommitting files.";
|
|
2968
|
+
const freshCommandUpdate = applyContinuationContractTransition(
|
|
2969
|
+
freshCommandState,
|
|
2970
|
+
freshCommandRequest,
|
|
2971
|
+
{ at: "2026-08-26T04:00:00.000Z" }
|
|
2972
|
+
);
|
|
2973
|
+
assert(
|
|
2974
|
+
freshCommandUpdate?.preserveTaskBoundary && freshCommandUpdate?.refreshExecutionContract,
|
|
2975
|
+
"an explicit same-task command rerun did not refresh the execution contract"
|
|
2976
|
+
);
|
|
2977
|
+
assert(
|
|
2978
|
+
JSON.stringify(freshCommandState.meta.activeExecutionContract.requiredProjectCommands) ===
|
|
2979
|
+
JSON.stringify([exactVerifier, exactStatus]),
|
|
2980
|
+
"the current turn lost an explicitly verified inline command"
|
|
2981
|
+
);
|
|
2982
|
+
assert(
|
|
2983
|
+
freshCommandState.meta.projectVerification.requiredCommandBatch.goalRevision === 6,
|
|
2984
|
+
"the fresh command batch was not bound to the current goal revision"
|
|
2985
|
+
);
|
|
2986
|
+
assert(
|
|
2987
|
+
freshCommandState.meta.projectVerification.requiredCommandBatch.completedCommands.length === 0,
|
|
2988
|
+
"retained command evidence pre-completed a current-turn rerun obligation"
|
|
2989
|
+
);
|
|
2990
|
+
const freshCommandConfig = {
|
|
2991
|
+
taskProfile: "security",
|
|
2992
|
+
commandCwd: workspace,
|
|
2993
|
+
};
|
|
2994
|
+
let freshCommandRuntime = nextStepRuntimeConfig(freshCommandConfig, freshCommandState);
|
|
2995
|
+
assert(
|
|
2996
|
+
freshCommandRuntime.requiredProjectCommandPending === true,
|
|
2997
|
+
"the current-turn verifier was not marked pending"
|
|
2998
|
+
);
|
|
2999
|
+
assert(
|
|
3000
|
+
freshCommandRuntime.requiredProjectCommand === exactVerifier,
|
|
3001
|
+
"the first explicitly repeated command was not pending"
|
|
3002
|
+
);
|
|
3003
|
+
const verifierResult = {
|
|
3004
|
+
ok: true,
|
|
3005
|
+
toolName: "run_command",
|
|
3006
|
+
args: { command: exactVerifier },
|
|
3007
|
+
exitCode: 0,
|
|
3008
|
+
stdout: "security_labshare_contract: PASS",
|
|
3009
|
+
stderr: "",
|
|
3010
|
+
};
|
|
3011
|
+
recordProjectVerificationOutcome(freshCommandState, verifierResult, freshCommandConfig);
|
|
3012
|
+
freshCommandState.messages.push(toolMessage(verifierResult));
|
|
3013
|
+
freshCommandRuntime = nextStepRuntimeConfig(freshCommandConfig, freshCommandState);
|
|
3014
|
+
assert(
|
|
3015
|
+
freshCommandRuntime.requiredProjectCommand === exactStatus,
|
|
3016
|
+
"the second current-turn command was not retained after the verifier passed"
|
|
3017
|
+
);
|
|
3018
|
+
const statusResult = {
|
|
3019
|
+
ok: true,
|
|
3020
|
+
toolName: "run_command",
|
|
3021
|
+
args: { command: exactStatus },
|
|
3022
|
+
exitCode: 0,
|
|
3023
|
+
stdout: "",
|
|
3024
|
+
stderr: "",
|
|
3025
|
+
};
|
|
3026
|
+
recordProjectVerificationOutcome(freshCommandState, statusResult, freshCommandConfig);
|
|
3027
|
+
freshCommandState.messages.push(toolMessage(statusResult));
|
|
3028
|
+
freshCommandRuntime = nextStepRuntimeConfig(freshCommandConfig, freshCommandState);
|
|
3029
|
+
assert(
|
|
3030
|
+
freshCommandRuntime.requiredProjectCommandPending !== true,
|
|
3031
|
+
"the fresh command batch stayed pending after both exact commands passed"
|
|
3032
|
+
);
|
|
3033
|
+
|
|
2888
3034
|
const bareContinuationState = {
|
|
2889
3035
|
goal: concreteContinuation,
|
|
2890
3036
|
plan: "Update both retained outputs, validate, and commit.",
|
package/src/agent-runner.js
CHANGED
|
@@ -93,6 +93,7 @@ import { longJobStatus, startLongJob } from "./long-job-tools.js";
|
|
|
93
93
|
import { executeAgentLinkTool, isAgentLinkTool } from "./agentlink.js";
|
|
94
94
|
import { classifyGoalIntent, isDirectAnswerIntent } from "./goal-intent.js";
|
|
95
95
|
import { normalizeProviderBaseURL, normalizeProviderId, providerRequiresApiKey } from "./provider-contract.js";
|
|
96
|
+
import { resolveProviderHandoff } from "./provider-handoff.js";
|
|
96
97
|
import { ProviderReadinessError, probeProviderRuntime } from "./provider-runtime.js";
|
|
97
98
|
import { probeLocalMaxResources } from "./local-resource-policy.js";
|
|
98
99
|
import {
|
|
@@ -3066,10 +3067,25 @@ export function resetSameTaskExecutionContract(state = {}, revision = 0) {
|
|
|
3066
3067
|
removed.push(key);
|
|
3067
3068
|
}
|
|
3068
3069
|
state.plan = "";
|
|
3070
|
+
const activeRevision = Math.max(0, Number(revision || state.meta?.goalContract?.revision || 0));
|
|
3071
|
+
const currentRequest = String(state.meta?.goalContract?.currentRequest || state.goal || "").trim();
|
|
3072
|
+
const currentTurnCommands = normalizedRequiredProjectCommands(
|
|
3073
|
+
deriveScsTaskContract({
|
|
3074
|
+
goal: currentRequest,
|
|
3075
|
+
taskProfile: state.meta?.taskProfile || "auto",
|
|
3076
|
+
}).requiredProjectCommands
|
|
3077
|
+
);
|
|
3069
3078
|
state.meta.activeExecutionContract = {
|
|
3070
|
-
revision:
|
|
3079
|
+
revision: activeRevision,
|
|
3071
3080
|
refreshedAt: new Date().toISOString(),
|
|
3081
|
+
requiredProjectCommands: currentTurnCommands,
|
|
3072
3082
|
};
|
|
3083
|
+
const verification = state.meta.projectVerification;
|
|
3084
|
+
if (verification && typeof verification === "object" && currentTurnCommands.length) {
|
|
3085
|
+
startRequiredCommandBatch(verification, currentTurnCommands, {
|
|
3086
|
+
goalRevision: activeRevision,
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3073
3089
|
return removed;
|
|
3074
3090
|
}
|
|
3075
3091
|
|
|
@@ -4289,10 +4305,27 @@ function effectiveRequiredProjectCommands(state = {}, verification = {}, config
|
|
|
4289
4305
|
|
|
4290
4306
|
function currentRequiredCommandBatch(verification = {}, requiredCommands = []) {
|
|
4291
4307
|
const batch = verification.requiredCommandBatch;
|
|
4292
|
-
const
|
|
4293
|
-
|
|
4308
|
+
const effectiveCommands = normalizedRequiredProjectCommands(requiredCommands);
|
|
4309
|
+
const batchCommands = normalizedRequiredProjectCommands(
|
|
4310
|
+
Array.isArray(batch?.requiredCommands)
|
|
4311
|
+
? batch.requiredCommands
|
|
4312
|
+
: batch?.key === requiredCommandBatchKey(effectiveCommands)
|
|
4313
|
+
? effectiveCommands
|
|
4314
|
+
: []
|
|
4315
|
+
);
|
|
4316
|
+
if (
|
|
4317
|
+
!batch ||
|
|
4318
|
+
typeof batch !== "object" ||
|
|
4319
|
+
!batch.id ||
|
|
4320
|
+
!batchCommands.length ||
|
|
4321
|
+
batch.key !== requiredCommandBatchKey(batchCommands) ||
|
|
4322
|
+
batchCommands.some((command) => !effectiveCommands.includes(command))
|
|
4323
|
+
) {
|
|
4324
|
+
return null;
|
|
4325
|
+
}
|
|
4294
4326
|
return {
|
|
4295
4327
|
...batch,
|
|
4328
|
+
requiredCommands: batchCommands,
|
|
4296
4329
|
completedCommands: Array.isArray(batch.completedCommands)
|
|
4297
4330
|
? batch.completedCommands.map(normalizeProjectCommand).filter(Boolean)
|
|
4298
4331
|
: [],
|
|
@@ -4307,13 +4340,20 @@ function currentRequiredCommandBatch(verification = {}, requiredCommands = []) {
|
|
|
4307
4340
|
};
|
|
4308
4341
|
}
|
|
4309
4342
|
|
|
4310
|
-
function startRequiredCommandBatch(
|
|
4343
|
+
function startRequiredCommandBatch(
|
|
4344
|
+
verification = {},
|
|
4345
|
+
requiredCommands = [],
|
|
4346
|
+
{ goalRevision = 0 } = {}
|
|
4347
|
+
) {
|
|
4348
|
+
const normalizedCommands = normalizedRequiredProjectCommands(requiredCommands);
|
|
4311
4349
|
const sequence = Math.max(0, Number(verification.requiredCommandBatchSequence || 0)) + 1;
|
|
4312
4350
|
verification.requiredCommandBatchSequence = sequence;
|
|
4313
4351
|
const revision = Math.max(0, Number(verification.mutationRevision || 0));
|
|
4314
4352
|
const batch = {
|
|
4315
4353
|
id: `required-command-batch-${sequence}`,
|
|
4316
|
-
key: requiredCommandBatchKey(
|
|
4354
|
+
key: requiredCommandBatchKey(normalizedCommands),
|
|
4355
|
+
requiredCommands: normalizedCommands,
|
|
4356
|
+
goalRevision: Math.max(0, Number(goalRevision || 0)),
|
|
4317
4357
|
completedCommands: [],
|
|
4318
4358
|
completedRuns: [],
|
|
4319
4359
|
startedMutationRevision: revision + 1,
|
|
@@ -4474,7 +4514,7 @@ function requiredCommandRunIsCurrent(
|
|
|
4474
4514
|
if (!required || !commandMatches) return false;
|
|
4475
4515
|
const requiredCommands = verificationRequiredProjectCommands(verification);
|
|
4476
4516
|
const batch = currentRequiredCommandBatch(verification, requiredCommands);
|
|
4477
|
-
if (batch?.id) {
|
|
4517
|
+
if (batch?.id && batch.requiredCommands.includes(required)) {
|
|
4478
4518
|
const accepted = batch.completedRuns.find((item) => item.command === required);
|
|
4479
4519
|
return Boolean(
|
|
4480
4520
|
accepted &&
|
|
@@ -4597,21 +4637,45 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
|
|
|
4597
4637
|
commandCanMutateProjectContent(mutationCommand, commandPolicy)
|
|
4598
4638
|
);
|
|
4599
4639
|
let requiredBatch = currentRequiredCommandBatch(verification, requiredCommands);
|
|
4640
|
+
const activeExecutionContract = state.meta?.activeExecutionContract;
|
|
4641
|
+
const activeTurnCommands =
|
|
4642
|
+
Number(activeExecutionContract?.revision || 0) ===
|
|
4643
|
+
Number(state.meta?.goalContract?.revision || 0)
|
|
4644
|
+
? normalizedRequiredProjectCommands(activeExecutionContract?.requiredProjectCommands)
|
|
4645
|
+
: [];
|
|
4646
|
+
if (
|
|
4647
|
+
!requiredBatch &&
|
|
4648
|
+
requiredCommand &&
|
|
4649
|
+
activeTurnCommands.includes(requiredCommand)
|
|
4650
|
+
) {
|
|
4651
|
+
requiredBatch = startRequiredCommandBatch(verification, activeTurnCommands, {
|
|
4652
|
+
goalRevision: state.meta?.goalContract?.revision || 0,
|
|
4653
|
+
});
|
|
4654
|
+
}
|
|
4655
|
+
let batchRequiredCommands = requiredBatch?.requiredCommands || requiredCommands;
|
|
4600
4656
|
if (projectContentMutation) {
|
|
4601
4657
|
delete state.meta.verifiedCompletionCandidate;
|
|
4602
4658
|
if (
|
|
4603
4659
|
requiredCommand &&
|
|
4660
|
+
batchRequiredCommands.includes(requiredCommand) &&
|
|
4604
4661
|
(!requiredBatch ||
|
|
4605
4662
|
requiredBatch.complete ||
|
|
4606
4663
|
requiredBatch.completedCommands.includes(requiredCommand))
|
|
4607
4664
|
) {
|
|
4608
|
-
requiredBatch = startRequiredCommandBatch(verification,
|
|
4665
|
+
requiredBatch = startRequiredCommandBatch(verification, batchRequiredCommands, {
|
|
4666
|
+
goalRevision: state.meta?.goalContract?.revision || 0,
|
|
4667
|
+
});
|
|
4668
|
+
batchRequiredCommands = requiredBatch.requiredCommands;
|
|
4609
4669
|
}
|
|
4610
4670
|
verification.mutationRevision += 1;
|
|
4611
|
-
if (
|
|
4671
|
+
if (
|
|
4672
|
+
requiredCommand &&
|
|
4673
|
+
requiredBatch &&
|
|
4674
|
+
requiredBatch.requiredCommands.includes(requiredCommand)
|
|
4675
|
+
) {
|
|
4612
4676
|
invalidateRequiredBatchValidations(
|
|
4613
4677
|
requiredBatch,
|
|
4614
|
-
requiredCommands,
|
|
4678
|
+
requiredBatch.requiredCommands,
|
|
4615
4679
|
config,
|
|
4616
4680
|
requiredCommand
|
|
4617
4681
|
);
|
|
@@ -4640,17 +4704,19 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
|
|
|
4640
4704
|
};
|
|
4641
4705
|
if (requiredCommand && commandSucceeded) {
|
|
4642
4706
|
if (!requiredBatch && requiredMutatingCommands.length === 0) {
|
|
4643
|
-
requiredBatch = startRequiredCommandBatch(verification, requiredCommands
|
|
4707
|
+
requiredBatch = startRequiredCommandBatch(verification, requiredCommands, {
|
|
4708
|
+
goalRevision: state.meta?.goalContract?.revision || 0,
|
|
4709
|
+
});
|
|
4644
4710
|
requiredBatch.startedMutationRevision = verification.mutationRevision;
|
|
4645
4711
|
requiredBatch.lastMutationRevision = verification.mutationRevision;
|
|
4646
4712
|
}
|
|
4647
|
-
if (requiredBatch) {
|
|
4713
|
+
if (requiredBatch?.requiredCommands.includes(requiredCommand)) {
|
|
4648
4714
|
recordRequiredBatchRun(
|
|
4649
4715
|
requiredBatch,
|
|
4650
4716
|
requiredCommand,
|
|
4651
4717
|
verification.mutationRevision
|
|
4652
4718
|
);
|
|
4653
|
-
requiredBatch.complete = requiredCommands.every((candidate) =>
|
|
4719
|
+
requiredBatch.complete = requiredBatch.requiredCommands.every((candidate) =>
|
|
4654
4720
|
requiredBatch.completedCommands.includes(candidate)
|
|
4655
4721
|
);
|
|
4656
4722
|
verification.requiredCommandBatch = requiredBatch;
|
|
@@ -10993,7 +11059,78 @@ async function recordPreInferenceFailure({ error, config, state, store, observer
|
|
|
10993
11059
|
emitConsole(config, result, { kind: "error", error: true });
|
|
10994
11060
|
}
|
|
10995
11061
|
|
|
10996
|
-
|
|
11062
|
+
class ProviderHandoffSignal extends Error {
|
|
11063
|
+
constructor({ sessionId, expectedRuntimeRevision, decision }) {
|
|
11064
|
+
super(`Provider handoff requested for ${decision.sourceProvider} -> ${decision.targetProvider}.`);
|
|
11065
|
+
this.name = "ProviderHandoffSignal";
|
|
11066
|
+
this.sessionId = sessionId;
|
|
11067
|
+
this.expectedRuntimeRevision = expectedRuntimeRevision;
|
|
11068
|
+
this.decision = decision;
|
|
11069
|
+
}
|
|
11070
|
+
}
|
|
11071
|
+
|
|
11072
|
+
async function prepareProviderHandoff({ error, config, state, store, observers, sessionId, stage = "runtime" }) {
|
|
11073
|
+
const decision = resolveProviderHandoff(error, config, { stage });
|
|
11074
|
+
if (!decision) return null;
|
|
11075
|
+
|
|
11076
|
+
state.meta = state.meta || {};
|
|
11077
|
+
const priorAttempts = Number(state.meta.providerHandoff?.attempts || 0);
|
|
11078
|
+
if (priorAttempts >= 1) return null;
|
|
11079
|
+
|
|
11080
|
+
const at = new Date().toISOString();
|
|
11081
|
+
const detail = {
|
|
11082
|
+
version: 1,
|
|
11083
|
+
attempts: priorAttempts + 1,
|
|
11084
|
+
status: "pending",
|
|
11085
|
+
sourceProvider: decision.sourceProvider,
|
|
11086
|
+
sourceModel: decision.sourceModel,
|
|
11087
|
+
targetProvider: decision.targetProvider,
|
|
11088
|
+
targetModel: decision.targetModel,
|
|
11089
|
+
failureCode: decision.failureCode,
|
|
11090
|
+
httpStatus: decision.status || 0,
|
|
11091
|
+
requestedAt: at,
|
|
11092
|
+
};
|
|
11093
|
+
state.meta.providerHandoff = detail;
|
|
11094
|
+
state.updatedAt = at;
|
|
11095
|
+
updateGoalStatus(state, "active", "provider_handoff", at);
|
|
11096
|
+
await store.saveState(state);
|
|
11097
|
+
await store.appendEvent("provider.handoff_requested", detail);
|
|
11098
|
+
observers.event("provider.handoff_requested", { ...detail, sessionId });
|
|
11099
|
+
emitConsole(
|
|
11100
|
+
config,
|
|
11101
|
+
`${decision.sourceProvider}/${decision.sourceModel} is unavailable (${decision.failureCode}); continuing the same session with ${decision.targetProvider}/${decision.targetModel}.`,
|
|
11102
|
+
{ kind: "meta" }
|
|
11103
|
+
);
|
|
11104
|
+
return new ProviderHandoffSignal({
|
|
11105
|
+
sessionId,
|
|
11106
|
+
expectedRuntimeRevision: Number(state.meta.runtimeConfig?.revision || 1),
|
|
11107
|
+
decision,
|
|
11108
|
+
});
|
|
11109
|
+
}
|
|
11110
|
+
|
|
11111
|
+
async function activatePendingProviderHandoff({ config, state, store, observers, sessionId }) {
|
|
11112
|
+
const handoff = state.meta?.providerHandoff;
|
|
11113
|
+
if (
|
|
11114
|
+
!handoff ||
|
|
11115
|
+
handoff.status !== "pending" ||
|
|
11116
|
+
normalizeProviderId(handoff.targetProvider, "") !== normalizeProviderId(config.provider, "") ||
|
|
11117
|
+
String(handoff.targetModel || "") !== String(config.model || "")
|
|
11118
|
+
) {
|
|
11119
|
+
return;
|
|
11120
|
+
}
|
|
11121
|
+
const detail = {
|
|
11122
|
+
...handoff,
|
|
11123
|
+
status: "active",
|
|
11124
|
+
activatedAt: new Date().toISOString(),
|
|
11125
|
+
};
|
|
11126
|
+
state.meta.providerHandoff = detail;
|
|
11127
|
+
state.updatedAt = detail.activatedAt;
|
|
11128
|
+
await store.saveState(state);
|
|
11129
|
+
await store.appendEvent("provider.handoff_activated", detail);
|
|
11130
|
+
observers.event("provider.handoff_activated", { ...detail, sessionId });
|
|
11131
|
+
}
|
|
11132
|
+
|
|
11133
|
+
async function runAgentOnce(config) {
|
|
10997
11134
|
assertIntegrationRunAgentInvocation(config);
|
|
10998
11135
|
const incomingConfig = config;
|
|
10999
11136
|
const sessionId = config.resume || config.sessionId || `web-agent-${crypto.randomUUID()}`;
|
|
@@ -11022,6 +11159,11 @@ export async function runAgent(config) {
|
|
|
11022
11159
|
state.meta = state.meta || {};
|
|
11023
11160
|
state.meta.runtimeConfig = runtime.snapshot;
|
|
11024
11161
|
config = rebuildResumedRuntimeConfig(incomingConfig, runtime.runtimeOverrides, sessionId);
|
|
11162
|
+
// Keep compatibility fields aligned with the authoritative runtime
|
|
11163
|
+
// snapshot. Provider/model patches otherwise leave stale top-level values
|
|
11164
|
+
// that can mislead later diagnostics and legacy session readers.
|
|
11165
|
+
state.provider = config.provider;
|
|
11166
|
+
state.model = config.model;
|
|
11025
11167
|
const patchedRuntimeFields = runtime.patched
|
|
11026
11168
|
? Object.keys(incomingConfig.runtimePatch || {}).filter((field) => isSessionRuntimeField(field))
|
|
11027
11169
|
: [];
|
|
@@ -11196,10 +11338,22 @@ export async function runAgent(config) {
|
|
|
11196
11338
|
}
|
|
11197
11339
|
client = config.clientFactory ? await config.clientFactory(config) : createClient(config);
|
|
11198
11340
|
} catch (error) {
|
|
11341
|
+
const handoff = await prepareProviderHandoff({
|
|
11342
|
+
error,
|
|
11343
|
+
config,
|
|
11344
|
+
state,
|
|
11345
|
+
store,
|
|
11346
|
+
observers,
|
|
11347
|
+
sessionId,
|
|
11348
|
+
stage: "preflight",
|
|
11349
|
+
});
|
|
11350
|
+
if (handoff) throw handoff;
|
|
11199
11351
|
await recordPreInferenceFailure({ error, config, state, store, observers, sessionId });
|
|
11200
11352
|
throw error;
|
|
11201
11353
|
}
|
|
11202
11354
|
|
|
11355
|
+
await activatePendingProviderHandoff({ config, state, store, observers, sessionId });
|
|
11356
|
+
|
|
11203
11357
|
ensureChatState(state);
|
|
11204
11358
|
|
|
11205
11359
|
const initialRepair = repairModelMessageHistory(state, config);
|
|
@@ -12872,6 +13026,9 @@ export async function runAgent(config) {
|
|
|
12872
13026
|
...goalRunMetadata(state),
|
|
12873
13027
|
};
|
|
12874
13028
|
} catch (error) {
|
|
13029
|
+
if (error instanceof ProviderHandoffSignal) throw error;
|
|
13030
|
+
const handoff = await prepareProviderHandoff({ error, config, state, store, observers, sessionId });
|
|
13031
|
+
if (handoff) throw handoff;
|
|
12875
13032
|
if (isModelTimeoutError(error)) {
|
|
12876
13033
|
const detail = {
|
|
12877
13034
|
reason: "model_timeout",
|
|
@@ -12935,3 +13092,20 @@ export async function runAgent(config) {
|
|
|
12935
13092
|
await flushHousekeeping();
|
|
12936
13093
|
}
|
|
12937
13094
|
}
|
|
13095
|
+
|
|
13096
|
+
export async function runAgent(config) {
|
|
13097
|
+
try {
|
|
13098
|
+
return await runAgentOnce(config);
|
|
13099
|
+
} catch (error) {
|
|
13100
|
+
if (!(error instanceof ProviderHandoffSignal)) throw error;
|
|
13101
|
+
const decision = error.decision;
|
|
13102
|
+
return runAgentOnce({
|
|
13103
|
+
...config,
|
|
13104
|
+
goal: "",
|
|
13105
|
+
resume: error.sessionId,
|
|
13106
|
+
sessionId: error.sessionId,
|
|
13107
|
+
runtimePatch: decision.runtimePatch,
|
|
13108
|
+
expectedRuntimeRevision: error.expectedRuntimeRevision,
|
|
13109
|
+
});
|
|
13110
|
+
}
|
|
13111
|
+
}
|
package/src/model-client.js
CHANGED
|
@@ -148,6 +148,22 @@ function shouldRetryWithoutReasoningEffort(error, payload = {}) {
|
|
|
148
148
|
return /reasoning[_\s.-]?effort|unsupported parameter|unknown parameter|unrecognized request argument/i.test(message);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
function annotateProviderRequestError(error, config = {}, label = "model request") {
|
|
152
|
+
if (!error || (typeof error !== "object" && typeof error !== "function")) return error;
|
|
153
|
+
try {
|
|
154
|
+
Object.defineProperties(error, {
|
|
155
|
+
agintiProviderRequest: { value: true, configurable: true },
|
|
156
|
+
agintiProvider: { value: String(config.provider || ""), configurable: true },
|
|
157
|
+
agintiProviderModel: { value: String(config.model || ""), configurable: true },
|
|
158
|
+
agintiProviderRequestLabel: { value: String(label || "model request"), configurable: true },
|
|
159
|
+
});
|
|
160
|
+
} catch {
|
|
161
|
+
// Some SDK errors can be non-extensible. Classification still has the
|
|
162
|
+
// explicit preflight path, while an unmarked runtime error fails normally.
|
|
163
|
+
}
|
|
164
|
+
return error;
|
|
165
|
+
}
|
|
166
|
+
|
|
151
167
|
export async function createChatCompletion(client, payload, config, label = "model request") {
|
|
152
168
|
const preparedPayload = withChatReasoningEffort(payload, config);
|
|
153
169
|
const timeout = resolveModelTimeoutMs(config);
|
|
@@ -157,9 +173,13 @@ export async function createChatCompletion(client, payload, config, label = "mod
|
|
|
157
173
|
} catch (error) {
|
|
158
174
|
if (shouldRetryWithoutReasoningEffort(error, preparedPayload)) {
|
|
159
175
|
const { reasoning_effort: _reasoningEffort, ...retryPayload } = preparedPayload;
|
|
160
|
-
|
|
176
|
+
try {
|
|
177
|
+
return await client.chat.completions.create(retryPayload, requestOptions(config));
|
|
178
|
+
} catch (retryError) {
|
|
179
|
+
throw annotateProviderRequestError(retryError, config, label);
|
|
180
|
+
}
|
|
161
181
|
}
|
|
162
|
-
throw error;
|
|
182
|
+
throw annotateProviderRequestError(error, config, label);
|
|
163
183
|
}
|
|
164
184
|
}
|
|
165
185
|
|
|
@@ -196,18 +216,22 @@ export async function createChatCompletion(client, payload, config, label = "mod
|
|
|
196
216
|
} catch (error) {
|
|
197
217
|
if (shouldRetryWithoutReasoningEffort(error, preparedPayload)) {
|
|
198
218
|
const { reasoning_effort: _reasoningEffort, ...retryPayload } = preparedPayload;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
219
|
+
try {
|
|
220
|
+
return await client.chat.completions.create(retryPayload, {
|
|
221
|
+
...requestOptions(config),
|
|
222
|
+
signal: controller.signal,
|
|
223
|
+
});
|
|
224
|
+
} catch (retryError) {
|
|
225
|
+
throw annotateProviderRequestError(retryError, config, label);
|
|
226
|
+
}
|
|
203
227
|
}
|
|
204
228
|
if (timedOut && error?.name !== "ModelTimeoutError") {
|
|
205
229
|
const timeoutError = new Error(`${label} timed out after ${timeout}ms`);
|
|
206
230
|
timeoutError.name = "ModelTimeoutError";
|
|
207
231
|
timeoutError.cause = error;
|
|
208
|
-
throw timeoutError;
|
|
232
|
+
throw annotateProviderRequestError(timeoutError, config, label);
|
|
209
233
|
}
|
|
210
|
-
throw error;
|
|
234
|
+
throw annotateProviderRequestError(error, config, label);
|
|
211
235
|
} finally {
|
|
212
236
|
if (timer) clearTimeout(timer);
|
|
213
237
|
if (config.abortSignal) {
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { LOCALLLM_MODEL_TIERS } from "./model-routing.js";
|
|
2
|
+
import { normalizeProviderId } from "./provider-contract.js";
|
|
3
|
+
|
|
4
|
+
const NETWORK_ERROR_CODES = new Set([
|
|
5
|
+
"ECONNREFUSED",
|
|
6
|
+
"ECONNRESET",
|
|
7
|
+
"EHOSTUNREACH",
|
|
8
|
+
"ENETUNREACH",
|
|
9
|
+
"ENOTFOUND",
|
|
10
|
+
"EAI_AGAIN",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function enabled(value, fallback = true) {
|
|
14
|
+
if (value === undefined || value === null || String(value).trim() === "") return fallback;
|
|
15
|
+
return !/^(?:0|false|off|no)$/i.test(String(value).trim());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function providerErrorText(error) {
|
|
19
|
+
return [
|
|
20
|
+
error?.code,
|
|
21
|
+
error?.type,
|
|
22
|
+
error?.message,
|
|
23
|
+
error?.error?.code,
|
|
24
|
+
error?.error?.type,
|
|
25
|
+
error?.error?.message,
|
|
26
|
+
error?.response?.data?.error?.code,
|
|
27
|
+
error?.response?.data?.error?.type,
|
|
28
|
+
error?.response?.data?.error?.message,
|
|
29
|
+
]
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.join(" ")
|
|
32
|
+
.toLowerCase();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function classifyProviderHandoffError(error) {
|
|
36
|
+
const status = Number(error?.status || error?.response?.status || 0);
|
|
37
|
+
const code = String(error?.code || "").trim().toUpperCase();
|
|
38
|
+
const text = providerErrorText(error);
|
|
39
|
+
|
|
40
|
+
if (
|
|
41
|
+
status === 402 ||
|
|
42
|
+
/insufficient[_ -]?(?:quota|balance)|quota exceeded|billing|account balance|credits? exhausted/.test(text)
|
|
43
|
+
) {
|
|
44
|
+
return { eligible: true, code: "provider_quota", status };
|
|
45
|
+
}
|
|
46
|
+
if (
|
|
47
|
+
code === "API_KEY_REQUIRED" ||
|
|
48
|
+
status === 401 ||
|
|
49
|
+
status === 403 ||
|
|
50
|
+
/authentication|unauthorized|invalid[_ -]?api[_ -]?key|permission denied/.test(text)
|
|
51
|
+
) {
|
|
52
|
+
return { eligible: true, code: "provider_auth", status };
|
|
53
|
+
}
|
|
54
|
+
if (status === 429 || /rate[_ -]?limit|too many requests/.test(text)) {
|
|
55
|
+
return { eligible: true, code: "provider_rate_limited", status };
|
|
56
|
+
}
|
|
57
|
+
if (
|
|
58
|
+
status === 404 &&
|
|
59
|
+
/model|deployment|endpoint|not found|does not exist|unavailable/.test(text)
|
|
60
|
+
) {
|
|
61
|
+
return { eligible: true, code: "provider_model_unavailable", status };
|
|
62
|
+
}
|
|
63
|
+
if (
|
|
64
|
+
status === 408 ||
|
|
65
|
+
status === 503 ||
|
|
66
|
+
status === 529 ||
|
|
67
|
+
status >= 500 ||
|
|
68
|
+
NETWORK_ERROR_CODES.has(code) ||
|
|
69
|
+
/overload|overloaded|capacity|temporarily unavailable|connection refused|network unreachable/.test(text)
|
|
70
|
+
) {
|
|
71
|
+
return { eligible: true, code: "provider_unavailable", status };
|
|
72
|
+
}
|
|
73
|
+
return { eligible: false, code: "provider_request_failed", status };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function resolveProviderHandoff(error, config = {}, { stage = "runtime" } = {}) {
|
|
77
|
+
const sourceProvider = normalizeProviderId(config.provider, "");
|
|
78
|
+
const targetProvider = normalizeProviderId(
|
|
79
|
+
config.providerHandoffProvider || process.env.AGINTI_PROVIDER_HANDOFF_PROVIDER || "localllm",
|
|
80
|
+
""
|
|
81
|
+
);
|
|
82
|
+
if (!enabled(config.allowProviderHandoff ?? process.env.AGINTI_PROVIDER_HANDOFF, true)) return null;
|
|
83
|
+
if (!sourceProvider || sourceProvider === "mock" || sourceProvider === "localllm") return null;
|
|
84
|
+
if (!targetProvider || targetProvider !== "localllm" || targetProvider === sourceProvider) return null;
|
|
85
|
+
if (String(config.routingMode || "smart").trim().toLowerCase() === "manual") return null;
|
|
86
|
+
if (String(config.integrationSessionProfile || "").trim()) return null;
|
|
87
|
+
if (stage !== "preflight" && error?.agintiProviderRequest !== true) return null;
|
|
88
|
+
|
|
89
|
+
const failure = classifyProviderHandoffError(error);
|
|
90
|
+
if (!failure.eligible) return null;
|
|
91
|
+
|
|
92
|
+
const routeModel =
|
|
93
|
+
String(config.providerHandoffRouteModel || process.env.AGINTI_LOCALLLM_ROUTE_MODEL || "").trim() ||
|
|
94
|
+
LOCALLLM_MODEL_TIERS.fast.model;
|
|
95
|
+
const mainModel =
|
|
96
|
+
String(
|
|
97
|
+
config.providerHandoffModel ||
|
|
98
|
+
process.env.AGINTI_PROVIDER_HANDOFF_MODEL ||
|
|
99
|
+
process.env.AGINTI_LOCALLLM_MAIN_MODEL ||
|
|
100
|
+
""
|
|
101
|
+
).trim() || LOCALLLM_MODEL_TIERS.deep.model;
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
version: 1,
|
|
105
|
+
sourceProvider,
|
|
106
|
+
sourceModel: String(config.model || ""),
|
|
107
|
+
targetProvider,
|
|
108
|
+
targetModel: mainModel,
|
|
109
|
+
routeModel,
|
|
110
|
+
mainModel,
|
|
111
|
+
spareModel: mainModel,
|
|
112
|
+
failureCode: failure.code,
|
|
113
|
+
status: failure.status,
|
|
114
|
+
runtimePatch: {
|
|
115
|
+
provider: targetProvider,
|
|
116
|
+
model: mainModel,
|
|
117
|
+
routingMode: "manual",
|
|
118
|
+
routeProvider: targetProvider,
|
|
119
|
+
routeModel,
|
|
120
|
+
mainProvider: targetProvider,
|
|
121
|
+
mainModel,
|
|
122
|
+
spareProvider: targetProvider,
|
|
123
|
+
spareModel: mainModel,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
package/src/scs-evidence.js
CHANGED
|
@@ -980,9 +980,9 @@ function prefixRequestsInlineCommandExecution(prefix = "") {
|
|
|
980
980
|
while (preamble.test(clause)) clause = clause.replace(preamble, "").trim();
|
|
981
981
|
clause = clause.replace(/(?:[::]|--?)\s*$/, "").trim();
|
|
982
982
|
return (
|
|
983
|
-
/^(?:run|execute|invoke|launch)(?:\s+(?:(?:the|this|that)\s+)?(?:following\s+)?command(?:\s+named)?)?\s*$/i.test(
|
|
983
|
+
/^(?:run|rerun|re-run|execute|invoke|launch|verify|validate|check|confirm)(?:\s+(?:(?:the|this|that)\s+)?(?:following\s+)?command(?:\s+named)?)?\s*$/i.test(
|
|
984
984
|
clause
|
|
985
|
-
) || /^(
|
|
985
|
+
) || /^(?:运行|運行|执行|執行|调用|調用|验证|驗證|检查|檢查|确认|確認)\s*$/.test(clause)
|
|
986
986
|
);
|
|
987
987
|
}
|
|
988
988
|
|
|
@@ -1239,9 +1239,19 @@ export function augmentScsTaskContractWithProjectVerification(contract = {}, sta
|
|
|
1239
1239
|
...verificationRequiredProjectCommands,
|
|
1240
1240
|
].map(normalizeProjectCommand).filter(Boolean)).slice(0, 24);
|
|
1241
1241
|
const requiredCommandBatch = verification.requiredCommandBatch;
|
|
1242
|
+
const batchRequiredCommands = unique(
|
|
1243
|
+
(Array.isArray(requiredCommandBatch?.requiredCommands)
|
|
1244
|
+
? requiredCommandBatch.requiredCommands
|
|
1245
|
+
: requiredCommandBatch?.key === JSON.stringify(requiredProjectCommands)
|
|
1246
|
+
? requiredProjectCommands
|
|
1247
|
+
: [])
|
|
1248
|
+
.map(normalizeProjectCommand)
|
|
1249
|
+
.filter((command) => command && requiredProjectCommands.includes(command))
|
|
1250
|
+
);
|
|
1242
1251
|
const requiredProjectCommandBatchId =
|
|
1243
1252
|
requiredCommandBatch &&
|
|
1244
|
-
|
|
1253
|
+
batchRequiredCommands.length > 0 &&
|
|
1254
|
+
requiredCommandBatch.key === JSON.stringify(batchRequiredCommands) &&
|
|
1245
1255
|
requiredCommandBatch.id
|
|
1246
1256
|
? String(requiredCommandBatch.id)
|
|
1247
1257
|
: "";
|
|
@@ -1286,7 +1296,7 @@ export function augmentScsTaskContractWithProjectVerification(contract = {}, sta
|
|
|
1286
1296
|
requiredProjectCommands,
|
|
1287
1297
|
requiredProjectCommandBatchId,
|
|
1288
1298
|
requiredProjectCommandBatchCommands: requiredProjectCommandBatchId
|
|
1289
|
-
?
|
|
1299
|
+
? batchRequiredCommands
|
|
1290
1300
|
: [],
|
|
1291
1301
|
requiredProjectCommandRuns,
|
|
1292
1302
|
projectMutationRevision: mutationRevision,
|