@lazyingart/agintiflow 0.20.202 → 0.20.203
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.
|
@@ -26,11 +26,31 @@ AGINTI_EVIDENCE_SCOPE_JSON: {"mode":"chat-response","request":"Produce only the
|
|
|
26
26
|
|
|
27
27
|
or:
|
|
28
28
|
|
|
29
|
+
```text
|
|
30
|
+
AGINTI_EVIDENCE_SCOPE_JSON: {"mode":"host-managed-response","request":"Return the report body; LabCanvas compiles and delivers it."}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
or:
|
|
34
|
+
|
|
29
35
|
```text
|
|
30
36
|
AGINTI_EVIDENCE_SCOPE_JSON: {"mode":"task","request":"Create the requested PDF from the supplied evidence."}
|
|
31
37
|
```
|
|
32
38
|
|
|
33
|
-
For `chat-response`, ordinary conversation and routing do not require file or command evidence. For `task`, evidence requirements are inferred only from the exact request, not from surrounding wrapper prose. Artifact requests still require real artifacts.
|
|
39
|
+
For `chat-response`, ordinary conversation and routing do not require file or command evidence. `host-managed-response` is for content-only subtasks such as a LaTeX body, translation, completion audit, or scheduled lesson where LabCanvas owns persistence, compilation, validation, and delivery. For `task`, evidence requirements are inferred only from the exact request, not from surrounding wrapper prose. Artifact requests still require real artifacts.
|
|
40
|
+
|
|
41
|
+
## Provider Tool-Batch Recovery
|
|
42
|
+
|
|
43
|
+
AgInTi requests one effectful tool call at a time, but some OpenAI-compatible
|
|
44
|
+
providers may still emit a small batch. A bounded batch of otherwise valid
|
|
45
|
+
calls is handled sequentially: AgInTi dispatches only the first call, records
|
|
46
|
+
`tool.batch_deferred`, and asks the model to continue from the resulting state.
|
|
47
|
+
Deferred calls are never executed automatically. Per-tool schema, permission,
|
|
48
|
+
secret, path, and irreversible-action checks remain authoritative.
|
|
49
|
+
|
|
50
|
+
Malformed calls, duplicate IDs, unavailable tools, hidden arguments, or an
|
|
51
|
+
oversized batch still stop with `tool_contract_violation`. A host may resume
|
|
52
|
+
the same saved session with another provider after that categorized stop; it
|
|
53
|
+
must not replay the original request or duplicate earlier side effects.
|
|
34
54
|
|
|
35
55
|
## Local Context Recovery
|
|
36
56
|
|
|
@@ -43,4 +63,3 @@ npm run check
|
|
|
43
63
|
npm run smoke:context-budget-recovery
|
|
44
64
|
npm run smoke:truthful-completion
|
|
45
65
|
```
|
|
46
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.203",
|
|
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",
|
|
@@ -18,6 +18,7 @@ import { SessionStore } from "../src/session-store.js";
|
|
|
18
18
|
import {
|
|
19
19
|
attachToolContract,
|
|
20
20
|
createToolContract,
|
|
21
|
+
resolveDispatchableToolCallBatch,
|
|
21
22
|
safeSequentialToolBatchLimit,
|
|
22
23
|
toolContractFromResponse,
|
|
23
24
|
validateToolCallBatch,
|
|
@@ -888,6 +889,14 @@ assert(
|
|
|
888
889
|
}).ok,
|
|
889
890
|
"mixed read/write batch unexpectedly passed"
|
|
890
891
|
);
|
|
892
|
+
const recoveredMixedBatch = resolveDispatchableToolCallBatch(
|
|
893
|
+
mixedReadWriteCalls,
|
|
894
|
+
createToolContract([...safeReadDescriptors, strictWriteDescriptor])
|
|
895
|
+
);
|
|
896
|
+
assert(recoveredMixedBatch.ok, "valid mixed batch could not recover through bounded sequential deferral");
|
|
897
|
+
assert(recoveredMixedBatch.recoveredSequentially, "mixed batch recovery was not recorded");
|
|
898
|
+
assert(recoveredMixedBatch.acceptedToolCalls.length === 1, "mixed batch recovery dispatched more than one call");
|
|
899
|
+
assert(recoveredMixedBatch.deferredToolCalls.length === 1, "mixed batch recovery did not defer the extra call");
|
|
891
900
|
const oversizedReadCalls = Array.from({ length: 5 }, (_, index) =>
|
|
892
901
|
contractCall(`read-${index}`, "read_file", { path: `file-${index}.txt` })
|
|
893
902
|
);
|
|
@@ -968,6 +977,9 @@ async function runToolContractCase({
|
|
|
968
977
|
allowShellTool = false,
|
|
969
978
|
toolSurfaceMaxTools = 12,
|
|
970
979
|
targets = [],
|
|
980
|
+
expectedTargets = [],
|
|
981
|
+
followupToolCalls = null,
|
|
982
|
+
expectSequentialRecovery = false,
|
|
971
983
|
}) {
|
|
972
984
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), `agintiflow-tool-contract-${id}-`));
|
|
973
985
|
const workspace = path.join(tempRoot, "workspace");
|
|
@@ -988,7 +1000,10 @@ async function runToolContractCase({
|
|
|
988
1000
|
if (textFallback) {
|
|
989
1001
|
return { choices: [{ message: { role: "assistant", content: responseText } }] };
|
|
990
1002
|
}
|
|
991
|
-
|
|
1003
|
+
const selectedToolCalls = Array.isArray(followupToolCalls) && requests.length > 1
|
|
1004
|
+
? followupToolCalls
|
|
1005
|
+
: toolCalls;
|
|
1006
|
+
return assistantWithToolCalls(selectedToolCalls);
|
|
992
1007
|
},
|
|
993
1008
|
},
|
|
994
1009
|
},
|
|
@@ -1082,6 +1097,23 @@ async function runToolContractCase({
|
|
|
1082
1097
|
const events = await store.loadEvents();
|
|
1083
1098
|
const contractFailures = events.filter((event) => event.type === "tool.failed" && event.data?.category === "tool-contract-violation");
|
|
1084
1099
|
assert(clientFactoryCalls === 1, `${id} did not complete readiness and construct exactly one deterministic client`);
|
|
1100
|
+
if (expectSequentialRecovery) {
|
|
1101
|
+
assert(result.stopped !== true, `${id} stopped instead of completing the recovered sequential batch`);
|
|
1102
|
+
assert(contractFailures.length === 0, `${id} recorded a contract failure for a recoverable valid batch`);
|
|
1103
|
+
assert(
|
|
1104
|
+
events.some((event) => event.type === "tool.batch_deferred" && event.data?.deferredCount === toolCalls.length - 1),
|
|
1105
|
+
`${id} did not record bounded sequential deferral`
|
|
1106
|
+
);
|
|
1107
|
+
for (const target of expectedTargets) {
|
|
1108
|
+
const exists = await fs.access(path.join(workspace, target)).then(() => true).catch(() => false);
|
|
1109
|
+
assert(exists, `${id} did not create expected artifact ${target}`);
|
|
1110
|
+
}
|
|
1111
|
+
for (const target of targets) {
|
|
1112
|
+
const exists = await fs.access(path.join(workspace, target)).then(() => true).catch(() => false);
|
|
1113
|
+
assert(!exists, `${id} dispatched deferred artifact ${target}`);
|
|
1114
|
+
}
|
|
1115
|
+
return { result, requests, events, contractFailures, clientFactoryCalls };
|
|
1116
|
+
}
|
|
1085
1117
|
assert(
|
|
1086
1118
|
result.stopped === true && result.reason === "tool_contract_violation",
|
|
1087
1119
|
`${id} did not stop after one bounded repair: ${JSON.stringify({
|
|
@@ -1151,16 +1183,19 @@ assert(
|
|
|
1151
1183
|
|
|
1152
1184
|
const multiCall = await runToolContractCase({
|
|
1153
1185
|
id: "native-multi-call",
|
|
1154
|
-
goal: "Create multi-one.txt
|
|
1186
|
+
goal: "Create multi-one.txt containing safe.",
|
|
1155
1187
|
toolCalls: [
|
|
1156
|
-
contractCall("multi-one", "write_file", { path: "multi-one.txt", content: "
|
|
1157
|
-
contractCall("multi-two", "write_file", { path: "multi-two.txt", content: "
|
|
1188
|
+
contractCall("multi-one", "write_file", { path: "multi-one.txt", content: "safe", mode: "create" }),
|
|
1189
|
+
contractCall("multi-two", "write_file", { path: "multi-two.txt", content: "deferred", mode: "create" }),
|
|
1158
1190
|
],
|
|
1159
|
-
|
|
1191
|
+
followupToolCalls: [contractCall("finish-recovered", "finish", { result: "Created and verified multi-one.txt." })],
|
|
1192
|
+
expectSequentialRecovery: true,
|
|
1193
|
+
expectedTargets: ["multi-one.txt"],
|
|
1194
|
+
targets: ["multi-two.txt"],
|
|
1160
1195
|
});
|
|
1161
1196
|
assert(
|
|
1162
|
-
multiCall.
|
|
1163
|
-
"multi-call batch did not
|
|
1197
|
+
multiCall.events.some((event) => event.type === "tool.completed" && event.data?.toolName === "write_file"),
|
|
1198
|
+
"recoverable multi-call batch did not dispatch its first valid call"
|
|
1164
1199
|
);
|
|
1165
1200
|
|
|
1166
1201
|
const duplicateId = await runToolContractCase({
|
package/src/agent-runner.js
CHANGED
|
@@ -96,9 +96,8 @@ import {
|
|
|
96
96
|
} from "./step-budget-controller.js";
|
|
97
97
|
import { selectExecutionPolicy } from "./execution-policy.js";
|
|
98
98
|
import {
|
|
99
|
-
|
|
99
|
+
resolveDispatchableToolCallBatch,
|
|
100
100
|
toolContractFromResponse,
|
|
101
|
-
validateToolCallBatch,
|
|
102
101
|
} from "./tool-contract.js";
|
|
103
102
|
import {
|
|
104
103
|
createContextBudgetState,
|
|
@@ -3909,17 +3908,18 @@ export async function runAgent(config) {
|
|
|
3909
3908
|
}
|
|
3910
3909
|
|
|
3911
3910
|
const rawToolCalls = assistantMessage.tool_calls;
|
|
3912
|
-
const
|
|
3911
|
+
const reportedToolCalls = Array.isArray(rawToolCalls) ? rawToolCalls : [];
|
|
3913
3912
|
const toolBatchValidation = rawToolCalls === undefined || rawToolCalls === null
|
|
3914
|
-
? { ok: true, calls: [] }
|
|
3915
|
-
:
|
|
3916
|
-
|
|
3917
|
-
|
|
3913
|
+
? { ok: true, calls: [], acceptedToolCalls: [], deferredToolCalls: [] }
|
|
3914
|
+
: resolveDispatchableToolCallBatch(rawToolCalls, toolContractFromResponse(response));
|
|
3915
|
+
const toolCalls = toolBatchValidation.ok
|
|
3916
|
+
? (toolBatchValidation.acceptedToolCalls || reportedToolCalls)
|
|
3917
|
+
: reportedToolCalls;
|
|
3918
3918
|
|
|
3919
3919
|
await store.appendEvent("model.responded", {
|
|
3920
3920
|
step,
|
|
3921
3921
|
content: redactSensitiveText(assistantMessage.content || ""),
|
|
3922
|
-
toolCalls:
|
|
3922
|
+
toolCalls: reportedToolCalls.map((call) => ({
|
|
3923
3923
|
id: call?.id,
|
|
3924
3924
|
name: call?.function?.name,
|
|
3925
3925
|
arguments: redactSensitiveText(call?.function?.arguments || ""),
|
|
@@ -3962,7 +3962,27 @@ export async function runAgent(config) {
|
|
|
3962
3962
|
continue;
|
|
3963
3963
|
}
|
|
3964
3964
|
|
|
3965
|
-
|
|
3965
|
+
if (toolBatchValidation.recoveredSequentially) {
|
|
3966
|
+
const deferredToolCalls = toolBatchValidation.deferredToolCalls || [];
|
|
3967
|
+
const detail = {
|
|
3968
|
+
step,
|
|
3969
|
+
reportedCount: reportedToolCalls.length,
|
|
3970
|
+
dispatchedCount: toolCalls.length,
|
|
3971
|
+
deferredCount: deferredToolCalls.length,
|
|
3972
|
+
dispatchedTool: String(toolCalls[0]?.function?.name || ""),
|
|
3973
|
+
deferredTools: deferredToolCalls.map((call) => String(call?.function?.name || "")).filter(Boolean),
|
|
3974
|
+
};
|
|
3975
|
+
await store.appendEvent("tool.batch_deferred", detail);
|
|
3976
|
+
observers.event("tool.batch_deferred", detail);
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
state.messages.push(
|
|
3980
|
+
preserveAssistantMessage(
|
|
3981
|
+
toolBatchValidation.recoveredSequentially
|
|
3982
|
+
? { ...assistantMessage, tool_calls: toolCalls }
|
|
3983
|
+
: assistantMessage
|
|
3984
|
+
)
|
|
3985
|
+
);
|
|
3966
3986
|
|
|
3967
3987
|
if (toolCalls.length === 0) {
|
|
3968
3988
|
const queuedCount = await injectQueuedUserMessages(store, state, observers);
|
package/src/scs-evidence.js
CHANGED
|
@@ -519,7 +519,7 @@ function scopedChatopsEvidenceGoal(goal = "", taskProfile = "") {
|
|
|
519
519
|
const payload = JSON.parse(match[1]);
|
|
520
520
|
if (!payload || typeof payload !== "object") return String(goal || "");
|
|
521
521
|
const mode = String(payload.mode || "").trim().toLowerCase();
|
|
522
|
-
if (["chat-response", "plan-response", "read-only-answer"].includes(mode)) {
|
|
522
|
+
if (["chat-response", "host-managed-response", "plan-response", "read-only-answer"].includes(mode)) {
|
|
523
523
|
return "Answer the current chat turn directly without external execution.";
|
|
524
524
|
}
|
|
525
525
|
const request = String(payload.request || "").trim();
|
package/src/tool-contract.js
CHANGED
|
@@ -29,6 +29,7 @@ const SAFE_SEQUENTIAL_READ_TOOLS = new Set([
|
|
|
29
29
|
const MAX_VALIDATION_ERRORS = 8;
|
|
30
30
|
const MAX_VALIDATION_NODES = 50_000;
|
|
31
31
|
const MAX_SAFE_SEQUENTIAL_READ_CALLS = 4;
|
|
32
|
+
const MAX_RECOVERABLE_SEQUENTIAL_CALLS = 4;
|
|
32
33
|
|
|
33
34
|
function cloneValue(value) {
|
|
34
35
|
return structuredClone(value);
|
|
@@ -331,3 +332,42 @@ export function validateToolCallBatch(toolCalls, contract, { maxToolCalls = 1 }
|
|
|
331
332
|
}
|
|
332
333
|
return { ok: true, calls: parsedCalls };
|
|
333
334
|
}
|
|
335
|
+
|
|
336
|
+
export function resolveDispatchableToolCallBatch(toolCalls, contract) {
|
|
337
|
+
const calls = Array.isArray(toolCalls) ? toolCalls : [];
|
|
338
|
+
const validation = validateToolCallBatch(calls, contract, {
|
|
339
|
+
maxToolCalls: safeSequentialToolBatchLimit(calls),
|
|
340
|
+
});
|
|
341
|
+
if (validation.ok) {
|
|
342
|
+
return {
|
|
343
|
+
...validation,
|
|
344
|
+
acceptedToolCalls: calls,
|
|
345
|
+
deferredToolCalls: [],
|
|
346
|
+
recoveredSequentially: false,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const errors = Array.isArray(validation.errors) ? validation.errors : [];
|
|
351
|
+
const onlyExceededBatchLimit =
|
|
352
|
+
errors.length > 0 && errors.every((error) => error?.code === "TOO_MANY_TOOL_CALLS");
|
|
353
|
+
if (
|
|
354
|
+
!onlyExceededBatchLimit ||
|
|
355
|
+
calls.length <= 1 ||
|
|
356
|
+
calls.length > MAX_RECOVERABLE_SEQUENTIAL_CALLS
|
|
357
|
+
) {
|
|
358
|
+
return validation;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const firstCallValidation = validateToolCallBatch([calls[0]], contract, {
|
|
362
|
+
maxToolCalls: 1,
|
|
363
|
+
});
|
|
364
|
+
if (!firstCallValidation.ok) return validation;
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
...firstCallValidation,
|
|
368
|
+
acceptedToolCalls: [calls[0]],
|
|
369
|
+
deferredToolCalls: calls.slice(1),
|
|
370
|
+
recoveredSequentially: true,
|
|
371
|
+
originalCode: validation.code,
|
|
372
|
+
};
|
|
373
|
+
}
|