@lazyingart/agintiflow 0.20.49 → 0.20.50

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.
@@ -0,0 +1,471 @@
1
+ import { redactSensitiveText, redactValue } from "./redaction.js";
2
+
3
+ export const SCS_MODES = ["off", "on", "auto"];
4
+
5
+ const COMPLEX_AUTO_PROFILES = new Set([
6
+ "android",
7
+ "app",
8
+ "code",
9
+ "codebase",
10
+ "database",
11
+ "devops",
12
+ "github",
13
+ "ios",
14
+ "large-codebase",
15
+ "latex",
16
+ "maintenance",
17
+ "paper",
18
+ "qa",
19
+ "research",
20
+ "review",
21
+ "security",
22
+ "supervision",
23
+ "website",
24
+ ]);
25
+
26
+ const COMPLEX_AUTO_HINTS = [
27
+ /\b(complex|complicated|large|multi[- ]file|cross[- ]file|repo[- ]wide|workspace[- ]wide)\b/i,
28
+ /\b(implement|refactor|debug|failing|regression|root cause|test|build|compile|migrate)\b/i,
29
+ /\b(android|ios|gradle|xcode|docker|systemd|github|pull request|release|deploy|latex|pdf)\b/i,
30
+ /\b(supervise|monitor|long[- ]running|resume|tmux|simulator|emulator)\b/i,
31
+ ];
32
+
33
+ function compact(value = "", limit = 1200) {
34
+ const text = redactSensitiveText(String(value || "").replace(/\s+/g, " ").trim());
35
+ if (text.length <= limit) return text;
36
+ return `${text.slice(0, limit - 24)} ... [truncated]`;
37
+ }
38
+
39
+ function compactJson(value, limit = 1800) {
40
+ try {
41
+ return compact(JSON.stringify(redactValue(value), null, 2), limit);
42
+ } catch {
43
+ return compact(String(value || ""), limit);
44
+ }
45
+ }
46
+
47
+ export function normalizeScsMode(value = "off") {
48
+ const text = String(value ?? "").trim().toLowerCase();
49
+ if (["1", "true", "yes", "y", "enable", "enabled", "on", "scs"].includes(text)) return "on";
50
+ if (["auto", "smart"].includes(text)) return "auto";
51
+ if (["0", "false", "no", "n", "disable", "disabled", "off"].includes(text)) return "off";
52
+ return "off";
53
+ }
54
+
55
+ export function shouldActivateScs(mode = "off", context = {}) {
56
+ const normalized = normalizeScsMode(mode);
57
+ if (normalized === "on") return true;
58
+ if (normalized !== "auto") return false;
59
+
60
+ const profile = String(context.taskProfile || "").toLowerCase();
61
+ const goal = String(context.goal || "");
62
+ if (Number(context.complexityScore || 0) >= 3) return true;
63
+ if (COMPLEX_AUTO_PROFILES.has(profile)) return true;
64
+ return COMPLEX_AUTO_HINTS.some((hint) => hint.test(goal));
65
+ }
66
+
67
+ function fallbackPlan(goal = "") {
68
+ return [
69
+ "1. Inspect the workspace, project instructions, and relevant manifests before editing.",
70
+ "2. Make the smallest coherent implementation or research pass that satisfies the request.",
71
+ "3. Run targeted checks or document why checks are unavailable.",
72
+ "4. Finish only after concrete evidence supports the result.",
73
+ ].join("\n");
74
+ }
75
+
76
+ function parseJsonObject(content = "") {
77
+ const text = String(content || "").trim();
78
+ if (!text) return null;
79
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
80
+ const candidates = [fenced?.[1], text];
81
+ for (const candidate of candidates.filter(Boolean)) {
82
+ try {
83
+ const parsed = JSON.parse(candidate.trim());
84
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
85
+ } catch {
86
+ // Try a balanced-looking object excerpt below.
87
+ }
88
+ const start = candidate.indexOf("{");
89
+ const end = candidate.lastIndexOf("}");
90
+ if (start >= 0 && end > start) {
91
+ try {
92
+ const parsed = JSON.parse(candidate.slice(start, end + 1));
93
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
94
+ } catch {
95
+ // Fall through.
96
+ }
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+
102
+ async function callJson(client, config, messages, fallback, label) {
103
+ if (client.mock) return fallback;
104
+ let response;
105
+ try {
106
+ response = await client.chat.completions.create(
107
+ {
108
+ model: config.model,
109
+ temperature: 0,
110
+ messages,
111
+ },
112
+ {
113
+ ...(config.abortSignal ? { signal: config.abortSignal } : {}),
114
+ timeout: Number(config.modelTimeoutMs || process.env.AGINTI_MODEL_TIMEOUT_MS || 90000),
115
+ }
116
+ );
117
+ } catch (error) {
118
+ if (config.abortSignal?.aborted || error?.name === "AbortError" || error?.code === "ABORT_ERR") throw error;
119
+ return {
120
+ ...fallback,
121
+ callWarning: `${label} call failed; fallback schema used: ${compact(error instanceof Error ? error.message : String(error), 500)}`,
122
+ };
123
+ }
124
+ const content = response.choices[0]?.message?.content || "";
125
+ const parsed = parseJsonObject(content);
126
+ if (!parsed) {
127
+ return {
128
+ ...fallback,
129
+ raw: compact(content, 1200),
130
+ parserWarning: `${label} did not return strict JSON; fallback schema used.`,
131
+ };
132
+ }
133
+ return parsed;
134
+ }
135
+
136
+ function normalizeStringList(value, fallback = []) {
137
+ if (Array.isArray(value)) return value.map((item) => compact(item, 220)).filter(Boolean).slice(0, 8);
138
+ if (typeof value === "string" && value.trim()) return [compact(value, 220)];
139
+ return fallback;
140
+ }
141
+
142
+ function normalizePlanText(plan) {
143
+ if (Array.isArray(plan)) {
144
+ return plan
145
+ .map((item, index) => {
146
+ const text = compact(item, 260).replace(/^\d+[.)]\s*/, "");
147
+ return `${index + 1}. ${text}`;
148
+ })
149
+ .filter(Boolean)
150
+ .join("\n");
151
+ }
152
+ const text = String(plan || "").trim();
153
+ if (!text) return "";
154
+ const redacted = redactSensitiveText(text);
155
+ return redacted.length <= 1800 ? redacted : `${redacted.slice(0, 1776)} ... [truncated]`;
156
+ }
157
+
158
+ export function buildScsEvidencePack(state = {}, context = {}) {
159
+ const messages = Array.isArray(state.messages) ? state.messages.slice(-16) : [];
160
+ const messageSummary = messages.map((message) => {
161
+ if (message.role === "tool") {
162
+ try {
163
+ const parsed = JSON.parse(message.content || "{}");
164
+ return `tool:${parsed.toolName || message.tool_call_id || "unknown"} ok=${parsed.ok !== false} done=${Boolean(parsed.done)} ${compactJson(
165
+ {
166
+ error: parsed.error || parsed.reason || "",
167
+ stdout: parsed.stdout ? String(parsed.stdout).slice(0, 300) : "",
168
+ path: parsed.path || "",
169
+ changes: Array.isArray(parsed.changes) ? parsed.changes.map((change) => change.path).filter(Boolean) : [],
170
+ },
171
+ 600
172
+ )}`;
173
+ } catch {
174
+ return `tool:${message.tool_call_id || "unknown"} ${compact(message.content, 600)}`;
175
+ }
176
+ }
177
+ const toolCalls = Array.isArray(message.tool_calls)
178
+ ? message.tool_calls.map((call) => call.function?.name || "tool").join(",")
179
+ : "";
180
+ return `${message.role}${toolCalls ? ` tools=${toolCalls}` : ""}: ${compact(message.content, 600)}`;
181
+ });
182
+
183
+ const events = Array.isArray(context.events)
184
+ ? context.events.slice(-20).map((event) => ({
185
+ type: event.type,
186
+ timestamp: event.timestamp,
187
+ data: redactValue(event.data || {}),
188
+ }))
189
+ : [];
190
+
191
+ return compactJson(
192
+ {
193
+ goal: state.goal || context.goal || "",
194
+ taskProfile: context.taskProfile || "",
195
+ approvedPlan: state.plan || "",
196
+ scs: state.meta?.scs || null,
197
+ recentEvents: events,
198
+ recentMessages: messageSummary,
199
+ },
200
+ 7000
201
+ );
202
+ }
203
+
204
+ export function buildSupervisorInstruction(scs = {}) {
205
+ const criteria = normalizeStringList(scs.acceptanceCriteria);
206
+ const stopConditions = normalizeStringList(scs.stopConditions);
207
+ return [
208
+ "SCS mode is enabled. You are the supervisor executor.",
209
+ "Execute the approved phase plan. You may choose exact tools and paths, but you may not replace the strategic plan with a new one.",
210
+ "If tool evidence invalidates the plan, stop repeating the failed path and explain the blocker through finish or wait for student review.",
211
+ "Approved phase plan:",
212
+ scs.plan || fallbackPlan(),
213
+ criteria.length ? `Acceptance criteria:\n${criteria.map((item) => `- ${item}`).join("\n")}` : "",
214
+ stopConditions.length ? `Stop conditions:\n${stopConditions.map((item) => `- ${item}`).join("\n")}` : "",
215
+ "Before calling finish, include concrete evidence: files changed, commands/checks run, artifacts created, or a clear limitation.",
216
+ ]
217
+ .filter(Boolean)
218
+ .join("\n");
219
+ }
220
+
221
+ function normalizeCommitteePlan(parsed, goal = "") {
222
+ const plan = normalizePlanText(parsed.plan) || fallbackPlan(goal);
223
+ return {
224
+ role: "committee",
225
+ phaseGoal: compact(parsed.phase_goal || parsed.phaseGoal || goal || "Complete the requested task.", 260),
226
+ plan,
227
+ acceptanceCriteria: normalizeStringList(parsed.acceptance_criteria || parsed.acceptanceCriteria, [
228
+ "The requested outcome is present in the workspace or answer.",
229
+ "Relevant checks were run or skipped with a concrete reason.",
230
+ ]),
231
+ allowedTools: normalizeStringList(parsed.allowed_tools || parsed.allowedTools, []),
232
+ stopConditions: normalizeStringList(parsed.stop_conditions || parsed.stopConditions, [
233
+ "The same tool/path fails twice.",
234
+ "Required credentials, SDKs, or external devices are unavailable.",
235
+ ]),
236
+ };
237
+ }
238
+
239
+ function normalizeDecision(parsed, fallbackDecision = "approve_plan") {
240
+ const allowed = new Set([
241
+ "approve_plan",
242
+ "veto_plan",
243
+ "rethink_plan",
244
+ "accept_phase",
245
+ "reject_phase",
246
+ "finish_allowed",
247
+ "finish_rejected",
248
+ ]);
249
+ const decision = allowed.has(String(parsed.decision || "")) ? parsed.decision : fallbackDecision;
250
+ const confidence = Number(parsed.confidence);
251
+ return {
252
+ role: "student",
253
+ decision,
254
+ confidence: Number.isFinite(confidence) ? Math.min(Math.max(confidence, 0), 1) : 0.6,
255
+ evidence: normalizeStringList(parsed.evidence, []),
256
+ reason: compact(parsed.reason || "", 400),
257
+ nextRequiredAction: compact(parsed.next_required_action || parsed.nextRequiredAction || "", 220),
258
+ };
259
+ }
260
+
261
+ export async function createScsPlan(client, config, state, context = {}) {
262
+ const evidence = buildScsEvidencePack(state, context);
263
+ const fallbackCommittee = normalizeCommitteePlan({ plan: fallbackPlan(state.goal), phase_goal: state.goal }, state.goal);
264
+ let committee = fallbackCommittee;
265
+ let student = normalizeDecision(
266
+ {
267
+ decision: "approve_plan",
268
+ confidence: 0.55,
269
+ reason: "Fallback approval for a conservative bounded phase plan.",
270
+ next_required_action: "supervisor_execute",
271
+ },
272
+ "approve_plan"
273
+ );
274
+
275
+ for (let attempt = 1; attempt <= 2; attempt += 1) {
276
+ const rawCommittee = await callJson(
277
+ client,
278
+ config,
279
+ [
280
+ {
281
+ role: "system",
282
+ content:
283
+ "You are the SCS committee. Draft one practical next-phase plan only. You cannot approve it and you cannot call tools. Return strict JSON with keys: role, phase_goal, plan, acceptance_criteria, allowed_tools, stop_conditions.",
284
+ },
285
+ {
286
+ role: "user",
287
+ content: `Goal and evidence:\n${evidence}\n\nReturn one short phase plan as JSON. Plan must be 3-6 concrete steps.`,
288
+ },
289
+ ],
290
+ fallbackCommittee,
291
+ "SCS committee"
292
+ );
293
+ committee = normalizeCommitteePlan(rawCommittee, state.goal);
294
+
295
+ const rawStudent = await callJson(
296
+ client,
297
+ config,
298
+ [
299
+ {
300
+ role: "system",
301
+ content:
302
+ "You are the SCS student monitor. You may approve_plan or veto_plan only. Judge whether the committee phase plan is safe, concrete, and evidence-oriented. Return strict JSON with keys: role, decision, confidence, evidence, reason, next_required_action.",
303
+ },
304
+ {
305
+ role: "user",
306
+ content: `Goal/evidence:\n${evidence}\n\nCommittee plan:\n${compactJson(committee, 4000)}`,
307
+ },
308
+ ],
309
+ student,
310
+ "SCS student plan gate"
311
+ );
312
+ student = normalizeDecision(rawStudent, "approve_plan");
313
+ if (student.decision !== "veto_plan" || attempt === 2) break;
314
+ }
315
+
316
+ if (student.decision === "veto_plan") {
317
+ student.decision = "approve_plan";
318
+ student.reason = `Plan veto was capped after retries; approving conservative fallback. Last concern: ${student.reason || "unspecified"}`;
319
+ committee = fallbackCommittee;
320
+ }
321
+
322
+ const scs = {
323
+ enabled: true,
324
+ mode: config.enableScs || "on",
325
+ active: true,
326
+ model: `${config.provider}/${config.model}`,
327
+ phase: 1,
328
+ phaseGoal: committee.phaseGoal,
329
+ plan: committee.plan,
330
+ acceptanceCriteria: committee.acceptanceCriteria,
331
+ allowedTools: committee.allowedTools,
332
+ stopConditions: committee.stopConditions,
333
+ committee,
334
+ student,
335
+ finishRejects: 0,
336
+ monitorReviews: 0,
337
+ };
338
+ return {
339
+ scs,
340
+ plan: committee.plan,
341
+ supervisorInstruction: buildSupervisorInstruction(scs),
342
+ };
343
+ }
344
+
345
+ export function shouldReviewToolResult(toolResult, state = {}) {
346
+ if (!toolResult || toolResult.done) return false;
347
+ if (toolResult.ok === false || toolResult.blocked || toolResult.error || toolResult.reason) return true;
348
+ const recent = state.meta?.toolLoop?.recent || [];
349
+ const warned = state.meta?.toolLoop?.warned || [];
350
+ return warned.length > 0 && recent.some((entry) => entry.toolName === toolResult.toolName && entry.ok === false);
351
+ }
352
+
353
+ export async function reviewScsToolResult(client, config, state, toolResult, context = {}) {
354
+ const fallback = normalizeDecision(
355
+ {
356
+ decision: "rethink_plan",
357
+ confidence: 0.65,
358
+ reason: `Tool evidence needs supervisor adjustment: ${toolResult?.error || toolResult?.reason || toolResult?.toolName || "unknown"}`,
359
+ next_required_action: "supervisor_continue",
360
+ evidence: [toolResult?.toolName || "tool"],
361
+ },
362
+ "rethink_plan"
363
+ );
364
+ if ((state.meta?.scs?.monitorReviews || 0) >= 4) {
365
+ return {
366
+ ...fallback,
367
+ decision: "accept_phase",
368
+ reason: "SCS monitor cap reached; continuing under existing runtime guardrails.",
369
+ };
370
+ }
371
+
372
+ const evidence = buildScsEvidencePack(state, context);
373
+ const raw = await callJson(
374
+ client,
375
+ config,
376
+ [
377
+ {
378
+ role: "system",
379
+ content:
380
+ "You are the SCS student monitor. Review the latest failed/blocked tool evidence. Emit one decision: accept_phase, reject_phase, or rethink_plan. Do not call tools. Return strict JSON with keys: role, decision, confidence, evidence, reason, next_required_action.",
381
+ },
382
+ {
383
+ role: "user",
384
+ content: `Latest tool result:\n${compactJson(toolResult, 2500)}\n\nEvidence pack:\n${evidence}`,
385
+ },
386
+ ],
387
+ fallback,
388
+ "SCS student monitor"
389
+ );
390
+ return normalizeDecision(raw, "rethink_plan");
391
+ }
392
+
393
+ export function shouldReviewScsProgress(step, state = {}) {
394
+ if (!Number.isFinite(Number(step)) || Number(step) <= 1 || Number(step) % 4 !== 0) return false;
395
+ return (state.meta?.scs?.monitorReviews || 0) < 4;
396
+ }
397
+
398
+ export async function reviewScsProgress(client, config, state, context = {}) {
399
+ const fallback = normalizeDecision(
400
+ {
401
+ decision: "accept_phase",
402
+ confidence: 0.55,
403
+ reason: "Periodic SCS progress fallback accepted. Continue under runtime guardrails.",
404
+ evidence: ["periodic progress review"],
405
+ next_required_action: "supervisor_continue",
406
+ },
407
+ "accept_phase"
408
+ );
409
+ const evidence = buildScsEvidencePack(state, context);
410
+ const raw = await callJson(
411
+ client,
412
+ config,
413
+ [
414
+ {
415
+ role: "system",
416
+ content:
417
+ "You are the SCS student monitor. Perform a periodic progress review. Emit accept_phase if progress is coherent, rethink_plan if the plan needs adjustment, or reject_phase if the supervisor is drifting or lacks evidence. Do not call tools. Return strict JSON with keys: role, decision, confidence, evidence, reason, next_required_action.",
418
+ },
419
+ {
420
+ role: "user",
421
+ content: `Evidence pack:\n${evidence}`,
422
+ },
423
+ ],
424
+ fallback,
425
+ "SCS progress monitor"
426
+ );
427
+ return normalizeDecision(raw, "accept_phase");
428
+ }
429
+
430
+ export async function reviewScsFinish(client, config, state, result = "", context = {}) {
431
+ const fallback = normalizeDecision(
432
+ {
433
+ decision: "finish_allowed",
434
+ confidence: 0.55,
435
+ reason: "Fallback finish approval. Runtime guardrails remain authoritative.",
436
+ evidence: ["finish requested"],
437
+ next_required_action: "finish",
438
+ },
439
+ "finish_allowed"
440
+ );
441
+ if ((state.meta?.scs?.finishRejects || 0) >= 2) {
442
+ return {
443
+ ...fallback,
444
+ reason: "Finish rejection cap reached; allowing finish to prevent SCS deadlock.",
445
+ };
446
+ }
447
+
448
+ const evidence = buildScsEvidencePack(state, context);
449
+ const raw = await callJson(
450
+ client,
451
+ config,
452
+ [
453
+ {
454
+ role: "system",
455
+ content:
456
+ "You are the SCS student final gate. Decide if the supervisor has enough concrete evidence to finish. Emit finish_allowed or finish_rejected only. Be strict for coding/system tasks, but do not demand impossible checks. Return strict JSON with keys: role, decision, confidence, evidence, reason, next_required_action.",
457
+ },
458
+ {
459
+ role: "user",
460
+ content: `Proposed final result:\n${compact(result, 2200)}\n\nEvidence pack:\n${evidence}`,
461
+ },
462
+ ],
463
+ fallback,
464
+ "SCS finish gate"
465
+ );
466
+ const decision = normalizeDecision(raw, "finish_allowed");
467
+ if (!["finish_allowed", "finish_rejected"].includes(decision.decision)) {
468
+ decision.decision = decision.decision === "reject_phase" ? "finish_rejected" : "finish_allowed";
469
+ }
470
+ return decision;
471
+ }
@@ -248,7 +248,7 @@ export const TASK_PROFILES = {
248
248
  id: "aaps",
249
249
  label: "AAPS",
250
250
  prompt:
251
- "Bias toward AAPS workflows while still handling normal project work. Recognize .aaps folders and @lazyingart/aaps conventions, keep work project-local, document assumptions, and avoid secrets or publishing unless explicitly requested.",
251
+ "Bias toward AAPS workflows while still handling normal project work. Treat AAPS as a declarative large-workflow control plane and AgInTiFlow as the interactive tool/runtime backend. Prefer the built-in `/aaps` adapter or `aginti aaps ...` commands for status, init, files, validate, parse, compile, check, and dry-run before manually inventing shell commands. Keep paths project-relative, inspect aaps.project.json and active .aaps files first, avoid secrets or publishing unless explicitly requested, and verify generated workflows with `aginti aaps validate` or `aginti aaps compile ... check` before reporting success.",
252
252
  tools: ["files", "shell", "sandbox"],
253
253
  },
254
254
  github: {
@@ -432,6 +432,7 @@ export function defaultMaxStepsForProfile(value = "auto") {
432
432
  if (profile === "android") return 60;
433
433
  if (profile === "latex") return 30;
434
434
  if (profile === "supervision") return 40;
435
+ if (profile === "aaps") return 36;
435
436
  if (["devops", "security"].includes(profile)) return 36;
436
437
  if (
437
438
  [