@tea-agent/loop-agent 0.39.0-beta.10 → 0.39.0-beta.12

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/executors/dag-pi-executor.js +93 -32
  4. package/dist/executors/pi-executor.js +4 -2
  5. package/dist/executors/pi-sdk-executor.js +46 -1
  6. package/dist/executors/pi-writer-tool-policy.js +57 -1
  7. package/dist/executors/shell-executor.js +10 -1
  8. package/dist/worker/console/chat/chat-event-store.js +13 -1
  9. package/dist/worker/console/chat/pi-runtime.js +70 -5
  10. package/dist/worker/console/chat/routes.js +99 -29
  11. package/dist/worker/console/chat/workspace-landing.js +65 -0
  12. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BAsjg7ew.js → abnfDiagram-N423BO3Z-CXj_GnSb.js} +1 -1
  13. package/dist/worker/console/static/assets/{arc-CyIlrwZw.js → arc-BZp6JAp7.js} +1 -1
  14. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Bo4tobhH.js → architectureDiagram-T3A2C74G-DfpcEuYU.js} +1 -1
  15. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Dfpow9gu.js → blockDiagram-VBNYF7ZC-_Gf0xadb.js} +1 -1
  16. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPmzG_B4.js → c4Diagram-5PPSVZJV-Ct2QPCmv.js} +1 -1
  17. package/dist/worker/console/static/assets/channel-3TxJgYaH.js +1 -0
  18. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-pKQpB1UW.js → chunk-2GRJ4B5K-BfklkzKl.js} +1 -1
  19. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-5n9YYquZ.js → chunk-2Q5K7J3B-Dy25vZJV.js} +1 -1
  20. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DP3CtA2a.js → chunk-5RXB4S5H-BFlCRZep.js} +1 -1
  21. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DYxMJPUy.js → chunk-5VM5RSS4-op3oVxIE.js} +1 -1
  22. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CS3D50Io.js → chunk-6Q2QTUOP-C0R7rzF2.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-gSdDfo5_.js → chunk-GF5L2VYU-Bt44TCGy.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-A48q0ZNt.js → chunk-JWPE2WC7-_uEE_XFx.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-ow8Mm3DY.js → chunk-KBJHAD2P-C3TOYGZ9.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CMKfLWYE.js → chunk-RYQCIY6F-Cz60oBPV.js} +1 -1
  27. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BiTBdQPX.js → chunk-XXDRQBXY-8Bik0qis.js} +1 -1
  28. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BHIkXpp3.js +1 -0
  29. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BHIkXpp3.js +1 -0
  30. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-Cm7exRz_.js → cose-bilkent-JH36ORCC-C2QIOA4a.js} +1 -1
  31. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-D9O5qGOu.js → cynefin-VYW2F7L2-CSH_yUUd.js} +1 -1
  32. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-CkvQnako.js → cynefinDiagram-MW4NZA55-BDLw2XFM.js} +1 -1
  33. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-B1rzp7CB.js → dagre-VZM6K2ZE-CcD9ZtF4.js} +1 -1
  34. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BP07Ipcc.js → diagram-7IWD3JNH-DqwtkBsI.js} +1 -1
  35. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BlPzweQb.js → diagram-B4RE2ZJO-BWVEcqsC.js} +1 -1
  36. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CxPa7n0S.js → diagram-LBJQPF4R-M-WAbtQi.js} +1 -1
  37. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CMTZs53A.js → diagram-Q27KOJAE-DKW7SixP.js} +1 -1
  38. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CxG_X52r.js → diagram-UB23O5K3-PHHPPtrj.js} +1 -1
  39. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-D8axtJM0.js → ebnfDiagram-BXEA7PRR-BQD-B4RX.js} +1 -1
  40. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DIsGzn7b.js → erDiagram-JOGREHBK-BFvULb51.js} +1 -1
  41. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CU9zkDG0.js → flowDiagram-UKHOOZJN-Bw-aXTCb.js} +1 -1
  42. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DCjTmz5P.js → ganttDiagram-PKOTCBZU-BGKw04Qx.js} +1 -1
  43. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BksdmFzg.js → gitGraphDiagram-DS77QQ5N-RqKaPR-I.js} +1 -1
  44. package/dist/worker/console/static/assets/index-B_D8rbWc.js +407 -0
  45. package/dist/worker/console/static/assets/index-BdNx6fj0.css +1 -0
  46. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-CGzzzJtL.js → infoDiagram-6WML65LV-YO5dnzrJ.js} +1 -1
  47. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BdseB80N.js → ishikawaDiagram-WSZJBQD7-Bi1VZHMq.js} +1 -1
  48. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DM9gsgha.js → journeyDiagram-NVQOT4AX-Bszls1DD.js} +1 -1
  49. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-3q-_9R2b.js → kanban-definition-27J2QSJJ-PhzeaZ09.js} +1 -1
  50. package/dist/worker/console/static/assets/{linear-B8N-Mxsd.js → linear-BsjbDoXi.js} +1 -1
  51. package/dist/worker/console/static/assets/{mermaid.core-EPIHUUsu.js → mermaid.core-0B7NnWKk.js} +5 -5
  52. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CxwO12aa.js → mindmap-definition-FAOFIHXS-BJr4Fj-q.js} +1 -1
  53. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-bPqbVX6Y.js → pegDiagram-VL7TDLO6-moC4fpGB.js} +1 -1
  54. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-KzqIvzeX.js → pieDiagram-7S7Q4E2Y-BEw37-2c.js} +1 -1
  55. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CyofpWiV.js → quadrantDiagram-CIZ2JOQS-Cq6LyasU.js} +1 -1
  56. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-cTWJvLTP.js → railroadDiagram-AXF67PYL-DUCMcK0D.js} +1 -1
  57. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BK--q27B.js → requirementDiagram-LRYGKXZP-C3upTZm7.js} +1 -1
  58. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C0SDOHXK.js → sankeyDiagram-W5VNT64P-BI_gMsCW.js} +1 -1
  59. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-Cong6YIo.js → sequenceDiagram-SI44F4Z6-YFOIRzfN.js} +1 -1
  60. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DUVkgZx8.js → sizeCapture-X5ZJPWSS-dOnB7UDD.js} +1 -1
  61. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-C7XgxuZg.js → stateDiagram-OKZ733FA-BXUniaIh.js} +1 -1
  62. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cdi6UhLa.js +1 -0
  63. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-D9Ayvwcy.js → swimlanes-SLNWSIFB-2tA4wTNu.js} +2 -2
  64. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D-RJBbb0.js +8 -0
  65. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Byb000cH.js → timeline-definition-Z64GVDOM-DO0HkJXC.js} +1 -1
  66. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DMLegPt4.js → vennDiagram-T6HMQDX7-DvIOzixv.js} +1 -1
  67. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-ChMMeOsP.js → wardleyDiagram-T6FBY63Y-DXDZ0cTj.js} +1 -1
  68. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-bEMEPisW.js → xychartDiagram-ELKLHX3M-B32Ark0D.js} +1 -1
  69. package/dist/worker/console/static/index.html +2 -2
  70. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +33 -32
  71. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  72. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  73. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  74. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +54 -56
  75. package/dist/worker/console/static-src/operator-chat/useChatStream.js +81 -27
  76. package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -25
  77. package/dist/worker/console/static-src/shell/workspace-route.js +7 -5
  78. package/dist/workflows/dag/backend-test-writer-completeness.js +7 -12
  79. package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
  80. package/dist/workflows/dag/init-hybrid.js +156 -55
  81. package/dist/workflows/dag/node-execution.js +168 -0
  82. package/dist/workflows/dag/types.js +17 -0
  83. package/docs/templates/README.md +1 -0
  84. package/docs/templates/agent-dag.schema.json +18 -0
  85. package/docs/templates/backend-test-dag.json +265 -237
  86. package/docs/templates/frontend-implementation-dag.json +89 -0
  87. package/harness.json +3 -3
  88. package/package.json +1 -1
  89. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  90. package/dist/worker/console/static/assets/channel-DSrJAn0-.js +0 -1
  91. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DeMmPl0_.js +0 -1
  92. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DeMmPl0_.js +0 -1
  93. package/dist/worker/console/static/assets/index-B-O5ChYq.css +0 -1
  94. package/dist/worker/console/static/assets/index-CfnY8AQx.js +0 -407
  95. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dzbnd5oX.js +0 -1
  96. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BY0hHk6G.js +0 -8
  97. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  98. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
@@ -238,6 +238,163 @@ async function readFrontendPlanCommittedSnapshot(runDir, nodeId) {
238
238
  const hasTerminalFact = committed.some((record) => record.fact.kind === "finalize_plan");
239
239
  return { digest, hasTerminalFact };
240
240
  }
241
+ function planInputRecord(value) {
242
+ return typeof value === "object" && value !== null && !Array.isArray(value);
243
+ }
244
+ function planInputText(value, maxChars = 240) {
245
+ if (typeof value !== "string" || value.trim().length === 0)
246
+ return undefined;
247
+ const normalized = value.trim();
248
+ return normalized.length <= maxChars
249
+ ? normalized
250
+ : `${normalized.slice(0, maxChars - 1)}…`;
251
+ }
252
+ function planInputStrings(value) {
253
+ return Array.isArray(value)
254
+ ? value.filter((item) => typeof item === "string")
255
+ : [];
256
+ }
257
+ /** Input bound for the planner evidence block; protects the model input budget. */
258
+ const FRONTEND_PLAN_INPUT_MAX_CHARS = 12_000;
259
+ const FRONTEND_PLAN_INPUT_CAP_LADDER = [
260
+ { text: 240, array: 40 },
261
+ { text: 120, array: 20 },
262
+ { text: 60, array: 10 },
263
+ { text: 24, array: 4 },
264
+ ];
265
+ /**
266
+ * Render the planner's complete-but-bounded evidence handoff from committed
267
+ * typed facts. It deliberately excludes upstream response prose and artifact
268
+ * paths: Contract and Scout have already established these facts, so Plan
269
+ * should decide and commit rather than spend another model turn reading them.
270
+ *
271
+ * The block is always valid JSON under the char bound: field texts shrink
272
+ * through a cap ladder before any fact is dropped, and the last-resort
273
+ * fallback keeps every requirement id (with `text: "(truncated)"`) while
274
+ * declaring the degradation, so the planner records targeted evidence gaps
275
+ * instead of receiving a silently corrupted tail.
276
+ */
277
+ export function renderFrontendPlanInputContext(input) {
278
+ const committedFacts = (records) => records.flatMap((record) => record.phase === "committed" && planInputRecord(record.fact)
279
+ ? [record.fact]
280
+ : []);
281
+ const contractFacts = committedFacts(input.contractRecords);
282
+ const scoutFacts = committedFacts(input.scoutRecords);
283
+ const requirements = contractFacts
284
+ .filter((fact) => fact.kind === "requirement" && fact.origin === "contract")
285
+ .map((fact) => ({
286
+ id: planInputText(fact.id, 80),
287
+ text: planInputText(fact.text),
288
+ sourceFragmentIds: planInputStrings(fact.sourceFragmentIds),
289
+ }))
290
+ .filter((fact) => fact.id !== undefined);
291
+ const targetSurface = scoutFacts
292
+ .filter((fact) => fact.kind === "target-surface" && fact.origin === "scout")
293
+ .map((fact) => ({
294
+ completeness: fact.completeness,
295
+ entrypoint: fact.entrypoint,
296
+ routeOrMount: fact.routeOrMount,
297
+ implementationPaths: fact.implementationPaths,
298
+ testPaths: fact.testPaths,
299
+ dataSource: fact.dataSource,
300
+ allowedPathConflicts: fact.allowedPathConflicts,
301
+ unresolvedPaths: fact.unresolvedPaths,
302
+ }));
303
+ const designEvidence = scoutFacts
304
+ .filter((fact) => fact.kind === "design-evidence" && fact.origin === "scout")
305
+ .map((fact) => ({
306
+ source: fact.source,
307
+ paths: fact.paths,
308
+ conflicts: fact.conflicts,
309
+ }));
310
+ const serializeAtCap = (cap) => JSON.stringify({
311
+ requirements: requirements.map((requirement) => ({
312
+ id: requirement.id,
313
+ text: planInputText(requirement.text, cap.text),
314
+ sourceFragmentIds: planInputStrings(requirement.sourceFragmentIds).slice(0, cap.array),
315
+ })),
316
+ targetSurface: targetSurface.map((surface) => ({
317
+ completeness: planInputText(surface.completeness, 32),
318
+ entrypoint: planInputText(surface.entrypoint, cap.text),
319
+ routeOrMount: planInputText(surface.routeOrMount, cap.text),
320
+ implementationPaths: planInputStrings(surface.implementationPaths).slice(0, cap.array),
321
+ testPaths: planInputStrings(surface.testPaths).slice(0, cap.array),
322
+ dataSource: planInputText(surface.dataSource, cap.text),
323
+ allowedPathConflicts: planInputStrings(surface.allowedPathConflicts).slice(0, cap.array),
324
+ unresolvedPaths: planInputStrings(surface.unresolvedPaths).slice(0, cap.array),
325
+ })),
326
+ designEvidence: designEvidence.map((evidence) => ({
327
+ source: planInputText(evidence.source, cap.text),
328
+ paths: planInputStrings(evidence.paths).slice(0, cap.array),
329
+ conflicts: planInputStrings(evidence.conflicts).slice(0, cap.array),
330
+ })),
331
+ });
332
+ let serialized = serializeAtCap(FRONTEND_PLAN_INPUT_CAP_LADDER[0]);
333
+ for (const cap of FRONTEND_PLAN_INPUT_CAP_LADDER.slice(1)) {
334
+ if (serialized.length <= FRONTEND_PLAN_INPUT_MAX_CHARS)
335
+ break;
336
+ serialized = serializeAtCap(cap);
337
+ }
338
+ if (serialized.length > FRONTEND_PLAN_INPUT_MAX_CHARS) {
339
+ // Last resort: keep every requirement id (ids are short and the plan
340
+ // prompt separately lists them) but drop their texts, shrink scout facts
341
+ // to the minimum, and declare the degradation instead of corrupting JSON.
342
+ let fallback = {
343
+ degraded: "requirement-texts-truncated",
344
+ requirements: requirements.map((requirement) => ({
345
+ id: requirement.id,
346
+ text: "(truncated)",
347
+ })),
348
+ targetSurface: targetSurface.map((surface) => ({
349
+ completeness: planInputText(surface.completeness, 32),
350
+ implementationPaths: planInputStrings(surface.implementationPaths).slice(0, FRONTEND_PLAN_INPUT_CAP_LADDER[3].array),
351
+ })),
352
+ designEvidence: [],
353
+ };
354
+ let bounded = JSON.stringify(fallback);
355
+ let keep = fallback.requirements.length;
356
+ while (bounded.length > FRONTEND_PLAN_INPUT_MAX_CHARS &&
357
+ keep > 0) {
358
+ keep = Math.max(0, Math.floor(keep / 2));
359
+ fallback = { ...fallback, requirements: fallback.requirements.slice(0, keep) };
360
+ bounded = JSON.stringify({
361
+ ...fallback,
362
+ requirementIdsTruncated: keep < requirements.length,
363
+ });
364
+ }
365
+ serialized = bounded;
366
+ }
367
+ return [
368
+ "<frontend_plan_input>",
369
+ "Committed Contract/Scout facts, compiled by the runner. Treat them as the complete planning evidence.",
370
+ serialized,
371
+ "Do not read upstream artifacts, task sources, or repository files. If this input cannot support a decision, record a genuine evidence gap.",
372
+ "</frontend_plan_input>",
373
+ ].join("\n");
374
+ }
375
+ export async function buildFrontendPlanInputContext(runDir) {
376
+ const contractFactsPath = path.join(runDir, "frontend-contract-pi", "contract-typed-facts.jsonl");
377
+ const scoutFactsPath = path.join(runDir, "frontend-scout-pi", "scout-typed-facts.jsonl");
378
+ let contractRecords;
379
+ let scoutRecords;
380
+ try {
381
+ [contractRecords, scoutRecords] = await Promise.all([
382
+ readTypedEventStoreFromJsonl(contractFactsPath),
383
+ readTypedEventStoreFromJsonl(scoutFactsPath),
384
+ ]);
385
+ }
386
+ catch (error) {
387
+ // A missing/unreadable upstream store is a broken pipeline, not an
388
+ // evidence gap; fail before spending a model turn on a prompt that
389
+ // forbids reading anything.
390
+ throw new Error(`frontend-plan-input-unavailable: cannot read committed typed facts (${error instanceof Error ? error.message : String(error)})`);
391
+ }
392
+ const committedCount = [...contractRecords, ...scoutRecords].filter((record) => record.phase === "committed").length;
393
+ if (committedCount === 0) {
394
+ throw new Error(`frontend-plan-input-unavailable: no committed Contract/Scout facts in ${contractFactsPath} / ${scoutFactsPath}`);
395
+ }
396
+ return renderFrontendPlanInputContext({ contractRecords, scoutRecords });
397
+ }
241
398
  export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep) {
242
399
  if (attemptNumber <= 1)
243
400
  return basePrompt;
@@ -938,6 +1095,17 @@ export async function executeDagNode(input) {
938
1095
  await failSkillSnapshot(error);
939
1096
  return;
940
1097
  }
1098
+ if (isFrontendPlanLadderTask(task)) {
1099
+ let planInputContext;
1100
+ try {
1101
+ planInputContext = await buildFrontendPlanInputContext(runDir);
1102
+ }
1103
+ catch (error) {
1104
+ await failBeforePrompt(error, "frontend-plan-input-unavailable");
1105
+ return;
1106
+ }
1107
+ prompt = `${prompt}\n\n${planInputContext}`;
1108
+ }
941
1109
  node.resolvedSkills = resolvedSkills;
942
1110
  await writeNodeSkillArtifacts(runDir, nodeId, resolvedSkills);
943
1111
  let model = resolveModelForTask(task, spec.executorModels);
@@ -770,6 +770,8 @@ export const dagDynamicExpansionChildTaskSchema = z.object({
770
770
  allowedPaths: z.array(z.string()).optional().default([]),
771
771
  forbiddenPaths: z.array(z.string()).optional().default([]),
772
772
  writeSet: z.array(z.string()).optional(),
773
+ /** Template-rendered strict repository-relative read allowlist. */
774
+ readSet: z.array(z.string()).optional(),
773
775
  /**
774
776
  * Materialized-child pass-through fields for bounded Pi writer children
775
777
  * (backend-test md/pytest case sharding). All optional so legacy
@@ -970,6 +972,21 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
970
972
  commandPolicy: dagCommandPolicySchema.optional(),
971
973
  writePolicy: dagWritePolicySchema.optional(),
972
974
  writeSet: z.array(z.string()).optional(),
975
+ /** Strict repository-relative read allowlist for bounded Pi nodes. */
976
+ readSet: z.array(z.string()).optional(),
977
+ /**
978
+ * Online per-attempt Pi context guard. Unlike run/map cost budgets, this
979
+ * aborts the active SDK session before an oversized conversation reaches
980
+ * the provider's context rejection boundary.
981
+ */
982
+ contextBudget: z
983
+ .object({
984
+ maxTurns: z.number().int().positive().optional(),
985
+ maxTokens: z.number().int().positive().optional(),
986
+ })
987
+ .strict()
988
+ .refine((value) => value.maxTurns !== undefined || value.maxTokens !== undefined, "contextBudget requires maxTurns or maxTokens")
989
+ .optional(),
973
990
  piStep: z.string().optional(),
974
991
  shell: dagShellConfigSchema.optional(),
975
992
  static: dagStaticConfigSchema.optional(),
@@ -38,6 +38,7 @@
38
38
 
39
39
  - `frontend-task-requirement.md`、`frontend-task-constraints.md`、`frontend-design-contract.md` — 前端任务输入与设计合同。
40
40
  - `frontend-implementation-contract.schema.json` — 前端实现合同 schema(sourceBinding v2 ledger 字段 all-or-none:任一出现则四项齐全,禁止 v1+optional 冒充 v2)。`requirements[].sourceRefs[].items` 使用合法 `$ref: #/$defs/path`(相对路径、无 `..` 逃逸、无反斜杠),模板 JSON 无非法键,运行时加载器可直接编译(AC-003)。
41
+ - `frontend-implementation-dag.json` — 自包含的前端实现 DAG 模板:契约 → 侦察 → 计划 → 设计策略/评审 → writer 准入 → 有界写入 → 验证 → 真实 diff 审查 → closeout。它不依赖 runtime builder 或其他模板;执行前必须将所有 `REPLACE/WITH/...` 路径和确定性命令替换为当前项目的具体值。
41
42
  - `frontend-test-dag.json` — frontend-test DAG 模板;其 deterministic checklist 会拒绝 alternative executable instructions,仅允许 allowlisted `playwright-cli` command。Pi generator/reviser 的 runtime writeSet 仅授权下列目标项目生成路径(不是本仓库文档索引目标):
42
43
 
43
44
  ~~~text
@@ -538,6 +538,24 @@
538
538
  "type": "array",
539
539
  "items": { "type": "string", "minLength": 1 }
540
540
  },
541
+ "readSet": {
542
+ "type": "array",
543
+ "items": { "type": "string", "minLength": 1 },
544
+ "description": "Strict repository-relative read allowlist for bounded Pi nodes."
545
+ },
546
+ "contextBudget": {
547
+ "type": "object",
548
+ "additionalProperties": false,
549
+ "description": "Online per-attempt Pi SDK conversation guard. At least one threshold is required; exhaustion aborts the active session with context-budget-exhausted partial evidence.",
550
+ "properties": {
551
+ "maxTurns": { "type": "integer", "minimum": 1 },
552
+ "maxTokens": { "type": "integer", "minimum": 1 }
553
+ },
554
+ "anyOf": [
555
+ { "required": ["maxTurns"] },
556
+ { "required": ["maxTokens"] }
557
+ ]
558
+ },
541
559
  "piStep": {
542
560
  "type": "string",
543
561
  "minLength": 1