@tea-agent/loop-agent 0.43.0-next.18 → 0.43.0-next.19

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 (161) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +60 -2308
  3. package/README.md +7 -0
  4. package/dist/application/task-lifecycle/advance.js +38 -7
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/commands/init.js +13 -9
  7. package/dist/commands/task-advance.js +32 -0
  8. package/dist/executors/dag-pi-executor.js +1615 -188
  9. package/dist/executors/pi-executor.js +85 -3
  10. package/dist/executors/pi-sdk-executor.js +18 -0
  11. package/dist/executors/shell-executor.js +92 -7
  12. package/dist/shared/backend-dogfood-preflight.js +47 -0
  13. package/dist/shared/dag-failure-category.js +3 -0
  14. package/dist/shared/operator/capabilities.js +180 -0
  15. package/dist/task/config-types.js +29 -0
  16. package/dist/task/contract/project.js +9 -0
  17. package/dist/task/contract/schema.js +17 -0
  18. package/dist/task/source-prepare/build-draft.js +60 -0
  19. package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
  20. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +24 -16
  21. package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
  22. package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
  23. package/dist/worker/console/chat/pi-runtime.js +86 -27
  24. package/dist/worker/console/chat/routes.js +19 -0
  25. package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
  26. package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
  27. package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
  28. package/dist/worker/console/chat/session-mode.js +7 -3
  29. package/dist/worker/console/chat/session-store.js +42 -9
  30. package/dist/worker/console/chat/tool-preview.js +115 -0
  31. package/dist/worker/console/chat/turn-order.js +13 -0
  32. package/dist/worker/console/chat/turn-process.js +32 -30
  33. package/dist/worker/console/operator-actions.js +50 -0
  34. package/dist/worker/console/prd-intake-bridge.js +54 -1
  35. package/dist/worker/console/scheduled-goal-host.js +98 -0
  36. package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
  37. package/dist/worker/console/server.js +15 -0
  38. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B_UDknhR.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
  39. package/dist/worker/console/static/assets/{arc-D6hU0drN.js → arc-eoQiMvuk.js} +1 -1
  40. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DmVikdRK.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
  41. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DydABCRg.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
  42. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DrXKC2Jl.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
  43. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
  44. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D2-qFxr7.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
  45. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ChfeF41P.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
  46. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Drwrz55x.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
  47. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CAQGErL7.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
  48. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DHO38w5U.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
  49. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-EYIN0EOI.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
  50. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CrFLKqkT.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
  51. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMchffoo.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
  52. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DaSvtKQa.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
  53. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DcW8W9Qj.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
  54. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
  55. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
  56. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BUirrMEi.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
  57. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXzO8iXR.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
  58. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DAtgaxIn.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
  59. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C1re9Noh.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
  60. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-N8Kj08J9.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
  61. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-D6BpSLs3.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
  62. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C2Vlgnl4.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
  63. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CMbqtVLS.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
  64. package/dist/worker/console/static/assets/{diagram-UB23O5K3-A770Eb-0.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
  65. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-LV2w_2pT.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
  66. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-bwqf56ah.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
  67. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BnVtHhZh.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
  68. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BGVToEqc.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
  69. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DL2l7Vne.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
  70. package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
  71. package/dist/worker/console/static/assets/{index-B_V4wvXs.js → index-Cdkvw_H6.js} +97 -97
  72. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BWIpubOx.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
  73. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DjYBD8vv.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
  74. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C0_mBaHb.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
  75. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Cy71zbX6.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
  76. package/dist/worker/console/static/assets/{linear-Dt3_w3Vn.js → linear-BDZ9riWi.js} +1 -1
  77. package/dist/worker/console/static/assets/{mermaid.core-DnNOlfEP.js → mermaid.core-7pKqYtpZ.js} +5 -5
  78. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DAbTspwq.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
  79. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-NNiM141p.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
  80. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CN80Z1Do.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
  81. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bxyfh0JU.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
  82. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BlWnC79M.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
  83. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BrEu4P_e.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
  84. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bt70igBr.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
  85. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C8DQR-r9.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
  86. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DEMinJAT.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
  87. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-DDdDBzQc.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
  88. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
  89. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-G2wcBs3T.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
  90. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
  91. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Dzn4g2gJ.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
  92. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-5v2rP9oO.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
  93. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-B4EOx7ew.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
  94. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CQ1v8eYP.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
  95. package/dist/worker/console/static/index.html +2 -2
  96. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
  97. package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
  98. package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
  99. package/dist/worker/console/static-src/operator-chat/useChatThread.js +2 -5
  100. package/dist/worker/console/workspace-context.js +11 -0
  101. package/dist/worker/observe/static/operator-chrome.js +3 -1
  102. package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
  103. package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
  104. package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
  105. package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
  106. package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
  107. package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
  108. package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
  109. package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
  110. package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
  111. package/dist/workflows/dag/dag-retry-schema.js +11 -0
  112. package/dist/workflows/dag/frontend-closeout.js +4 -2
  113. package/dist/workflows/dag/frontend-committed-facts.js +461 -0
  114. package/dist/workflows/dag/frontend-durable-tools.js +15 -3
  115. package/dist/workflows/dag/frontend-implementation-contract.js +365 -8
  116. package/dist/workflows/dag/frontend-plan-canary.js +53 -0
  117. package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
  118. package/dist/workflows/dag/frontend-plan-render.js +0 -2
  119. package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
  120. package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
  121. package/dist/workflows/dag/frontend-recovery-run.js +57 -0
  122. package/dist/workflows/dag/frontend-review-context.js +43 -70
  123. package/dist/workflows/dag/frontend-risk.js +92 -10
  124. package/dist/workflows/dag/frontend-session-budget.js +117 -3
  125. package/dist/workflows/dag/frontend-shape.js +11 -55
  126. package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
  127. package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
  128. package/dist/workflows/dag/frontend-verification-trace.js +11 -2
  129. package/dist/workflows/dag/frontend-writer-admission.js +3 -47
  130. package/dist/workflows/dag/frontend-writer-status.js +0 -23
  131. package/dist/workflows/dag/init-hybrid.js +182 -185
  132. package/dist/workflows/dag/lifecycle.js +7 -2
  133. package/dist/workflows/dag/node-execution.js +48 -13
  134. package/dist/workflows/dag/rerun-plan.js +10 -0
  135. package/dist/workflows/dag/rerun-task.js +77 -1
  136. package/dist/workflows/dag/retry-policy.js +18 -0
  137. package/dist/workflows/dag/scheduler.js +2 -4
  138. package/dist/workflows/dag/types.js +23 -0
  139. package/docs/README.md +1 -0
  140. package/docs/init-surface.manifest.json +1 -0
  141. package/docs/operations/README.md +2 -0
  142. package/docs/templates/README.md +1 -1
  143. package/docs/templates/agent-dag.schema.json +2 -2
  144. package/docs/templates/backend-test-dag.json +14 -10
  145. package/docs/templates/frontend-implementation-contract.schema.json +0 -7
  146. package/docs/templates/frontend-implementation-dag.json +8 -9
  147. package/package.json +6 -3
  148. package/skills/frontend-bounded-implement/SKILL.md +3 -4
  149. package/skills/frontend-design-review/SKILL.md +7 -12
  150. package/skills/frontend-design-review/references/review-checklist.md +7 -7
  151. package/skills/frontend-plan/SKILL.md +8 -18
  152. package/skills/frontend-plan/references/decision-contract.md +19 -26
  153. package/skills/frontend-review/SKILL.md +21 -25
  154. package/skills/frontend-review/references/review-findings.md +48 -24
  155. package/dist/worker/console/static/assets/channel-Drecd94a.js +0 -1
  156. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-8Udu0t8-.js +0 -1
  157. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-8Udu0t8-.js +0 -1
  158. package/dist/worker/console/static/assets/index-DcudonhZ.css +0 -1
  159. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CyCiS0Lt.js +0 -1
  160. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-U0lhi6N0.js +0 -8
  161. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
@@ -90,6 +90,42 @@ function resolveAllowedRoots(input) {
90
90
  return normalized.ok ? normalized.value : raw.trim();
91
91
  });
92
92
  }
93
+ function resolveInteractionIds(input) {
94
+ if (input.flags.interactionIds !== undefined) {
95
+ return input.flags.interactionIds;
96
+ }
97
+ if (input.baseDraft?.interactionIds?.length) {
98
+ return input.baseDraft.interactionIds;
99
+ }
100
+ if (input.existing?.interactionIds?.length) {
101
+ return input.existing.interactionIds;
102
+ }
103
+ return undefined;
104
+ }
105
+ function resolveCapabilityBoundary(input) {
106
+ if (input.flags.capabilityBoundary !== undefined) {
107
+ return input.flags.capabilityBoundary;
108
+ }
109
+ if (input.baseDraft?.capabilityBoundary) {
110
+ return input.baseDraft.capabilityBoundary;
111
+ }
112
+ if (input.existing?.capabilityBoundary) {
113
+ return input.existing.capabilityBoundary;
114
+ }
115
+ return undefined;
116
+ }
117
+ function resolveRequirementOwnership(input) {
118
+ if (input.flags.requirementOwnership !== undefined) {
119
+ return input.flags.requirementOwnership;
120
+ }
121
+ if (input.baseDraft?.requirementOwnership?.length) {
122
+ return input.baseDraft.requirementOwnership;
123
+ }
124
+ if (input.existing?.requirementOwnership?.length) {
125
+ return input.existing.requirementOwnership;
126
+ }
127
+ return undefined;
128
+ }
93
129
  function resolveVerifyCommands(input) {
94
130
  if (input.flags.verifyCommands !== undefined) {
95
131
  return input.flags.verifyCommands.map((cmd) => ({
@@ -178,6 +214,21 @@ export function buildPrepareDraft(input) {
178
214
  baseDraft,
179
215
  existing: existingTaskConfig,
180
216
  });
217
+ const interactionIds = resolveInteractionIds({
218
+ flags,
219
+ baseDraft,
220
+ existing: existingTaskConfig,
221
+ });
222
+ const capabilityBoundary = resolveCapabilityBoundary({
223
+ flags,
224
+ baseDraft,
225
+ existing: existingTaskConfig,
226
+ });
227
+ const requirementOwnership = resolveRequirementOwnership({
228
+ flags,
229
+ baseDraft,
230
+ existing: existingTaskConfig,
231
+ });
181
232
  const pathAssessment = assessPreparePaths({
182
233
  allowedPaths,
183
234
  forbiddenPaths: finalForbidden,
@@ -215,6 +266,15 @@ export function buildPrepareDraft(input) {
215
266
  })),
216
267
  },
217
268
  };
269
+ if (requirementOwnership !== undefined) {
270
+ draft.requirementOwnership = requirementOwnership;
271
+ }
272
+ if (capabilityBoundary !== undefined) {
273
+ draft.capabilityBoundary = capabilityBoundary;
274
+ }
275
+ if (interactionIds !== undefined && interactionIds.length > 0) {
276
+ draft.interactionIds = interactionIds;
277
+ }
218
278
  if (featureId)
219
279
  draft.featureId = featureId;
220
280
  const backendTest = resolveBackendTest(flags, baseDraft, existingTaskConfig);
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Pi-mode loop isolation for loop-agent init projections.
3
+ *
4
+ * Pi mode ("自由对话") should not auto-load the governance AGENTS.md managed
5
+ * block that `loop-agent init` writes into a target project, nor the loop-agent
6
+ * built-in skills it mirrors into `<cwd>/.agents/skills/`. Manual `/skills:name`
7
+ * invocation and third-party/custom project skills must stay available; the
8
+ * Workflow mode is untouched (isolation applied only for `sessionMode ===
9
+ * "pi"`).
10
+ *
11
+ * Everything here is a pure function over discovered resources + persisted
12
+ * auto-invocation prefs, so it is trivially unit-testable.
13
+ */
14
+ import path from "node:path";
15
+ import { skillResourceId, } from "./resource-preferences-store.js";
16
+ /** init.ts `MANAGED_BLOCK_START` / `MANAGED_BLOCK_END` (docs/templates/init-managed-agents.md). */
17
+ export const LOOP_AGENTS_MANAGED_BLOCK_START = "<!-- LOOP_AGENT_INIT_START -->";
18
+ export const LOOP_AGENTS_MANAGED_BLOCK_END = "<!-- LOOP_AGENT_INIT_END -->";
19
+ /**
20
+ * Built-in skill directory names that `loop-agent init` mirrors from the
21
+ * package's `skills/` into a target project's `.agents/skills/`.
22
+ *
23
+ * MUST be kept in sync with the repo-root `skills/` directory; a CI check
24
+ * asserts they match (see `scripts/check-loop-builtin-skills.sh`).
25
+ */
26
+ export const LOOP_BUILTIN_SKILL_DIR_NAMES = [
27
+ "agent-worker",
28
+ "ai-engineering-context",
29
+ "analyze-product-dependencies",
30
+ "analyze-product-requirements",
31
+ "browser-tools",
32
+ "code-review-core",
33
+ "codebase-scout",
34
+ "fe-test-ui-scout",
35
+ "frontend-bounded-implement",
36
+ "frontend-contract",
37
+ "frontend-design-review",
38
+ "frontend-plan",
39
+ "frontend-review",
40
+ "frontend-scout",
41
+ "frontend-verification",
42
+ "grill-me",
43
+ "grill-with-docs",
44
+ "improve-codebase-architecture",
45
+ "init-capability-evolution",
46
+ "local-jacoco-coverage",
47
+ "loop-agent",
48
+ "playwright-cli",
49
+ "playwright-cli-case-generator",
50
+ "requesting-code-review",
51
+ "systematic-debugging",
52
+ "test-driven-development",
53
+ "using-git-worktrees",
54
+ "verification-before-completion",
55
+ "webapp-testing",
56
+ ];
57
+ /* Case-insensitive set for fast membership (paths are compared lower-cased). */
58
+ const LOOP_BUILTIN_SKILL_NAMES_LOWER = new Set(LOOP_BUILTIN_SKILL_DIR_NAMES.map((name) => name.toLowerCase()));
59
+ /** Normalize to forward slashes + lower case for Windows/macOS path compares. */
60
+ function normalizeForCompare(value) {
61
+ return value.replace(/\\/g, "/").toLowerCase();
62
+ }
63
+ /**
64
+ * Strip the loop-agent init managed block (inclusive of both markers) from an
65
+ * AGENTS.md content string.
66
+ *
67
+ * Returns the content unchanged when no valid marker pair is present (missing
68
+ * either marker, or END before START), so user-authored AGENTS.md and non-loop
69
+ * roots are never touched.
70
+ */
71
+ export function stripLoopManagedBlock(content) {
72
+ const start = content.indexOf(LOOP_AGENTS_MANAGED_BLOCK_START);
73
+ if (start < 0)
74
+ return content;
75
+ const end = content.indexOf(LOOP_AGENTS_MANAGED_BLOCK_END, start + LOOP_AGENTS_MANAGED_BLOCK_START.length);
76
+ if (end < 0)
77
+ return content;
78
+ const endPos = end + LOOP_AGENTS_MANAGED_BLOCK_END.length;
79
+ const before = content.slice(0, start).trimEnd();
80
+ const after = content.slice(endPos).trimStart();
81
+ return `${before}${before && after ? "\n\n" : ""}${after}`;
82
+ }
83
+ /**
84
+ * True when a discovered skill is a loop-agent init projection: its file path
85
+ * is under `<cwd>/.agents/skills/<builtin-name>/` for a name in the builtin
86
+ * whitelist. Global user skills (`~/.agents/skills`, `~/.pi/agent/skills`) and
87
+ * third-party project skills outside the whitelist return false.
88
+ */
89
+ export function isLoopInitProjectedSkill(skill, cwd) {
90
+ if (!skill?.filePath)
91
+ return false;
92
+ const cwdNorm = normalizeForCompare(path.resolve(cwd));
93
+ const fileNorm = normalizeForCompare(path.resolve(skill.filePath));
94
+ const prefix = `${cwdNorm}/.agents/skills/`;
95
+ if (!fileNorm.startsWith(prefix))
96
+ return false;
97
+ const rest = fileNorm.slice(prefix.length);
98
+ const topSeg = rest.split("/")[0];
99
+ if (!topSeg)
100
+ return false;
101
+ return LOOP_BUILTIN_SKILL_NAMES_LOWER.has(topSeg);
102
+ }
103
+ /**
104
+ * Pi-mode default isolation over skills: for skills that are loop init
105
+ * projections, default `disableModelInvocation` to true UNLESS the user has
106
+ * EXPLICITLY tuned auto-invocation ON for that resource id in the persisted
107
+ * prefs. Explicitly ON wins over the Pi default; explicit OFF is treated as the
108
+ * Pi default. Non-loop skills pass through untouched.
109
+ */
110
+ export function applyPiModeSkillIsolation(skills, prefs, cwd) {
111
+ return skills.map((skill) => {
112
+ if (!isLoopInitProjectedSkill(skill, cwd))
113
+ return skill;
114
+ const id = skillResourceId(skill);
115
+ const pref = prefs.skills[id];
116
+ if (pref?.autoInvocationEnabled === true)
117
+ return skill;
118
+ if (skill.disableModelInvocation === true)
119
+ return skill;
120
+ return { ...skill, disableModelInvocation: true };
121
+ });
122
+ }
123
+ /**
124
+ * Build the pi-mode loop isolation resource-loader option fragments.
125
+ *
126
+ * - When `enabled` (session mode pi): returns an `agentsFilesOverride` that
127
+ * strips the loop-agent managed AGENTS.md block, and wraps the given
128
+ * `skillsOverride` so it ALSO applies Pi-mode default isolation after the
129
+ * persisted auto-invocation prefs.
130
+ * - When disabled (workflow): returns ONLY the provided `skillsOverride`
131
+ * unchanged, so workflow session behavior is byte-for-byte identical to today.
132
+ */
133
+ export function buildPiModeLoopIsolationOptions(options) {
134
+ if (!options.enabled) {
135
+ return { skillsOverride: options.skillsOverride };
136
+ }
137
+ const { cwd, readPreferences } = options;
138
+ const agentsFilesOverride = (current) => ({
139
+ agentsFiles: current.agentsFiles.map((file) => ({
140
+ ...file,
141
+ content: stripLoopManagedBlock(file.content),
142
+ })),
143
+ });
144
+ return {
145
+ agentsFilesOverride,
146
+ skillsOverride: (current) => {
147
+ const prefs = readPreferences() ?? { schemaVersion: 1, skills: {} };
148
+ const withPrefs = options.skillsOverride(current);
149
+ return {
150
+ skills: applyPiModeSkillIsolation(withPrefs.skills, prefs, cwd),
151
+ diagnostics: current.diagnostics,
152
+ };
153
+ },
154
+ };
155
+ }
@@ -3,6 +3,8 @@
3
3
  * Definitions and execute() live here; ConsolePiRuntime owns caching and
4
4
  * composition with session-bound tools.
5
5
  */
6
+ import { zodToJsonSchema } from "zod-to-json-schema";
7
+ import { scheduledGoalBookingSchema } from "../../scheduled-goal-booking.js";
6
8
  import { authorizeChatSessionTool, DEFAULT_CONSOLE_CHAT_SESSION_MODE, } from "../../session-mode.js";
7
9
  import { buildModelCallableToolSchemas } from "../../tool-adapter.js";
8
10
  /**
@@ -17,7 +19,7 @@ export async function buildCustomOperatorTools(options) {
17
19
  import("typebox"),
18
20
  import("@earendil-works/pi-coding-agent"),
19
21
  ]);
20
- const schemas = buildModelCallableToolSchemas();
22
+ const schemas = buildModelCallableToolSchemas().filter((schema) => schema.toolId !== "prepareScheduledTask");
21
23
  const ctx = options.actionContext;
22
24
  return schemas.map((schema) => {
23
25
  // TypeBox schema is built dynamically; cast through unknown to the builder
@@ -29,22 +31,28 @@ export async function buildCustomOperatorTools(options) {
29
31
  ? { description: param.description }
30
32
  : {};
31
33
  let t;
32
- switch (param.type) {
33
- case "string":
34
- t = Type.String(desc);
35
- break;
36
- case "boolean":
37
- t = Type.Boolean(desc);
38
- break;
39
- case "number":
40
- t = Type.Number(desc);
41
- break;
42
- case "array":
43
- t = Type.Array(Type.String({}), Object.keys(desc).length ? desc : {});
44
- break;
45
- default:
46
- t = Type.Object({}, { additionalProperties: true });
34
+ if (schema.toolId === "prepareScheduledGoal" && param.name === "booking") {
35
+ // Provider adapters need every nested type (not an opaque object).
36
+ // Keep the strict service schema as the single input contract.
37
+ t = zodToJsonSchema(scheduledGoalBookingSchema, { $refStrategy: "none" });
47
38
  }
39
+ else
40
+ switch (param.type) {
41
+ case "string":
42
+ t = Type.String(desc);
43
+ break;
44
+ case "boolean":
45
+ t = Type.Boolean(desc);
46
+ break;
47
+ case "number":
48
+ t = Type.Number(desc);
49
+ break;
50
+ case "array":
51
+ t = Type.Array(Type.String({}), Object.keys(desc).length ? desc : {});
52
+ break;
53
+ default:
54
+ t = Type.Object({}, { additionalProperties: true });
55
+ }
48
56
  acc[param.name] = param.required ? t : Type.Optional(t);
49
57
  return acc;
50
58
  }, {}), { additionalProperties: false });
@@ -0,0 +1,22 @@
1
+ import { zodToJsonSchema } from "zod-to-json-schema";
2
+ import { scheduledTaskRequestSchema } from "../../scheduled-goal-request.js";
3
+ import { buildModelCallableToolSchemas } from "../../tool-adapter.js";
4
+ /** Host-bound identity is not a model parameter and cannot leak across sessions. */
5
+ export async function buildScheduledGoalTools(options) {
6
+ const { Type } = await import("typebox");
7
+ const { defineTool } = await import("@earendil-works/pi-coding-agent");
8
+ const schema = buildModelCallableToolSchemas().find((tool) => tool.action === "prepareScheduledTask");
9
+ if (!schema)
10
+ throw new Error("scheduled task capability missing");
11
+ return [defineTool({
12
+ name: schema.toolId, label: "准备定时任务", description: schema.description, promptSnippet: schema.description,
13
+ parameters: Type.Object({ request: zodToJsonSchema(scheduledTaskRequestSchema, { $refStrategy: "none" }) }, { additionalProperties: false }),
14
+ async execute(callId, params) {
15
+ const { dispatchChatToolCall } = await import("../../session-store.js");
16
+ const result = await dispatchChatToolCall({ ...options.actionContext, chatSessionRef: options.sessionId }, schema.toolId, params, callId, "workflow");
17
+ const value = result.ok ? result.result : { ok: false, error: result.message, code: result.errorCode,
18
+ nextAction: "Correct the business request and retry prepareScheduledTask. Do not edit PRD/runtime files, substitute sleep/immediate execution, or claim a booking exists." };
19
+ return { isError: !result.ok, content: [{ type: "text", text: JSON.stringify(value) }], details: value };
20
+ },
21
+ })];
22
+ }
@@ -0,0 +1,195 @@
1
+ /** Model exposure only. Capability registry and dispatch remain the authority. */
2
+ import { Type } from "typebox";
3
+ import { buildModelCallableToolSchemas } from "../tool-adapter.js";
4
+ import { computeChatActiveToolNames } from "../session-mode.js";
5
+ import { filterActiveInterviewTools } from "../../interview/tools.js";
6
+ export const TOOL_EXPOSURE_ENTRY = "console-tool-exposure";
7
+ export const TOOL_CATALOG_VERSION = 1;
8
+ export const CORE_WORKFLOW_TOOLS = Object.freeze(`prepareScheduledTask scheduledGoalGet scheduledGoalList newTask importPrd sourceInstructions contractDraftSave contractShow contractValidate contractDiff taskAdvance prepareDagConfirmation prepareDagExecution runDag operationGet operationList operationEventSummary operationWait observeLink dagReport dagDoctor dagRerunPlan dagRerun prepareMutationGate standaloneTaskRerun workerTaskRetry status planList operatorCapabilities`.split(" "));
9
+ const SCHEDULED_BOOKING_READ_TOOLS = new Set(["read", "find", "ls", "grep"]);
10
+ export function isScheduledBookingTurn(text) {
11
+ return /(?:预约|定时|稍后|分钟后|小时后|schedule(?:d)?|\blater\b)/i.test(text);
12
+ }
13
+ /** Keep booking turns on typed workflow actions and local read-only inspection. */
14
+ export function pinScheduledBookingTurnTools(session) {
15
+ const allowed = session.getActiveToolNames().filter(name => CORE_WORKFLOW_TOOLS.includes(name) || SCHEDULED_BOOKING_READ_TOOLS.has(name));
16
+ session.setActiveToolsByName(allowed);
17
+ return allowed;
18
+ }
19
+ export const WORKFLOW_TOOL_GROUPS = [
20
+ { id: "scheduling", examples: ["prepareScheduledGoal"], description: "高级预约:已有 DAG/Task 的冻结预约", tools: ["prepareScheduledGoal"] },
21
+ { id: "diagnostics", examples: ["inspect", "doctor", "dagValidate"], description: "DAG 深度诊断、校验、决策、上下文与初始化检查", tools: ["inspect", "doctor", "statsContext", "spineCheck", "dagRunTask", "dagValidate", "dagPromptOverrideConfig", "dagInterruptEligibility", "dagStatus", "dagDecisionInspect", "dagDecisionValidate"] },
22
+ { id: "governance", examples: ["planCreate", "docsAudit", "closeoutTask"], description: "计划管理、文档审计、交接覆盖与任务收口", tools: ["planCreate", "planComplete", "planCheck", "docsAudit", "handoffCheck", "handoffCoverage", "coverageReport", "stats", "promoteRun", "closeoutTask", "dagCloseoutDraft"] },
23
+ { id: "knowledge", examples: ["knowledgeQuery"], description: "查询项目知识", tools: ["knowledgeQuery"] },
24
+ { id: "worker", examples: ["workerSchedulerStatus", "workerPoolDoctor", "workerReportMorning"], description: "Worker 任务池、功能审查、调度诊断与报表", tools: ["workerPoolDoctor", "workerFeatureReview", "workerFeatureDoctor", "workerTaskValidate", "workerBatchPlanReady", "workerReportMorning", "workerReportMetrics", "workerAdmissionShow", "workerSchedulerList", "workerSchedulerStatus", "workerSchedulerLedger", "workerSchedulerDoctor"] },
25
+ ];
26
+ export function configuredToolExposure(value) {
27
+ if (value === undefined || value === "")
28
+ return undefined;
29
+ if (value === "full" || value === "progressive")
30
+ return value;
31
+ throw new Error("CONSOLE_CHAT_TOOL_EXPOSURE must be full or progressive");
32
+ }
33
+ /** Fail closed on catalog drift, including duplicate or human-only entries. */
34
+ export function validateWorkflowToolCatalog() {
35
+ const expected = new Set(buildModelCallableToolSchemas().map(t => t.toolId));
36
+ const names = [...CORE_WORKFLOW_TOOLS, ...WORKFLOW_TOOL_GROUPS.flatMap(g => [...g.tools])];
37
+ if (names.length !== new Set(names).size || names.length !== expected.size || names.some(n => !expected.has(n)))
38
+ throw new Error("WORKFLOW_TOOL_CATALOG_DRIFT");
39
+ }
40
+ export const TOOL_DISCOVERY_DESCRIPTION = "Load a workflow capability missing from the visible tools. Choose target by the user's intent from the capability directory, even when no tool name was mentioned. Pass a group ID or exact tool name; omit target if unsure. After loading, call the matching typed tool in the NEXT model response. Discovery alone does not fulfill the request, execute work or grant approval. Prefer discovery over recreating a workflow with shell or file edits.";
41
+ export class WorkflowToolExposure {
42
+ override;
43
+ state;
44
+ session;
45
+ interview = false;
46
+ manager;
47
+ // Failed persistence must not let the next turn silently undo full fallback.
48
+ // The active branch must still contain this ancestor; sibling branches do not inherit it.
49
+ volatileFallback;
50
+ constructor(manager, override) {
51
+ this.override = override;
52
+ this.manager = (manager ?? {});
53
+ this.state = { version: TOOL_CATALOG_VERSION, mode: override ?? "full", groups: [] };
54
+ try {
55
+ this.restore();
56
+ }
57
+ catch {
58
+ this.state = { version: TOOL_CATALOG_VERSION, mode: "full", groups: [], fallbackReason: "TOOL_EXPOSURE_STATE_READ_FAILED" };
59
+ }
60
+ }
61
+ get mode() { return this.state.mode; }
62
+ get snapshot() { return { ...this.state, groups: [...this.state.groups] }; }
63
+ restore() {
64
+ const entries = this.manager.getBranch?.() ?? [];
65
+ const latest = entries.filter(e => e.type === "custom" && e.customType === TOOL_EXPOSURE_ENTRY).at(-1);
66
+ if (!latest) {
67
+ this.state = { version: TOOL_CATALOG_VERSION, mode: this.override ?? "full", groups: [] };
68
+ return;
69
+ }
70
+ const raw = latest.data;
71
+ if (raw?.version !== TOOL_CATALOG_VERSION || !["full", "progressive"].includes(raw.mode ?? "") || !Array.isArray(raw.groups) || raw.groups.some(id => !WORKFLOW_TOOL_GROUPS.some(g => g.id === id))) {
72
+ this.state = { version: TOOL_CATALOG_VERSION, mode: "full", groups: [], fallbackReason: "INVALID_SAVED_TOOL_EXPOSURE" };
73
+ return;
74
+ }
75
+ const mode = raw.mode === "full" && raw.fallbackReason ? "full" : (this.override ?? raw.mode);
76
+ this.state = { version: TOOL_CATALOG_VERSION, mode, groups: [...new Set(raw.groups)], ...(mode === "full" && raw.fallbackReason ? { fallbackReason: raw.fallbackReason } : {}) };
77
+ }
78
+ persist() {
79
+ if (!this.manager.appendCustomEntry || !this.manager.getBranch)
80
+ throw new Error("TOOL_EXPOSURE_PERSISTENCE_UNAVAILABLE");
81
+ const previous = this.manager.getBranch().filter(e => e.type === "custom" && e.customType === TOOL_EXPOSURE_ENTRY).at(-1)?.data;
82
+ if (JSON.stringify(previous) !== JSON.stringify(this.state))
83
+ this.manager.appendCustomEntry(TOOL_EXPOSURE_ENTRY, this.snapshot);
84
+ }
85
+ exposed() {
86
+ if (this.state.mode === "full")
87
+ return undefined;
88
+ return [...CORE_WORKFLOW_TOOLS, ...WORKFLOW_TOOL_GROUPS.filter(g => this.state.groups.includes(g.id)).flatMap(g => [...g.tools])];
89
+ }
90
+ apply() {
91
+ if (!this.session)
92
+ throw new Error("TOOL_EXPOSURE_SESSION_UNAVAILABLE");
93
+ const names = computeChatActiveToolNames(this.session, "workflow", this.exposed());
94
+ const allowed = this.interview ? filterActiveInterviewTools(names).allowed : names;
95
+ this.session.setActiveToolsByName(allowed);
96
+ // SDK silently ignores unknown names: verify actual registration, not intent.
97
+ if (this.session.getAllTools) {
98
+ const registered = new Set(this.session.getAllTools().map(t => t.name));
99
+ const actual = new Set(this.session.getActiveToolNames());
100
+ const required = this.interview ? [] : (this.exposed() ?? CORE_WORKFLOW_TOOLS);
101
+ if (required.some(name => !registered.has(name) || !actual.has(name)))
102
+ throw new Error("TOOL_EXPOSURE_REQUIRED_TOOL_MISSING");
103
+ if (allowed.some(n => registered.has(n) && !actual.has(n)) || [...actual].some(n => !allowed.includes(n)))
104
+ throw new Error("TOOL_EXPOSURE_ACTIVATION_MISMATCH");
105
+ }
106
+ }
107
+ fallback(reason) {
108
+ this.state = { version: TOOL_CATALOG_VERSION, mode: "full", groups: [], fallbackReason: reason };
109
+ this.apply(); // A failure here is fatal; never pretend full mode is active.
110
+ try {
111
+ this.persist();
112
+ this.volatileFallback = undefined;
113
+ }
114
+ catch {
115
+ let anchor;
116
+ try {
117
+ anchor = this.manager.getBranch?.().at(-1);
118
+ }
119
+ catch { /* Conservatively keep full when branch cannot be read. */ }
120
+ this.volatileFallback = { anchor, state: this.snapshot };
121
+ }
122
+ }
123
+ pin(session, interview = false) {
124
+ this.session = session;
125
+ this.interview = interview;
126
+ // Reconstruct from the active branch, including after compaction/reopen.
127
+ try {
128
+ const pending = this.volatileFallback;
129
+ const branch = this.manager.getBranch?.() ?? [];
130
+ if (pending && (!pending.anchor || branch.some(entry => entry === pending.anchor ||
131
+ (pending.anchor?.id !== undefined && entry.id === pending.anchor.id)))) {
132
+ this.state = { ...pending.state, groups: [...pending.state.groups] };
133
+ }
134
+ else {
135
+ this.volatileFallback = undefined;
136
+ this.restore();
137
+ }
138
+ validateWorkflowToolCatalog();
139
+ this.apply();
140
+ // Full mode also records the session policy when supported.
141
+ if (this.mode === "progressive" || this.manager.appendCustomEntry) {
142
+ this.persist();
143
+ this.volatileFallback = undefined;
144
+ }
145
+ }
146
+ catch {
147
+ this.fallback(this.state.fallbackReason ?? "TOOL_EXPOSURE_RESTORE_OR_ACTIVATION_FAILED");
148
+ }
149
+ }
150
+ discover(target) {
151
+ if (this.interview)
152
+ return { ok: false, code: "TOOL_DISCOVERY_INTERVIEW_DENIED" };
153
+ const catalog = WORKFLOW_TOOL_GROUPS.map(g => ({ id: g.id, description: g.description, examples: [...g.examples], active: this.mode === "full" || this.state.groups.includes(g.id) }));
154
+ if (!target)
155
+ return { ok: true, ...this.snapshot, catalog };
156
+ if (CORE_WORKFLOW_TOOLS.includes(target)) {
157
+ if (!this.session?.getActiveToolNames().includes(target))
158
+ return { ok: false, code: "TOOL_EXPOSURE_REQUIRED_TOOL_MISSING" };
159
+ return { ok: true, alreadyActive: true, tools: [target] };
160
+ }
161
+ const group = WORKFLOW_TOOL_GROUPS.find(g => g.id === target || g.tools.includes(target));
162
+ if (!group)
163
+ return { ok: false, code: "TOOL_DISCOVERY_NOT_FOUND", catalog };
164
+ if (!this.session)
165
+ return { ok: false, code: "TOOL_EXPOSURE_SESSION_UNAVAILABLE" };
166
+ const registered = new Set(this.session.getAllTools?.().map(t => t.name) ?? []);
167
+ if (group.tools.some(n => !registered.has(n))) {
168
+ this.fallback("TOOL_EXPOSURE_GROUP_UNAVAILABLE");
169
+ return { ok: false, code: "TOOL_EXPOSURE_GROUP_UNAVAILABLE", ...this.snapshot };
170
+ }
171
+ try {
172
+ this.state = { ...this.state, groups: [...new Set([...this.state.groups, group.id])] };
173
+ this.apply();
174
+ this.persist();
175
+ }
176
+ catch {
177
+ this.fallback("TOOL_EXPOSURE_LOAD_FAILED");
178
+ }
179
+ return { ok: true, ...this.snapshot, group: group.id, tools: [...group.tools], effective: "next-model-response", guidance: "Use the activated typed tools in your next response. Loading has not executed any business action." };
180
+ }
181
+ buildTool(fullTool) {
182
+ return { name: "operatorCapabilities", label: "发现工作流工具", description: TOOL_DISCOVERY_DESCRIPTION,
183
+ // One short navigation line; detailed semantics live in schema description.
184
+ promptSnippet: "工作流能力目录:按用户意图选组;所需能力未显示时先调用 operatorCapabilities(target=组ID),再调用加载后的业务工具完成请求:" + WORKFLOW_TOOL_GROUPS.map(g => `${g.id}: ${g.description} (${g.examples.join(", ")})`).join("; "),
185
+ parameters: Type.Object({ target: Type.Optional(Type.String({ maxLength: 80, description: "Group ID or exact tool name; omit to list groups." })) }, { additionalProperties: false }),
186
+ execute: async (_id, params, signal) => {
187
+ if (signal?.aborted)
188
+ throw new Error("Tool discovery aborted");
189
+ if (this.mode === "full" && !this.interview && !params.target && fullTool)
190
+ return fullTool.execute(_id, {});
191
+ const result = this.discover(params.target);
192
+ return { isError: result.ok === false, content: [{ type: "text", text: JSON.stringify(result) }], details: result };
193
+ } };
194
+ }
195
+ }