@lazyingart/agintiflow 0.20.259 → 0.20.261

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.259",
3
+ "version": "0.20.261",
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",
@@ -175,13 +175,86 @@ async function main() {
175
175
  toolChoiceForProvider(
176
176
  { provider: "deepseek" },
177
177
  [{ role: "user", content: "Emit exactly one enabled tool call that performs the next concrete action." }]
178
- ) === "auto",
179
- "DeepSeek thinking mode received unsupported required tool selection during recovery"
178
+ ) === "required",
179
+ "DeepSeek action recovery did not require a tool call"
180
+ );
181
+ assert(
182
+ JSON.stringify(toolChoiceForProvider(
183
+ { provider: "deepseek" },
184
+ [{ role: "user", content: "Emit exactly one enabled tool call that performs the next concrete action." }],
185
+ [
186
+ { type: "function", function: { name: "apply_patch" } },
187
+ { type: "function", function: { name: "finish" } },
188
+ ]
189
+ )) === JSON.stringify({ type: "function", function: { name: "apply_patch" } }),
190
+ "DeepSeek action recovery did not force the only executable tool"
180
191
  );
181
192
  assert(
182
193
  JSON.stringify(providerStructuredOutputAttempts("deepseek")) === JSON.stringify(["json_object", "prompt"]),
183
194
  "DeepSeek structured extraction still probes an unsupported JSON Schema mode"
184
195
  );
196
+ let deepSeekActionPayload = null;
197
+ await requestNextStep(
198
+ {
199
+ chat: {
200
+ completions: {
201
+ create: async (payload) => {
202
+ deepSeekActionPayload = payload;
203
+ return {
204
+ choices: [{
205
+ message: {
206
+ role: "assistant",
207
+ content: "",
208
+ tool_calls: [{
209
+ id: "deepseek-action-patch",
210
+ type: "function",
211
+ function: {
212
+ name: "apply_patch",
213
+ arguments: JSON.stringify({
214
+ path: "service.py",
215
+ search: "old",
216
+ replace: "new",
217
+ expectedReplacements: 1,
218
+ }),
219
+ },
220
+ }],
221
+ },
222
+ }],
223
+ };
224
+ },
225
+ },
226
+ },
227
+ },
228
+ {
229
+ provider: "deepseek",
230
+ model: "deepseek-v4-pro",
231
+ reasoning: "xhigh",
232
+ goal: "Repair service.py from exact retained source.",
233
+ taskProfile: "code",
234
+ allowFileTools: true,
235
+ allowShellTool: false,
236
+ allowWebSearch: false,
237
+ allowMcpTools: false,
238
+ allowWrapperTools: false,
239
+ allowAuxiliaryTools: false,
240
+ completionFreshMutationRequired: true,
241
+ completionFreshMutationNeedsSourceRead: false,
242
+ completionFreshMutationPaths: ["service.py"],
243
+ },
244
+ [{ role: "user", content: "Emit exactly one enabled tool call that performs the next concrete action." }]
245
+ );
246
+ assert(
247
+ deepSeekActionPayload?.thinking?.type === "disabled",
248
+ "DeepSeek reasoning-only recovery did not disable a second expensive thinking pass"
249
+ );
250
+ assert(
251
+ deepSeekActionPayload?.tool_choice?.function?.name === "apply_patch",
252
+ "DeepSeek reasoning-only recovery did not force its single executable tool"
253
+ );
254
+ assert(
255
+ !Object.hasOwn(deepSeekActionPayload || {}, "reasoning_effort"),
256
+ "DeepSeek action-only recovery sent a conflicting reasoning effort"
257
+ );
185
258
 
186
259
  assert(!isPublicWebUrl("http://127.0.0.1/private"), "public URL guard accepted loopback");
187
260
  assert(!isPublicWebUrl("http://10.0.0.1/private"), "public URL guard accepted RFC1918 address");
@@ -705,6 +705,24 @@ try {
705
705
  tracebackPacket.content.includes("### tests/test_traceback_packet.py"),
706
706
  "exact same-workspace test files named by a traceback were omitted when discovery metadata was empty"
707
707
  );
708
+ const repeatedTracebackPacketState = structuredClone(tracebackPacketState);
709
+ repeatedTracebackPacketState.meta.projectVerification.testRuns[0].failureSignature =
710
+ "repeated-validator-traceback-file-recovery";
711
+ repeatedTracebackPacketState.meta.projectVerification.testRuns[0].failureSummary =
712
+ [
713
+ `File "${path.join(workspace, "duplicate-source.py")}", line 1, in first`,
714
+ `File "${path.join(workspace, "duplicate-source.py")}", line 2, in second`,
715
+ `File "${path.join(workspace, "duplicate-source.py")}", line 3, in third`,
716
+ `File "${path.join(workspace, "tests", "test_traceback_packet.py")}", line 4, in test_contract`,
717
+ ].join("\n");
718
+ const repeatedTracebackPacket = await buildFailedTestRecoveryPacket(
719
+ { commandCwd: workspace },
720
+ repeatedTracebackPacketState
721
+ );
722
+ assert(
723
+ repeatedTracebackPacket.paths.includes("tests/test_traceback_packet.py"),
724
+ "duplicate frames from one validator crowded the actual failing test out of recovery evidence"
725
+ );
708
726
  assert(
709
727
  (await failedTestRepairPatchBlock(
710
728
  duplicateRepairState,
@@ -2650,6 +2668,82 @@ try {
2650
2668
  JSON.stringify(["python3 -m unittest discover -s tests -v"]),
2651
2669
  "ordinary operator examples became mandatory acceptance commands"
2652
2670
  );
2671
+ const refreshedAcceptanceState = {
2672
+ commandCwd: workspace,
2673
+ meta: {
2674
+ projectVerification: {
2675
+ requiredOutputs: ["stale-output.txt"],
2676
+ requiredCommands: [
2677
+ "python3 service_ctl.py start --state-dir .runtime",
2678
+ "python3 service_ctl.py status --state-dir .runtime",
2679
+ ],
2680
+ acceptanceSource: "README.md",
2681
+ },
2682
+ },
2683
+ };
2684
+ recordProjectVerificationOutcome(
2685
+ refreshedAcceptanceState,
2686
+ {
2687
+ ok: true,
2688
+ toolName: "read_file",
2689
+ path: "README.md",
2690
+ content: operatorGuideMarkdown,
2691
+ },
2692
+ { commandCwd: workspace, taskProfile: "devops" }
2693
+ );
2694
+ assert(
2695
+ JSON.stringify(refreshedAcceptanceState.meta.projectVerification.requiredCommands) ===
2696
+ JSON.stringify(["python3 -m unittest discover -s tests -v"]) &&
2697
+ refreshedAcceptanceState.meta.projectVerification.requiredOutputs.length === 0,
2698
+ "rereading one authoritative source retained its stale acceptance requirements"
2699
+ );
2700
+ const resumedAcceptanceWorkspace = path.join(workspace, "resumed-acceptance");
2701
+ await fs.mkdir(resumedAcceptanceWorkspace, { recursive: true });
2702
+ await fs.writeFile(
2703
+ path.join(resumedAcceptanceWorkspace, "README.md"),
2704
+ operatorGuideMarkdown,
2705
+ "utf8"
2706
+ );
2707
+ const resumedAcceptanceState = {
2708
+ commandCwd: resumedAcceptanceWorkspace,
2709
+ goal: "Repair the service and run its tests.",
2710
+ meta: {
2711
+ goalContract: { revision: 4, currentRequest: "Repair the service and run its tests." },
2712
+ projectVerification: {
2713
+ requiredCommands: [
2714
+ "python3 service_ctl.py start --state-dir .runtime",
2715
+ "python3 service_ctl.py status --state-dir .runtime",
2716
+ ],
2717
+ acceptanceSource: "README.md",
2718
+ },
2719
+ },
2720
+ };
2721
+ resetSameTaskExecutionContract(resumedAcceptanceState, 4);
2722
+ assert(
2723
+ JSON.stringify(resumedAcceptanceState.meta.projectVerification.requiredCommands) ===
2724
+ JSON.stringify(["python3 -m unittest discover -s tests -v"]),
2725
+ "same-task resume retained stale requirements from an older README parser"
2726
+ );
2727
+ recordProjectVerificationOutcome(
2728
+ refreshedAcceptanceState,
2729
+ {
2730
+ ok: true,
2731
+ toolName: "read_file",
2732
+ path: "TASK.md",
2733
+ content: requiredCommandMarkdown,
2734
+ },
2735
+ { commandCwd: workspace, taskProfile: "devops" }
2736
+ );
2737
+ assert(
2738
+ JSON.stringify(refreshedAcceptanceState.meta.projectVerification.requiredCommands) ===
2739
+ JSON.stringify([
2740
+ "python3 -m unittest discover -s tests -v",
2741
+ "npm run check",
2742
+ "npm test",
2743
+ "python scripts/verify.py --strict",
2744
+ ]),
2745
+ "source-scoped acceptance refresh discarded a distinct authoritative source"
2746
+ );
2653
2747
  const operatorContinuedCommandMarkdown = [
2654
2748
  "# Task",
2655
2749
  "",
@@ -3769,6 +3769,67 @@ export function resetGoalScopedRuntimeState(state = {}) {
3769
3769
  return removed;
3770
3770
  }
3771
3771
 
3772
+ function refreshPersistedProjectAcceptance(state = {}) {
3773
+ const verification = state.meta?.projectVerification;
3774
+ if (!verification || typeof verification !== "object") return false;
3775
+ const priorBySource =
3776
+ verification.acceptanceBySource &&
3777
+ typeof verification.acceptanceBySource === "object" &&
3778
+ !Array.isArray(verification.acceptanceBySource)
3779
+ ? verification.acceptanceBySource
3780
+ : {};
3781
+ const sources = [
3782
+ ...Object.keys(priorBySource),
3783
+ String(verification.acceptanceSource || "").trim(),
3784
+ ].filter(Boolean);
3785
+ if (!sources.length) return false;
3786
+
3787
+ const commandCwd = path.resolve(state.commandCwd || process.cwd());
3788
+ const refreshed = {};
3789
+ let inspected = 0;
3790
+ for (const source of [...new Set(sources)].slice(-16)) {
3791
+ const sourceKey = normalizedProjectAcceptancePath(source, commandCwd);
3792
+ if (!sourceKey || !projectAcceptanceSourceIsAuthoritative(sourceKey, {
3793
+ commandCwd,
3794
+ authoritativePaths: [sourceKey],
3795
+ })) {
3796
+ continue;
3797
+ }
3798
+ const absolutePath = path.resolve(commandCwd, sourceKey);
3799
+ if (absolutePath !== commandCwd && !absolutePath.startsWith(`${commandCwd}${path.sep}`)) continue;
3800
+ inspected += 1;
3801
+ if (!fsSync.existsSync(absolutePath)) continue;
3802
+ try {
3803
+ const acceptance = projectAcceptanceFromMarkdown(
3804
+ fsSync.readFileSync(absolutePath, "utf8"),
3805
+ sourceKey,
3806
+ { commandCwd, authoritativePaths: [sourceKey] }
3807
+ );
3808
+ refreshed[sourceKey] = {
3809
+ requiredOutputs: acceptance.requiredOutputs,
3810
+ requiredCommands: acceptance.requiredCommands,
3811
+ readAt: new Date().toISOString(),
3812
+ };
3813
+ } catch {
3814
+ inspected -= 1;
3815
+ }
3816
+ }
3817
+ if (!inspected) return false;
3818
+ verification.acceptanceBySource = refreshed;
3819
+ verification.requiredOutputs = [
3820
+ ...new Set(
3821
+ Object.values(refreshed).flatMap((item) => item.requiredOutputs || [])
3822
+ ),
3823
+ ].slice(0, 64);
3824
+ verification.requiredCommands = [
3825
+ ...new Set(
3826
+ Object.values(refreshed).flatMap((item) => item.requiredCommands || [])
3827
+ ),
3828
+ ].slice(0, 24);
3829
+ verification.acceptanceRefreshedAt = new Date().toISOString();
3830
+ return true;
3831
+ }
3832
+
3772
3833
  export function resetSameTaskExecutionContract(state = {}, revision = 0) {
3773
3834
  state.meta = state.meta || {};
3774
3835
  const keys = [
@@ -3786,6 +3847,7 @@ export function resetSameTaskExecutionContract(state = {}, revision = 0) {
3786
3847
  removed.push(key);
3787
3848
  }
3788
3849
  state.plan = "";
3850
+ refreshPersistedProjectAcceptance(state);
3789
3851
  const activeRevision = Math.max(0, Number(revision || state.meta?.goalContract?.revision || 0));
3790
3852
  const currentRequest = String(state.meta?.goalContract?.currentRequest || state.goal || "").trim();
3791
3853
  const currentTurnContract = deriveScsTaskContract({
@@ -4715,8 +4777,8 @@ async function failedTestTracebackSourceDocuments(testRun = {}, config = {}) {
4715
4777
  ? path.resolve(reportedPath)
4716
4778
  : path.resolve(commandCwd, reportedPath);
4717
4779
  const line = Math.max(1, Number(match[2] || 1));
4718
- if (seen.has(`${absolutePath}:${line}`)) continue;
4719
- seen.add(`${absolutePath}:${line}`);
4780
+ if (seen.has(absolutePath)) continue;
4781
+ seen.add(absolutePath);
4720
4782
  const insideWorkspace =
4721
4783
  absolutePath === commandCwd || absolutePath.startsWith(`${commandCwd}${path.sep}`);
4722
4784
  const namedByCommand = command.includes(absolutePath);
@@ -4735,7 +4797,7 @@ async function failedTestTracebackSourceDocuments(testRun = {}, config = {}) {
4735
4797
  const raw = await fs.readFile(absolutePath, "utf8").catch(() => "");
4736
4798
  if (!raw) continue;
4737
4799
  documents.push({ absolutePath, line, raw });
4738
- if (documents.length >= 3) break;
4800
+ if (documents.length >= 5) break;
4739
4801
  }
4740
4802
  return documents;
4741
4803
  }
@@ -5381,11 +5443,9 @@ function normalizedProjectAcceptancePath(sourcePath = "", commandCwd = "") {
5381
5443
  return raw.replace(/\\/g, "/").replace(/^\.\//, "");
5382
5444
  }
5383
5445
 
5384
- export function projectAcceptanceFromMarkdown(content = "", sourcePath = "", options = {}) {
5446
+ function projectAcceptanceSourceIsAuthoritative(sourcePath = "", options = {}) {
5385
5447
  const basename = path.basename(String(sourcePath || "")).toLowerCase();
5386
- if (!/^(?:readme|task|agents?|aginti)(?:\.[^.]+)?$/.test(basename)) {
5387
- return { requiredOutputs: [], requiredCommands: [] };
5388
- }
5448
+ if (!/^(?:readme|task|agents?|aginti)(?:\.[^.]+)?$/.test(basename)) return false;
5389
5449
  const normalizedSource = normalizedProjectAcceptancePath(
5390
5450
  sourcePath,
5391
5451
  options.commandCwd
@@ -5395,10 +5455,11 @@ export function projectAcceptanceFromMarkdown(content = "", sourcePath = "", opt
5395
5455
  .map((item) => normalizedProjectAcceptancePath(item, options.commandCwd))
5396
5456
  .filter(Boolean)
5397
5457
  );
5398
- if (
5399
- normalizedSource.includes("/") &&
5400
- !authoritativePaths.has(normalizedSource)
5401
- ) {
5458
+ return !normalizedSource.includes("/") || authoritativePaths.has(normalizedSource);
5459
+ }
5460
+
5461
+ export function projectAcceptanceFromMarkdown(content = "", sourcePath = "", options = {}) {
5462
+ if (!projectAcceptanceSourceIsAuthoritative(sourcePath, options)) {
5402
5463
  return { requiredOutputs: [], requiredCommands: [] };
5403
5464
  }
5404
5465
  return {
@@ -5778,29 +5839,72 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
5778
5839
  goal: completionContractGoal(config, state),
5779
5840
  taskProfile: config.taskProfile || state.meta?.taskProfile || "auto",
5780
5841
  });
5842
+ const sourcePath = String(toolResult.path || toolResult.args?.path || "");
5843
+ const acceptanceOptions = {
5844
+ commandCwd: config.commandCwd || state.commandCwd || process.cwd(),
5845
+ authoritativePaths: [
5846
+ ...(Array.isArray(currentTaskContract.exactInputPaths)
5847
+ ? currentTaskContract.exactInputPaths
5848
+ : []),
5849
+ ...(Array.isArray(currentTaskContract.exactOutputPaths)
5850
+ ? currentTaskContract.exactOutputPaths
5851
+ : []),
5852
+ ],
5853
+ };
5781
5854
  const acceptance = projectAcceptanceFromMarkdown(
5782
5855
  toolResult.content,
5783
- toolResult.path || toolResult.args?.path || "",
5784
- {
5785
- commandCwd: config.commandCwd || state.commandCwd || process.cwd(),
5786
- authoritativePaths: [
5787
- ...(Array.isArray(currentTaskContract.exactInputPaths)
5788
- ? currentTaskContract.exactInputPaths
5789
- : []),
5790
- ...(Array.isArray(currentTaskContract.exactOutputPaths)
5791
- ? currentTaskContract.exactOutputPaths
5792
- : []),
5793
- ],
5794
- }
5856
+ sourcePath,
5857
+ acceptanceOptions
5795
5858
  );
5796
- verification.requiredOutputs = [
5797
- ...new Set([...verification.requiredOutputs, ...acceptance.requiredOutputs]),
5798
- ].slice(0, 64);
5799
- verification.requiredCommands = [
5800
- ...new Set([...verification.requiredCommands, ...acceptance.requiredCommands]),
5801
- ].slice(0, 24);
5802
- if (acceptance.requiredOutputs.length || acceptance.requiredCommands.length) {
5803
- verification.acceptanceSource = String(toolResult.path || toolResult.args?.path || "");
5859
+ if (projectAcceptanceSourceIsAuthoritative(sourcePath, acceptanceOptions)) {
5860
+ const sourceKey = normalizedProjectAcceptancePath(
5861
+ sourcePath,
5862
+ acceptanceOptions.commandCwd
5863
+ );
5864
+ const acceptanceBySource =
5865
+ verification.acceptanceBySource &&
5866
+ typeof verification.acceptanceBySource === "object" &&
5867
+ !Array.isArray(verification.acceptanceBySource)
5868
+ ? { ...verification.acceptanceBySource }
5869
+ : {};
5870
+ const legacySourceKey = normalizedProjectAcceptancePath(
5871
+ verification.acceptanceSource || "",
5872
+ acceptanceOptions.commandCwd
5873
+ );
5874
+ if (
5875
+ !Object.keys(acceptanceBySource).length &&
5876
+ legacySourceKey &&
5877
+ legacySourceKey !== sourceKey
5878
+ ) {
5879
+ acceptanceBySource[legacySourceKey] = {
5880
+ requiredOutputs: [...verification.requiredOutputs],
5881
+ requiredCommands: [...verification.requiredCommands],
5882
+ readAt: verification.acceptanceReadAt || "",
5883
+ };
5884
+ }
5885
+ acceptanceBySource[sourceKey] = {
5886
+ requiredOutputs: acceptance.requiredOutputs,
5887
+ requiredCommands: acceptance.requiredCommands,
5888
+ readAt: now,
5889
+ };
5890
+ verification.acceptanceBySource = Object.fromEntries(
5891
+ Object.entries(acceptanceBySource).slice(-16)
5892
+ );
5893
+ verification.requiredOutputs = [
5894
+ ...new Set(
5895
+ Object.values(verification.acceptanceBySource).flatMap((item) =>
5896
+ Array.isArray(item?.requiredOutputs) ? item.requiredOutputs : []
5897
+ )
5898
+ ),
5899
+ ].slice(0, 64);
5900
+ verification.requiredCommands = [
5901
+ ...new Set(
5902
+ Object.values(verification.acceptanceBySource).flatMap((item) =>
5903
+ Array.isArray(item?.requiredCommands) ? item.requiredCommands : []
5904
+ )
5905
+ ),
5906
+ ].slice(0, 24);
5907
+ verification.acceptanceSource = sourcePath;
5804
5908
  verification.acceptanceReadAt = now;
5805
5909
  }
5806
5910
  }
@@ -134,6 +134,10 @@ function chatReasoningEffort(config = {}) {
134
134
  }
135
135
 
136
136
  function withChatReasoningEffort(payload = {}, config = {}) {
137
+ if (payload.thinking?.type === "disabled") {
138
+ const { reasoning_effort: _reasoningEffort, ...rest } = payload;
139
+ return rest;
140
+ }
137
141
  const reasoning = chatReasoningEffort(config);
138
142
  if (!reasoning) {
139
143
  const { reasoning_effort: _reasoningEffort, ...rest } = payload;
@@ -336,17 +340,35 @@ function assertLocalRequestWithinContext(payload = {}, config = {}, label = "age
336
340
 
337
341
  export { hasExplicitDeepResearchIntent } from "./research-routing.js";
338
342
 
339
- export function toolChoiceForProvider(config, messages = []) {
340
- const requiresConcreteContinuation = messages.slice(-3).some(
343
+ function requiresConcreteToolContinuation(messages = []) {
344
+ return messages.slice(-3).some(
341
345
  (message) =>
342
346
  message?.role === "user" &&
343
347
  /emit exactly one enabled tool call that performs the next concrete action/i.test(String(message.content || ""))
344
348
  );
345
- // DeepSeek thinking models reject tool_choice="required" even though they
346
- // accept the same native tool schema with auto selection. The continuation
347
- // instruction remains explicit in the messages, so keep the supported
348
- // provider mode instead of turning a recoverable truncation into HTTP 400.
349
- if (requiresConcreteContinuation && normalizeProviderId(config.provider, "") !== "deepseek") {
349
+ }
350
+
351
+ function deepSeekActionOnlyRequest(config = {}, messages = []) {
352
+ return normalizeProviderId(config.provider, "") === "deepseek" &&
353
+ requiresConcreteToolContinuation(messages);
354
+ }
355
+
356
+ export function toolChoiceForProvider(config, messages = [], tools = []) {
357
+ const requiresConcreteContinuation = requiresConcreteToolContinuation(messages);
358
+ if (requiresConcreteContinuation) {
359
+ const actionable = (Array.isArray(tools) ? tools : [])
360
+ .filter((tool) => tool?.type === "function" && tool.function?.name !== "finish")
361
+ .map((tool) => String(tool.function?.name || ""))
362
+ .filter(Boolean);
363
+ if (
364
+ normalizeProviderId(config.provider, "") === "deepseek" &&
365
+ actionable.length === 1
366
+ ) {
367
+ return {
368
+ type: "function",
369
+ function: { name: actionable[0] },
370
+ };
371
+ }
350
372
  return "required";
351
373
  }
352
374
  if (config.provider !== "venice") return "auto";
@@ -2649,10 +2671,13 @@ export async function requestNextStep(client, config, messages) {
2649
2671
  const nativePayload = {
2650
2672
  model: config.model,
2651
2673
  temperature: 0,
2652
- tool_choice: toolChoiceForProvider(config, messages),
2674
+ tool_choice: toolChoiceForProvider(config, messages, requestTools),
2653
2675
  parallel_tool_calls: false,
2654
2676
  messages: prepareMessages(config, messages),
2655
2677
  tools: requestTools,
2678
+ ...(deepSeekActionOnlyRequest(config, messages)
2679
+ ? { thinking: { type: "disabled" } }
2680
+ : {}),
2656
2681
  ...(Number(config.maxOutputTokens || 0) > 0 ? { max_tokens: Number(config.maxOutputTokens) } : {}),
2657
2682
  };
2658
2683
  const textPayload = {
@@ -75,7 +75,7 @@ export const PROVIDER_CONTRACTS = Object.freeze({
75
75
  defaultModel: "deepseek-v4-flash",
76
76
  toolProtocol: "native",
77
77
  structuredOutput: "json_object",
78
- supportsReasoningEffort: false,
78
+ supportsReasoningEffort: true,
79
79
  textToolFallback: false,
80
80
  }),
81
81
  openai: Object.freeze({