@lazyingart/agintiflow 0.20.329 → 0.20.331
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.
|
@@ -700,3 +700,43 @@ status" prompt, triggers a DeepSeek quota handoff, and verifies that LocalLLM is
|
|
|
700
700
|
called only after compaction. The run completes without `session.failed`, keeps
|
|
701
701
|
the provider handoff active on the same session, and records the compaction
|
|
702
702
|
events as durable evidence.
|
|
703
|
+
|
|
704
|
+
### Harmless tool-call annotations during repository discovery
|
|
705
|
+
|
|
706
|
+
`inspect-project-annotation-093` covers a DeepSeek-first tool-loop boundary
|
|
707
|
+
that was still under-tested after the provider handoff work. Recent runtime
|
|
708
|
+
evidence showed ordinary project-inspection tasks stopping with
|
|
709
|
+
`tool_contract_violation` before dispatch because the model included a
|
|
710
|
+
non-executable `reason` field in an otherwise valid `inspect_project` call.
|
|
711
|
+
The strict per-turn schema correctly rejected unknown executable fields, but
|
|
712
|
+
the existing benign-annotation normalizer only recognized `description`.
|
|
713
|
+
|
|
714
|
+
AgInTiFlow now treats a bounded string `reason` exactly like `description`: it
|
|
715
|
+
is removed before schema validation only when the offered tool schema forbids
|
|
716
|
+
additional properties and the schema does not define that field. Structured,
|
|
717
|
+
non-string, oversized, or executable unknown fields still fail closed.
|
|
718
|
+
|
|
719
|
+
The regression uses a normal weak prompt asking the agent to look over a small
|
|
720
|
+
repository and report whether a README exists. The scripted DeepSeek-shaped
|
|
721
|
+
response calls `inspect_project` with `reason` plus a real `limit`; the persisted
|
|
722
|
+
runtime dispatches the inspection, records zero tool-contract failures, and
|
|
723
|
+
finishes with the verified README status without mutating the workspace.
|
|
724
|
+
|
|
725
|
+
### Native tmux recovery from generic shell aliases
|
|
726
|
+
|
|
727
|
+
`tmux-run-command-native-recovery-094` covers a reusable coordination-tool
|
|
728
|
+
handoff gap seen in recent retained runtime evidence. A normal supervision
|
|
729
|
+
prompt selected the host tmux tool bundle, but the provider tried the native
|
|
730
|
+
tool name `tmux_list_sessions` and later `tmux list-sessions` through
|
|
731
|
+
`run_command`. AgInTiFlow treated those as generic shell commands, producing a
|
|
732
|
+
host permission pause or shell failure instead of using the already offered
|
|
733
|
+
native tmux listing tool.
|
|
734
|
+
|
|
735
|
+
The runtime now auto-corrects only exact read-only tmux session-list aliases
|
|
736
|
+
from `run_command` to `tmux_list_sessions` before shell guardrails run. It
|
|
737
|
+
records `tool.auto_corrected`, preserves the original requested tool in
|
|
738
|
+
`tool.started`, dispatches no generic shell command, and leaves arbitrary tmux
|
|
739
|
+
startup/send/mutation shell commands blocked by the existing permission policy.
|
|
740
|
+
The focused persisted-runtime regression verifies both exact aliases and the
|
|
741
|
+
negative mutating command case; the tmux guardrail, progressive-tool,
|
|
742
|
+
provider-handoff, syntax, package-audit, dry-pack, and full npm suites pass.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.331",
|
|
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",
|
|
@@ -7238,6 +7238,54 @@ assert(
|
|
|
7238
7238
|
).ok,
|
|
7239
7239
|
"valid offered tool call did not satisfy its exact schema"
|
|
7240
7240
|
);
|
|
7241
|
+
const strictInspectDescriptor = {
|
|
7242
|
+
type: "function",
|
|
7243
|
+
function: {
|
|
7244
|
+
name: "inspect_project",
|
|
7245
|
+
description: "Inspect the project.",
|
|
7246
|
+
parameters: {
|
|
7247
|
+
type: "object",
|
|
7248
|
+
properties: {
|
|
7249
|
+
path: { type: "string" },
|
|
7250
|
+
maxDepth: { type: "integer" },
|
|
7251
|
+
limit: { type: "integer" },
|
|
7252
|
+
includeFiles: { type: "boolean" },
|
|
7253
|
+
},
|
|
7254
|
+
additionalProperties: false,
|
|
7255
|
+
},
|
|
7256
|
+
},
|
|
7257
|
+
};
|
|
7258
|
+
const recoveredInspectReason = resolveDispatchableToolCallBatch(
|
|
7259
|
+
[
|
|
7260
|
+
contractCall("inspect-with-reason", "inspect_project", {
|
|
7261
|
+
reason: "Orient before answering the user's project question.",
|
|
7262
|
+
limit: 120,
|
|
7263
|
+
}),
|
|
7264
|
+
],
|
|
7265
|
+
createToolContract([strictInspectDescriptor])
|
|
7266
|
+
);
|
|
7267
|
+
assert(
|
|
7268
|
+
recoveredInspectReason.ok && recoveredInspectReason.recoveredToolCallAnnotations,
|
|
7269
|
+
"a harmless inspect_project reason annotation was not stripped before schema validation"
|
|
7270
|
+
);
|
|
7271
|
+
assertStrict.deepEqual(
|
|
7272
|
+
JSON.parse(recoveredInspectReason.acceptedToolCalls[0].function.arguments),
|
|
7273
|
+
{ limit: 120 },
|
|
7274
|
+
"inspect_project reason annotation recovery changed executable arguments"
|
|
7275
|
+
);
|
|
7276
|
+
const rejectedStructuredInspectReason = resolveDispatchableToolCallBatch(
|
|
7277
|
+
[
|
|
7278
|
+
contractCall("inspect-structured-reason", "inspect_project", {
|
|
7279
|
+
reason: { intent: "orient" },
|
|
7280
|
+
limit: 120,
|
|
7281
|
+
}),
|
|
7282
|
+
],
|
|
7283
|
+
createToolContract([strictInspectDescriptor])
|
|
7284
|
+
);
|
|
7285
|
+
assert(
|
|
7286
|
+
!rejectedStructuredInspectReason.ok,
|
|
7287
|
+
"a structured inspect_project reason annotation was stripped even though it is not a bounded text note"
|
|
7288
|
+
);
|
|
7241
7289
|
|
|
7242
7290
|
const safeReadDescriptors = [
|
|
7243
7291
|
{
|
|
@@ -7877,6 +7925,73 @@ assert(
|
|
|
7877
7925
|
"authoritative routine flow still dispatched private/raw exploratory commands"
|
|
7878
7926
|
);
|
|
7879
7927
|
|
|
7928
|
+
let annotatedInspectStep = 0;
|
|
7929
|
+
const annotatedInspectRun = await runToolContractCase({
|
|
7930
|
+
id: "inspect-project-reason-annotation",
|
|
7931
|
+
provider: "deepseek",
|
|
7932
|
+
profile: "code",
|
|
7933
|
+
goal: "Please look over this little repo and tell me whether it has a README. Keep it short.",
|
|
7934
|
+
toolCalls: [
|
|
7935
|
+
contractCall("unused-annotated-inspect", "finish", { result: "unused" }),
|
|
7936
|
+
],
|
|
7937
|
+
expectSuccess: true,
|
|
7938
|
+
expectedContractFailures: 0,
|
|
7939
|
+
setupWorkspace: async (workspace) => {
|
|
7940
|
+
await fs.writeFile(
|
|
7941
|
+
path.join(workspace, "README.md"),
|
|
7942
|
+
"# Demo\nThis workspace has a README.\n",
|
|
7943
|
+
"utf8"
|
|
7944
|
+
);
|
|
7945
|
+
},
|
|
7946
|
+
responseFactory: ({ payload }) => {
|
|
7947
|
+
annotatedInspectStep += 1;
|
|
7948
|
+
const offered = Array.isArray(payload.tools) ? names(payload.tools) : [];
|
|
7949
|
+
if (annotatedInspectStep === 1) {
|
|
7950
|
+
assert(
|
|
7951
|
+
offered.includes("inspect_project"),
|
|
7952
|
+
"normal repository orientation did not offer inspect_project"
|
|
7953
|
+
);
|
|
7954
|
+
return assistantWithToolCalls([
|
|
7955
|
+
contractCall("inspect-with-natural-reason", "inspect_project", {
|
|
7956
|
+
reason: "Orient before answering the user's project question.",
|
|
7957
|
+
limit: 120,
|
|
7958
|
+
}),
|
|
7959
|
+
]);
|
|
7960
|
+
}
|
|
7961
|
+
return assistantWithToolCalls([
|
|
7962
|
+
contractCall("finish-after-annotated-inspect", "finish", {
|
|
7963
|
+
result: "README.md is present.",
|
|
7964
|
+
}),
|
|
7965
|
+
]);
|
|
7966
|
+
},
|
|
7967
|
+
});
|
|
7968
|
+
assertStrict.equal(
|
|
7969
|
+
annotatedInspectRun.requests.length,
|
|
7970
|
+
2,
|
|
7971
|
+
"annotated inspect_project recovery used extra model turns"
|
|
7972
|
+
);
|
|
7973
|
+
assert(
|
|
7974
|
+
annotatedInspectRun.events.some(
|
|
7975
|
+
(event) =>
|
|
7976
|
+
event.type === "tool.completed" &&
|
|
7977
|
+
event.data?.toolName === "inspect_project" &&
|
|
7978
|
+
event.data?.ok === true
|
|
7979
|
+
),
|
|
7980
|
+
"annotated inspect_project call was not dispatched through the real runtime"
|
|
7981
|
+
);
|
|
7982
|
+
assert(
|
|
7983
|
+
!annotatedInspectRun.events.some(
|
|
7984
|
+
(event) =>
|
|
7985
|
+
event.type === "tool.failed" &&
|
|
7986
|
+
event.data?.category === "tool-contract-violation"
|
|
7987
|
+
),
|
|
7988
|
+
"annotated inspect_project still produced a tool-contract violation"
|
|
7989
|
+
);
|
|
7990
|
+
assert(
|
|
7991
|
+
/README\.md is present/.test(String(annotatedInspectRun.result?.result || "")),
|
|
7992
|
+
"annotated inspect_project run did not finish with the promised repository status"
|
|
7993
|
+
);
|
|
7994
|
+
|
|
7880
7995
|
const hiddenDryRun = await runToolContractCase({
|
|
7881
7996
|
id: "native-hidden-dry-run",
|
|
7882
7997
|
goal: "Create hidden-dry-run.txt containing unsafe if this tool dispatches.",
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
evaluateSourceFreeResponseClaims,
|
|
20
20
|
finishResultClaimsIncompleteWork,
|
|
21
21
|
} from "../src/scs-evidence.js";
|
|
22
|
+
import { tmuxAvailable } from "../src/tmux-tools.js";
|
|
22
23
|
|
|
23
24
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
24
25
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-truthful-completion-"));
|
|
@@ -860,6 +861,118 @@ try {
|
|
|
860
861
|
"permission blocker triggered an SCS replan instead of waiting for approval"
|
|
861
862
|
);
|
|
862
863
|
|
|
864
|
+
if (await tmuxAvailable()) {
|
|
865
|
+
const tmuxAliasRecovery = await runCase({
|
|
866
|
+
id: "tmux-shell-alias-recovery",
|
|
867
|
+
goal: "Check durable host tmux sessions and report whether any sessions exist.",
|
|
868
|
+
taskProfile: "auto",
|
|
869
|
+
executionTier: "focused",
|
|
870
|
+
allowShellTool: true,
|
|
871
|
+
responses: [
|
|
872
|
+
assistant("", [toolCall("tmux-as-shell", "run_command", { command: "tmux_list_sessions" })]),
|
|
873
|
+
assistant("", [
|
|
874
|
+
toolCall("finish-tmux-alias", "finish", {
|
|
875
|
+
result: "Checked durable host tmux sessions through the native tmux listing tool.",
|
|
876
|
+
}),
|
|
877
|
+
]),
|
|
878
|
+
],
|
|
879
|
+
});
|
|
880
|
+
assert.equal(tmuxAliasRecovery.calls.length, 2);
|
|
881
|
+
assert.equal(tmuxAliasRecovery.result.stopped, undefined);
|
|
882
|
+
assert(
|
|
883
|
+
tmuxAliasRecovery.events.some(
|
|
884
|
+
(event) =>
|
|
885
|
+
event.type === "tool.auto_corrected" &&
|
|
886
|
+
event.data?.requestedToolName === "run_command" &&
|
|
887
|
+
event.data?.toolName === "tmux_list_sessions" &&
|
|
888
|
+
event.data?.originalCommand === "tmux_list_sessions"
|
|
889
|
+
),
|
|
890
|
+
"run_command tmux_list_sessions alias was not recovered to the native tmux tool"
|
|
891
|
+
);
|
|
892
|
+
assert(
|
|
893
|
+
tmuxAliasRecovery.events.some(
|
|
894
|
+
(event) =>
|
|
895
|
+
event.type === "tool.started" &&
|
|
896
|
+
event.data?.toolName === "tmux_list_sessions" &&
|
|
897
|
+
event.data?.requestedToolName === "run_command"
|
|
898
|
+
),
|
|
899
|
+
"native tmux listing was not started with original requested tool evidence"
|
|
900
|
+
);
|
|
901
|
+
assert(
|
|
902
|
+
tmuxAliasRecovery.events.some(
|
|
903
|
+
(event) =>
|
|
904
|
+
event.type === "tool.completed" &&
|
|
905
|
+
event.data?.toolName === "tmux_list_sessions"
|
|
906
|
+
),
|
|
907
|
+
"native tmux listing did not complete after alias recovery"
|
|
908
|
+
);
|
|
909
|
+
assert(
|
|
910
|
+
!tmuxAliasRecovery.events.some(
|
|
911
|
+
(event) => event.type === "tool.started" && event.data?.toolName === "run_command"
|
|
912
|
+
),
|
|
913
|
+
"tmux alias recovery still dispatched the generic shell command"
|
|
914
|
+
);
|
|
915
|
+
assert(
|
|
916
|
+
!tmuxAliasRecovery.events.some(
|
|
917
|
+
(event) => event.type === "session.stopped" && event.data?.reason === "permission_required"
|
|
918
|
+
),
|
|
919
|
+
"tmux alias recovery still paused on shell permission"
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
const tmuxReadonlyCommandRecovery = await runCase({
|
|
923
|
+
id: "tmux-readonly-command-recovery",
|
|
924
|
+
goal: "List durable host tmux sessions using the available coordination tool.",
|
|
925
|
+
taskProfile: "auto",
|
|
926
|
+
executionTier: "focused",
|
|
927
|
+
allowShellTool: true,
|
|
928
|
+
responses: [
|
|
929
|
+
assistant("", [toolCall("tmux-command-as-shell", "run_command", { command: "tmux list-sessions" })]),
|
|
930
|
+
assistant("", [
|
|
931
|
+
toolCall("finish-tmux-command", "finish", {
|
|
932
|
+
result: "Checked durable host tmux sessions through the native tmux listing tool.",
|
|
933
|
+
}),
|
|
934
|
+
]),
|
|
935
|
+
],
|
|
936
|
+
});
|
|
937
|
+
assert.equal(tmuxReadonlyCommandRecovery.result.stopped, undefined);
|
|
938
|
+
assert(
|
|
939
|
+
tmuxReadonlyCommandRecovery.events.some(
|
|
940
|
+
(event) =>
|
|
941
|
+
event.type === "tool.auto_corrected" &&
|
|
942
|
+
event.data?.toolName === "tmux_list_sessions" &&
|
|
943
|
+
event.data?.originalCommand === "tmux list-sessions"
|
|
944
|
+
),
|
|
945
|
+
"exact tmux list-sessions shell command was not recovered"
|
|
946
|
+
);
|
|
947
|
+
assert(
|
|
948
|
+
!tmuxReadonlyCommandRecovery.events.some(
|
|
949
|
+
(event) => event.type === "tool.started" && event.data?.toolName === "run_command"
|
|
950
|
+
),
|
|
951
|
+
"exact tmux list-sessions recovery still dispatched run_command"
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
const tmuxMutationStillBlocked = await runCase({
|
|
955
|
+
id: "tmux-mutating-command-still-blocked",
|
|
956
|
+
goal: "Try to create a tmux session through the generic shell.",
|
|
957
|
+
taskProfile: "auto",
|
|
958
|
+
executionTier: "focused",
|
|
959
|
+
allowShellTool: true,
|
|
960
|
+
responses: [
|
|
961
|
+
assistant("", [
|
|
962
|
+
toolCall("tmux-mutating-shell", "run_command", {
|
|
963
|
+
command: "tmux new-session -d -s should-not-autocorrect",
|
|
964
|
+
}),
|
|
965
|
+
]),
|
|
966
|
+
],
|
|
967
|
+
});
|
|
968
|
+
assert.equal(tmuxMutationStillBlocked.result.stopped, true);
|
|
969
|
+
assert.equal(tmuxMutationStillBlocked.result.reason, "permission_required");
|
|
970
|
+
assert(
|
|
971
|
+
!tmuxMutationStillBlocked.events.some((event) => event.type === "tool.auto_corrected"),
|
|
972
|
+
"mutating tmux shell command was incorrectly auto-corrected"
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
|
|
863
976
|
const reasoningTruncation = await runCase({
|
|
864
977
|
id: "reasoning-only-tool-continuation",
|
|
865
978
|
goal: "Run pwd and report the verified working directory.",
|
package/src/agent-runner.js
CHANGED
|
@@ -5058,6 +5058,58 @@ export function normalizeNoMatchQueryResult(result = {}, policy = {}) {
|
|
|
5058
5058
|
};
|
|
5059
5059
|
}
|
|
5060
5060
|
|
|
5061
|
+
function runtimeFlagDisabled(value) {
|
|
5062
|
+
if (value === false || value === 0) return true;
|
|
5063
|
+
return /^(false|off|no|0)$/i.test(String(value ?? "").trim());
|
|
5064
|
+
}
|
|
5065
|
+
|
|
5066
|
+
function exactTmuxListSessionsShellAlias(command = "") {
|
|
5067
|
+
const canonical = canonicalizeShellCommand(command);
|
|
5068
|
+
if (!canonical || canonical.includes("\n")) return null;
|
|
5069
|
+
const sequence = parseTopLevelShellSequence(canonical);
|
|
5070
|
+
if (
|
|
5071
|
+
sequence.openQuote ||
|
|
5072
|
+
sequence.trailingEscape ||
|
|
5073
|
+
sequence.trailingSeparator ||
|
|
5074
|
+
sequence.commands.length !== 1
|
|
5075
|
+
) {
|
|
5076
|
+
return null;
|
|
5077
|
+
}
|
|
5078
|
+
const tokens = tokenizeShellWords(sequence.commands[0]).map((token) =>
|
|
5079
|
+
String(token || "")
|
|
5080
|
+
);
|
|
5081
|
+
if (tokens.length === 1 && tokens[0] === "tmux_list_sessions") {
|
|
5082
|
+
return { command: canonical, alias: "tool-name-as-shell-command" };
|
|
5083
|
+
}
|
|
5084
|
+
if (
|
|
5085
|
+
tokens.length === 2 &&
|
|
5086
|
+
tokens[0] === "tmux" &&
|
|
5087
|
+
(tokens[1] === "list-sessions" || tokens[1] === "ls")
|
|
5088
|
+
) {
|
|
5089
|
+
return { command: canonical, alias: "tmux-readonly-list-command" };
|
|
5090
|
+
}
|
|
5091
|
+
return null;
|
|
5092
|
+
}
|
|
5093
|
+
|
|
5094
|
+
function recoverRunCommandTmuxListAlias(requestedToolName, args = {}, config = {}) {
|
|
5095
|
+
if (requestedToolName !== "run_command") return null;
|
|
5096
|
+
if (
|
|
5097
|
+
runtimeFlagDisabled(config.allowShellTool) ||
|
|
5098
|
+
runtimeFlagDisabled(config.allowTmuxTools) ||
|
|
5099
|
+
runtimeFlagDisabled(config.allowCoordinationTools)
|
|
5100
|
+
) {
|
|
5101
|
+
return null;
|
|
5102
|
+
}
|
|
5103
|
+
const alias = exactTmuxListSessionsShellAlias(args.command);
|
|
5104
|
+
if (!alias) return null;
|
|
5105
|
+
return {
|
|
5106
|
+
requestedToolName,
|
|
5107
|
+
toolName: "tmux_list_sessions",
|
|
5108
|
+
reason: alias.alias,
|
|
5109
|
+
originalCommand: alias.command,
|
|
5110
|
+
};
|
|
5111
|
+
}
|
|
5112
|
+
|
|
5061
5113
|
function hashForLog(value) {
|
|
5062
5114
|
return crypto.createHash("sha256").update(String(value ?? "")).digest("hex");
|
|
5063
5115
|
}
|
|
@@ -20506,6 +20558,14 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
|
|
|
20506
20558
|
prompt: "Inspect this exact generated image as verification evidence. Describe the visible content, readability, clipping, labels, scale, and any defects that require repair.",
|
|
20507
20559
|
};
|
|
20508
20560
|
}
|
|
20561
|
+
const tmuxListAliasCorrection = !autoCorrection
|
|
20562
|
+
? recoverRunCommandTmuxListAlias(requestedToolName, args, config)
|
|
20563
|
+
: null;
|
|
20564
|
+
if (tmuxListAliasCorrection) {
|
|
20565
|
+
toolName = tmuxListAliasCorrection.toolName;
|
|
20566
|
+
args = { includePanes: true };
|
|
20567
|
+
autoCorrection = tmuxListAliasCorrection;
|
|
20568
|
+
}
|
|
20509
20569
|
const repositoryCommitPaths = [
|
|
20510
20570
|
...(Array.isArray(config.repositoryStateRepairCommitPaths)
|
|
20511
20571
|
? config.repositoryStateRepairCommitPaths
|
package/src/tool-contract.js
CHANGED
|
@@ -30,7 +30,7 @@ const MAX_VALIDATION_ERRORS = 8;
|
|
|
30
30
|
const MAX_VALIDATION_NODES = 50_000;
|
|
31
31
|
const MAX_SAFE_SEQUENTIAL_READ_CALLS = 4;
|
|
32
32
|
const MAX_REPORTED_SEQUENTIAL_CALLS = 12;
|
|
33
|
-
const BENIGN_TOOL_CALL_ANNOTATION_KEYS = new Set(["description"]);
|
|
33
|
+
const BENIGN_TOOL_CALL_ANNOTATION_KEYS = new Set(["description", "reason"]);
|
|
34
34
|
|
|
35
35
|
function cloneValue(value) {
|
|
36
36
|
return structuredClone(value);
|