@abelxiaoxing/cadence 1.8.4 → 2.0.1
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.
- package/README.md +324 -117
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/agents/design-explorer.md +14 -18
- package/agents/diagnosis-worker.md +14 -14
- package/agents/implementation-worker.md +16 -17
- package/config/workflow-task.example.json +7 -0
- package/docs/remote-access.md +60 -0
- package/docs/reuse-and-distribution.md +44 -0
- package/docs/service-management.md +108 -0
- package/licenses/bundled-web.txt +6245 -0
- package/licenses/pi-web-ui-MIT.txt +21 -0
- package/licenses/pi-web-ui-reuse.md +22 -0
- package/package.json +92 -20
- package/src/index.ts +21 -1802
- package/src/native-build.ts +6 -0
- package/src/native-client.ts +103 -0
- package/src/native-export.ts +58 -0
- package/src/native-history.ts +359 -0
- package/src/operator-cli.mjs +453 -1937
- package/src/plan-proposal.ts +87 -0
- package/src/sdk-worker.mjs +549 -0
- package/src/service/contracts/active.ts +82 -0
- package/src/service/contracts/budget.ts +148 -0
- package/src/service/contracts/build.ts +67 -0
- package/src/service/contracts/capture.ts +48 -0
- package/src/service/contracts/core.ts +33 -0
- package/src/service/contracts/current.ts +261 -0
- package/src/service/contracts/domain.ts +68 -0
- package/src/service/contracts/history.ts +2245 -0
- package/src/service/contracts/models.ts +139 -0
- package/src/service/coordination.ts +862 -0
- package/src/service/private-files.ts +204 -0
- package/src/service-cli.mjs +5592 -0
- package/src/service-client.ts +747 -0
- package/src/service-contract.ts +245 -0
- package/src/service-host.mjs +22382 -0
- package/src/service-http-client.ts +425 -0
- package/src/service-wire.ts +372 -0
- package/src/task-shell.ts +1045 -0
- package/src/task-store.ts +479 -0
- package/src/task-types.ts +184 -0
- package/src/verification-diagnostics.ts +29 -72
- package/src/worker-capture.mjs +1197 -0
- package/src/worker-public.mjs +1753 -0
- package/src/worker-redaction.mjs +608 -0
- package/src/worker-tools.mjs +202 -0
- package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/web/dist/assets/cadence.css +2 -0
- package/web/dist/assets/cadence.js +7 -0
- package/web/dist/assets/markdown.js +296 -0
- package/web/dist/assets/react.js +9 -0
- package/web/dist/assets/rolldown-runtime.js +1 -0
- package/web/dist/assets/timeline-client.js +41 -0
- package/web/dist/assets/vis-timeline.css +1274 -0
- package/web/dist/assets/vis-timeline.js +32 -0
- package/web/dist/index.html +18 -0
- package/web/dist/manifest.json +505 -0
- package/config/plan-draft.example.json +0 -125
- package/config/plan-draft.multiple-tasks.example.json +0 -375
- package/config/plan-draft.quick.example.json +0 -78
- package/prompts/abel-design.md +0 -382
- package/prompts/abel-diagnose.md +0 -71
- package/prompts/abel-implement.md +0 -284
- package/prompts/abel-init.md +0 -52
- package/src/activation.ts +0 -44
- package/src/activity-contracts.ts +0 -82
- package/src/agent-registry.ts +0 -53
- package/src/apply-transaction.ts +0 -1437
- package/src/artifact-store.ts +0 -213
- package/src/build-windows-job.mjs +0 -95
- package/src/candidate-artifact.ts +0 -143
- package/src/candidate-context.ts +0 -289
- package/src/candidate-patch.ts +0 -271
- package/src/candidate-workspace.ts +0 -80
- package/src/canonical.ts +0 -16
- package/src/change-contract-schema.ts +0 -191
- package/src/change-contract.ts +0 -324
- package/src/change-verification.ts +0 -1617
- package/src/contracts.ts +0 -2466
- package/src/control-contracts.ts +0 -382
- package/src/delivery-compiler.ts +0 -1805
- package/src/delivery-revision.ts +0 -42
- package/src/design-control.ts +0 -1299
- package/src/design-diagnostics.ts +0 -181
- package/src/design-journal.ts +0 -1773
- package/src/directory-sync.ts +0 -36
- package/src/durable-contracts.ts +0 -227
- package/src/durable-workflow.ts +0 -822
- package/src/evidence-draft.ts +0 -122
- package/src/execution-profile.ts +0 -64
- package/src/execution-retention.ts +0 -77
- package/src/file-snapshot.ts +0 -163
- package/src/implement-continuation.ts +0 -559
- package/src/implement-graph.ts +0 -690
- package/src/implement-plan.ts +0 -72
- package/src/install-config.d.mts +0 -6
- package/src/isolation-backend.ts +0 -353
- package/src/model-source.ts +0 -22
- package/src/openspec-cli.ts +0 -494
- package/src/operator-cli-entry.mjs +0 -24
- package/src/operator-tools.ts +0 -309
- package/src/package-candidate-process.ts +0 -206
- package/src/package-candidate.ts +0 -24
- package/src/package-delivery.ts +0 -486
- package/src/package-state.ts +0 -320
- package/src/package-verification.ts +0 -635
- package/src/package-workflow.ts +0 -305
- package/src/packet-runtime.ts +0 -459
- package/src/phase-execution.ts +0 -2163
- package/src/pi-adapter.ts +0 -19
- package/src/plan-draft-summary.ts +0 -527
- package/src/plan-draft.ts +0 -502
- package/src/remove-path.ts +0 -32
- package/src/run-state.ts +0 -420
- package/src/run-store.ts +0 -1179
- package/src/safe-path.ts +0 -77
- package/src/scoped-grep-worker.mjs +0 -23
- package/src/scoped-tools.ts +0 -775
- package/src/single-task-draft.ts +0 -142
- package/src/sqlite-schema.ts +0 -171
- package/src/state-root.ts +0 -215
- package/src/storage-schema.ts +0 -360
- package/src/subagent-activity.ts +0 -1417
- package/src/subagent-process.ts +0 -476
- package/src/task-ledger.ts +0 -1338
- package/src/transport-budget.ts +0 -50
- package/src/verification-capability.ts +0 -1191
- package/src/verification-environment-io.ts +0 -389
- package/src/verification-environment.ts +0 -162
- package/src/verification-identity.ts +0 -77
- package/src/verification-scan.ts +0 -64
- package/src/windows-job-backend.ts +0 -419
- package/src/windows-job.c +0 -149
- package/src/workflow-engine.ts +0 -28
- package/src/workflow-policy.ts +0 -1670
- package/src/workflow-recovery-policy.ts +0 -97
- package/src/workflow-scheduling.ts +0 -105
- package/src/workflow-state-machine.ts +0 -4341
- package/src/workflow-status.ts +0 -576
- package/src/workspace-io-entry.mjs +0 -76
- package/src/workspace-io-worker.mjs +0 -1312
- package/src/workspace-io.ts +0 -89
- package/src/workspace-store.ts +0 -634
|
@@ -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
|
+
}
|