@abelxiaoxing/cadence 1.8.3 → 2.0.0

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 (208) hide show
  1. package/README.md +324 -111
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -26
  4. package/agents/diagnosis-worker.md +14 -25
  5. package/agents/implementation-worker.md +16 -35
  6. package/config/.env.example +2 -5
  7. package/config/workflow-task.example.json +7 -0
  8. package/docs/remote-access.md +60 -0
  9. package/docs/reuse-and-distribution.md +44 -0
  10. package/docs/service-management.md +108 -0
  11. package/licenses/bundled-web.txt +6245 -0
  12. package/licenses/pi-web-ui-MIT.txt +21 -0
  13. package/licenses/pi-web-ui-reuse.md +22 -0
  14. package/package.json +94 -21
  15. package/src/index.ts +22 -1807
  16. package/src/native-build.ts +6 -0
  17. package/src/native-client.ts +103 -0
  18. package/src/native-export.ts +58 -0
  19. package/src/native-history.ts +359 -0
  20. package/src/operator-cli.mjs +453 -1937
  21. package/src/plan-proposal.ts +87 -0
  22. package/src/sdk-worker.mjs +549 -0
  23. package/src/service/contracts/active.ts +82 -0
  24. package/src/service/contracts/budget.ts +148 -0
  25. package/src/service/contracts/build.ts +67 -0
  26. package/src/service/contracts/capture.ts +48 -0
  27. package/src/service/contracts/core.ts +33 -0
  28. package/src/service/contracts/current.ts +261 -0
  29. package/src/service/contracts/domain.ts +68 -0
  30. package/src/service/contracts/history.ts +2245 -0
  31. package/src/service/contracts/models.ts +139 -0
  32. package/src/service/coordination.ts +862 -0
  33. package/src/service/private-files.ts +204 -0
  34. package/src/service-cli.mjs +5592 -0
  35. package/src/service-client.ts +747 -0
  36. package/src/service-contract.ts +245 -0
  37. package/src/service-host.mjs +22382 -0
  38. package/src/service-http-client.ts +425 -0
  39. package/src/service-wire.ts +372 -0
  40. package/src/task-shell.ts +1045 -0
  41. package/src/task-store.ts +479 -0
  42. package/src/task-types.ts +184 -0
  43. package/src/verification-diagnostics.ts +29 -72
  44. package/src/worker-capture.mjs +1197 -0
  45. package/src/worker-public.mjs +1753 -0
  46. package/src/worker-redaction.mjs +608 -0
  47. package/src/worker-tools.mjs +202 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  50. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  56. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  62. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  65. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  68. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  71. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  74. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  77. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  80. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  89. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  92. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  95. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  98. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  101. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  104. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  107. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  108. package/web/dist/assets/cadence.css +2 -0
  109. package/web/dist/assets/cadence.js +7 -0
  110. package/web/dist/assets/markdown.js +296 -0
  111. package/web/dist/assets/react.js +9 -0
  112. package/web/dist/assets/rolldown-runtime.js +1 -0
  113. package/web/dist/assets/timeline-client.js +41 -0
  114. package/web/dist/assets/vis-timeline.css +1274 -0
  115. package/web/dist/assets/vis-timeline.js +32 -0
  116. package/web/dist/index.html +18 -0
  117. package/web/dist/manifest.json +505 -0
  118. package/config/plan-draft.example.json +0 -125
  119. package/config/plan-draft.multiple-tasks.example.json +0 -375
  120. package/config/plan-draft.quick.example.json +0 -78
  121. package/config/routes.example.json +0 -39
  122. package/prompts/abel-design.md +0 -382
  123. package/prompts/abel-diagnose.md +0 -71
  124. package/prompts/abel-implement.md +0 -284
  125. package/prompts/abel-init.md +0 -52
  126. package/src/activation.ts +0 -44
  127. package/src/activity-contracts.ts +0 -82
  128. package/src/agent-registry.ts +0 -53
  129. package/src/apply-transaction.ts +0 -1437
  130. package/src/artifact-store.ts +0 -213
  131. package/src/build-windows-job.mjs +0 -95
  132. package/src/candidate-artifact.ts +0 -143
  133. package/src/candidate-patch.ts +0 -271
  134. package/src/candidate-workspace.ts +0 -80
  135. package/src/canonical.ts +0 -16
  136. package/src/change-contract-schema.ts +0 -191
  137. package/src/change-contract.ts +0 -324
  138. package/src/change-verification.ts +0 -1617
  139. package/src/child-budget.ts +0 -58
  140. package/src/child-model.ts +0 -142
  141. package/src/child-session.ts +0 -640
  142. package/src/contracts.ts +0 -2476
  143. package/src/control-contracts.ts +0 -382
  144. package/src/delivery-compiler.ts +0 -1805
  145. package/src/delivery-revision.ts +0 -42
  146. package/src/design-control.ts +0 -1299
  147. package/src/design-diagnostics.ts +0 -181
  148. package/src/design-journal.ts +0 -1773
  149. package/src/directory-sync.ts +0 -36
  150. package/src/durable-contracts.ts +0 -231
  151. package/src/durable-workflow.ts +0 -978
  152. package/src/evidence-draft.ts +0 -122
  153. package/src/execution-profile.ts +0 -64
  154. package/src/execution-retention.ts +0 -77
  155. package/src/file-snapshot.ts +0 -163
  156. package/src/implement-continuation.ts +0 -559
  157. package/src/implement-graph.ts +0 -690
  158. package/src/implement-plan.ts +0 -72
  159. package/src/install-config.d.mts +0 -6
  160. package/src/isolation-backend.ts +0 -353
  161. package/src/model-source.ts +0 -22
  162. package/src/openspec-cli.ts +0 -494
  163. package/src/operator-cli-entry.mjs +0 -24
  164. package/src/operator-tools.ts +0 -309
  165. package/src/package-candidate.ts +0 -227
  166. package/src/package-delivery.ts +0 -486
  167. package/src/package-state.ts +0 -320
  168. package/src/package-verification.ts +0 -635
  169. package/src/package-workflow.ts +0 -342
  170. package/src/packet-runtime.ts +0 -570
  171. package/src/parent-provider.ts +0 -375
  172. package/src/phase-execution.ts +0 -2259
  173. package/src/pi-adapter.ts +0 -19
  174. package/src/plan-draft-summary.ts +0 -527
  175. package/src/plan-draft.ts +0 -502
  176. package/src/remove-path.ts +0 -32
  177. package/src/route-policy.ts +0 -545
  178. package/src/run-state.ts +0 -420
  179. package/src/run-store.ts +0 -1179
  180. package/src/safe-path.ts +0 -77
  181. package/src/scoped-grep-worker.mjs +0 -23
  182. package/src/scoped-tools.ts +0 -775
  183. package/src/single-task-draft.ts +0 -142
  184. package/src/sqlite-schema.ts +0 -171
  185. package/src/state-root.ts +0 -215
  186. package/src/storage-schema.ts +0 -360
  187. package/src/subagent-activity.ts +0 -1417
  188. package/src/submit-tool.ts +0 -950
  189. package/src/task-ledger.ts +0 -1338
  190. package/src/transport-budget.ts +0 -52
  191. package/src/verification-capability.ts +0 -1191
  192. package/src/verification-environment-io.ts +0 -389
  193. package/src/verification-environment.ts +0 -162
  194. package/src/verification-identity.ts +0 -77
  195. package/src/verification-scan.ts +0 -64
  196. package/src/windows-job-backend.ts +0 -419
  197. package/src/windows-job.c +0 -149
  198. package/src/worker-broker.ts +0 -806
  199. package/src/workflow-engine.ts +0 -28
  200. package/src/workflow-policy.ts +0 -1679
  201. package/src/workflow-recovery-policy.ts +0 -97
  202. package/src/workflow-scheduling.ts +0 -105
  203. package/src/workflow-state-machine.ts +0 -4355
  204. package/src/workflow-status.ts +0 -576
  205. package/src/workspace-io-entry.mjs +0 -76
  206. package/src/workspace-io-worker.mjs +0 -1312
  207. package/src/workspace-io.ts +0 -89
  208. package/src/workspace-store.ts +0 -634
@@ -0,0 +1,6 @@
1
+ // Generated by scripts/build-service.mjs; do not edit.
2
+ export const nativeBuild = {
3
+ kind: "native",
4
+ contractHash:
5
+ "e7e5248f8273a373827ac70d2d8be1d5aaea1e329379e59e4947fc10f82be818",
6
+ } as const;
@@ -0,0 +1,103 @@
1
+ /** Native attachment only. No SDK/auth resolution, worker or storage mutation. */
2
+
3
+ import { Agent } from "undici";
4
+ import { nativeBuild } from "./native-build.ts";
5
+ import { ServiceCoordination } from "./service/coordination.ts";
6
+ import { ServiceClient } from "./service-client.ts";
7
+ import {
8
+ ContractError,
9
+ compatibleBuild,
10
+ type SessionBootstrap,
11
+ } from "./service-contract.ts";
12
+ import { HttpServiceTransport } from "./service-http-client.ts";
13
+
14
+ export const MANAGEMENT_GUIDANCE =
15
+ "Cadence owner absent, unavailable or incompatible. Explicitly run cadence-service status --workspace <path>, cadence-service diagnose --workspace <path>, or cadence-service start --workspace <path>. Stop older Cadence sessions first. For retained fences use cadence-service recover inspect; resolve requires a local terminal and separate process, credential and storage acknowledgments. Never treat unavailable/quarantined as stopped. No service was started and no embedded executor is available.";
16
+
17
+ export class NativeConnection {
18
+ private transport?: HttpServiceTransport;
19
+ private network?: Agent;
20
+ private networkClose?: Promise<void>;
21
+ private closing?: Promise<void>;
22
+ private allocating?: Promise<void>;
23
+ client?: ServiceClient;
24
+ session?: SessionBootstrap;
25
+ private epoch?: string;
26
+
27
+ async attach(cwd: string): Promise<ServiceClient> {
28
+ // Failed detach keeps its exact transport/slot. Explicit re-entry may retry
29
+ // detach, but never forget it and allocate a replacement on uncertainty.
30
+ await this.detach();
31
+ const access = new ServiceCoordination(cwd).localAccess();
32
+ this.epoch = access.epoch;
33
+ const network = new Agent();
34
+ this.network = network;
35
+ this.transport = new HttpServiceTransport({
36
+ origin: access.endpoint,
37
+ localCapability: access.capability,
38
+ build: nativeBuild,
39
+ // Bound finite requests, not the entire streaming archive. Export keeps
40
+ // caller cancellation and the owner's download-idle policy, like WebUI.
41
+ requestTimeoutMs: 10000,
42
+ // Local capability MUST stay on direct numeric loopback even when the Pi
43
+ // process routes provider auth through a proxy. Public per-request Node
44
+ // dispatcher only: never replace/restore the parent's global dispatcher.
45
+ fetch: (url, init) =>
46
+ globalThis.fetch(url, Object.assign({}, init, { dispatcher: network })),
47
+ });
48
+ const client = new ServiceClient(this.transport);
49
+ this.client = client;
50
+ const allocation = (async () => {
51
+ await this.refresh();
52
+ })();
53
+ this.allocating = allocation;
54
+ try {
55
+ await allocation;
56
+ } finally {
57
+ if (this.allocating === allocation) this.allocating = undefined;
58
+ }
59
+ return client;
60
+ }
61
+ async refresh(): Promise<SessionBootstrap> {
62
+ if (!this.client) throw new ContractError("unavailable");
63
+ const reply = await this.client.session();
64
+ if (reply.kind === "error") throw new ContractError(reply.error.code);
65
+ if (reply.value.authority !== "local" || reply.value.epoch !== this.epoch)
66
+ throw new ContractError("stale");
67
+ this.session = reply.value;
68
+ return reply.value;
69
+ }
70
+ compatible(safety = false): boolean {
71
+ return compatibleBuild(this.session?.build, nativeBuild, safety);
72
+ }
73
+ detach(): Promise<void> {
74
+ if (this.closing) return this.closing;
75
+ const transport = this.transport;
76
+ const network = this.network;
77
+ if (!transport && !network) return Promise.resolve();
78
+ const allocation = this.allocating;
79
+ const closing = (async () => {
80
+ // Track late allocations; a caller's departure isn't evidence of release.
81
+ await allocation?.catch(() => {});
82
+ await transport?.detach();
83
+ if (network) {
84
+ this.networkClose ??= Promise.resolve().then(() => network.close());
85
+ await this.networkClose; // original close; failure retains this exact resource
86
+ }
87
+ if (this.transport === transport && this.network === network) {
88
+ this.network = undefined;
89
+ this.networkClose = undefined;
90
+ this.transport = undefined;
91
+ this.client = undefined;
92
+ this.session = undefined;
93
+ }
94
+ })();
95
+ this.closing = closing;
96
+ void closing
97
+ .finally(() => {
98
+ if (this.closing === closing) this.closing = undefined;
99
+ })
100
+ .catch(() => {});
101
+ return closing;
102
+ }
103
+ }
@@ -0,0 +1,58 @@
1
+ /** Explicit user-selected native export. Exclusive destination, verified stream, no overwrite. */
2
+ import { createHash } from "node:crypto";
3
+ import { constants } from "node:fs";
4
+ import { lstat, open } from "node:fs/promises";
5
+ import { dirname, isAbsolute, resolve } from "node:path";
6
+ import type { ServiceClient } from "./service-client.ts";
7
+ import {
8
+ ContractError,
9
+ type ExportRequest,
10
+ encodeExportFrame,
11
+ } from "./service-contract.ts";
12
+
13
+ export async function exportNative(
14
+ client: ServiceClient,
15
+ request: ExportRequest,
16
+ destination: string,
17
+ signal: AbortSignal,
18
+ ): Promise<"complete" | "incomplete"> {
19
+ // Preserve operational path bytes, including spaces; refuse aliases, not trim.
20
+ if (!isAbsolute(destination) || resolve(destination) !== destination)
21
+ throw new ContractError("invalid");
22
+ for (let at = dirname(destination); ; at = dirname(at)) {
23
+ const stat = await lstat(at);
24
+ if (stat.isSymbolicLink() || !stat.isDirectory())
25
+ throw new ContractError("forbidden");
26
+ if (dirname(at) === at) break;
27
+ }
28
+ if (signal.aborted) throw new ContractError("unavailable");
29
+ const file = await open(
30
+ destination,
31
+ constants.O_WRONLY |
32
+ constants.O_CREAT |
33
+ constants.O_EXCL |
34
+ (constants.O_NOFOLLOW ?? 0),
35
+ 0o600,
36
+ );
37
+ const digest = createHash("sha256");
38
+ let completed: "complete" | "incomplete" | undefined;
39
+ try {
40
+ // ServiceClient checks counts/sequence/scope and withholds trailer until EOF.
41
+ for await (const frame of client.export(request, signal)) {
42
+ const encoded = encodeExportFrame(frame, request.format);
43
+ if (frame.event.kind === "trailer") {
44
+ if (digest.digest("hex") !== frame.event.sha256)
45
+ throw new ContractError("conflict");
46
+ completed = frame.event.complete ? "complete" : "incomplete";
47
+ } else digest.update(encoded);
48
+ await file.writeFile(encoded, "utf8");
49
+ }
50
+ if (!completed || signal.aborted) throw new ContractError("unavailable");
51
+ await file.sync();
52
+ return completed;
53
+ } finally {
54
+ // On any error the partial destination is retained, never removed/replaced or
55
+ // relabelled complete. The caller reports its exact path as unverified.
56
+ await file.close();
57
+ }
58
+ }
@@ -0,0 +1,359 @@
1
+ /** Native retained readers: one human-paced request per page/proof continuation. */
2
+
3
+ import { exportNative } from "./native-export.ts";
4
+ import type { ServiceClient } from "./service-client.ts";
5
+ import {
6
+ type ContentRef,
7
+ ContractError,
8
+ type DetailReadResult,
9
+ type ExportRequest,
10
+ type GraphReadRequest,
11
+ type HistoryRecord,
12
+ type ListRequest,
13
+ type ReadCursor,
14
+ type ReaderKind,
15
+ type Reply,
16
+ type Scope,
17
+ type Snapshot,
18
+ } from "./service-contract.ts";
19
+ import { diagnosticText } from "./verification-diagnostics.ts";
20
+
21
+ export function value<T>(reply: Reply<T>): T {
22
+ if (reply.kind === "error") throw new ContractError(reply.error.code);
23
+ return reply.value;
24
+ }
25
+ export const pretty = (data: unknown) => JSON.stringify(data, null, 2);
26
+ export interface NativeViews {
27
+ select(title: string, choices: string[]): Promise<string | undefined>;
28
+ input(title: string, prefill?: string): Promise<string | undefined>;
29
+ show(text: string): Promise<void>;
30
+ confirm(title: string, text: string): Promise<boolean>;
31
+ notify(text: string): void;
32
+ current(): void;
33
+ signal: AbortSignal;
34
+ }
35
+ export class NativeHistory {
36
+ constructor(
37
+ readonly client: ServiceClient,
38
+ readonly storeId: string,
39
+ readonly ui: NativeViews,
40
+ ) {}
41
+ async detail(seed: ReadCursor): Promise<void> {
42
+ let cursor: ReadCursor | null = seed;
43
+ const previous: ReadCursor[] = [];
44
+ while (cursor) {
45
+ const page: DetailReadResult = value(
46
+ await this.client.detail(cursor, this.ui.signal),
47
+ );
48
+ this.ui.current();
49
+ await this.ui.show(
50
+ "kind" in page
51
+ ? `Detail proof progress — no content yet\n${pretty(page.coverage)}`
52
+ : `${page.content.field} • ${page.content.recordId} v${page.content.version}\nUTF-8 bytes ${pretty(page.range)} / ${page.content.bytes}\nCoverage ${pretty(page.coverage)}\n\n${page.text}`,
53
+ );
54
+ const action = await this.ui.select("Retained detail (not a preview)", [
55
+ ...(page.next ? ["Next detail page / proof"] : []),
56
+ ...(previous.length ? ["Previous detail page"] : []),
57
+ "Return",
58
+ ]);
59
+ if (action === "Next detail page / proof" && page.next) {
60
+ previous.push(cursor);
61
+ cursor = page.next;
62
+ } else if (action === "Previous detail page")
63
+ cursor = previous.pop() ?? null;
64
+ else return;
65
+ }
66
+ }
67
+ async graph(seed: GraphReadRequest) {
68
+ let request = seed;
69
+ for (;;) {
70
+ const page = value(await this.client.graph(request, this.ui.signal));
71
+ this.ui.current();
72
+ if (!("kind" in page)) return page.graph;
73
+ if (
74
+ (await this.ui.select(
75
+ `Graph proof progress • ${pretty(page.coverage)}`,
76
+ ["Continue graph proof", "Return"],
77
+ )) !== "Continue graph proof"
78
+ )
79
+ return;
80
+ request = page.next;
81
+ }
82
+ }
83
+ private cursor(scope: Scope, seq: string, ref: ContentRef): ReadCursor {
84
+ return {
85
+ storeId: this.storeId,
86
+ scope,
87
+ snapshotSeq: seq,
88
+ query: null,
89
+ recordId: ref.recordId,
90
+ recordVersion: ref.version,
91
+ field: ref.field,
92
+ offset: 0,
93
+ };
94
+ }
95
+ async record(record: HistoryRecord, request: ListRequest): Promise<void> {
96
+ await this.ui.show(pretty(record));
97
+ if (request.scope.kind === "workspace" || !request.snapshotSeq) return;
98
+ const scope = request.scope,
99
+ refs: ContentRef[] = [];
100
+ switch (record.kind) {
101
+ case "task":
102
+ if (record.plan) refs.push(record.plan);
103
+ break;
104
+ case "message":
105
+ if (record.finalText) refs.push(record.finalText);
106
+ break;
107
+ case "content":
108
+ refs.push(record.content);
109
+ break;
110
+ case "owner-fact": {
111
+ const fact = record.fact;
112
+ if (fact.kind === "changes" || fact.kind === "review")
113
+ refs.push(fact.content);
114
+ if (fact.kind === "check-settled" && fact.output)
115
+ refs.push(fact.output);
116
+ break;
117
+ }
118
+ case "run": {
119
+ const r = record.projection;
120
+ await this.scope({
121
+ kind: r.childRunId ? "child" : "run",
122
+ id: r.childRunId ?? r.runId,
123
+ });
124
+ return;
125
+ }
126
+ }
127
+ for (;;) {
128
+ const options = refs.map(
129
+ (r) => `${r.field} • ${r.recordId} v${r.version} • ${r.bytes} bytes`,
130
+ );
131
+ const selected = await this.ui.select(
132
+ "Recorded fields • full arguments/results are in Retained content",
133
+ [...options, "Retained content", "Return"],
134
+ );
135
+ if (selected === "Retained content")
136
+ await this.pages("contents", scope, request.snapshotSeq);
137
+ else if (selected && options.includes(selected))
138
+ await this.detail(
139
+ this.cursor(
140
+ scope,
141
+ request.snapshotSeq,
142
+ refs[options.indexOf(selected)],
143
+ ),
144
+ );
145
+ else return;
146
+ }
147
+ }
148
+ async pages(
149
+ reader: ReaderKind,
150
+ scope: ListRequest["scope"],
151
+ snapshotSeq: string | null = null,
152
+ chooseTask = false,
153
+ ): Promise<string | undefined> {
154
+ let request: ListRequest = {
155
+ reader,
156
+ storeId: this.storeId,
157
+ scope,
158
+ snapshotSeq,
159
+ after: null,
160
+ limit: 50,
161
+ };
162
+ const previous: ListRequest[] = [];
163
+ for (;;) {
164
+ const page = value(await this.client[reader](request, this.ui.signal));
165
+ this.ui.current();
166
+ // A first data page can select the committed snapshot too. Pin its exact
167
+ // request before inspecting a record or going Back; never repeat a null
168
+ // seed and silently rebase an already displayed list.
169
+ if (page.request.snapshotSeq !== null) request = page.request;
170
+ const records = "kind" in page ? [] : page.records;
171
+ const labels = records.map((r, i) => `${i + 1}. ${recordLabel(r)}`);
172
+ const action = await this.ui.select(
173
+ `${reader} • snapshot ${page.request.snapshotSeq ?? "selecting"} • ${"kind" in page ? page.kind : "retained page"} • coverage ${pretty(page.coverage)}`,
174
+ [
175
+ ...labels,
176
+ ...(page.next ? ["Next page / proof"] : []),
177
+ ...(previous.length ? ["Previous page"] : []),
178
+ "Return",
179
+ ],
180
+ );
181
+ if (action === "Next page / proof" && page.next) {
182
+ if (request.snapshotSeq !== null) previous.push(request);
183
+ request = page.next;
184
+ } else if (action === "Previous page")
185
+ request = previous.pop() ?? request;
186
+ else if (action && labels.includes(action)) {
187
+ const record = records[labels.indexOf(action)];
188
+ if (chooseTask && record.kind === "task") return record.owner.taskId;
189
+ await this.record(record, page.request);
190
+ } else return;
191
+ }
192
+ }
193
+ async search(scope: Scope, snapshotSeq: string): Promise<void> {
194
+ const query = await this.ui.input(
195
+ "Literal case-sensitive search (1–1024 UTF-8 bytes)",
196
+ );
197
+ if (!query) return;
198
+ let cursor: ReadCursor = {
199
+ storeId: this.storeId,
200
+ scope,
201
+ snapshotSeq,
202
+ query,
203
+ recordId: null,
204
+ recordVersion: null,
205
+ field: null,
206
+ offset: 0,
207
+ };
208
+ const previous: ReadCursor[] = [];
209
+ for (;;) {
210
+ const page = value(await this.client.search(cursor, this.ui.signal));
211
+ this.ui.current();
212
+ const labels = page.hits.map(
213
+ (hit, i) =>
214
+ `${i + 1}. ${hit.content.recordId} v${hit.content.version} ${hit.content.field} bytes ${hit.range.start}–${hit.range.end} • ${diagnosticText(hit.preview, 160)}`,
215
+ );
216
+ const selected = await this.ui.select(
217
+ `Search ${JSON.stringify(query)} • ${page.next ? "more retained ranges remain (zero hits is not completion)" : "retained scan ended"} • ${pretty(page.coverage)}`,
218
+ [
219
+ ...labels,
220
+ ...(page.next ? ["Next search page / proof"] : []),
221
+ ...(previous.length ? ["Previous search page"] : []),
222
+ "Return",
223
+ ],
224
+ );
225
+ if (selected === "Next search page / proof" && page.next) {
226
+ previous.push(cursor);
227
+ cursor = page.next;
228
+ } else if (selected === "Previous search page")
229
+ cursor = previous.pop() ?? cursor;
230
+ else if (selected && labels.includes(selected)) {
231
+ const hit = page.hits[labels.indexOf(selected)];
232
+ await this.ui.show(
233
+ `Exact hit (UTF-8 range, not character indexes)\n${pretty(hit)}`,
234
+ );
235
+ await this.detail(hit.cursor);
236
+ } else return;
237
+ }
238
+ }
239
+ async export(scope: Scope, snapshotSeq: string): Promise<void> {
240
+ const format = await this.ui.select(
241
+ "Full retained export • credentials/private auth are excluded",
242
+ ["ndjson", "markdown"],
243
+ );
244
+ if (format !== "ndjson" && format !== "markdown") return;
245
+ const destination = await this.ui.input(
246
+ "Absolute NEW export file path (no overwrite; partial files preserved)",
247
+ );
248
+ if (!destination) return;
249
+ if (
250
+ !(await this.ui.confirm(
251
+ "Write full verified export?",
252
+ `${JSON.stringify(destination)}\n${pretty(scope)} at ${snapshotSeq}\nInterrupted or incomplete output is not a complete archive.`,
253
+ ))
254
+ )
255
+ return;
256
+ const request: ExportRequest = {
257
+ storeId: this.storeId,
258
+ scope,
259
+ snapshotSeq,
260
+ format,
261
+ };
262
+ try {
263
+ const result = await exportNative(
264
+ this.client,
265
+ request,
266
+ destination,
267
+ this.ui.signal,
268
+ );
269
+ this.ui.current();
270
+ this.ui.notify(
271
+ `Export ${result}: count/digest/trailer/EOF verified; ${JSON.stringify(destination)}. Coverage is independent of original-source completeness.`,
272
+ );
273
+ } catch {
274
+ this.ui.notify(
275
+ `Export unverified/incomplete or destination refused: ${JSON.stringify(destination)}. Any partial file is preserved; no overwrite or cleanup.`,
276
+ );
277
+ }
278
+ }
279
+ async scope(scope: Scope): Promise<void> {
280
+ for (;;) {
281
+ const snapshot: Snapshot = value(
282
+ await this.client.snapshot(scope, this.ui.signal),
283
+ );
284
+ this.ui.current();
285
+ const selected = await this.ui.select(
286
+ `${scope.kind} ${scope.id} • committed ${snapshot.committedSeq} (observation only)`,
287
+ [
288
+ "Status / graph",
289
+ "Runs / children",
290
+ "Messages",
291
+ "Tools",
292
+ "Retained content",
293
+ "Timeline",
294
+ "Checks history",
295
+ "Changes history",
296
+ "Human review history",
297
+ "Search",
298
+ "Export",
299
+ "Return",
300
+ ],
301
+ );
302
+ if (selected === "Status / graph") {
303
+ await this.ui.show(pretty(snapshot));
304
+ for (const r of snapshot.runs) {
305
+ if (
306
+ !r.childRunId &&
307
+ r.admission.graph &&
308
+ (await this.ui.confirm("Read recorded graph?", r.runId))
309
+ ) {
310
+ const graph = await this.graph({
311
+ storeId: this.storeId,
312
+ scope,
313
+ snapshotSeq: snapshot.committedSeq,
314
+ graph: r.admission.graph,
315
+ });
316
+ if (graph) await this.ui.show(pretty(graph));
317
+ }
318
+ }
319
+ } else if (selected === "Search")
320
+ await this.search(scope, snapshot.committedSeq);
321
+ else if (selected === "Export")
322
+ await this.export(scope, snapshot.committedSeq);
323
+ else {
324
+ const readers: Record<string, ReaderKind> = {
325
+ "Runs / children": "runs",
326
+ Messages: "messages",
327
+ Tools: "tools",
328
+ "Retained content": "contents",
329
+ Timeline: "events",
330
+ "Checks history": "checks",
331
+ "Changes history": "changes",
332
+ "Human review history": "reviews",
333
+ };
334
+ if (!selected || !readers[selected]) return;
335
+ await this.pages(readers[selected], scope, snapshot.committedSeq);
336
+ }
337
+ }
338
+ }
339
+ }
340
+ export function recordLabel(r: HistoryRecord): string {
341
+ switch (r.kind) {
342
+ case "task":
343
+ return `${r.owner.taskId} • ${diagnosticText(r.goal, 160)}`;
344
+ case "run":
345
+ return `${r.projection.childRunId ?? r.projection.runId} • ${r.projection.assignmentId ?? "parent"} • ${r.projection.lifecycle} / ${r.projection.outcome ?? "unsettled"}`;
346
+ case "message":
347
+ return `${r.owner.entity.id} • ${r.role} • ${r.state}`;
348
+ case "tool":
349
+ return `${r.owner.entity.id} • ${diagnosticText(r.name)} • SDK error ${r.result?.isError ?? "unavailable"} (not a parent check)`;
350
+ case "content":
351
+ return `${r.content.recordId} v${r.content.version} • ${r.content.field} • ${r.content.bytes} retained bytes`;
352
+ case "omitted":
353
+ return `${r.owner.entity.id} • omitted`;
354
+ case "owner-fact":
355
+ return `seq ${r.committedSeq} • observed ${r.observedMs}ms • ${r.fact.kind}`;
356
+ case "capture-fact":
357
+ return `seq ${r.committedSeq} • ${r.capture.event.kind} • child ${r.link.childRunId}`;
358
+ }
359
+ }