@galaxy-stack/ai-coder-core 0.3.0-alpha.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.d/2026-09-19-advisory-guard-policy.md +1 -1
- package/CHANGELOG.d/2026-09-25-tail-anchored-bounding.md +5 -0
- package/CHANGELOG.d/2026-09-26-binary-review-pipe-guidance-finalization-test.md +14 -0
- package/CHANGELOG.d/2026-09-26-cache-capability-and-mcp-reconnect.md +16 -0
- package/CHANGELOG.d/2026-09-26-cache-hit-harness.md +13 -0
- package/CHANGELOG.d/2026-09-26-cache-hit-rate-and-inhistory-option.md +12 -0
- package/CHANGELOG.d/2026-09-26-cumulative-cache-hit.md +13 -0
- package/CHANGELOG.d/2026-09-26-default-model-deepseek.md +8 -0
- package/CHANGELOG.d/2026-09-26-finalization-prefix-and-verified-inhistory.md +12 -0
- package/CHANGELOG.d/2026-09-26-generated-state-and-churn-guard.md +16 -0
- package/CHANGELOG.d/2026-09-26-mcp-reconnect-fixture.md +11 -0
- package/CHANGELOG.d/2026-09-26-memory-hybrid-semantic-consolidation.md +16 -0
- package/CHANGELOG.d/2026-09-26-memory-recall-benchmark.md +12 -0
- package/CHANGELOG.d/2026-09-26-ollama-cache-usage.md +14 -0
- package/CHANGELOG.d/2026-09-26-ollama-embeddings-provider.md +13 -0
- package/CHANGELOG.d/2026-09-26-rerecord-live-fixture.md +6 -0
- package/CHANGELOG.d/2026-09-26-skill-index-signing.md +11 -0
- package/CHANGELOG.d/2026-09-26-skill-version-and-marketplace.md +14 -0
- package/CHANGELOG.d/2026-09-27-workspace-derived-build-output.md +16 -0
- package/CHANGELOG.d/2026-09-28-advisory-lenh-check-va-server.md +26 -0
- package/CHANGELOG.d/2026-09-28-clamp-tham-so-vuot-cap.md +20 -0
- package/CHANGELOG.d/2026-09-28-digest-context-item.md +16 -0
- package/CHANGELOG.d/2026-09-28-gate-bo-qua-path-da-xoa.md +20 -0
- package/CHANGELOG.d/2026-09-28-ngan-sach-luot-finalization.md +20 -0
- package/CHANGELOG.d/2026-09-28-scope-cua-project-validate.md +22 -0
- package/CHANGELOG.d/2026-09-28-staleness-theo-pham-vi.md +22 -0
- package/CHANGELOG.d/2026-09-28-thong-bao-guard-dependency.md +24 -0
- package/CHANGELOG.d/2026-09-28-thu-tu-mandatory-state-cache.md +20 -0
- package/CHANGELOG.md +71 -1
- package/README.md +10 -10
- package/dist/adapters/node/config/manual-provider-config.d.ts +3 -0
- package/dist/adapters/node/config/manual-provider-config.d.ts.map +1 -1
- package/dist/adapters/node/config/manual-provider-config.js +4 -0
- package/dist/adapters/node/config/manual-provider-config.js.map +1 -1
- package/dist/adapters/node/host/node-workspace-evidence-verifier.d.ts.map +1 -1
- package/dist/adapters/node/host/node-workspace-evidence-verifier.js +8 -13
- package/dist/adapters/node/host/node-workspace-evidence-verifier.js.map +1 -1
- package/dist/adapters/node/host/node-workspace-port.d.ts +1 -1
- package/dist/adapters/node/host/node-workspace-port.d.ts.map +1 -1
- package/dist/adapters/node/host/node-workspace-port.js +47 -4
- package/dist/adapters/node/host/node-workspace-port.js.map +1 -1
- package/dist/adapters/node/host/node-workspace-snapshot.d.ts.map +1 -1
- package/dist/adapters/node/host/node-workspace-snapshot.js +60 -4
- package/dist/adapters/node/host/node-workspace-snapshot.js.map +1 -1
- package/dist/adapters/node/host/project-tools.d.ts.map +1 -1
- package/dist/adapters/node/host/project-tools.js +8 -1
- package/dist/adapters/node/host/project-tools.js.map +1 -1
- package/dist/adapters/node/host/workspace-generated-state.d.ts +8 -0
- package/dist/adapters/node/host/workspace-generated-state.d.ts.map +1 -0
- package/dist/adapters/node/host/workspace-generated-state.js +8 -0
- package/dist/adapters/node/host/workspace-generated-state.js.map +1 -0
- package/dist/adapters/node/mcp/mcp-client.d.ts +7 -2
- package/dist/adapters/node/mcp/mcp-client.d.ts.map +1 -1
- package/dist/adapters/node/mcp/mcp-client.js +41 -6
- package/dist/adapters/node/mcp/mcp-client.js.map +1 -1
- package/dist/adapters/node/memory/ollama-embeddings.d.ts +26 -0
- package/dist/adapters/node/memory/ollama-embeddings.d.ts.map +1 -0
- package/dist/adapters/node/memory/ollama-embeddings.js +98 -0
- package/dist/adapters/node/memory/ollama-embeddings.js.map +1 -0
- package/dist/adapters/node/memory/sqlite-memory.d.ts +16 -3
- package/dist/adapters/node/memory/sqlite-memory.d.ts.map +1 -1
- package/dist/adapters/node/memory/sqlite-memory.js +138 -16
- package/dist/adapters/node/memory/sqlite-memory.js.map +1 -1
- package/dist/adapters/node/provider/ollama-chat-stream.d.ts.map +1 -1
- package/dist/adapters/node/provider/ollama-chat-stream.js +4 -0
- package/dist/adapters/node/provider/ollama-chat-stream.js.map +1 -1
- package/dist/adapters/node/provider/ollama-coding-model.d.ts +16 -2
- package/dist/adapters/node/provider/ollama-coding-model.d.ts.map +1 -1
- package/dist/adapters/node/provider/ollama-coding-model.js +18 -0
- package/dist/adapters/node/provider/ollama-coding-model.js.map +1 -1
- package/dist/adapters/node/skills/directory-skills.d.ts.map +1 -1
- package/dist/adapters/node/skills/directory-skills.js +16 -1
- package/dist/adapters/node/skills/directory-skills.js.map +1 -1
- package/dist/adapters/node/skills/skill-marketplace.d.ts +71 -0
- package/dist/adapters/node/skills/skill-marketplace.d.ts.map +1 -0
- package/dist/adapters/node/skills/skill-marketplace.js +259 -0
- package/dist/adapters/node/skills/skill-marketplace.js.map +1 -0
- package/dist/adapters/node/tools/tool-executor.d.ts.map +1 -1
- package/dist/adapters/node/tools/tool-executor.js +36 -15
- package/dist/adapters/node/tools/tool-executor.js.map +1 -1
- package/dist/adapters/node/tools/workspace-review.d.ts.map +1 -1
- package/dist/adapters/node/tools/workspace-review.js +51 -12
- package/dist/adapters/node/tools/workspace-review.js.map +1 -1
- package/dist/agent/index.d.ts +17 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/context/checkpoint.js +1 -1
- package/dist/context/checkpoint.js.map +1 -1
- package/dist/context/context-manager.d.ts +27 -1
- package/dist/context/context-manager.d.ts.map +1 -1
- package/dist/context/context-manager.js +54 -7
- package/dist/context/context-manager.js.map +1 -1
- package/dist/context/context-profile.d.ts.map +1 -1
- package/dist/context/context-profile.js +6 -2
- package/dist/context/context-profile.js.map +1 -1
- package/dist/context/token-ledger.d.ts +9 -0
- package/dist/context/token-ledger.d.ts.map +1 -1
- package/dist/context/token-ledger.js +8 -0
- package/dist/context/token-ledger.js.map +1 -1
- package/dist/context/tool-output.d.ts.map +1 -1
- package/dist/context/tool-output.js +23 -4
- package/dist/context/tool-output.js.map +1 -1
- package/dist/ports/capability-port.d.ts +8 -0
- package/dist/ports/capability-port.d.ts.map +1 -1
- package/dist/ports/workspace-port.d.ts +2 -1
- package/dist/ports/workspace-port.d.ts.map +1 -1
- package/dist/prompt/prompt-assembler.d.ts +1 -1
- package/dist/prompt/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt/prompt-assembler.js +12 -4
- package/dist/prompt/prompt-assembler.js.map +1 -1
- package/dist/runtime/completion-gate.d.ts +1 -0
- package/dist/runtime/completion-gate.d.ts.map +1 -1
- package/dist/runtime/completion-gate.js +54 -11
- package/dist/runtime/completion-gate.js.map +1 -1
- package/dist/runtime/run-controller.d.ts +6 -0
- package/dist/runtime/run-controller.d.ts.map +1 -1
- package/dist/runtime/run-controller.js +138 -13
- package/dist/runtime/run-controller.js.map +1 -1
- package/dist/runtime/runtime-error.d.ts +1 -1
- package/dist/runtime/runtime-error.d.ts.map +1 -1
- package/dist/runtime/runtime-error.js.map +1 -1
- package/dist/runtime/runtime-types.d.ts +10 -0
- package/dist/runtime/runtime-types.d.ts.map +1 -1
- package/dist/runtime/workspace-generated-path.d.ts +26 -0
- package/dist/runtime/workspace-generated-path.d.ts.map +1 -0
- package/dist/runtime/workspace-generated-path.js +61 -0
- package/dist/runtime/workspace-generated-path.js.map +1 -0
- package/dist/tools/command-shape.d.ts +4 -0
- package/dist/tools/command-shape.d.ts.map +1 -0
- package/dist/tools/command-shape.js +62 -0
- package/dist/tools/command-shape.js.map +1 -0
- package/dist/tools/json-schema.d.ts +13 -0
- package/dist/tools/json-schema.d.ts.map +1 -1
- package/dist/tools/json-schema.js +45 -0
- package/dist/tools/json-schema.js.map +1 -1
- package/dist/tools/settings-types.js +1 -1
- package/dist/tools/settings-types.js.map +1 -1
- package/dist/tools/tool-registry.js +2 -2
- package/dist/tools/tool-registry.js.map +1 -1
- package/dist/tools/validation-scope.d.ts +14 -0
- package/dist/tools/validation-scope.d.ts.map +1 -0
- package/dist/tools/validation-scope.js +16 -0
- package/dist/tools/validation-scope.js.map +1 -0
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CACHE_HIT.md +109 -0
- package/docs/GALAXY_AGENT_PLATFORM_PLAN.md +171 -348
- package/docs/GALAXY_AGENT_PLATFORM_TODO.md +13 -0
- package/docs/SKILL_MARKETPLACE.md +59 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AiCoderContextBudgetError, AiCoderContextManager, } from "../context/context-manager.js";
|
|
2
2
|
import { compareAiCoderText } from "../deterministic-order.js";
|
|
3
|
+
import { isGeneratedWorkspacePath } from "./workspace-generated-path.js";
|
|
3
4
|
import { assertAiCoderRunCheckpoint, createAiCoderRunCheckpoint, hashAiCoderCanonicalValue, isAiCoderWorkspaceMutationEvidence, canonicalJson, redactAiCoderCheckpointText, } from "../context/checkpoint.js";
|
|
4
5
|
import { boundAiCoderToolOutput } from "../context/tool-output.js";
|
|
5
6
|
import { assembleAiCoderPrompt, createAiCoderTaskContract, formatAiCoderUserTask, } from "../prompt/prompt-assembler.js";
|
|
@@ -18,13 +19,23 @@ const DEFAULT_BUDGET = Object.freeze({
|
|
|
18
19
|
maxModelRetries: 3,
|
|
19
20
|
modelRetryDelaysMs: Object.freeze([1_000, 3_000, 8_000]),
|
|
20
21
|
maxRepeatedToolRequests: 2,
|
|
21
|
-
maxToolCalls:
|
|
22
|
+
maxToolCalls: 256,
|
|
22
23
|
maxTurns: 48,
|
|
23
24
|
noProgressPolicy: "advisory",
|
|
24
25
|
observationNudgeThresholds: Object.freeze([3, 5, 8]),
|
|
25
26
|
persistenceGraceMs: 10_000,
|
|
26
27
|
toolOutput: Object.freeze({ maxBytes: 48_000, maxTokens: 12_000, tailFraction: 0.25 }),
|
|
27
28
|
});
|
|
29
|
+
/** Common model aliases for Galaxy tool names, mapped only when the target is active. */
|
|
30
|
+
const TOOL_NAME_ALIASES = Object.freeze({
|
|
31
|
+
bash: "run_command", shell: "run_command", exec: "run_command", execute: "run_command", terminal: "run_command",
|
|
32
|
+
read: "read_file", cat: "read_file", view_file: "read_file",
|
|
33
|
+
write: "write_file", create_file: "write_file",
|
|
34
|
+
edit: "edit_file", str_replace: "edit_file", replace: "edit_file", apply_patch: "edit_file",
|
|
35
|
+
glob: "glob_files", find_files: "glob_files",
|
|
36
|
+
grep: "search_text", search: "search_text",
|
|
37
|
+
ls: "list_files", list: "list_files",
|
|
38
|
+
});
|
|
28
39
|
const STABLE_OBSERVATION_TOOL_IDS = new Set([
|
|
29
40
|
"artifact.list",
|
|
30
41
|
"artifact.read",
|
|
@@ -660,7 +671,8 @@ function mandatoryState(session) {
|
|
|
660
671
|
acceptanceCriteria: session.evidence.acceptanceCriteria,
|
|
661
672
|
approvals: session.evidence.approvals,
|
|
662
673
|
decisions: session.evidence.decisions,
|
|
663
|
-
editedFiles: session.evidence.writes,
|
|
674
|
+
editedFiles: session.evidence.writes.slice(-200),
|
|
675
|
+
editedFilesTotal: session.evidence.writes.length,
|
|
664
676
|
executionBudget: {
|
|
665
677
|
remainingModelTurns: Math.max(0, session.budget.maxTurns - session.modelTurns),
|
|
666
678
|
remainingToolCalls: Math.max(0, session.budget.maxToolCalls - session.toolCalls),
|
|
@@ -787,7 +799,9 @@ export class AiCoderRunController {
|
|
|
787
799
|
evidence: createEvidence(requestSnapshot),
|
|
788
800
|
executionId,
|
|
789
801
|
failedToolFamilies: new Map(),
|
|
802
|
+
finalizationAttempted: false,
|
|
790
803
|
finalizationMode: false,
|
|
804
|
+
generatedChurnEvents: 0,
|
|
791
805
|
hostStateVersions: new Map(),
|
|
792
806
|
integrity: null,
|
|
793
807
|
latestCheckpoint: null,
|
|
@@ -1019,7 +1033,7 @@ export class AiCoderRunController {
|
|
|
1019
1033
|
...session.integrity,
|
|
1020
1034
|
systemPromptHash,
|
|
1021
1035
|
});
|
|
1022
|
-
session.contextManager?.replaceSystemPrompt(promptSnapshot.systemPrompt, session.modelTurns);
|
|
1036
|
+
session.contextManager?.replaceSystemPrompt(promptSnapshot.systemPrompt, session.modelTurns, session.capabilities?.systemPromptUpdate === "in-history" ? "in-history" : "in-place");
|
|
1023
1037
|
return true;
|
|
1024
1038
|
}
|
|
1025
1039
|
async prepare(session) {
|
|
@@ -1113,7 +1127,20 @@ export class AiCoderRunController {
|
|
|
1113
1127
|
await this.notify(session, { pressure: diagnostic.pressure, tokens: diagnostic.currentInputTokens, type: "context_pressure" });
|
|
1114
1128
|
},
|
|
1115
1129
|
goalMessage: userTaskMessage,
|
|
1116
|
-
ledgerSink: async (entry) =>
|
|
1130
|
+
ledgerSink: async (entry) => {
|
|
1131
|
+
await session.trace.emit("token_ledger", entry);
|
|
1132
|
+
await this.notify(session, {
|
|
1133
|
+
ledger: Object.freeze({
|
|
1134
|
+
actualInput: entry.actualInput,
|
|
1135
|
+
cacheHitRate: entry.cacheHitRate,
|
|
1136
|
+
cachedInput: entry.cachedInput,
|
|
1137
|
+
cumulativeCacheHitRate: entry.cumulativeCacheHitRate,
|
|
1138
|
+
outputTokens: entry.outputTokens,
|
|
1139
|
+
turn: entry.turn,
|
|
1140
|
+
}),
|
|
1141
|
+
type: "token_ledger",
|
|
1142
|
+
});
|
|
1143
|
+
},
|
|
1117
1144
|
profile: session.request.tokenProfile ?? "balanced",
|
|
1118
1145
|
...(checkpoint ? { resumeCheckpoint: checkpoint } : {}),
|
|
1119
1146
|
runId: session.context.runId,
|
|
@@ -1318,6 +1345,9 @@ export class AiCoderRunController {
|
|
|
1318
1345
|
await this.emitPromptSnapshot(session);
|
|
1319
1346
|
const contextManager = session.contextManager;
|
|
1320
1347
|
const roundToolSet = session.toolSet;
|
|
1348
|
+
// The finalization turn must be tool-free (host conformance). The prompt
|
|
1349
|
+
// cache still benefits from the stable message prefix and appended
|
|
1350
|
+
// feedback; only the tool list is dropped.
|
|
1321
1351
|
const roundDefinitions = session.finalizationMode
|
|
1322
1352
|
? Object.freeze([])
|
|
1323
1353
|
: roundToolSet.definitions;
|
|
@@ -1393,7 +1423,7 @@ export class AiCoderRunController {
|
|
|
1393
1423
|
const observations = [];
|
|
1394
1424
|
for (let index = 0; index < preparedCalls.length; index += 1) {
|
|
1395
1425
|
const preparedCall = preparedCalls[index];
|
|
1396
|
-
observations.push(await this.
|
|
1426
|
+
observations.push(await this.executeToolCallGuarded(session, preparedCall, roundToolSet));
|
|
1397
1427
|
if (session.evidence.pendingApprovals.size && index + 1 < preparedCalls.length) {
|
|
1398
1428
|
for (const skipped of preparedCalls.slice(index + 1)) {
|
|
1399
1429
|
observations.push(await this.recordApprovalBlockedToolCall(session, skipped, roundToolSet));
|
|
@@ -1438,6 +1468,18 @@ export class AiCoderRunController {
|
|
|
1438
1468
|
return result;
|
|
1439
1469
|
session.finalizationMode = false;
|
|
1440
1470
|
}
|
|
1471
|
+
// Out of turns is not out of options. A heavy step can reach the turn cap with
|
|
1472
|
+
// its evidence already satisfied (observed: a gymflow step failed right after
|
|
1473
|
+
// "Project validation passed"). Grant exactly one tool-free finalization turn
|
|
1474
|
+
// so the run reports the evidence it has; the completion gate still decides
|
|
1475
|
+
// whether that report is acceptable, and a second exhaustion still fails.
|
|
1476
|
+
if (!session.finalizationAttempted && this.shouldAttemptFinalization(session)) {
|
|
1477
|
+
session.finalizationAttempted = true;
|
|
1478
|
+
this.enterFinalizationMode(session);
|
|
1479
|
+
session.budget = Object.freeze({ ...session.budget, maxTurns: session.modelTurns + 1 });
|
|
1480
|
+
await this.tracePolicyDecision(session, Object.freeze({ action: "budget_finalization_turn", maxTurns: session.budget.maxTurns }));
|
|
1481
|
+
return this.runLoop(session);
|
|
1482
|
+
}
|
|
1441
1483
|
throw new AiCoderRuntimeError("MAX_TURNS", `AI Coder reached maxTurns=${session.budget.maxTurns}.`);
|
|
1442
1484
|
}
|
|
1443
1485
|
async runModelRound(session, messages, includeAttachments, maxOutputTokens, tools) {
|
|
@@ -1689,6 +1731,35 @@ export class AiCoderRunController {
|
|
|
1689
1731
|
if (new Set(callIds).size !== callIds.length || callIds.some((id) => !id.trim())) {
|
|
1690
1732
|
throw new AiCoderRuntimeError("INVALID_MODEL_STREAM", "Model returned duplicate or empty toolCallId values.");
|
|
1691
1733
|
}
|
|
1734
|
+
// Models trained on other agent frameworks occasionally emit well-known
|
|
1735
|
+
// aliases (for example "bash" for run_command). Map an alias onto the
|
|
1736
|
+
// canonical model name when it is active this round.
|
|
1737
|
+
const visibleNames = new Set(roundToolSet.definitions.map((definition) => definition.function.name));
|
|
1738
|
+
const normalizedCalls = calls.map((call) => {
|
|
1739
|
+
let normalized = call;
|
|
1740
|
+
if (!visibleNames.has(normalized.name)) {
|
|
1741
|
+
const alias = TOOL_NAME_ALIASES[normalized.name];
|
|
1742
|
+
if (alias !== undefined && visibleNames.has(alias))
|
|
1743
|
+
normalized = Object.freeze({ ...normalized, name: alias });
|
|
1744
|
+
}
|
|
1745
|
+
if (normalized.name === "edit_file") {
|
|
1746
|
+
// The edit precondition is optional and advisory at the agent layer:
|
|
1747
|
+
// a hash remembered from an earlier turn or compacted context is stale
|
|
1748
|
+
// and would otherwise loop on PRECONDITION_FAILED. Drop it and let the
|
|
1749
|
+
// oldText match be the guard. The port still enforces a precondition
|
|
1750
|
+
// when a host supplies one directly.
|
|
1751
|
+
const args = normalized.arguments;
|
|
1752
|
+
if (args !== null && typeof args === "object" && !Array.isArray(args) && "precondition" in args) {
|
|
1753
|
+
const { precondition: _drop, ...rest } = args;
|
|
1754
|
+
normalized = Object.freeze({ ...normalized, arguments: Object.freeze(rest) });
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
return normalized;
|
|
1758
|
+
});
|
|
1759
|
+
const unavailableName = normalizedCalls.find((call) => !visibleNames.has(call.name))?.name;
|
|
1760
|
+
if (unavailableName !== undefined) {
|
|
1761
|
+
throw new AiCoderRuntimeError("INVALID_MODEL_STREAM", `Tool ${unavailableName} was not active in the registry snapshot shown to the model for this round.`);
|
|
1762
|
+
}
|
|
1692
1763
|
const reusedId = callIds.find((id) => session.evidence.seenToolCallIds.has(id));
|
|
1693
1764
|
if (reusedId !== undefined) {
|
|
1694
1765
|
throw new AiCoderRuntimeError("INVALID_MODEL_STREAM", `toolCallId ${reusedId} was already used in this run.`);
|
|
@@ -1696,11 +1767,6 @@ export class AiCoderRunController {
|
|
|
1696
1767
|
if (session.toolCalls + calls.length > session.budget.maxToolCalls) {
|
|
1697
1768
|
throw new AiCoderRuntimeError("MAX_TOOL_CALLS", `AI Coder reached maxToolCalls=${session.budget.maxToolCalls}.`);
|
|
1698
1769
|
}
|
|
1699
|
-
const visibleNames = new Set(roundToolSet.definitions.map((definition) => definition.function.name));
|
|
1700
|
-
const unavailableName = calls.find((call) => !visibleNames.has(call.name))?.name;
|
|
1701
|
-
if (unavailableName !== undefined) {
|
|
1702
|
-
throw new AiCoderRuntimeError("INVALID_MODEL_STREAM", `Tool ${unavailableName} was not active in the registry snapshot shown to the model for this round.`);
|
|
1703
|
-
}
|
|
1704
1770
|
const prepared = [];
|
|
1705
1771
|
for (const call of calls) {
|
|
1706
1772
|
try {
|
|
@@ -1712,6 +1778,28 @@ export class AiCoderRunController {
|
|
|
1712
1778
|
}
|
|
1713
1779
|
return Object.freeze(prepared);
|
|
1714
1780
|
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Bound a single tool dispatch. A native or host tool that blocks forever
|
|
1783
|
+
* would otherwise hang the whole run; a timeout fails the run cleanly with a
|
|
1784
|
+
* diagnosable code instead.
|
|
1785
|
+
*/
|
|
1786
|
+
async executeToolCallGuarded(session, prepared, roundToolSet) {
|
|
1787
|
+
const timeoutMs = 600_000;
|
|
1788
|
+
let timer;
|
|
1789
|
+
try {
|
|
1790
|
+
return await Promise.race([
|
|
1791
|
+
this.executeToolCall(session, prepared, roundToolSet),
|
|
1792
|
+
new Promise((_, reject) => {
|
|
1793
|
+
timer = setTimeout(() => reject(new AiCoderRuntimeError("TOOL_TIMEOUT", `Tool ${prepared.call.name} did not return within ${timeoutMs}ms.`)), timeoutMs);
|
|
1794
|
+
timer.unref?.();
|
|
1795
|
+
}),
|
|
1796
|
+
]);
|
|
1797
|
+
}
|
|
1798
|
+
finally {
|
|
1799
|
+
if (timer !== undefined)
|
|
1800
|
+
clearTimeout(timer);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1715
1803
|
async executeToolCall(session, prepared, roundToolSet) {
|
|
1716
1804
|
if (!session.contextManager || !session.toolSet)
|
|
1717
1805
|
throw new Error("Context manager or tool set is unavailable.");
|
|
@@ -1740,7 +1828,9 @@ export class AiCoderRunController {
|
|
|
1740
1828
|
const callRecord = {
|
|
1741
1829
|
// Bounded, redacted digest so the post-compaction checkpoint shows WHICH
|
|
1742
1830
|
// paths/queries were already inspected; hashes alone cannot stop re-listing.
|
|
1743
|
-
argumentDigest: redactAiCoderCheckpointText(canonicalJson(call.arguments))
|
|
1831
|
+
argumentDigest: redactAiCoderCheckpointText(canonicalJson(call.arguments))
|
|
1832
|
+
.replace(/"contentSha256":"[a-f0-9]{64}"/gu, '"contentSha256":"<re-read to refresh>"')
|
|
1833
|
+
.slice(0, 160),
|
|
1744
1834
|
argumentsHash,
|
|
1745
1835
|
idempotencyKey,
|
|
1746
1836
|
name: call.name,
|
|
@@ -1959,9 +2049,19 @@ export class AiCoderRunController {
|
|
|
1959
2049
|
research: session.evidence.researchSources,
|
|
1960
2050
|
writes: session.evidence.writes,
|
|
1961
2051
|
});
|
|
2052
|
+
const authoredWrites = (normalizedResult.effects?.writes ?? []).filter((write) => !isGeneratedWorkspacePath(write.path.split("/")));
|
|
2053
|
+
const durableAuthoredProgress = Boolean(effectCanChangeState && normalizedResult.effects && (authoredWrites.length > 0
|
|
2054
|
+
|| normalizedResult.effects.diffReview
|
|
2055
|
+
|| normalizedResult.effects.plan
|
|
2056
|
+
|| (normalizedResult.effects.researchSources?.length ?? 0) > 0
|
|
2057
|
+
|| (normalizedResult.effects.acceptanceCriteriaSatisfied?.length ?? 0) > 0
|
|
2058
|
+
|| (normalizedResult.effects.acceptanceCriteriaWaived?.length ?? 0) > 0
|
|
2059
|
+
|| (normalizedResult.effects.inspectedPaths?.length ?? 0) > 0));
|
|
1962
2060
|
if (hasStateEffect && nextStateVersion !== previousStateVersion) {
|
|
1963
2061
|
session.stateVersion = nextStateVersion;
|
|
1964
|
-
|
|
2062
|
+
// A generated-only state change (build output, caches, tsbuildinfo) is not
|
|
2063
|
+
// authored progress, so it must not clear the no-progress budget.
|
|
2064
|
+
if (durableAuthoredProgress && (normalizedResult.ok || normalizedResult.effects?.approval === "denied") && !noProgressDetected) {
|
|
1965
2065
|
session.repeatedToolFingerprint = 0;
|
|
1966
2066
|
session.noProgressEpisodes = 0;
|
|
1967
2067
|
session.lastNoProgressEpisodeTurn = -1;
|
|
@@ -2345,6 +2445,10 @@ export class AiCoderRunController {
|
|
|
2345
2445
|
}
|
|
2346
2446
|
const cyclingPaths = [];
|
|
2347
2447
|
for (const write of effects.writes ?? []) {
|
|
2448
|
+
// Build output/caches are durable host effects (run_command proof) but are
|
|
2449
|
+
// not authored source; keep them out of the authored write evidence.
|
|
2450
|
+
if (isGeneratedWorkspacePath(write.path.split("/")))
|
|
2451
|
+
continue;
|
|
2348
2452
|
const beforeState = workspaceStateKey(write.beforeKind, write.beforeHash);
|
|
2349
2453
|
const afterState = workspaceStateKey(write.afterKind, write.afterHash);
|
|
2350
2454
|
let history = session.writeStateHistory.get(write.path) ?? [];
|
|
@@ -2597,7 +2701,27 @@ export class AiCoderRunController {
|
|
|
2597
2701
|
const messages = gate.issues.map((item) => `${item.code}: ${item.detail}`);
|
|
2598
2702
|
const researchEvidence = completionRejectionResearchEvidence(session, content);
|
|
2599
2703
|
const remediation = gate.issues.flatMap((item) => {
|
|
2704
|
+
if (item.code === "WORKSPACE_EVIDENCE_STALE") {
|
|
2705
|
+
return [
|
|
2706
|
+
"WORKSPACE_EVIDENCE_STALE next action: workspace changed after those validations ran, so their evidence is void. Re-run validate_project with the SAME checks for each stale id AFTER the latest mutation; a validation older than the last write never counts. Do not re-run validations that are already current.",
|
|
2707
|
+
];
|
|
2708
|
+
}
|
|
2709
|
+
if (item.code === "WRITE_NOT_VALIDATED") {
|
|
2710
|
+
return [
|
|
2711
|
+
"WRITE_NOT_VALIDATED next action: run validate_project (checks covering the written paths) AFTER the final write; a validation older than the write does not count. If the project has no matching script, create the smallest correct test script first, then validate.",
|
|
2712
|
+
];
|
|
2713
|
+
}
|
|
2600
2714
|
if (item.code === "DIFF_NOT_REVIEWED") {
|
|
2715
|
+
// In a workspace without Git the git advice is a trap: the model
|
|
2716
|
+
// searched the catalog for a tool that can never exist and drained
|
|
2717
|
+
// the turn budget. Point it to the workspace review tool instead.
|
|
2718
|
+
const activeCanonicalIds = session.toolSet ? Object.values(session.toolSet.canonicalToolIds) : [];
|
|
2719
|
+
const gitAvailable = activeCanonicalIds.includes("git.diff") || activeCanonicalIds.includes("git.exec");
|
|
2720
|
+
if (!gitAvailable) {
|
|
2721
|
+
return [
|
|
2722
|
+
"DIFF_NOT_REVIEWED next action: this workspace has no Git, so call review_changes (workspace review) once after the last workspace mutation to obtain diff-review evidence. If review_changes reports the change exceeds its size cap, run it again after removing temporary artifacts; output from run_command does not provide trusted diff_review evidence.",
|
|
2723
|
+
];
|
|
2724
|
+
}
|
|
2601
2725
|
return [
|
|
2602
2726
|
"DIFF_NOT_REVIEWED next action: call git_operation with action 'diff' after the last workspace mutation. If git_operation is not active, first call search_tools with query 'final git diff' and category 'git', then call git_operation on the following turn. Output from run_command, including git diff or git status, does not provide trusted diff_review evidence.",
|
|
2603
2727
|
];
|
|
@@ -2708,7 +2832,8 @@ export class AiCoderRunController {
|
|
|
2708
2832
|
constraints: Object.freeze([...(session.request.constraints ?? [])]),
|
|
2709
2833
|
decisions: Object.freeze([...session.evidence.decisions]),
|
|
2710
2834
|
delivery: Object.freeze({ attachmentsDelivered: session.attachmentsDelivered }),
|
|
2711
|
-
|
|
2835
|
+
editsTotal: session.evidence.writes.length,
|
|
2836
|
+
edits: Object.freeze(session.evidence.writes.slice(-500).map((item) => Object.freeze({
|
|
2712
2837
|
afterHash: item.afterHash,
|
|
2713
2838
|
...(item.afterKind !== undefined ? { afterKind: item.afterKind } : {}),
|
|
2714
2839
|
beforeHash: item.beforeHash,
|