@lazyingart/agintiflow 0.20.305 → 0.20.307

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.305",
3
+ "version": "0.20.307",
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",
@@ -33,6 +33,51 @@ const groupBriefingContract = deriveScsTaskContract({
33
33
  ].join(" "),
34
34
  taskProfile: "research",
35
35
  });
36
+ const scopedReadThenWriteRoot = fs.mkdtempSync(
37
+ path.join(os.tmpdir(), "aginti-scoped-read-then-write-")
38
+ );
39
+ const scopedReadThenWriteGoal = [
40
+ `AGINTI_EVIDENCE_SCOPE_JSON: ${JSON.stringify({
41
+ mode: "task",
42
+ request:
43
+ "Read AGENTS.md and configs/model-policy.json, then create provider-fallback-readiness.md in the task artifact directory.",
44
+ artifact_root: scopedReadThenWriteRoot,
45
+ })}`,
46
+ "Use the task artifact directory for the requested output.",
47
+ ].join("\n");
48
+ const scopedReadThenWriteContract = deriveScsTaskContract({
49
+ goal: scopedReadThenWriteGoal,
50
+ taskProfile: "auto",
51
+ });
52
+ assert.deepEqual(
53
+ scopedReadThenWriteContract.exactInputPaths,
54
+ ["AGENTS.md", "configs/model-policy.json"],
55
+ "explicit read-then-write artifact contract lost its exact source inputs"
56
+ );
57
+ assert.equal(
58
+ scopedReadThenWriteContract.requiresSourceGrounding,
59
+ true,
60
+ "explicit exact-file reads were not required before artifact completion"
61
+ );
62
+ fs.writeFileSync(
63
+ path.join(scopedReadThenWriteRoot, "provider-fallback-readiness.md"),
64
+ "# Provider fallback readiness\n",
65
+ "utf8"
66
+ );
67
+ const ungroundedScopedArtifact = evaluateScsSemanticContract(
68
+ scopedReadThenWriteContract,
69
+ { commandCwd: scopedReadThenWriteRoot, events: [], state: {} }
70
+ );
71
+ assert.equal(
72
+ ungroundedScopedArtifact.ok,
73
+ false,
74
+ "artifact completion passed without the explicitly requested source reads"
75
+ );
76
+ assert.deepEqual(
77
+ ungroundedScopedArtifact.missingSourceReads,
78
+ ["AGENTS.md", "configs/model-policy.json"],
79
+ "artifact completion did not report every unread exact input"
80
+ );
36
81
  assert.deepEqual(
37
82
  groupBriefingContract.requiredArtifactKinds.map((item) => item.id),
38
83
  ["format:.pdf"],
@@ -11,7 +11,7 @@ import {
11
11
  } from "../src/agent-runner.js";
12
12
  import { resolveRuntimeConfig } from "../src/config.js";
13
13
  import { SessionStore } from "../src/session-store.js";
14
- import { finishResultClaimsIncompleteWork } from "../src/scs-evidence.js";
14
+ import { deriveScsTaskContract, finishResultClaimsIncompleteWork } from "../src/scs-evidence.js";
15
15
 
16
16
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
17
17
  const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-truthful-completion-"));
@@ -198,6 +198,9 @@ async function runCase({
198
198
  id,
199
199
  goal,
200
200
  taskProfile = "auto",
201
+ provider = "openai",
202
+ model = "scripted-model",
203
+ routingMode = "manual",
201
204
  responses,
202
205
  allowShellTool = false,
203
206
  allowFileTools = false,
@@ -205,6 +208,9 @@ async function runCase({
205
208
  executionTier = "",
206
209
  maxOutputTokens = undefined,
207
210
  resume = false,
211
+ runtimePatch = undefined,
212
+ expectedRuntimeRevision = undefined,
213
+ providerReadinessMode = undefined,
208
214
  setup = null,
209
215
  scsActive = false,
210
216
  }) {
@@ -215,11 +221,20 @@ async function runCase({
215
221
  if (typeof setup === "function") await setup(workspace);
216
222
  const calls = [];
217
223
  const client = scriptedClient([...responses], calls);
224
+ const factoryConfigs = [];
225
+ const clientFactory = async (runtimeConfig = {}) => {
226
+ factoryConfigs.push({
227
+ provider: runtimeConfig.provider,
228
+ model: runtimeConfig.model,
229
+ });
230
+ return client;
231
+ };
232
+ clientFactory.agintiDeterministicTest = true;
218
233
  const config = resolveRuntimeConfig(
219
234
  {
220
- provider: "openai",
221
- routingMode: "manual",
222
- model: "scripted-model",
235
+ provider,
236
+ routingMode,
237
+ model,
223
238
  goal,
224
239
  taskProfile,
225
240
  executionTier,
@@ -236,9 +251,9 @@ async function runCase({
236
251
  {
237
252
  baseDir: workspace,
238
253
  packageDir: repoRoot,
239
- provider: "openai",
240
- routingMode: "manual",
241
- model: "scripted-model",
254
+ provider,
255
+ routingMode,
256
+ model,
242
257
  executionTier,
243
258
  sessionId: id,
244
259
  resume: resume ? id : "",
@@ -255,13 +270,13 @@ async function runCase({
255
270
  allowMcpTools: false,
256
271
  allowParallelScouts: false,
257
272
  enableScs: scsActive ? "auto" : "off",
258
- clientFactory: async () => client,
273
+ clientFactory,
259
274
  }
260
275
  );
261
276
  Object.assign(config, {
262
277
  apiKey: "scripted-test-only",
263
278
  resume: resume ? id : "",
264
- clientFactory: async () => client,
279
+ clientFactory,
265
280
  sessionsDir,
266
281
  projectSessionsDir,
267
282
  useDockerSandbox: false,
@@ -283,17 +298,51 @@ async function runCase({
283
298
  : undefined,
284
299
  modelTimeoutMs: 1_000,
285
300
  ...(executionTier ? { executionTier, executionPolicy: { tier: executionTier, requiresPlan: false, reason: "Scripted completion smoke." } } : {}),
301
+ ...(providerReadinessMode ? { providerReadinessMode } : {}),
302
+ ...(runtimePatch ? { runtimePatch } : {}),
303
+ ...(expectedRuntimeRevision !== undefined ? { expectedRuntimeRevision } : {}),
286
304
  });
287
305
  const result = await runAgent(config);
288
306
  const store = new SessionStore(sessionsDir, id, { projectRoot: workspace, commandCwd: workspace, projectSessionsDir });
289
307
  return {
290
308
  result,
291
309
  calls,
310
+ factoryConfigs,
292
311
  events: await store.loadEvents(),
293
312
  state: await store.loadState(),
294
313
  };
295
314
  }
296
315
 
316
+ function providerRuntimePatch(provider, model) {
317
+ return {
318
+ provider,
319
+ model,
320
+ routingMode: "manual",
321
+ routeProvider: provider,
322
+ routeModel: model,
323
+ mainProvider: provider,
324
+ mainModel: model,
325
+ spareProvider: provider,
326
+ spareModel: model,
327
+ };
328
+ }
329
+
330
+ function scopedTaskGoal(request, artifactRoot) {
331
+ return [
332
+ "User request:",
333
+ request,
334
+ "",
335
+ `AGINTI_EVIDENCE_SCOPE_JSON: ${JSON.stringify({
336
+ mode: "task",
337
+ request,
338
+ artifact_root: artifactRoot,
339
+ })}`,
340
+ "",
341
+ "Artifact contract:",
342
+ "- If no file is produced, use an empty artifacts list.",
343
+ ].join("\n");
344
+ }
345
+
297
346
  try {
298
347
  const explanation = await runCase({
299
348
  id: "ordinary-explanation",
@@ -410,6 +459,125 @@ try {
410
459
  assert.equal(quotedChatClassification.result.result, '{"intent":"generation_only","publish":false}');
411
460
  assert(!quotedChatClassification.events.some((event) => event.type === "completion.evidence_rejected"));
412
461
 
462
+ const providerSwitchSessionId = "provider-switch-resume-contract";
463
+ const providerSwitchFirstTurn = await runCase({
464
+ id: providerSwitchSessionId,
465
+ goal: "Create provider-switch-proof.md with the exact text provider switch proof.",
466
+ taskProfile: "auto",
467
+ provider: "localllm",
468
+ model: "localllm-fast",
469
+ providerReadinessMode: "deterministic-test",
470
+ allowFileTools: true,
471
+ responses: [
472
+ assistant("", [
473
+ toolCall("write-provider-switch-proof", "write_file", {
474
+ path: "provider-switch-proof.md",
475
+ mode: "create",
476
+ content: "provider switch proof\n",
477
+ }),
478
+ ]),
479
+ assistant("", [
480
+ toolCall("finish-provider-switch-proof", "finish", {
481
+ result: "Created provider-switch-proof.md.",
482
+ }),
483
+ ]),
484
+ ],
485
+ });
486
+ assert.equal(providerSwitchFirstTurn.result.stopped, undefined);
487
+ assert.equal(providerSwitchFirstTurn.state.meta?.runtimeConfig?.provider, "localllm");
488
+ assert.equal(providerSwitchFirstTurn.state.meta?.runtimeConfig?.model, "localllm-fast");
489
+
490
+ const providerSwitchSecondTurn = await runCase({
491
+ id: providerSwitchSessionId,
492
+ goal: "Resume this exact session with the default provider and return exactly: DEEPSEEK_RESUME_OK",
493
+ taskProfile: "auto",
494
+ provider: "deepseek",
495
+ model: "deepseek-v4-flash",
496
+ resume: true,
497
+ runtimePatch: providerRuntimePatch("deepseek", "deepseek-v4-flash"),
498
+ expectedRuntimeRevision: providerSwitchFirstTurn.state.meta.runtimeConfig.revision,
499
+ responses: [
500
+ assistant("", [
501
+ toolCall("finish-provider-switch-deepseek", "finish", {
502
+ result: "DEEPSEEK_RESUME_OK",
503
+ }),
504
+ ]),
505
+ ],
506
+ });
507
+ assert.equal(providerSwitchSecondTurn.result.stopped, undefined);
508
+ assert.equal(providerSwitchSecondTurn.result.result, "DEEPSEEK_RESUME_OK");
509
+ assert.equal(providerSwitchSecondTurn.state.meta?.runtimeConfig?.provider, "deepseek");
510
+ assert.equal(providerSwitchSecondTurn.state.meta?.runtimeConfig?.model, "deepseek-v4-flash");
511
+ assert.equal(
512
+ providerSwitchSecondTurn.events.filter(
513
+ (event) => event.type === "session.runtime_resolved" && event.data?.provider === "deepseek"
514
+ ).length,
515
+ 1,
516
+ "explicit DeepSeek resume did not persist a provider switch"
517
+ );
518
+
519
+ const forcedLocalRequest =
520
+ "Resume this exact session and return exactly: LOCALLLM_FORCED_RESUME_OK Do not create or modify any file.";
521
+ const forcedLocalGoal = scopedTaskGoal(
522
+ forcedLocalRequest,
523
+ path.join(tempRoot, "artifacts", "provider-switch-resume-contract")
524
+ );
525
+ const forcedLocalContract = deriveScsTaskContract({ goal: forcedLocalGoal, taskProfile: "auto" });
526
+ assert.equal(
527
+ forcedLocalContract.requiresExternalEvidence,
528
+ false,
529
+ "a forbidden file-mutation clause made a pure response-only resume require external evidence"
530
+ );
531
+ assert.deepEqual(forcedLocalContract.requiredEvidence, []);
532
+ assert.deepEqual(forcedLocalContract.exactOutputPaths, []);
533
+ assert(
534
+ forcedLocalContract.forbiddenActions.some((item) => /create or modify any file/i.test(item)),
535
+ "forbidden file mutation was not retained as a guardrail"
536
+ );
537
+
538
+ const forcedLocalResume = await runCase({
539
+ id: providerSwitchSessionId,
540
+ goal: forcedLocalGoal,
541
+ taskProfile: "auto",
542
+ provider: "localllm",
543
+ model: "localllm-fast",
544
+ providerReadinessMode: "deterministic-test",
545
+ resume: true,
546
+ runtimePatch: providerRuntimePatch("localllm", "localllm-fast"),
547
+ expectedRuntimeRevision: providerSwitchSecondTurn.state.meta.runtimeConfig.revision,
548
+ responses: [
549
+ assistant("", [
550
+ toolCall("finish-provider-switch-localllm", "finish", {
551
+ result: "LOCALLLM_FORCED_RESUME_OK",
552
+ }),
553
+ ]),
554
+ ],
555
+ });
556
+ assert.equal(forcedLocalResume.result.stopped, undefined);
557
+ assert.equal(forcedLocalResume.result.result, "LOCALLLM_FORCED_RESUME_OK");
558
+ assert.equal(forcedLocalResume.calls.length, 1);
559
+ assert.deepEqual(forcedLocalResume.factoryConfigs, [{ provider: "localllm", model: "localllm-fast" }]);
560
+ assert.equal(forcedLocalResume.state.meta?.runtimeConfig?.provider, "localllm");
561
+ assert.equal(forcedLocalResume.state.meta?.runtimeConfig?.model, "localllm-fast");
562
+ assert.equal(
563
+ forcedLocalResume.events.filter(
564
+ (event) =>
565
+ event.type === "session.runtime_resolved" &&
566
+ event.data?.provider === "localllm" &&
567
+ event.data?.model === "localllm-fast"
568
+ ).length,
569
+ 1,
570
+ "explicit LocalLLM resume did not persist the forced provider/model switch"
571
+ );
572
+ assert(
573
+ !forcedLocalResume.events.some(
574
+ (event) =>
575
+ event.type === "completion.evidence_rejected" &&
576
+ /ledger is empty/i.test(String(event.data?.reason || ""))
577
+ ),
578
+ "pure LocalLLM resume was rejected for missing external evidence"
579
+ );
580
+
413
581
  const proseOnlyAction = await runCase({
414
582
  id: "prose-only-action",
415
583
  goal: "Run pwd and report the output.",
@@ -1414,7 +1414,17 @@ function isReadOnlyReadinessTask(goal = "") {
1414
1414
 
1415
1415
  function requiresSourceGrounding(goal = "") {
1416
1416
  const text = String(goal || "");
1417
+ const positiveText = stripForbiddenLanguage(text);
1418
+ const inferredOutputs = new Set(inferExactOutputPaths(positiveText));
1419
+ const explicitSourceInputs = inferExactInputPaths(positiveText).filter(
1420
+ (item) => !inferredOutputs.has(item)
1421
+ );
1422
+ const explicitlyRequestedInputRead = Boolean(
1423
+ /\b(?:read|inspect|review|audit|consult|examine)\b/iu.test(positiveText) &&
1424
+ explicitSourceInputs.length > 0
1425
+ );
1417
1426
  return (
1427
+ explicitlyRequestedInputRead ||
1418
1428
  isReadOnlyReadinessTask(text) ||
1419
1429
  /\b(?:re-?read|read|inspect|review|audit)\b[^.\n;]{0,120}\b(?:repository|project|workspace)\b[^.\n;]{0,120}\b(?:requirements?|instructions?|implementation|source|tests?)\b/i.test(
1420
1430
  text
@@ -2049,7 +2059,8 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
2049
2059
  if (requiredGitActions.length && !requirementCategories.includes("git")) {
2050
2060
  requirementCategories.push("git");
2051
2061
  }
2052
- const requiresExternalEvidence = requirementCategories.length > 0 || requiredToolCalls.length > 0 || goalRequiresEvidence(evidenceGoal, taskProfile);
2062
+ const requiresExternalEvidence =
2063
+ requirementCategories.length > 0 || requiredToolCalls.length > 0 || goalRequiresEvidence(positiveEvidenceGoal, taskProfile);
2053
2064
  const requiredEvidence = requirementCategories.map((category) => ({
2054
2065
  id: category,
2055
2066
  category,
@@ -3580,7 +3591,10 @@ function sourceEvidencePaths({ events = [], state = {}, contract = {}, commandCw
3580
3591
  }
3581
3592
 
3582
3593
  function sourceScopeCoverage(contract = {}, { events = [], state = {}, commandCwd = process.cwd() } = {}) {
3583
- const roots = (contract.declaredSourceRoots || []).map((rawPath) => ({
3594
+ const roots = unique([
3595
+ ...(contract.declaredSourceRoots || []),
3596
+ ...(contract.exactInputPaths || []),
3597
+ ]).map((rawPath) => ({
3584
3598
  rawPath,
3585
3599
  absolutePath: resolveContractPath(commandCwd, rawPath).replace(/\/+$/, ""),
3586
3600
  }));