@galaxy-stack/ai-coder-core 0.3.0-alpha.9 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.d/2026-09-19-advisory-guard-policy.md +1 -1
- 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-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.d/2026-09-29-advisory-doc-source-node-modules-bang-shell.md +24 -0
- package/CHANGELOG.d/2026-09-29-retry-khi-model-dung-vi-output-length.md +21 -0
- package/CHANGELOG.d/README.md +3 -0
- package/CHANGELOG.md +78 -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/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 +146 -14
- 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 +11 -0
- package/dist/tools/command-shape.d.ts.map +1 -0
- package/dist/tools/command-shape.js +76 -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/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_TODO.md +11 -2
- 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) {
|
|
@@ -1514,7 +1556,14 @@ export class AiCoderRunController {
|
|
|
1514
1556
|
}
|
|
1515
1557
|
if (!done)
|
|
1516
1558
|
throw new CodingProviderError("MALFORMED_STREAM", "Model stream ended without a done event.");
|
|
1517
|
-
if (done.stopReason === "length"
|
|
1559
|
+
if (done.stopReason === "length") {
|
|
1560
|
+
// The model spent its whole output allowance (usually on hidden thinking)
|
|
1561
|
+
// before emitting a tool call or an answer. That is a capacity failure, not
|
|
1562
|
+
// a task failure: retry the same verified context with thinking disabled
|
|
1563
|
+
// instead of failing the run (measured: a gymflow planning turn died here).
|
|
1564
|
+
throw new CodingProviderError("MALFORMED_STREAM", `Model stopped at the output limit ('length') after ${calls.length} tool call(s) and ${content.length} character(s) of content; retry with hidden thinking disabled and a shorter response.`, true, "without_thinking");
|
|
1565
|
+
}
|
|
1566
|
+
if (done.stopReason === "unknown") {
|
|
1518
1567
|
throw new AiCoderRuntimeError("INVALID_MODEL_STREAM", `Model stopped with non-final reason '${done.stopReason}'.`);
|
|
1519
1568
|
}
|
|
1520
1569
|
if (done.stopReason === "tool_calls" && !calls.length) {
|
|
@@ -1689,6 +1738,35 @@ export class AiCoderRunController {
|
|
|
1689
1738
|
if (new Set(callIds).size !== callIds.length || callIds.some((id) => !id.trim())) {
|
|
1690
1739
|
throw new AiCoderRuntimeError("INVALID_MODEL_STREAM", "Model returned duplicate or empty toolCallId values.");
|
|
1691
1740
|
}
|
|
1741
|
+
// Models trained on other agent frameworks occasionally emit well-known
|
|
1742
|
+
// aliases (for example "bash" for run_command). Map an alias onto the
|
|
1743
|
+
// canonical model name when it is active this round.
|
|
1744
|
+
const visibleNames = new Set(roundToolSet.definitions.map((definition) => definition.function.name));
|
|
1745
|
+
const normalizedCalls = calls.map((call) => {
|
|
1746
|
+
let normalized = call;
|
|
1747
|
+
if (!visibleNames.has(normalized.name)) {
|
|
1748
|
+
const alias = TOOL_NAME_ALIASES[normalized.name];
|
|
1749
|
+
if (alias !== undefined && visibleNames.has(alias))
|
|
1750
|
+
normalized = Object.freeze({ ...normalized, name: alias });
|
|
1751
|
+
}
|
|
1752
|
+
if (normalized.name === "edit_file") {
|
|
1753
|
+
// The edit precondition is optional and advisory at the agent layer:
|
|
1754
|
+
// a hash remembered from an earlier turn or compacted context is stale
|
|
1755
|
+
// and would otherwise loop on PRECONDITION_FAILED. Drop it and let the
|
|
1756
|
+
// oldText match be the guard. The port still enforces a precondition
|
|
1757
|
+
// when a host supplies one directly.
|
|
1758
|
+
const args = normalized.arguments;
|
|
1759
|
+
if (args !== null && typeof args === "object" && !Array.isArray(args) && "precondition" in args) {
|
|
1760
|
+
const { precondition: _drop, ...rest } = args;
|
|
1761
|
+
normalized = Object.freeze({ ...normalized, arguments: Object.freeze(rest) });
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return normalized;
|
|
1765
|
+
});
|
|
1766
|
+
const unavailableName = normalizedCalls.find((call) => !visibleNames.has(call.name))?.name;
|
|
1767
|
+
if (unavailableName !== undefined) {
|
|
1768
|
+
throw new AiCoderRuntimeError("INVALID_MODEL_STREAM", `Tool ${unavailableName} was not active in the registry snapshot shown to the model for this round.`);
|
|
1769
|
+
}
|
|
1692
1770
|
const reusedId = callIds.find((id) => session.evidence.seenToolCallIds.has(id));
|
|
1693
1771
|
if (reusedId !== undefined) {
|
|
1694
1772
|
throw new AiCoderRuntimeError("INVALID_MODEL_STREAM", `toolCallId ${reusedId} was already used in this run.`);
|
|
@@ -1696,11 +1774,6 @@ export class AiCoderRunController {
|
|
|
1696
1774
|
if (session.toolCalls + calls.length > session.budget.maxToolCalls) {
|
|
1697
1775
|
throw new AiCoderRuntimeError("MAX_TOOL_CALLS", `AI Coder reached maxToolCalls=${session.budget.maxToolCalls}.`);
|
|
1698
1776
|
}
|
|
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
1777
|
const prepared = [];
|
|
1705
1778
|
for (const call of calls) {
|
|
1706
1779
|
try {
|
|
@@ -1712,6 +1785,28 @@ export class AiCoderRunController {
|
|
|
1712
1785
|
}
|
|
1713
1786
|
return Object.freeze(prepared);
|
|
1714
1787
|
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Bound a single tool dispatch. A native or host tool that blocks forever
|
|
1790
|
+
* would otherwise hang the whole run; a timeout fails the run cleanly with a
|
|
1791
|
+
* diagnosable code instead.
|
|
1792
|
+
*/
|
|
1793
|
+
async executeToolCallGuarded(session, prepared, roundToolSet) {
|
|
1794
|
+
const timeoutMs = 600_000;
|
|
1795
|
+
let timer;
|
|
1796
|
+
try {
|
|
1797
|
+
return await Promise.race([
|
|
1798
|
+
this.executeToolCall(session, prepared, roundToolSet),
|
|
1799
|
+
new Promise((_, reject) => {
|
|
1800
|
+
timer = setTimeout(() => reject(new AiCoderRuntimeError("TOOL_TIMEOUT", `Tool ${prepared.call.name} did not return within ${timeoutMs}ms.`)), timeoutMs);
|
|
1801
|
+
timer.unref?.();
|
|
1802
|
+
}),
|
|
1803
|
+
]);
|
|
1804
|
+
}
|
|
1805
|
+
finally {
|
|
1806
|
+
if (timer !== undefined)
|
|
1807
|
+
clearTimeout(timer);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1715
1810
|
async executeToolCall(session, prepared, roundToolSet) {
|
|
1716
1811
|
if (!session.contextManager || !session.toolSet)
|
|
1717
1812
|
throw new Error("Context manager or tool set is unavailable.");
|
|
@@ -1740,7 +1835,9 @@ export class AiCoderRunController {
|
|
|
1740
1835
|
const callRecord = {
|
|
1741
1836
|
// Bounded, redacted digest so the post-compaction checkpoint shows WHICH
|
|
1742
1837
|
// paths/queries were already inspected; hashes alone cannot stop re-listing.
|
|
1743
|
-
argumentDigest: redactAiCoderCheckpointText(canonicalJson(call.arguments))
|
|
1838
|
+
argumentDigest: redactAiCoderCheckpointText(canonicalJson(call.arguments))
|
|
1839
|
+
.replace(/"contentSha256":"[a-f0-9]{64}"/gu, '"contentSha256":"<re-read to refresh>"')
|
|
1840
|
+
.slice(0, 160),
|
|
1744
1841
|
argumentsHash,
|
|
1745
1842
|
idempotencyKey,
|
|
1746
1843
|
name: call.name,
|
|
@@ -1959,9 +2056,19 @@ export class AiCoderRunController {
|
|
|
1959
2056
|
research: session.evidence.researchSources,
|
|
1960
2057
|
writes: session.evidence.writes,
|
|
1961
2058
|
});
|
|
2059
|
+
const authoredWrites = (normalizedResult.effects?.writes ?? []).filter((write) => !isGeneratedWorkspacePath(write.path.split("/")));
|
|
2060
|
+
const durableAuthoredProgress = Boolean(effectCanChangeState && normalizedResult.effects && (authoredWrites.length > 0
|
|
2061
|
+
|| normalizedResult.effects.diffReview
|
|
2062
|
+
|| normalizedResult.effects.plan
|
|
2063
|
+
|| (normalizedResult.effects.researchSources?.length ?? 0) > 0
|
|
2064
|
+
|| (normalizedResult.effects.acceptanceCriteriaSatisfied?.length ?? 0) > 0
|
|
2065
|
+
|| (normalizedResult.effects.acceptanceCriteriaWaived?.length ?? 0) > 0
|
|
2066
|
+
|| (normalizedResult.effects.inspectedPaths?.length ?? 0) > 0));
|
|
1962
2067
|
if (hasStateEffect && nextStateVersion !== previousStateVersion) {
|
|
1963
2068
|
session.stateVersion = nextStateVersion;
|
|
1964
|
-
|
|
2069
|
+
// A generated-only state change (build output, caches, tsbuildinfo) is not
|
|
2070
|
+
// authored progress, so it must not clear the no-progress budget.
|
|
2071
|
+
if (durableAuthoredProgress && (normalizedResult.ok || normalizedResult.effects?.approval === "denied") && !noProgressDetected) {
|
|
1965
2072
|
session.repeatedToolFingerprint = 0;
|
|
1966
2073
|
session.noProgressEpisodes = 0;
|
|
1967
2074
|
session.lastNoProgressEpisodeTurn = -1;
|
|
@@ -2345,6 +2452,10 @@ export class AiCoderRunController {
|
|
|
2345
2452
|
}
|
|
2346
2453
|
const cyclingPaths = [];
|
|
2347
2454
|
for (const write of effects.writes ?? []) {
|
|
2455
|
+
// Build output/caches are durable host effects (run_command proof) but are
|
|
2456
|
+
// not authored source; keep them out of the authored write evidence.
|
|
2457
|
+
if (isGeneratedWorkspacePath(write.path.split("/")))
|
|
2458
|
+
continue;
|
|
2348
2459
|
const beforeState = workspaceStateKey(write.beforeKind, write.beforeHash);
|
|
2349
2460
|
const afterState = workspaceStateKey(write.afterKind, write.afterHash);
|
|
2350
2461
|
let history = session.writeStateHistory.get(write.path) ?? [];
|
|
@@ -2597,7 +2708,27 @@ export class AiCoderRunController {
|
|
|
2597
2708
|
const messages = gate.issues.map((item) => `${item.code}: ${item.detail}`);
|
|
2598
2709
|
const researchEvidence = completionRejectionResearchEvidence(session, content);
|
|
2599
2710
|
const remediation = gate.issues.flatMap((item) => {
|
|
2711
|
+
if (item.code === "WORKSPACE_EVIDENCE_STALE") {
|
|
2712
|
+
return [
|
|
2713
|
+
"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.",
|
|
2714
|
+
];
|
|
2715
|
+
}
|
|
2716
|
+
if (item.code === "WRITE_NOT_VALIDATED") {
|
|
2717
|
+
return [
|
|
2718
|
+
"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.",
|
|
2719
|
+
];
|
|
2720
|
+
}
|
|
2600
2721
|
if (item.code === "DIFF_NOT_REVIEWED") {
|
|
2722
|
+
// In a workspace without Git the git advice is a trap: the model
|
|
2723
|
+
// searched the catalog for a tool that can never exist and drained
|
|
2724
|
+
// the turn budget. Point it to the workspace review tool instead.
|
|
2725
|
+
const activeCanonicalIds = session.toolSet ? Object.values(session.toolSet.canonicalToolIds) : [];
|
|
2726
|
+
const gitAvailable = activeCanonicalIds.includes("git.diff") || activeCanonicalIds.includes("git.exec");
|
|
2727
|
+
if (!gitAvailable) {
|
|
2728
|
+
return [
|
|
2729
|
+
"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.",
|
|
2730
|
+
];
|
|
2731
|
+
}
|
|
2601
2732
|
return [
|
|
2602
2733
|
"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
2734
|
];
|
|
@@ -2708,7 +2839,8 @@ export class AiCoderRunController {
|
|
|
2708
2839
|
constraints: Object.freeze([...(session.request.constraints ?? [])]),
|
|
2709
2840
|
decisions: Object.freeze([...session.evidence.decisions]),
|
|
2710
2841
|
delivery: Object.freeze({ attachmentsDelivered: session.attachmentsDelivered }),
|
|
2711
|
-
|
|
2842
|
+
editsTotal: session.evidence.writes.length,
|
|
2843
|
+
edits: Object.freeze(session.evidence.writes.slice(-500).map((item) => Object.freeze({
|
|
2712
2844
|
afterHash: item.afterHash,
|
|
2713
2845
|
...(item.afterKind !== undefined ? { afterKind: item.afterKind } : {}),
|
|
2714
2846
|
beforeHash: item.beforeHash,
|