@tea-agent/loop-agent 0.39.0-next.26 → 0.39.0-next.28

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 (115) hide show
  1. package/AGENTS.md +3 -0
  2. package/CHANGELOG.md +148 -96
  3. package/README.md +8 -4
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/cli/command-definitions.js +16 -5
  7. package/dist/cli/program.js +16 -2
  8. package/dist/cli/update/notifier.js +2 -2
  9. package/dist/cli.js +2 -1
  10. package/dist/commands/init-model-catalog.js +464 -0
  11. package/dist/commands/init-upgrade.js +265 -97
  12. package/dist/commands/init.js +428 -27
  13. package/dist/commands/inspect-next.js +8 -0
  14. package/dist/executors/dag-pi-executor.js +76 -10
  15. package/dist/executors/pi-extension-resolver.js +233 -0
  16. package/dist/executors/pi-sdk-executor.js +79 -2
  17. package/dist/executors/shell-executor.js +50 -6
  18. package/dist/executors/shell-write-guard.js +7 -0
  19. package/dist/shared/operator/capabilities.js +7 -0
  20. package/dist/shared/update/console-notifier.js +100 -0
  21. package/dist/{cli → shared}/update/npm-client.js +41 -8
  22. package/dist/{cli → shared}/update/state.js +41 -13
  23. package/dist/worker/cli.js +31 -1
  24. package/dist/worker/console/chat/routes.js +1 -0
  25. package/dist/worker/console/operator-user-error.js +1 -1
  26. package/dist/worker/console/routes.js +3 -0
  27. package/dist/worker/console/security.js +53 -5
  28. package/dist/worker/console/server.js +10 -4
  29. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-DGn9iNrd.js} +1 -1
  30. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-Dc0C2Nyv.js} +1 -1
  31. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-COnz_Bhe.js} +1 -1
  32. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-VhIP6vgf.js} +1 -1
  33. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-D74lpmlb.js} +1 -1
  34. package/dist/worker/console/static/assets/channel-DWcZjWB3.js +1 -0
  35. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BxmiogQE.js} +1 -1
  36. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-C00Oi-2I.js} +1 -1
  37. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DTrT1Vze.js} +1 -1
  38. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DdKW7-fL.js} +1 -1
  39. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-D0q1WN6w.js} +1 -1
  40. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-DBa14MSI.js} +1 -1
  41. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-DkP-pEox.js} +1 -1
  42. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-lgPogpcX.js} +1 -1
  43. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-D-apZer0.js} +1 -1
  44. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-B1T2yAIe.js} +1 -1
  45. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-IjoI3dqU.js +1 -0
  46. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-IjoI3dqU.js +1 -0
  47. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-0P6hi3jF.js} +1 -1
  48. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-CntzMSR8.js} +1 -1
  49. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-C9zmXJtG.js} +1 -1
  50. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-DfBCZqZw.js} +1 -1
  51. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-DLp-bhGL.js} +1 -1
  52. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BksDcwZm.js} +1 -1
  53. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-Bdag7DYP.js} +1 -1
  54. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CoY5uNbk.js} +1 -1
  55. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-KcgrDXHt.js} +1 -1
  56. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-CikErwJb.js} +1 -1
  57. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DqmwwzvZ.js} +1 -1
  58. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-35krh9WG.js} +1 -1
  59. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-CvTF1gJ4.js} +1 -1
  60. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BiOWWClJ.js} +1 -1
  61. package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-CqWlWE4l.js} +3 -3
  62. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-Crha5dcA.js} +1 -1
  63. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-DEtbscOZ.js} +1 -1
  64. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-D2YYzx3z.js} +1 -1
  65. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-C7x18ttQ.js} +1 -1
  66. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-CCLZkz45.js} +1 -1
  67. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-Dmy4lSGv.js} +5 -5
  68. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CmvIol59.js} +1 -1
  69. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-DVpEdirN.js} +1 -1
  70. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-cKQy456v.js} +1 -1
  71. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-bx06I38m.js} +1 -1
  72. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-BjfIVIJr.js} +1 -1
  73. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-DcuPGbKM.js} +1 -1
  74. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-DvgVeDoV.js} +1 -1
  75. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-DLBG9nm-.js} +1 -1
  76. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-D33L7FBF.js} +1 -1
  77. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-hvqD9TpS.js} +1 -1
  78. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-C6MII0D1.js +1 -0
  79. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-BtjH81Mz.js} +2 -2
  80. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D_tk0V8D.js +8 -0
  81. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-CJ47Kraf.js} +1 -1
  82. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-COVPl-wh.js} +1 -1
  83. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-CCoQJLUY.js} +1 -1
  84. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-DTWJvfAd.js} +1 -1
  85. package/dist/worker/console/static/index.html +1 -1
  86. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  87. package/dist/worker/observe/node-input.js +72 -3
  88. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  89. package/dist/worker/observe/static/views/dag-inspector.js +54 -1
  90. package/dist/worker/observe/static/views/session-timeline.js +60 -4
  91. package/dist/workflows/dag/contract-output-registry.js +15 -0
  92. package/dist/workflows/dag/failure-category.js +5 -0
  93. package/dist/workflows/dag/frontend-implementation-contract.js +145 -40
  94. package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
  95. package/dist/workflows/dag/init-hybrid.js +71 -80
  96. package/dist/workflows/dag/node-execution.js +139 -10
  97. package/dist/workflows/dag/prompt.js +15 -1
  98. package/dist/workflows/dag/retry-policy.js +91 -0
  99. package/dist/workflows/dag/structured-output-repair.js +712 -0
  100. package/dist/workflows/dag/types.js +28 -1
  101. package/dist/workflows/dag/validate.js +39 -1
  102. package/docs/init-surface.manifest.json +5 -0
  103. package/docs/operations/README.md +1 -1
  104. package/docs/templates/agent-dag.schema.json +13 -0
  105. package/docs/templates/init-managed-agents.md +13 -3
  106. package/package.json +5 -1
  107. package/scripts/next-info.mjs +356 -0
  108. package/scripts/next-publish-gate.mjs +238 -0
  109. package/scripts/release-source-binding.mjs +251 -0
  110. package/skills/loop-agent/references/command-reference.md +11 -4
  111. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  112. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  113. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  114. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  115. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * next-publish-gate: throttle main -> npm `next` publishing.
4
+ *
5
+ * Every main push used to publish a new next build. With this gate a
6
+ * push-triggered run only publishes when HEAD is at least COMMIT_THRESHOLD
7
+ * commits ahead of the commit that produced the current dist-tags.next
8
+ * build (read from the published tarball's dist/build-stamp.json).
9
+ *
10
+ * Bypasses (publish regardless of the count):
11
+ * - workflow_dispatch runs (manual trigger, e.g. right before cutting a
12
+ * release line); the workflow passes --force for those.
13
+ * - recovery: HEAD already IS the source of the current next build, so
14
+ * re-publishing would be the same version anyway.
15
+ * - unknown last-published SHA (no npm history / unreadable stamp /
16
+ * SHA not an ancestor of HEAD): fail open — a missed next build is
17
+ * cheap, permanently stuck publishing is not.
18
+ *
19
+ * Exit codes: 0 = publish, 2 = skip (gate), 1 = hard error. The workflow
20
+ * maps skip to a successful early exit so non-publishing runs are green.
21
+ */
22
+ import { execFileSync } from "node:child_process";
23
+ import { mkdtempSync, readdirSync, rmSync } from "node:fs";
24
+ import { tmpdir } from "node:os";
25
+ import { join } from "node:path";
26
+ import process from "node:process";
27
+ import { readTarballBuildStamp } from "./release-source-binding.mjs";
28
+
29
+ export const COMMIT_THRESHOLD_DEFAULT = 5;
30
+ const SKIP_EXIT_CODE = 2;
31
+
32
+ /** Run a command and return trimmed stdout; injected for tests. */
33
+ function normalizeWindowsTarArg(value) {
34
+ const match = /^([A-Za-z]):[\\/](.*)$/.exec(String(value));
35
+ if (!match) return value;
36
+ return `/${match[1].toLowerCase()}/${match[2].replaceAll("\\", "/")}`;
37
+ }
38
+
39
+ export function normalizeCommandArgs(cmd, args, platform = process.platform) {
40
+ if (platform !== "win32" || cmd !== "tar") return args;
41
+ return args.map(normalizeWindowsTarArg);
42
+ }
43
+
44
+ export function commandLaunch(cmd, args, platform = process.platform) {
45
+ if (platform === "win32" && cmd === "npm") {
46
+ return {
47
+ cmd: process.env.ComSpec || "cmd.exe",
48
+ args: ["/d", "/s", "/c", "npm", ...args],
49
+ };
50
+ }
51
+ return { cmd, args };
52
+ }
53
+
54
+ export function defaultRun(cmd, args) {
55
+ const launch = commandLaunch(cmd, normalizeCommandArgs(cmd, args));
56
+ return execFileSync(launch.cmd, launch.args, {
57
+ encoding: "utf8",
58
+ stdio: ["ignore", "pipe", "pipe"],
59
+ });
60
+ }
61
+
62
+ export function npmViewDistTags(run = defaultRun) {
63
+ const out = run("npm", [
64
+ "view",
65
+ "@tea-agent/loop-agent",
66
+ "dist-tags",
67
+ "--json",
68
+ ]);
69
+ const trimmed = String(out ?? "").trim();
70
+ if (!trimmed) return {};
71
+ let parsed;
72
+ try {
73
+ parsed = JSON.parse(trimmed);
74
+ } catch {
75
+ return {};
76
+ }
77
+ return parsed && typeof parsed === "object" ? parsed : {};
78
+ }
79
+
80
+ /** Download the exact published next version and read its build stamp. */
81
+ export function defaultStampProbe(version) {
82
+ const dir = mkdtempSync(join(tmpdir(), "loop-agent-next-gate-"));
83
+ try {
84
+ defaultRun("npm", [
85
+ "pack",
86
+ `@tea-agent/loop-agent@${version}`,
87
+ "--pack-destination",
88
+ dir,
89
+ ]);
90
+ const file = readdirSync(dir).find((entry) => entry.endsWith(".tgz"));
91
+ if (!file) return null;
92
+ try {
93
+ return readTarballBuildStamp(join(dir, file), defaultRun);
94
+ } catch {
95
+ return null;
96
+ }
97
+ } catch {
98
+ return null;
99
+ } finally {
100
+ rmSync(dir, { recursive: true, force: true });
101
+ }
102
+ }
103
+
104
+ export function isAncestor(sha, head, run = defaultRun) {
105
+ try {
106
+ run("git", ["merge-base", "--is-ancestor", sha, head]);
107
+ return true;
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Pure decision. Inputs:
115
+ * - threshold: minimum commits ahead of lastPublishedSha to publish
116
+ * - aheadBy: commits between lastPublishedSha and headSha (null = unknown)
117
+ * - lastPublishedSha / headSha: for the recovery equality check
118
+ * Returns { publish, reason, aheadBy }.
119
+ */
120
+ export function shouldPublishNext({
121
+ threshold = COMMIT_THRESHOLD_DEFAULT,
122
+ lastPublishedSha,
123
+ headSha,
124
+ aheadBy,
125
+ force = false,
126
+ }) {
127
+ if (force) {
128
+ return { publish: true, reason: "force-bypass", aheadBy };
129
+ }
130
+ if (!headSha) {
131
+ return { publish: true, reason: "head-unknown-fail-open", aheadBy };
132
+ }
133
+ if (!lastPublishedSha) {
134
+ return {
135
+ publish: true,
136
+ reason: "last-published-sha-unknown-fail-open",
137
+ aheadBy,
138
+ };
139
+ }
140
+ const last = String(lastPublishedSha).toLowerCase();
141
+ const head = String(headSha).toLowerCase();
142
+ if (last === head) {
143
+ return { publish: true, reason: "same-commit-recovery", aheadBy: 0 };
144
+ }
145
+ if (aheadBy === null || aheadBy === undefined || !Number.isFinite(aheadBy)) {
146
+ return { publish: true, reason: "ahead-unknown-fail-open", aheadBy: null };
147
+ }
148
+ const normalizedThreshold = Number(threshold);
149
+ if (!Number.isFinite(normalizedThreshold) || normalizedThreshold <= 0) {
150
+ return { publish: true, reason: "threshold-invalid-fail-open", aheadBy };
151
+ }
152
+ if (aheadBy >= normalizedThreshold) {
153
+ return { publish: true, reason: "ahead-threshold-met", aheadBy };
154
+ }
155
+ return { publish: false, reason: "below-threshold-skip", aheadBy };
156
+ }
157
+
158
+ function parseArgs(argv) {
159
+ return Object.fromEntries(
160
+ argv
161
+ .filter((arg) => arg.startsWith("--"))
162
+ .map((arg) => {
163
+ const [key, ...value] = arg.replace(/^--/, "").split("=");
164
+ return [key, value.join("=") || "true"];
165
+ }),
166
+ );
167
+ }
168
+
169
+ function main() {
170
+ const args = parseArgs(process.argv.slice(2));
171
+ const threshold = Number(args.threshold || COMMIT_THRESHOLD_DEFAULT);
172
+ const distTags = npmViewDistTags();
173
+ const currentNext = distTags.next ? String(distTags.next) : null;
174
+ let lastPublishedSha = null;
175
+ let currentNextNote = "no dist-tags.next";
176
+ if (currentNext) {
177
+ const stamp = defaultStampProbe(currentNext);
178
+ if (stamp && stamp.gitSha) {
179
+ lastPublishedSha = stamp.gitSha;
180
+ currentNextNote = `next=${currentNext} from ${stamp.gitSha.slice(0, 10)}`;
181
+ } else {
182
+ currentNextNote = `next=${currentNext} (stamp unreadable)`;
183
+ }
184
+ }
185
+ let headSha = null;
186
+ let aheadBy = null;
187
+ try {
188
+ headSha = defaultRun("git", ["rev-parse", "HEAD"]).trim();
189
+ } catch {
190
+ headSha = null;
191
+ }
192
+ if (headSha && lastPublishedSha) {
193
+ if (!isAncestor(lastPublishedSha, headSha)) {
194
+ aheadBy = null;
195
+ } else {
196
+ aheadBy = Number(
197
+ defaultRun("git", [
198
+ "rev-list",
199
+ "--count",
200
+ `${lastPublishedSha}..${headSha}`,
201
+ ]).trim(),
202
+ );
203
+ }
204
+ }
205
+ const decision = shouldPublishNext({
206
+ threshold,
207
+ lastPublishedSha,
208
+ headSha,
209
+ aheadBy,
210
+ force: args.force === "true",
211
+ });
212
+ process.stdout.write(
213
+ `next-publish-gate: ${currentNextNote}\n` +
214
+ `next-publish-gate: head=${headSha ? headSha.slice(0, 10) : "unknown"} ` +
215
+ `aheadBy=${aheadBy === null ? "unknown" : aheadBy} ` +
216
+ `threshold=${threshold}\n` +
217
+ `next-publish-gate: ${decision.publish ? "PUBLISH" : "SKIP"} ` +
218
+ `(${decision.reason})\n`,
219
+ );
220
+ process.exit(decision.publish ? 0 : SKIP_EXIT_CODE);
221
+ }
222
+
223
+ // CLI 入口安全判断(argv[1] 非合法 file: URL 时 new URL 会抛异常)
224
+ function scriptEntryHref(argv1) {
225
+ try {
226
+ return argv1 ? new URL(`file://${argv1}`).href : "";
227
+ } catch {
228
+ return "";
229
+ }
230
+ }
231
+
232
+ const invokedAsScript =
233
+ Boolean(process.argv[1]) &&
234
+ import.meta.url === scriptEntryHref(process.argv[1]);
235
+
236
+ if (invokedAsScript) {
237
+ main();
238
+ }
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * release-source-binding: resolve the exact source SHA behind a selected next
4
+ * build, and validate cut-release-line preconditions.
5
+ *
6
+ * The next tarball carries dist/build-stamp.json { version, gitSha }. A release
7
+ * line must be cut from THAT commit — never from a floating main HEAD at
8
+ * workflow runtime. This module provides:
9
+ *
10
+ * - resolveSourceBinding: given a next version, return its gitSha (from the
11
+ * registry tarball build stamp) and validated facts.
12
+ * - validateCutPreconditions: line monotonicity, branch absence, no parallel
13
+ * un-promoted train, source sha on main.
14
+ */
15
+ import { execFileSync } from "node:child_process";
16
+ import process from "node:process";
17
+
18
+ export const NEXT_TARGET_ENV = "LOOP_AGENT_NEXT_TARGET";
19
+
20
+ const FORMAL_LINE_RE = /^\d+\.\d+$/;
21
+
22
+ /**
23
+ * Read build stamp fields from a packed tarball without installing it.
24
+ * @param {string} tarballPath
25
+ * @param {(cmd: string, args: string[]) => string} [run]
26
+ */
27
+ export function readTarballBuildStamp(tarballPath, run = runCommand) {
28
+ const listing = run("tar", ["-tzf", tarballPath]);
29
+ const stampEntry = listing
30
+ .split("\n")
31
+ .map((line) => line.trim())
32
+ .find((line) => /\/dist\/build-stamp\.json$/.test(line));
33
+ if (!stampEntry) {
34
+ throw new Error(`tarball ${tarballPath} does not contain dist/build-stamp.json`);
35
+ }
36
+ const stampJson = run("tar", ["-xzf", tarballPath, "-O", stampEntry]);
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(stampJson);
40
+ } catch (error) {
41
+ throw new Error(
42
+ `build stamp is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
43
+ );
44
+ }
45
+ if (typeof parsed.gitSha !== "string" || !/^[0-9a-f]{7,40}$/i.test(parsed.gitSha)) {
46
+ throw new Error("build stamp gitSha missing or malformed");
47
+ }
48
+ return { gitSha: parsed.gitSha, version: parsed.version ?? null };
49
+ }
50
+
51
+ function runCommand(cmd, args) {
52
+ return execFileSync(cmd, args, {
53
+ encoding: "utf8",
54
+ stdio: ["ignore", "pipe", "pipe"],
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Download an exact next version tarball (without install scripts) into a
60
+ * directory and return its path.
61
+ */
62
+ export function downloadNpmTarball(version, destDir, run = runCommand) {
63
+ run("npm", [
64
+ "pack",
65
+ `@tea-agent/loop-agent@${version}`,
66
+ "--pack-destination",
67
+ destDir,
68
+ ]);
69
+ const listing = run("ls", [destDir]);
70
+ const file = listing
71
+ .split("\n")
72
+ .map((line) => line.trim())
73
+ .find((line) => line.endsWith(".tgz"));
74
+ if (!file) throw new Error(`npm pack did not produce a tarball in ${destDir}`);
75
+ return `${destDir}/${file}`;
76
+ }
77
+
78
+ /**
79
+ * Pure checks for cutting a release line.
80
+ *
81
+ * @param {object} input
82
+ * @param {string} input.selectedNextVersion e.g. "0.37.0-next.42"
83
+ * @param {string} input.releaseLine e.g. "0.37"
84
+ * @param {string} input.sourceSha commit the next tarball was built from
85
+ * @param {string[]} input.remoteBranches remote release branch names (e.g. ["release/0.36"])
86
+ * @param {string|null} [input.latestFormalVersion] current npm latest (for reference in results)
87
+ * @param {(sha: string) => boolean} [input.shaOnMain] whether sha is an ancestor of origin/main
88
+ * @returns {{ok: true} | {ok: false, reason: string, detail?: string}}
89
+ */
90
+ export function validateCutPreconditions(input) {
91
+ const nextMatch = /^(\d+)\.(\d+)\.(0|[1-9]\d*)-next\.([0-9]\d*)$/.exec(
92
+ String(input.selectedNextVersion ?? ""),
93
+ );
94
+ if (!nextMatch) {
95
+ return {
96
+ ok: false,
97
+ reason: "invalid-selected-next",
98
+ detail: `selectedNextVersion=${input.selectedNextVersion}`,
99
+ };
100
+ }
101
+ const nextLine = `${nextMatch[1]}.${nextMatch[2]}`;
102
+ const patch = nextMatch[3];
103
+ if (patch !== "0") {
104
+ return {
105
+ ok: false,
106
+ reason: "selected-next-not-minor-zero",
107
+ detail: `next target patch must be 0, got ${patch}`,
108
+ };
109
+ }
110
+ if (!FORMAL_LINE_RE.test(String(input.releaseLine ?? ""))) {
111
+ return {
112
+ ok: false,
113
+ reason: "invalid-release-line",
114
+ detail: `releaseLine=${input.releaseLine}`,
115
+ };
116
+ }
117
+ if (input.releaseLine !== nextLine) {
118
+ return {
119
+ ok: false,
120
+ reason: "line-mismatch",
121
+ detail: `selected next ${nextLine} vs release line ${input.releaseLine}`,
122
+ };
123
+ }
124
+ if (!/^[0-9a-f]{7,40}$/i.test(String(input.sourceSha ?? ""))) {
125
+ return {
126
+ ok: false,
127
+ reason: "invalid-source-sha",
128
+ detail: `sourceSha=${input.sourceSha}`,
129
+ };
130
+ }
131
+ const branches = Array.isArray(input.remoteBranches)
132
+ ? input.remoteBranches
133
+ : [];
134
+ const existing = branches.find((b) => b === `release/${input.releaseLine}`);
135
+ if (existing) {
136
+ return {
137
+ ok: false,
138
+ reason: "release-branch-exists",
139
+ detail: existing,
140
+ };
141
+ }
142
+ const existingLines = branches
143
+ .map((b) => /^release\/(\d+)\.(\d+)$/.exec(b))
144
+ .filter(Boolean)
145
+ .map((m) => ({ major: Number(m[1]), minor: Number(m[2]), name: m[0] }));
146
+ const [cutMajor, cutMinor] = input.releaseLine.split(".").map(Number);
147
+ const higher = existingLines.find(
148
+ (l) => l.major > cutMajor || (l.major === cutMajor && l.minor > cutMinor),
149
+ );
150
+ if (higher) {
151
+ return {
152
+ ok: false,
153
+ reason: "line-not-monotonic",
154
+ detail: `higher release branch exists: ${higher.name}`,
155
+ };
156
+ }
157
+ if (input.shaOnMain !== undefined && input.shaOnMain(input.sourceSha) === false) {
158
+ return {
159
+ ok: false,
160
+ reason: "source-sha-not-on-main",
161
+ detail: input.sourceSha,
162
+ };
163
+ }
164
+ return { ok: true };
165
+ }
166
+
167
+ /**
168
+ * Resolve the next target line from env override (LOOP_AGENT_NEXT_TARGET).
169
+ * Returns null when unset/invalid — callers decide the fallback derivation.
170
+ */
171
+ export function nextTargetFromEnv(env = process.env) {
172
+ const raw = env[NEXT_TARGET_ENV];
173
+ if (!raw) return null;
174
+ return /^\d+\.\d+\.0$/.test(raw) ? raw : null;
175
+ }
176
+
177
+ // CLI 入口安全判断(argv[1] 非合法 file: URL 时 new URL 会抛异常)
178
+ function scriptEntryHref(argv1) {
179
+ try {
180
+ return argv1 ? new URL(`file://${argv1}`).href : "";
181
+ } catch {
182
+ return "";
183
+ }
184
+ }
185
+
186
+ const invokedAsScript =
187
+ Boolean(process.argv[1]) && import.meta.url === scriptEntryHref(process.argv[1]);
188
+
189
+ if (invokedAsScript) {
190
+ const args = Object.fromEntries(
191
+ process.argv
192
+ .filter((arg) => arg.startsWith("--"))
193
+ .map((arg) => {
194
+ const [key, ...value] = arg.replace(/^--/, "").split("=");
195
+ return [key, value.join("=") || "true"];
196
+ }),
197
+ );
198
+ if (args["self-test"]) {
199
+ const scenario = String(args["self-test"]);
200
+ const scenarios = {
201
+ "cut-ok": () =>
202
+ validateCutPreconditions({
203
+ selectedNextVersion: "0.37.0-next.42",
204
+ releaseLine: "0.37",
205
+ sourceSha: "abc1234567890abcdef",
206
+ remoteBranches: ["release/0.36"],
207
+ }),
208
+ "cut-exists": () =>
209
+ validateCutPreconditions({
210
+ selectedNextVersion: "0.37.0-next.42",
211
+ releaseLine: "0.37",
212
+ sourceSha: "abc1234567890abcdef",
213
+ remoteBranches: ["release/0.36", "release/0.37"],
214
+ }),
215
+ "cut-not-monotonic": () =>
216
+ validateCutPreconditions({
217
+ selectedNextVersion: "0.37.0-next.42",
218
+ releaseLine: "0.37",
219
+ sourceSha: "abc1234567890abcdef",
220
+ remoteBranches: ["release/0.38"],
221
+ }),
222
+ "cut-line-mismatch": () =>
223
+ validateCutPreconditions({
224
+ selectedNextVersion: "0.37.0-next.42",
225
+ releaseLine: "0.38",
226
+ sourceSha: "abc1234567890abcdef",
227
+ remoteBranches: [],
228
+ }),
229
+ "cut-sha-not-on-main": () =>
230
+ validateCutPreconditions({
231
+ selectedNextVersion: "0.37.0-next.42",
232
+ releaseLine: "0.37",
233
+ sourceSha: "abc1234567890abcdef",
234
+ remoteBranches: [],
235
+ shaOnMain: () => false,
236
+ }),
237
+ };
238
+ const run = scenarios[scenario];
239
+ if (!run) {
240
+ process.stderr.write(`unknown self-test scenario: ${scenario}\n`);
241
+ process.exitCode = 2;
242
+ } else {
243
+ process.stdout.write(`${JSON.stringify(run())}\n`);
244
+ }
245
+ } else {
246
+ process.stderr.write(
247
+ "usage: node scripts/release-source-binding.mjs --self-test=<scenario>\n",
248
+ );
249
+ process.exitCode = 2;
250
+ }
251
+ }
@@ -100,6 +100,8 @@ loop-agent --help
100
100
  ```bash
101
101
  loop-agent inspect # 当前 repo(自动检测)
102
102
  loop-agent --repo-root /path/to/target-repo inspect # 指定 repo
103
+ loop-agent inspect next # 当前 npm next 绑定(version / SHA / unpublished commits)
104
+ loop-agent inspect next --json
103
105
  ```
104
106
 
105
107
  ### 健康检查
@@ -132,13 +134,18 @@ SDK 回归或 SDK 可选依赖不可用时用 `cli-only` 诊断。CLI fallback
132
134
 
133
135
  ```bash
134
136
  loop-agent init instructions --repo-root <target-repo>
137
+ loop-agent init model-catalog --repo-root <target-repo> --json
135
138
  loop-agent init --repo-root <target-repo> --profile full --merge \
136
- --provider deepseek --model deepseek/deepseek-v4-flash
139
+ --low-model deepseek/deepseek-chat \
140
+ --med-model deepseek/deepseek-v4-flash \
141
+ --high-model deepseek/deepseek-v4-pro
137
142
  loop-agent init doctor --repo-root <target-repo>
138
143
  loop-agent init check-update --repo-root <target-repo> --json
139
144
  loop-agent init check-update --repo-root <target-repo> --markdown
140
145
  loop-agent init update --repo-root <target-repo> --bootstrap-surface
141
146
  loop-agent init update --repo-root <target-repo> --apply-safe
147
+ loop-agent init update --repo-root <target-repo> --apply-safe \
148
+ --low-model <current-or-selected> --med-model <current-or-selected> --high-model <current-or-selected>
142
149
  loop-agent init reconcile --repo-root <target-repo> [--json|--markdown] # compatibility low-level diagnostic
143
150
  loop-agent init upgrade --repo-root <target-repo> --json
144
151
  loop-agent init upgrade --repo-root <target-repo> --run-id <run-id> --status --json
@@ -146,13 +153,13 @@ loop-agent init upgrade --repo-root <target-repo> --run-id <run-id> --continue -
146
153
  loop-agent init upgrade --repo-root <target-repo> --run-id <run-id> --report --markdown
147
154
  ```
148
155
 
149
- fresh init 将精确引用写到 `executors.pi.LOW/MED/HIGH`。fresh `loop-agent init` 会将合格且完整的 `provider/model` 引用投影到 `executors.pi.LOW/MED/HIGH`,即 `executors.pi.LOW`、`executors.pi.MED` `executors.pi.HIGH`。三个 tier 齐全时不会写入 `defaultModel`,也不会恢复或建议旧顶层 routing 字段。
156
+ fresh init 将精确引用写到 `executors.pi.LOW/MED/HIGH`。模型先运行只读 `init model-catalog`:Pi SDK runtime 与 OpenCode 非 verbose 列表由代码确定性发现,只向模型暴露 provider/model、能力/成本/窗口元数据、Pi setup、兼容性与 credential-ready 布尔值,不暴露原始配置和凭据。已有档位仍存在于 Pi 时原样保留,只重选失效/缺失档位;代码在 update 时拒绝替换仍有效档位。Pi 无定义时先向用户补齐必要模型元数据并帮助字段级配置 `models.json`;只有认证缺失时保留 harness 并修复认证。三个 tier 齐全时不会写入 `defaultModel`,也不会恢复旧顶层 routing 字段。
150
157
 
151
158
  `init check-update` 是只读升级报告,用于发现目标项目是否落后于当前包内初始化 surface。输出会区分 deterministic actions、model merge tasks、human decisions 和 recommended next。`--markdown` 会渲染可直接交给模型执行的合并指引,包含 `allowedPaths`、`forbiddenPaths`、`mergeRules` 和 `verification`。
152
159
 
153
- `init update --bootstrap-surface` 为旧项目写入 `.harness/init-surface.json` 的 `inferred-baseline`,不伪装成历史 recorded baseline。`init update --apply-safe` 只执行确定性安全动作:补缺失文件、创建目录、刷新 managed block;已有但无法确认与当前包一致的文件会进入 model merge tasks,不会被覆盖。模型 merge 只会自动展开不含 tier 或其他旧字段的唯一、完整且可证明等价的旧 `defaultModel: "provider/model"`;顶层 `model`、`models`、`modelProfiles`、`modelRouting`、`verify`、`sequentialWorkflowRole`、部分/对象 tier、bare model、冲突 provider 或未知语义均 fail closed,并保留原始 `harness.json`。
160
+ `init update --bootstrap-surface` 为旧项目写入 `.harness/init-surface.json` 的 `inferred-baseline`,不伪装成历史 recorded baseline。`init update --apply-safe` 只执行确定性安全动作:补缺失文件、创建目录、刷新 managed block;`harness.json` 永不走整文件 `refresh-owned-file`,即使它仍与旧 recorded hash 一致,也只允许字段级确定性迁移或 bounded merge。带完整三档参数的 update 只替换 catalog 已判定失效的档位,仍有效档位的对象形态与 thinking 配置保持不变。模型 merge 只会自动展开不含 tier 或其他旧字段的唯一、完整且可证明等价的旧 `defaultModel: "provider/model"`;其他歧义均 fail closed 并保留原文件。
154
161
 
155
- `init upgrade` 是写入型升级入口:首次运行冻结 controller identity、检查 npm latest、扫描 surface、自动执行安全动作,并把 run facts 持久化到 `.harness/init-upgrades/<run-id>/`。`--status` `--report` 严格只读;`--continue` 会重新核验 identity、当前 hash 与不变量。版本选择、无法判定冲突、凭据、破坏性删除或 active writer 才进入 human decision。普通语义冲突返回具体单文件 `allowedPaths` merge task,主会话必须合并后继续,不能把 `needs-model-merge` 作为完成。默认管理项目级 OpenCode/Pi recovery 与 `.pi/settings.json` nested merge,保留显式 disabled,Pi trust 后才加载;默认不读写 `~/.pi/agent/settings.json`。旧 `init reconcile` 保留为兼容低层诊断。
162
+ `init upgrade` 是写入型升级入口:首次运行冻结 controller identity 与脱敏模型目录,检查 npm latest,并用一次 surface scan 形成完整计划。固定投影按 recorded bytes 确定性增删改;recorded `harness.json` 按属性树增删字段并保留项目值、同步 schema;项目自适应内容由模型决定。多个语义合并在同一计划中串行排队,每次仍只开放一个文件的 `allowedPaths`;接受后直接激活下一项,不重复版本发现/规划,队列清空后只跑一次最终验证。确定性收敛最多两轮。完成态的 verification/report 均有 controller hash anchor,`--status`、`--report` 和已完成 run 的幂等 `--continue` 只读持久化证据,不重新跑项目校验。默认管理项目级 OpenCode/Pi recovery 与 `.pi/settings.json` nested merge,默认不读写 `~/.pi/agent/settings.json`。旧 `init reconcile` 保留为兼容低层诊断。
156
163
 
157
164
  升级在 managed block 收敛后生成只读 gitignore 迁移评估(`.harness/init-upgrades/<run-id>/gitignore-migration.json`):inventory 已跟踪的 `.harness/**` 与 `.agents/**`,建议 index-only 命令 `git rm -r --cached --ignore-unmatch .harness` / `.agents`(只改 index、保留工作区文件;`scripts/**` 与 `ai_workspace/loop-agent/**` 永不出现)。`.agents` 已跟踪内容会触发 human-decision 暂停等待审查;非 Git 仓库、git 查询失败或存在 staged 条目时 `status: blocked` 且无命令建议。controller 不自动修改 Git index。
158
165
 
@@ -1 +0,0 @@
1
- import{ag as o,ah as n}from"./mermaid.core-BVjAT9b8.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-CPcdNeew.js";import{_ as i}from"./mermaid.core-BVjAT9b8.js";import"./chunk-5VM5RSS4-CtiPlKel.js";import"./chunk-XXDRQBXY-UrVoM9z1.js";import"./chunk-KBJHAD2P-DQ_T-hg8.js";import"./chunk-2GRJ4B5K-K0C744p1.js";import"./index-DTZOKgAn.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-CPcdNeew.js";import{_ as i}from"./mermaid.core-BVjAT9b8.js";import"./chunk-5VM5RSS4-CtiPlKel.js";import"./chunk-XXDRQBXY-UrVoM9z1.js";import"./chunk-KBJHAD2P-DQ_T-hg8.js";import"./chunk-2GRJ4B5K-K0C744p1.js";import"./index-DTZOKgAn.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as r,b as e,a,S as s}from"./chunk-5RXB4S5H-DjLaSDUO.js";import{_ as i}from"./mermaid.core-BVjAT9b8.js";import"./chunk-XXDRQBXY-UrVoM9z1.js";import"./chunk-KBJHAD2P-DQ_T-hg8.js";import"./chunk-2GRJ4B5K-K0C744p1.js";import"./index-DTZOKgAn.js";var u={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1,8 +0,0 @@
1
- import{c as r,s as e}from"./flowDiagram-UKHOOZJN-CihnROSm.js";import{_ as a}from"./mermaid.core-BVjAT9b8.js";import"./chunk-5VM5RSS4-CtiPlKel.js";import"./chunk-XXDRQBXY-UrVoM9z1.js";import"./chunk-KBJHAD2P-DQ_T-hg8.js";import"./chunk-2GRJ4B5K-K0C744p1.js";import"./channel-CPF4N7pf.js";import"./index-DTZOKgAn.js";var o=a(t=>`${e(t)}
2
- .swimlane.cluster rect {
3
- stroke: ${t.clusterBorder} !important;
4
- }
5
- [data-look="neo"].cluster rect {
6
- filter: none;
7
- }
8
- `,"getStyles"),s=o,n=r({defaultLayout:"swimlane",styles:s});export{n as diagram};