@tea-agent/loop-agent 0.35.1-beta.0 → 0.35.1-beta.2

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 (179) hide show
  1. package/AGENTS.md +110 -108
  2. package/CHANGELOG.md +24 -26
  3. package/README.md +165 -165
  4. package/bin/agent-worker.js +0 -0
  5. package/bin/loop-agent.js +57 -21
  6. package/dist/application/task-lifecycle/advance.js +0 -1
  7. package/dist/build-stamp.json +6 -0
  8. package/dist/cli/program.js +2 -2
  9. package/dist/commands/cursor-prompt.js +6 -6
  10. package/dist/commands/init-upgrade.js +19 -351
  11. package/dist/commands/init.js +67 -14
  12. package/dist/commands/loop-benchmark.js +11 -11
  13. package/dist/commands/pi-reuse-benchmark.js +16 -16
  14. package/dist/commands/run-dag-progress.js +0 -14
  15. package/dist/commands/task-advance.js +3 -33
  16. package/dist/executors/dag-pi-executor.js +44 -0
  17. package/dist/shared/operator/capabilities.js +1 -38
  18. package/dist/shared/package-metadata.js +42 -0
  19. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  20. package/dist/worker/console/chat/pi-runtime.js +25 -41
  21. package/dist/worker/console/chat/routes.js +4 -27
  22. package/dist/worker/console/operation-runner.js +0 -24
  23. package/dist/worker/console/operator-actions.js +0 -58
  24. package/dist/worker/console/static/assets/index-CvsQgALl.js +56 -0
  25. package/dist/worker/console/static/assets/{index-Dups4sSM.css → index-hJqCPs_g.css} +1 -1
  26. package/dist/worker/console/static/index.html +2 -2
  27. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -0
  28. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -13
  29. package/dist/worker/console/static-src/operator-chat/useComposer.js +7 -30
  30. package/dist/worker/loop-agent/loop-agent-client.js +17 -3
  31. package/dist/worker/observability/read-model.js +20 -0
  32. package/dist/worker/observe/static/copy.js +67 -67
  33. package/dist/worker/observe/static/dag-layout.d.ts +36 -36
  34. package/dist/worker/observe/static/dom.js +220 -220
  35. package/dist/worker/observe/static/relations.js +133 -133
  36. package/dist/worker/observe/static/run-processing.js +148 -148
  37. package/dist/worker/observe/static/views/batch.js +227 -227
  38. package/dist/worker/observe/static/views/failures.js +143 -143
  39. package/dist/worker/observe/static/views/feature.js +492 -492
  40. package/dist/worker/observe/static/views/run.js +453 -453
  41. package/dist/worker/observe/static/views/shell.js +7 -7
  42. package/dist/worker/observe/static/views/timeline.js +163 -163
  43. package/dist/worker/preflight.js +2 -1
  44. package/dist/workflows/dag/backend-test-scenario-param.js +33 -23
  45. package/dist/workflows/dag/canvas-observer.js +275 -275
  46. package/dist/workflows/dag/contract-output-registry.js +14 -0
  47. package/dist/workflows/dag/contract-validator-registrations.js +8 -0
  48. package/dist/workflows/dag/dynamic-runtime/shared.js +9 -1
  49. package/dist/workflows/dag/frontend-implementation-contract.js +233 -39
  50. package/dist/workflows/dag/frontend-prewrite-gate.js +364 -61
  51. package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
  52. package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
  53. package/dist/workflows/dag/frontend-recovery-run.js +539 -0
  54. package/dist/workflows/dag/frontend-repair.js +219 -18
  55. package/dist/workflows/dag/frontend-verification-trace.js +47 -32
  56. package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
  57. package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
  58. package/dist/workflows/dag/init-hybrid.js +41 -24
  59. package/dist/workflows/dag/node-execution.js +89 -0
  60. package/dist/workflows/dag/recovery-recommendation.js +58 -0
  61. package/dist/workflows/dag/runner.js +245 -11
  62. package/dist/workflows/dag/scheduler.js +257 -3
  63. package/dist/workflows/dag/types.js +130 -2
  64. package/docs/architecture/evolution.md +73 -73
  65. package/docs/architecture/system-overview.md +100 -100
  66. package/docs/architecture/worker-and-feature.md +122 -122
  67. package/docs/skills/README.md +7 -7
  68. package/docs/templates/adr.md +60 -60
  69. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  70. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  71. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  72. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  73. package/docs/templates/agent-dag-report.schema.json +473 -473
  74. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  75. package/docs/templates/backend-test-result.schema.json +99 -99
  76. package/docs/templates/evaluation/agents-map-slim-v1.md +87 -87
  77. package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -153
  78. package/docs/templates/feature-spec.md +53 -53
  79. package/docs/templates/frontend-design-contract.md +42 -42
  80. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
  81. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
  82. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
  83. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
  84. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
  85. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
  86. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
  87. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
  88. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
  89. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
  90. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
  91. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
  92. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
  93. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
  94. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
  95. package/docs/templates/frontend-eval/metrics.md +138 -138
  96. package/docs/templates/frontend-eval/smoke-targets.md +53 -53
  97. package/docs/templates/frontend-task-constraints.md +35 -35
  98. package/docs/templates/frontend-task-requirement.md +70 -70
  99. package/docs/templates/init-evolution-review.md +35 -35
  100. package/docs/templates/init-managed-agents.md +154 -156
  101. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  102. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
  103. package/docs/templates/knowledge-sync-dag.json +178 -178
  104. package/docs/templates/knowledge-sync-draft.schema.json +71 -71
  105. package/docs/templates/product-line/closeout.yaml +9 -9
  106. package/docs/templates/product-line/design.md +13 -13
  107. package/docs/templates/product-line/links.md +10 -10
  108. package/docs/templates/product-line/requirement.md +17 -17
  109. package/docs/templates/product-line/test-plan.md +7 -7
  110. package/docs/templates/project-start-checklist.md +9 -9
  111. package/docs/templates/qa-report.md +48 -48
  112. package/docs/templates/sprint-contract.md +29 -29
  113. package/docs/templates/worker-dogfood-evidence.md +80 -80
  114. package/docs/templates/worker-dogfood-setup.md +68 -68
  115. package/harness.json +2 -5
  116. package/package.json +2 -2
  117. package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
  118. package/scripts/kb-graph-incremental-prepare.mjs +0 -0
  119. package/scripts/kb-graph-materialize.mjs +105 -105
  120. package/scripts/kb-graph-promote.mjs +164 -164
  121. package/scripts/kb-query.mjs +554 -554
  122. package/skills/agent-worker/SKILL.md +48 -48
  123. package/skills/agent-worker/references/agent-worker-operator.md +159 -159
  124. package/skills/ai-engineering-context/SKILL.md +48 -48
  125. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +0 -0
  126. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +0 -0
  127. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +0 -0
  128. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +0 -0
  129. package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +0 -0
  130. package/skills/analyze-product-requirements/scripts/test-validators.mjs +0 -0
  131. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +0 -0
  132. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +0 -0
  133. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +0 -0
  134. package/skills/browser-tools/browser-content.js +103 -103
  135. package/skills/browser-tools/browser-cookies.js +35 -35
  136. package/skills/browser-tools/browser-eval.js +53 -53
  137. package/skills/browser-tools/browser-hn-scraper.js +108 -108
  138. package/skills/browser-tools/browser-nav.js +44 -44
  139. package/skills/browser-tools/browser-pick.js +162 -162
  140. package/skills/browser-tools/browser-screenshot.js +34 -34
  141. package/skills/browser-tools/browser-start.js +86 -86
  142. package/skills/browser-tools/package-lock.json +2556 -2556
  143. package/skills/browser-tools/package.json +19 -19
  144. package/skills/code-review-core/SKILL.md +20 -20
  145. package/skills/codebase-scout/SKILL.md +19 -19
  146. package/skills/grill-me/SKILL.md +10 -10
  147. package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +0 -0
  148. package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +0 -0
  149. package/skills/loop-agent/SKILL.md +0 -1
  150. package/skills/loop-agent/references/command-reference.md +639 -641
  151. package/skills/loop-agent/references/docs-converge.md +126 -126
  152. package/skills/loop-agent/references/learned/README.md +21 -21
  153. package/skills/loop-agent/references/pi-prompt.md +23 -23
  154. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  155. package/skills/playwright-cli/references/element-attributes.md +23 -23
  156. package/skills/playwright-cli/references/playwright-tests.md +39 -39
  157. package/skills/playwright-cli/references/request-mocking.md +87 -87
  158. package/skills/playwright-cli/references/running-code.md +241 -241
  159. package/skills/playwright-cli/references/session-management.md +225 -225
  160. package/skills/playwright-cli/references/storage-state.md +275 -275
  161. package/skills/playwright-cli/references/test-generation.md +433 -433
  162. package/skills/requesting-code-review/SKILL.md +101 -101
  163. package/skills/requesting-code-review/code-reviewer.md +168 -168
  164. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  165. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  166. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  167. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  168. package/skills/systematic-debugging/find-polluter.sh +63 -63
  169. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  170. package/skills/systematic-debugging/test-academic.md +14 -14
  171. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  172. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  173. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  174. package/skills/using-git-worktrees/SKILL.md +215 -215
  175. package/skills/verification-before-completion/SKILL.md +154 -154
  176. package/skills/webapp-testing/SKILL.md +19 -19
  177. package/dist/worker/console/operation-wait.js +0 -241
  178. package/dist/worker/console/static/assets/index-SjjjZnV3.js +0 -56
  179. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
@@ -1,7 +1,6 @@
1
1
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
2
2
  import { filterSlashCommands, mergeSlashCommands, } from "../../chat/shortcuts.js";
3
3
  import { confirmationToken } from "./format.js";
4
- import { clampShortcutIndex } from "./slash-palette-nav.js";
5
4
  import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
6
5
  /** Composer state: input text, @file palette, image attachments, /shortcuts,
7
6
  * IME composition guards, auto-grow and server-side draft sync. */
@@ -17,22 +16,12 @@ export function useComposer(params) {
17
16
  const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
18
17
  const [commandsEpoch, setCommandsEpoch] = useState(0);
19
18
  const textareaRef = useRef(null);
20
- /** Active session generation for /commands; bumped immediately on switch (AC-2). */
21
- const commandsSessionRef = useRef(sessionId);
22
- const commandsGenerationRef = useRef(0);
19
+ const commandsSessionRef = useRef(undefined);
23
20
  const handleInputChange = useCallback((value) => {
24
21
  setInput(value);
25
22
  setShortcutOpen(value.startsWith("/"));
26
23
  setShortcutIndex(0);
27
24
  }, []);
28
- // Immediate invalidation on A→B even when B palette never opens (AC-2).
29
- useLayoutEffect(() => {
30
- commandsSessionRef.current = sessionId;
31
- commandsGenerationRef.current += 1;
32
- setDynamicCommands([]);
33
- setDynamicCommandsError(null);
34
- setDynamicCommandsLoading(false);
35
- }, [sessionId]);
36
25
  const loadDynamicCommands = useCallback(async () => {
37
26
  if (!sessionId) {
38
27
  setDynamicCommands([]);
@@ -41,15 +30,12 @@ export function useComposer(params) {
41
30
  return;
42
31
  }
43
32
  const target = sessionId;
44
- const generation = commandsGenerationRef.current;
45
33
  commandsSessionRef.current = target;
46
34
  setDynamicCommandsLoading(true);
47
35
  setDynamicCommandsError(null);
48
- const isCurrent = () => commandsSessionRef.current === target &&
49
- commandsGenerationRef.current === generation;
50
36
  try {
51
37
  const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(target)}/commands`, { credentials: "include" });
52
- if (!isCurrent())
38
+ if (commandsSessionRef.current !== target)
53
39
  return;
54
40
  if (!res.ok) {
55
41
  setDynamicCommandsError("动态命令加载失败");
@@ -75,27 +61,26 @@ export function useComposer(params) {
75
61
  source,
76
62
  });
77
63
  }
78
- if (!isCurrent())
64
+ if (commandsSessionRef.current !== target)
79
65
  return;
80
66
  setDynamicCommands(items);
81
- // Compact server fail-soft / partial-source warning; local Operator/Web still merge.
82
- const warning = typeof body?.warning === "string" ? body.warning.trim() : "";
83
- setDynamicCommandsError(warning ? warning.slice(0, 160) : null);
84
67
  }
85
68
  catch {
86
- if (!isCurrent())
69
+ if (commandsSessionRef.current !== target)
87
70
  return;
88
71
  setDynamicCommandsError("动态命令加载失败");
89
72
  setDynamicCommands([]);
90
73
  }
91
74
  finally {
92
- if (isCurrent()) {
75
+ if (commandsSessionRef.current === target) {
93
76
  setDynamicCommandsLoading(false);
94
77
  }
95
78
  }
96
79
  }, [origin, sessionId]);
97
80
  // Lazy-load dynamic commands when slash palette opens or session changes.
98
81
  useEffect(() => {
82
+ setDynamicCommands([]);
83
+ setDynamicCommandsError(null);
99
84
  if (!sessionId)
100
85
  return;
101
86
  if (!shortcutOpen && commandsEpoch === 0)
@@ -187,14 +172,6 @@ export function useComposer(params) {
187
172
  const filteredShortcuts = useMemo(() => input.startsWith("/")
188
173
  ? filterSlashCommands(mergedCommands, input)
189
174
  : [], [input, mergedCommands]);
190
- // Keep the active option index inside the current candidate range (UI-12
191
- // AC-011): dynamic loads completing/failing empty, retries, session
192
- // switches, late responses and search shrink all funnel through
193
- // filteredShortcuts.length. The clamped index always matches a rendered
194
- // `oc-shortcut-option-${globalIndex}` id (same source of truth).
195
- useEffect(() => {
196
- setShortcutIndex((index) => clampShortcutIndex(index, filteredShortcuts.length));
197
- }, [filteredShortcuts.length]);
198
175
  const onCompositionStart = useCallback(() => {
199
176
  refs.isComposingRef.current = true;
200
177
  }, [refs]);
@@ -2,11 +2,15 @@ import { spawn } from "node:child_process";
2
2
  import { appendFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
3
3
  import { mkdir, readFile, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { computeBinarySha256, computePackageFingerprint, findPackageRoot, isJavaScriptScript, readPackageName, readPackageVersion, resolveBinPaths, resolveCmdShim, } from "../../shared/package-metadata.js";
5
+ import { computeBinarySha256, computePackageFingerprint, findPackageRoot, isJavaScriptScript, normalizeCliVersionLabel, readPackageName, readPackageVersion, resolveBinPaths, resolveCmdShim, } from "../../shared/package-metadata.js";
6
6
  import { parseCommandJson } from "./parse-json.js";
7
7
  export const DEFAULT_WORKER_COMMAND_TIMEOUT_MS = 120_000;
8
8
  export const DEFAULT_HEARTBEAT_INTERVAL_MS = 15_000;
9
9
  export const LOOP_AGENT_PACKAGE_NAME = "@tea-agent/loop-agent";
10
+ /** Grace window between the SIGTERM and SIGKILL stages of a hard timeout, so a
11
+ * runner can persist terminal state (see executeDagCheckpoint) before being
12
+ * force-killed. */
13
+ export const HARD_TIMEOUT_KILL_GRACE_MS = 10_000;
10
14
  const MAX_BUFFERED_OUTPUT_BYTES = 64 * 1024;
11
15
  /**
12
16
  * Bind evidence supplied by an orchestration layer to the identity exposed by
@@ -178,10 +182,11 @@ export class LoopAgentClient {
178
182
  if (!this._identity) {
179
183
  throw new Error("controller identity: cannot record a reported version without a pinned identity");
180
184
  }
181
- if (reportedVersion !== this._identity.packageVersion) {
185
+ const normalized = normalizeCliVersionLabel(reportedVersion);
186
+ if (normalized !== this._identity.packageVersion) {
182
187
  throw new Error(`controller identity: reported version ${reportedVersion} does not match package version ${this._identity.packageVersion}`);
183
188
  }
184
- this._identity = { ...this._identity, reportedVersion };
189
+ this._identity = { ...this._identity, reportedVersion: normalized };
185
190
  return this._identity;
186
191
  }
187
192
  async run(args, options) {
@@ -381,10 +386,17 @@ function spawnCommand(input) {
381
386
  });
382
387
  };
383
388
  let hardTimeout;
389
+ let killGraceTimeout;
384
390
  if (input.hardTimeoutMs !== undefined && input.hardTimeoutMs > 0) {
385
391
  hardTimeout = setTimeout(() => {
386
392
  timedOut = true;
393
+ // Two-stage termination: SIGTERM first so the runner can persist a
394
+ // terminal state (see executeDagCheckpoint signal handler), then
395
+ // SIGKILL only after the grace window expires.
387
396
  child.kill("SIGTERM");
397
+ killGraceTimeout = setTimeout(() => {
398
+ child.kill("SIGKILL");
399
+ }, HARD_TIMEOUT_KILL_GRACE_MS);
388
400
  }, input.hardTimeoutMs);
389
401
  }
390
402
  let heartbeatTimer;
@@ -394,6 +406,8 @@ function spawnCommand(input) {
394
406
  const clearTimers = () => {
395
407
  if (hardTimeout !== undefined)
396
408
  clearTimeout(hardTimeout);
409
+ if (killGraceTimeout !== undefined)
410
+ clearTimeout(killGraceTimeout);
397
411
  if (heartbeatTimer !== undefined) {
398
412
  clearInterval(heartbeatTimer);
399
413
  heartbeatTimer = undefined;
@@ -1491,6 +1491,7 @@ function mergeDagRun(existing, incoming) {
1491
1491
  failureCategory: incoming.failureCategory ?? existing.failureCategory,
1492
1492
  stateConsistent: incoming.stateConsistent ?? existing.stateConsistent,
1493
1493
  recoveryEligibility: incoming.recoveryEligibility ?? existing.recoveryEligibility,
1494
+ frontendRecovery: incoming.frontendRecovery ?? existing.frontendRecovery,
1494
1495
  title: incoming.title ?? existing.title,
1495
1496
  startedAt,
1496
1497
  finishedAt,
@@ -1790,6 +1791,23 @@ async function loadFrontendLintProjection(runDir) {
1790
1791
  blockingReasons: parsed.data.blockingReasons,
1791
1792
  };
1792
1793
  }
1794
+ function projectFrontendRecovery(state) {
1795
+ const s = state.frontendRecoveryState;
1796
+ const r = state.frontendRecoveryResult;
1797
+ if (!s && !r)
1798
+ return undefined;
1799
+ return {
1800
+ ...(s?.recoveryRootRunId ? { recoveryRootRunId: s.recoveryRootRunId } : {}),
1801
+ ...(s?.parentRunId ? { parentRunId: s.parentRunId } : {}),
1802
+ ...(s?.childRunId ? { childRunId: s.childRunId } : {}),
1803
+ ...(s?.attemptIndex !== undefined ? { attemptIndex: s.attemptIndex } : {}),
1804
+ ...(s?.continuationCount !== undefined
1805
+ ? { continuationCount: s.continuationCount }
1806
+ : {}),
1807
+ ...(s?.phase ? { phase: s.phase } : {}),
1808
+ ...(r?.outcome ? { outcome: r.outcome } : {}),
1809
+ };
1810
+ }
1793
1811
  async function parseDagStateFile(statePath, now) {
1794
1812
  try {
1795
1813
  if (!existsSync(statePath))
@@ -1860,6 +1878,7 @@ async function parseDagStateFile(statePath, now) {
1860
1878
  loadBackendTestProjection(runDir, nodes),
1861
1879
  loadFrontendLintProjection(runDir),
1862
1880
  ]);
1881
+ const frontendRecovery = projectFrontendRecovery(state);
1863
1882
  const continuationRecord = state.continuation &&
1864
1883
  typeof state.continuation.parentRunId === "string" &&
1865
1884
  typeof state.continuation.effectiveFromNodeId === "string" &&
@@ -1918,6 +1937,7 @@ async function parseDagStateFile(statePath, now) {
1918
1937
  ...(continuation ? { continuation } : {}),
1919
1938
  ...(backendTest ? { backendTest } : {}),
1920
1939
  ...(frontendLint ? { frontendLint } : {}),
1940
+ ...(frontendRecovery ? { frontendRecovery } : {}),
1921
1941
  };
1922
1942
  }
1923
1943
  catch {
@@ -1,67 +1,67 @@
1
- /** Copy-only recommended commands (Observe UI R5). */
2
- import { el } from "./dom.js";
3
-
4
- export async function copyText(text) {
5
- const value = String(text ?? "");
6
- if (!value) return false;
7
- try {
8
- if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
9
- await navigator.clipboard.writeText(value);
10
- return true;
11
- }
12
- } catch {
13
- // fall through
14
- }
15
- try {
16
- const ta = document.createElement("textarea");
17
- ta.value = value;
18
- ta.setAttribute("readonly", "");
19
- ta.style.position = "fixed";
20
- ta.style.left = "-9999px";
21
- document.body.appendChild(ta);
22
- ta.select();
23
- const ok = document.execCommand("copy");
24
- document.body.removeChild(ta);
25
- return ok;
26
- } catch {
27
- return false;
28
- }
29
- }
30
-
31
- /** Advisory recommended command: copy-only, never executes. */
32
- export function renderRecommendedCommand(action) {
33
- const block = el("div", "recommended-command");
34
- if (!action) {
35
- block.appendChild(el("span", "muted", "缺失"));
36
- return block;
37
- }
38
- const head = el("div", "recommended-command-head");
39
- if (action.kind) {
40
- head.appendChild(el("span", "recommended-command-kind", action.kind));
41
- }
42
- head.appendChild(
43
- el("span", "recommended-command-label", action.label ?? "—"),
44
- );
45
- block.appendChild(head);
46
- if (action.command) {
47
- const body = el("div", "recommended-command-body");
48
- const code = el("code", "recommended-command-text", action.command);
49
- body.appendChild(code);
50
- const btn = el("button", "copy-command", "复制");
51
- btn.type = "button";
52
- btn.addEventListener("click", (e) => {
53
- e.preventDefault();
54
- e.stopPropagation();
55
- void copyText(action.command).then((ok) => {
56
- btn.textContent = ok ? "已复制" : "复制失败";
57
- setTimeout(() => {
58
- btn.textContent = "复制";
59
- }, 1500);
60
- });
61
- });
62
- body.appendChild(btn);
63
- block.appendChild(body);
64
- }
65
- return block;
66
- }
67
-
1
+ /** Copy-only recommended commands (Observe UI R5). */
2
+ import { el } from "./dom.js";
3
+
4
+ export async function copyText(text) {
5
+ const value = String(text ?? "");
6
+ if (!value) return false;
7
+ try {
8
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
9
+ await navigator.clipboard.writeText(value);
10
+ return true;
11
+ }
12
+ } catch {
13
+ // fall through
14
+ }
15
+ try {
16
+ const ta = document.createElement("textarea");
17
+ ta.value = value;
18
+ ta.setAttribute("readonly", "");
19
+ ta.style.position = "fixed";
20
+ ta.style.left = "-9999px";
21
+ document.body.appendChild(ta);
22
+ ta.select();
23
+ const ok = document.execCommand("copy");
24
+ document.body.removeChild(ta);
25
+ return ok;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ /** Advisory recommended command: copy-only, never executes. */
32
+ export function renderRecommendedCommand(action) {
33
+ const block = el("div", "recommended-command");
34
+ if (!action) {
35
+ block.appendChild(el("span", "muted", "缺失"));
36
+ return block;
37
+ }
38
+ const head = el("div", "recommended-command-head");
39
+ if (action.kind) {
40
+ head.appendChild(el("span", "recommended-command-kind", action.kind));
41
+ }
42
+ head.appendChild(
43
+ el("span", "recommended-command-label", action.label ?? "—"),
44
+ );
45
+ block.appendChild(head);
46
+ if (action.command) {
47
+ const body = el("div", "recommended-command-body");
48
+ const code = el("code", "recommended-command-text", action.command);
49
+ body.appendChild(code);
50
+ const btn = el("button", "copy-command", "复制");
51
+ btn.type = "button";
52
+ btn.addEventListener("click", (e) => {
53
+ e.preventDefault();
54
+ e.stopPropagation();
55
+ void copyText(action.command).then((ok) => {
56
+ btn.textContent = ok ? "已复制" : "复制失败";
57
+ setTimeout(() => {
58
+ btn.textContent = "复制";
59
+ }, 1500);
60
+ });
61
+ });
62
+ body.appendChild(btn);
63
+ block.appendChild(body);
64
+ }
65
+ return block;
66
+ }
67
+
@@ -1,36 +1,36 @@
1
- export type DagLayoutNodeInput = { nodeId: string };
2
- export type DagLayoutEdgeInput = { from: string; to: string };
3
- export type DagLayoutNode = {
4
- nodeId: string;
5
- rank: number;
6
- index: number;
7
- x: number;
8
- y: number;
9
- width: number;
10
- height: number;
11
- };
12
- export type DagLayoutEdge = DagLayoutEdgeInput & { path: string };
13
- export type DagLayout = {
14
- width: number;
15
- height: number;
16
- nodes: DagLayoutNode[];
17
- edges: DagLayoutEdge[];
18
- };
19
-
20
- export type DagLayoutOptions = Partial<{
21
- nodeWidth: number;
22
- nodeHeight: number;
23
- rankGap: number;
24
- nodeGap: number;
25
- padding: number;
26
- controlLaneCount: number;
27
- /** Per-node visual card height overrides; ordinary ports still use nodeHeight axis. */
28
- nodeHeights: Record<string, number>;
29
- }>;
30
-
31
- export function layoutDag(
32
- nodes?: DagLayoutNodeInput[],
33
- edges?: DagLayoutEdgeInput[],
34
- ranks?: string[][],
35
- options?: DagLayoutOptions,
36
- ): DagLayout;
1
+ export type DagLayoutNodeInput = { nodeId: string };
2
+ export type DagLayoutEdgeInput = { from: string; to: string };
3
+ export type DagLayoutNode = {
4
+ nodeId: string;
5
+ rank: number;
6
+ index: number;
7
+ x: number;
8
+ y: number;
9
+ width: number;
10
+ height: number;
11
+ };
12
+ export type DagLayoutEdge = DagLayoutEdgeInput & { path: string };
13
+ export type DagLayout = {
14
+ width: number;
15
+ height: number;
16
+ nodes: DagLayoutNode[];
17
+ edges: DagLayoutEdge[];
18
+ };
19
+
20
+ export type DagLayoutOptions = Partial<{
21
+ nodeWidth: number;
22
+ nodeHeight: number;
23
+ rankGap: number;
24
+ nodeGap: number;
25
+ padding: number;
26
+ controlLaneCount: number;
27
+ /** Per-node visual card height overrides; ordinary ports still use nodeHeight axis. */
28
+ nodeHeights: Record<string, number>;
29
+ }>;
30
+
31
+ export function layoutDag(
32
+ nodes?: DagLayoutNodeInput[],
33
+ edges?: DagLayoutEdgeInput[],
34
+ ranks?: string[][],
35
+ options?: DagLayoutOptions,
36
+ ): DagLayout;