@itookit/dsht 0.3.7 → 0.5.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.i18n.yaml +2 -2
- package/README.md +31 -11
- package/README.zh.md +31 -11
- package/dist/cli/dsht.js +207 -19
- package/dist/cli/startup.d.ts +40 -0
- package/dist/cli/startup.js +295 -0
- package/dist/cli/trace-summary.d.ts +78 -0
- package/dist/cli/trace-summary.js +241 -0
- package/dist/cli/verifier.d.ts +60 -0
- package/dist/cli/verifier.js +242 -0
- package/dist/contracts.d.ts +344 -0
- package/dist/contracts.js +1 -0
- package/dist/controller/commands.d.ts +47 -0
- package/dist/controller/commands.js +322 -0
- package/dist/controller/connection.d.ts +11 -29
- package/dist/controller/connection.js +26 -60
- package/dist/controller/controller.d.ts +619 -164
- package/dist/controller/controller.js +1420 -141
- package/dist/controller/index.d.ts +8 -1
- package/dist/controller/index.js +5 -0
- package/dist/controller/loop-contract.d.ts +136 -0
- package/dist/controller/loop-contract.js +308 -0
- package/dist/controller/loop-prompts-schema.d.ts +56 -0
- package/dist/controller/loop-prompts-schema.js +144 -0
- package/dist/controller/loop-prompts.d.ts +55 -0
- package/dist/controller/loop-prompts.generated.d.ts +104 -0
- package/dist/controller/loop-prompts.generated.js +185 -0
- package/dist/controller/loop-prompts.js +104 -0
- package/dist/controller/loop-protocols.d.ts +39 -0
- package/dist/controller/loop-protocols.js +115 -0
- package/dist/controller/loop.d.ts +275 -0
- package/dist/controller/loop.js +378 -0
- package/dist/controller/prompts.d.ts +54 -0
- package/dist/controller/prompts.js +162 -0
- package/dist/controller/trace-log.d.ts +45 -0
- package/dist/controller/trace-log.js +144 -0
- package/dist/controller/verifier.d.ts +126 -0
- package/dist/controller/verifier.js +75 -0
- package/dist/cost/index.d.ts +1 -1
- package/dist/cost/index.js +1 -1
- package/dist/cost/ledger.d.ts +0 -1
- package/dist/cost/ledger.js +0 -1
- package/dist/json.d.ts +18 -0
- package/dist/json.js +19 -0
- package/dist/references.d.ts +25 -0
- package/dist/references.js +26 -0
- package/dist/session/connection-view.d.ts +2 -11
- package/dist/session/controller.d.ts +82 -72
- package/dist/session/controller.js +211 -209
- package/dist/session/history.d.ts +9 -1
- package/dist/session/history.js +1 -9
- package/dist/session/index.d.ts +9 -4
- package/dist/session/index.js +7 -3
- package/dist/session/info.d.ts +25 -52
- package/dist/session/info.js +39 -25
- package/dist/session/markdown.js +1 -1
- package/dist/session/math.js +1 -1
- package/dist/session/mutation-gate.d.ts +51 -0
- package/dist/session/mutation-gate.js +73 -0
- package/dist/session/navigation.d.ts +2 -89
- package/dist/session/navigation.js +2 -129
- package/dist/session/peek.d.ts +38 -0
- package/dist/session/peek.js +103 -0
- package/dist/session/references.d.ts +2 -20
- package/dist/session/references.js +1 -26
- package/dist/session/runtime.d.ts +26 -0
- package/dist/session/runtime.js +28 -0
- package/dist/session/telemetry.d.ts +12 -13
- package/dist/session/telemetry.js +27 -58
- package/dist/session/transcript.d.ts +0 -6
- package/dist/session/transcript.js +2 -15
- package/dist/session/types.d.ts +25 -0
- package/dist/session/types.js +0 -1
- package/dist/session-title.d.ts +9 -0
- package/dist/session-title.js +21 -0
- package/dist/shell/controller.d.ts +97 -0
- package/dist/shell/controller.js +158 -0
- package/dist/shell/index.d.ts +5 -0
- package/dist/shell/index.js +3 -0
- package/dist/shell/runner.d.ts +38 -0
- package/dist/shell/runner.js +147 -0
- package/dist/slash/index.d.ts +10 -0
- package/dist/slash/index.js +7 -0
- package/dist/slash/parse.d.ts +166 -0
- package/dist/slash/parse.js +259 -0
- package/dist/slash/pipeline.d.ts +140 -0
- package/dist/slash/pipeline.js +115 -0
- package/dist/slash/registry.d.ts +88 -0
- package/dist/slash/registry.js +177 -0
- package/dist/state.d.ts +14 -4
- package/dist/state.js +3 -2
- package/dist/text.d.ts +28 -0
- package/dist/text.js +55 -0
- package/dist/transport/events.d.ts +104 -0
- package/dist/transport/events.js +149 -0
- package/dist/transport/wire.d.ts +9 -17
- package/dist/transport/wire.js +2 -27
- package/dist/ui/app.js +865 -431
- package/dist/ui/chat/header.js +1 -1
- package/dist/ui/chat/history-view.d.ts +1 -1
- package/dist/ui/chat/history-view.js +1 -1
- package/dist/ui/chat/loop-status.d.ts +11 -0
- package/dist/ui/chat/loop-status.js +28 -0
- package/dist/ui/chat/navigation-model.d.ts +86 -0
- package/dist/ui/chat/navigation-model.js +107 -0
- package/dist/ui/chat/shell-view.d.ts +47 -0
- package/dist/ui/chat/shell-view.js +145 -0
- package/dist/ui/chat/status.d.ts +47 -3
- package/dist/ui/chat/status.js +65 -50
- package/dist/ui/chat/viewport.d.ts +1 -1
- package/dist/ui/dialogs/cost.d.ts +21 -4
- package/dist/ui/dialogs/cost.js +7 -12
- package/dist/ui/dialogs/index.d.ts +22 -5
- package/dist/ui/dialogs/index.js +19 -3
- package/dist/ui/dialogs/loop.d.ts +43 -0
- package/dist/ui/dialogs/loop.js +224 -0
- package/dist/ui/dialogs/peek.d.ts +25 -0
- package/dist/ui/dialogs/peek.js +35 -0
- package/dist/ui/dialogs/picker.d.ts +2 -0
- package/dist/ui/dialogs/picker.js +4 -2
- package/dist/ui/input/mouse.d.ts +12 -2
- package/dist/ui/input/mouse.js +20 -7
- package/dist/ui/input/references.d.ts +1 -1
- package/dist/ui/status/model.d.ts +7 -0
- package/dist/ui/status/model.js +5 -0
- package/dist/ui/theme/index.d.ts +6 -1
- package/dist/ui/theme/index.js +2 -1
- package/package.json +6 -4
- package/dist/ui/commands/parse.d.ts +0 -99
- package/dist/ui/commands/parse.js +0 -126
- package/dist/ui/commands/registry.d.ts +0 -33
- package/dist/ui/commands/registry.js +0 -73
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** Append-only diagnostic trace of connection, screen and selection transitions.
|
|
2
|
+
*
|
|
3
|
+
* The memory log answers "is retained content growing"; this log answers "why did the client end up
|
|
4
|
+
* where it is" — every reconnect, picker and selection change is written with the values it moved
|
|
5
|
+
* between, so a jump that only happens in the field can be read instead of guessed at. Events carry
|
|
6
|
+
* identifiers and screen names only, never prompt, tool or session text, and a failing log stops
|
|
7
|
+
* itself instead of breaking the client.
|
|
8
|
+
*/
|
|
9
|
+
import { dirname } from 'node:path';
|
|
10
|
+
import { appendPrivateFile, createPrivateFile, ensureDirectory, readText, writePrivateFile } from "../storage/index.js";
|
|
11
|
+
import { errorText } from "../transport/wire.js";
|
|
12
|
+
/** Events kept; reaching the cap rewrites the file with just these lines. */
|
|
13
|
+
const KEEP_EVENTS = 2000;
|
|
14
|
+
/** First line of the file, so a reader knows what the lines are without reading the source. */
|
|
15
|
+
const HEADER = '# dsht trace: one JSON event per line, oldest first\n';
|
|
16
|
+
/** Events whose `begin` opens a span a later phase closes; only these constrain where a cut may fall. */
|
|
17
|
+
const SPAN_EVENTS = new Set(['command', 'loop', 'generation']);
|
|
18
|
+
/** Phases that close a span opened by `begin`. */
|
|
19
|
+
const CLOSE_PHASES = new Set(['end', 'failed', 'cancelled', 'ended']);
|
|
20
|
+
/** Identifier fields, in priority order, that let a span's `begin` and its close be matched. */
|
|
21
|
+
const SPAN_IDS = ['commandId', 'operationId', 'turnId', 'runId', 'generationId'];
|
|
22
|
+
/** Read the span one line belongs to, if any.
|
|
23
|
+
*
|
|
24
|
+
* A line without a known event or phase carries no span: it is an ordinary event whose position in the
|
|
25
|
+
* file means nothing to a reader, so compaction may cut on either side of it. A span without its own id
|
|
26
|
+
* — `generation` has none — still pairs by event name, because only one of them is ever open at a time
|
|
27
|
+
* and dropping that pairing would erase the marker that separates two connections.
|
|
28
|
+
* @param line - One JSON line of the trace.
|
|
29
|
+
* @returns The span key and whether it opens or closes, or undefined.
|
|
30
|
+
*/
|
|
31
|
+
function spanOf(line) {
|
|
32
|
+
let record;
|
|
33
|
+
try {
|
|
34
|
+
record = JSON.parse(line);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const event = record.event;
|
|
40
|
+
const phase = record.phase;
|
|
41
|
+
if (typeof event !== 'string' || !SPAN_EVENTS.has(event) || typeof phase !== 'string')
|
|
42
|
+
return undefined;
|
|
43
|
+
const open = phase === 'begin';
|
|
44
|
+
if (!open && !CLOSE_PHASES.has(phase))
|
|
45
|
+
return undefined;
|
|
46
|
+
const id = SPAN_IDS.map(field => record[field]).find(value => typeof value === 'string');
|
|
47
|
+
return { key: `${event}:${id ?? ''}`, open };
|
|
48
|
+
}
|
|
49
|
+
/** The index the newest `keep` lines start at, moved forward past any split lifecycle span.
|
|
50
|
+
*
|
|
51
|
+
* A span opened before the cut and closed after it would be kept as a close without its begin, so the
|
|
52
|
+
* cut moves to just after that close and the check repeats: the newly dropped region may itself open a
|
|
53
|
+
* span that closes later. A span whose close never appears cannot be split, so it does not move the
|
|
54
|
+
* cut — that is the in-flight command the file exists to show.
|
|
55
|
+
* @param lines - Every line this process appended, oldest first.
|
|
56
|
+
* @param keep - How many of the newest lines the caller wants to keep.
|
|
57
|
+
* @returns Index of the first line to keep; always at most `lines.length - keep`.
|
|
58
|
+
*/
|
|
59
|
+
export function compactCut(lines, keep = KEEP_EVENTS) {
|
|
60
|
+
let cut = Math.max(0, lines.length - keep);
|
|
61
|
+
for (;;) {
|
|
62
|
+
const open = new Set();
|
|
63
|
+
for (let index = 0; index < cut; index += 1) {
|
|
64
|
+
const span = spanOf(lines[index]);
|
|
65
|
+
if (span === undefined)
|
|
66
|
+
continue;
|
|
67
|
+
if (span.open)
|
|
68
|
+
open.add(span.key);
|
|
69
|
+
else
|
|
70
|
+
open.delete(span.key);
|
|
71
|
+
}
|
|
72
|
+
if (open.size === 0)
|
|
73
|
+
return cut;
|
|
74
|
+
let moved = -1;
|
|
75
|
+
for (let index = cut; index < lines.length; index += 1) {
|
|
76
|
+
const span = spanOf(lines[index]);
|
|
77
|
+
if (span !== undefined && !span.open && open.has(span.key))
|
|
78
|
+
moved = index + 1;
|
|
79
|
+
}
|
|
80
|
+
if (moved === -1)
|
|
81
|
+
return cut;
|
|
82
|
+
cut = moved;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Serialized append-only trace; the file holds at most `KEEP_EVENTS` lines, fewer when a lifecycle span would be split.
|
|
86
|
+
*
|
|
87
|
+
* Writes are chained rather than awaited by the caller, because transitions are published from
|
|
88
|
+
* synchronous state updates. A failed write is remembered and reported on the next event instead of
|
|
89
|
+
* throwing into the controller.
|
|
90
|
+
*/
|
|
91
|
+
export class TraceLog {
|
|
92
|
+
path;
|
|
93
|
+
queue = Promise.resolve();
|
|
94
|
+
lines = [];
|
|
95
|
+
directoryEnsured = false;
|
|
96
|
+
/** Last write failure, when the log stopped growing. */
|
|
97
|
+
error;
|
|
98
|
+
constructor(path) {
|
|
99
|
+
this.path = path;
|
|
100
|
+
}
|
|
101
|
+
/** Append one event; an absent path makes this a no-op. */
|
|
102
|
+
record(event) {
|
|
103
|
+
if (this.path === undefined)
|
|
104
|
+
return;
|
|
105
|
+
const line = `${JSON.stringify({ time: new Date().toISOString(), ...event })}\n`;
|
|
106
|
+
this.lines.push(line);
|
|
107
|
+
this.queue = this.queue.then(() => this.append(line))
|
|
108
|
+
.catch(error => { this.error = errorText(error); });
|
|
109
|
+
}
|
|
110
|
+
/** Wait for queued writes, so a shutdown does not lose the last events. */
|
|
111
|
+
async settle() { await this.queue; }
|
|
112
|
+
/** Write one line, then bound the file when the cap is reached. */
|
|
113
|
+
async append(line) {
|
|
114
|
+
if (!this.directoryEnsured) {
|
|
115
|
+
await ensureDirectory(dirname(this.path));
|
|
116
|
+
this.directoryEnsured = true;
|
|
117
|
+
}
|
|
118
|
+
// Seeding a header only affects a new file; an existing trace is appended to.
|
|
119
|
+
await createPrivateFile(this.path, HEADER);
|
|
120
|
+
await appendPrivateFile(this.path, line);
|
|
121
|
+
this.error = undefined;
|
|
122
|
+
if (this.lines.length >= KEEP_EVENTS)
|
|
123
|
+
await this.compact();
|
|
124
|
+
}
|
|
125
|
+
/** Rewrite the file with the header and the newest kept lines, bounding its size.
|
|
126
|
+
*
|
|
127
|
+
* Older runs are not read back: `lines` is what this process appended, which bounds both the file
|
|
128
|
+
* and the read that rewrites it. The cut is pairing-aware, so a kept `begin` is never orphaned from
|
|
129
|
+
* its close and a kept close never loses its begin.
|
|
130
|
+
*/
|
|
131
|
+
async compact() {
|
|
132
|
+
const kept = this.lines.slice(compactCut(this.lines, KEEP_EVENTS));
|
|
133
|
+
await writePrivateFile(this.path, HEADER + kept.join(''));
|
|
134
|
+
this.lines = kept;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Read a trace back, so a test or a diagnostic tool can assert what was recorded.
|
|
138
|
+
* @param path - Trace file to read.
|
|
139
|
+
* @returns Every non-empty line, oldest first.
|
|
140
|
+
*/
|
|
141
|
+
export async function readTrace(path) {
|
|
142
|
+
const text = await readText(path);
|
|
143
|
+
return text === undefined ? [] : text.split('\n').filter(line => line !== '');
|
|
144
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { LoopResult } from './loop.ts';
|
|
2
|
+
/** Identity one verification carries, so a file can never be read as another run's verdict.
|
|
3
|
+
*
|
|
4
|
+
* Two clients reviewing the same step would otherwise write and delete the same path, and the
|
|
5
|
+
* kind/step/attempt triple cannot tell them apart. The sequence is the finer half: one `attempt`
|
|
6
|
+
* may start several verification tasks (a failure retry, an operator answer), and only the task
|
|
7
|
+
* that is still awaited may decide the attempt.
|
|
8
|
+
* @param runId - Identity of one loop run, unique per client run.
|
|
9
|
+
* @param kind - Protocol kind.
|
|
10
|
+
* @param step - Step in flight.
|
|
11
|
+
* @param attempt - Attempt in flight.
|
|
12
|
+
* @param seq - This verification task's sequence within the run.
|
|
13
|
+
* @returns The identity string embedded in the verdict file.
|
|
14
|
+
*/
|
|
15
|
+
export declare function verificationId(runId: string, kind: string, step: number, attempt: number, seq: number): string;
|
|
16
|
+
/** Directory holding one run's verdicts, so a whole review can be audited or removed as a group.
|
|
17
|
+
* @param directory - Workspace directory the review runs in.
|
|
18
|
+
* @param runId - Identity of the run.
|
|
19
|
+
* @returns The absolute directory.
|
|
20
|
+
*/
|
|
21
|
+
export declare function verdictDirectory(directory: string, runId: string): string;
|
|
22
|
+
/** Path of the verdict one verification task must write.
|
|
23
|
+
* @param directory - Workspace directory the review runs in.
|
|
24
|
+
* @param runId - Identity of the run; part of the path so concurrent runs never collide.
|
|
25
|
+
* @param kind - Protocol kind.
|
|
26
|
+
* @param step - Step in flight.
|
|
27
|
+
* @param attempt - Attempt in flight.
|
|
28
|
+
* @param seq - This verification task's sequence within the run; part of the name so a retry
|
|
29
|
+
* cannot be read as, or overwrite, the task it replaced.
|
|
30
|
+
* @returns The absolute verdict path.
|
|
31
|
+
*/
|
|
32
|
+
export declare function verdictFile(directory: string, runId: string, kind: string, step: number, attempt: number, seq: number): string;
|
|
33
|
+
/** One round handed to an independent verifier. */
|
|
34
|
+
export interface VerifierRequest {
|
|
35
|
+
/** Identity of this verification; the verdict file must declare it back. */
|
|
36
|
+
verificationId: string;
|
|
37
|
+
/** Protocol kind the verdict must declare, so a stale file cannot score this round. */
|
|
38
|
+
kind: string;
|
|
39
|
+
/** Step in flight. */
|
|
40
|
+
step: number;
|
|
41
|
+
/** Attempt in flight. */
|
|
42
|
+
attempt: number;
|
|
43
|
+
/** Prompt the verifier session receives, built by the protocol. */
|
|
44
|
+
prompt: string;
|
|
45
|
+
/** Session title, so a reader can tell verifier sessions apart in the host's list. */
|
|
46
|
+
title: string;
|
|
47
|
+
/** Absolute path the verifier must write its verdict to. */
|
|
48
|
+
file: string;
|
|
49
|
+
/** Absolute path of the workspace the artifact belongs to.
|
|
50
|
+
*
|
|
51
|
+
* Declared per request instead of inferred from the verifier's own working directory: the
|
|
52
|
+
* reviewed tree and the process that happens to run the check are different things, and only the
|
|
53
|
+
* caller knows which workspace this round is about.
|
|
54
|
+
*/
|
|
55
|
+
workspace: string;
|
|
56
|
+
/** Workspace file the verifier must not change, when the protocol names one. */
|
|
57
|
+
artifact?: string;
|
|
58
|
+
}
|
|
59
|
+
/** What a host is waiting for when the verifier cannot answer by itself. */
|
|
60
|
+
export interface VerificationHumanRequest {
|
|
61
|
+
/** `approval` or `question`, as the host reported it. */
|
|
62
|
+
kind: string;
|
|
63
|
+
/** One line a reader can act on. */
|
|
64
|
+
text: string;
|
|
65
|
+
}
|
|
66
|
+
/** Marker a child prints on the line that reports an unanswerable human request.
|
|
67
|
+
*
|
|
68
|
+
* The child sees the interaction on its own session and exits; the parent parses the same line back
|
|
69
|
+
* out of the child's output. One definition keeps both sides from drifting.
|
|
70
|
+
*/
|
|
71
|
+
export declare const NEEDS_HUMAN_MARKER = "dsht-verify-needs-human:";
|
|
72
|
+
/** Render the line the child logs and the parent parses.
|
|
73
|
+
* @param request - What the host is waiting for.
|
|
74
|
+
* @returns The marker line.
|
|
75
|
+
*/
|
|
76
|
+
export declare function needsHumanLine(request: VerificationHumanRequest): string;
|
|
77
|
+
/** Read that line back, ignoring every other line the child prints.
|
|
78
|
+
* @param line - One child output line.
|
|
79
|
+
* @returns The request, or undefined when the line is not this marker.
|
|
80
|
+
*/
|
|
81
|
+
export declare function parseNeedsHumanLine(line: string): VerificationHumanRequest | undefined;
|
|
82
|
+
/** What one verification produced.
|
|
83
|
+
*
|
|
84
|
+
* A missing verdict is not a failed review: the reviewed session produced work and nobody judged it.
|
|
85
|
+
* The two are kept apart so an outage never spends an attempt and never lets the reviewer pass itself.
|
|
86
|
+
*/
|
|
87
|
+
export type VerifierOutcome =
|
|
88
|
+
/** An independent verdict was produced and can decide the attempt. */
|
|
89
|
+
{
|
|
90
|
+
type: 'verified';
|
|
91
|
+
result: LoopResult;
|
|
92
|
+
sessionId: string;
|
|
93
|
+
}
|
|
94
|
+
/** The verifier could not judge: no session, no child, no verdict, or a timeout.
|
|
95
|
+
*
|
|
96
|
+
* `retryable: false` means the same failure will not fix itself — a remote task may still be
|
|
97
|
+
* running, or the client is configured wrong — so the controller reports it instead of spending
|
|
98
|
+
* its retry budget. Absent means the failure is treated as transient.
|
|
99
|
+
*/
|
|
100
|
+
| {
|
|
101
|
+
type: 'unavailable';
|
|
102
|
+
reason: string;
|
|
103
|
+
sessionId?: string;
|
|
104
|
+
retryable?: boolean;
|
|
105
|
+
}
|
|
106
|
+
/** The review itself was cancelled, so there is nothing to decide. */
|
|
107
|
+
| {
|
|
108
|
+
type: 'cancelled';
|
|
109
|
+
}
|
|
110
|
+
/** The host is waiting for a human; the verifier session's turn was cancelled, not judged. */
|
|
111
|
+
| {
|
|
112
|
+
type: 'needs-human';
|
|
113
|
+
request: VerificationHumanRequest;
|
|
114
|
+
sessionId: string;
|
|
115
|
+
};
|
|
116
|
+
/** Judge one round independently. */
|
|
117
|
+
export interface VerifierPort {
|
|
118
|
+
/** Which harness judges: `dsht` for the forked client, a future adapter names itself here. */
|
|
119
|
+
readonly name: string;
|
|
120
|
+
/** Run one verification to completion, or to cancellation.
|
|
121
|
+
* @param request - Round identity, prompt and verdict path.
|
|
122
|
+
* @param signal - Cancels the run and its child process.
|
|
123
|
+
* @returns The verdict, or a note explaining why there is none.
|
|
124
|
+
*/
|
|
125
|
+
verify(request: VerifierRequest, signal: AbortSignal): Promise<VerifierOutcome>;
|
|
126
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Independent verification of one scored round, as a capability rather than a mechanism.
|
|
2
|
+
*
|
|
3
|
+
* The loop owns when a round is judged; this port owns how. The implementation runs a child `dsht`
|
|
4
|
+
* against a session of its own and reads the verdict file that session writes, but the controller
|
|
5
|
+
* only ever sees a port, so a test can decide a round without spawning anything.
|
|
6
|
+
*/
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
/** Identity one verification carries, so a file can never be read as another run's verdict.
|
|
9
|
+
*
|
|
10
|
+
* Two clients reviewing the same step would otherwise write and delete the same path, and the
|
|
11
|
+
* kind/step/attempt triple cannot tell them apart. The sequence is the finer half: one `attempt`
|
|
12
|
+
* may start several verification tasks (a failure retry, an operator answer), and only the task
|
|
13
|
+
* that is still awaited may decide the attempt.
|
|
14
|
+
* @param runId - Identity of one loop run, unique per client run.
|
|
15
|
+
* @param kind - Protocol kind.
|
|
16
|
+
* @param step - Step in flight.
|
|
17
|
+
* @param attempt - Attempt in flight.
|
|
18
|
+
* @param seq - This verification task's sequence within the run.
|
|
19
|
+
* @returns The identity string embedded in the verdict file.
|
|
20
|
+
*/
|
|
21
|
+
export function verificationId(runId, kind, step, attempt, seq) {
|
|
22
|
+
return `${runId}/${kind}/${step}/${attempt}/${seq}`;
|
|
23
|
+
}
|
|
24
|
+
/** Directory holding one run's verdicts, so a whole review can be audited or removed as a group.
|
|
25
|
+
* @param directory - Workspace directory the review runs in.
|
|
26
|
+
* @param runId - Identity of the run.
|
|
27
|
+
* @returns The absolute directory.
|
|
28
|
+
*/
|
|
29
|
+
export function verdictDirectory(directory, runId) {
|
|
30
|
+
return join(directory, '.dsht', 'verify', runId);
|
|
31
|
+
}
|
|
32
|
+
/** Path of the verdict one verification task must write.
|
|
33
|
+
* @param directory - Workspace directory the review runs in.
|
|
34
|
+
* @param runId - Identity of the run; part of the path so concurrent runs never collide.
|
|
35
|
+
* @param kind - Protocol kind.
|
|
36
|
+
* @param step - Step in flight.
|
|
37
|
+
* @param attempt - Attempt in flight.
|
|
38
|
+
* @param seq - This verification task's sequence within the run; part of the name so a retry
|
|
39
|
+
* cannot be read as, or overwrite, the task it replaced.
|
|
40
|
+
* @returns The absolute verdict path.
|
|
41
|
+
*/
|
|
42
|
+
export function verdictFile(directory, runId, kind, step, attempt, seq) {
|
|
43
|
+
return join(verdictDirectory(directory, runId), `${kind}-${step}-${attempt}-${seq}.json`);
|
|
44
|
+
}
|
|
45
|
+
/** Marker a child prints on the line that reports an unanswerable human request.
|
|
46
|
+
*
|
|
47
|
+
* The child sees the interaction on its own session and exits; the parent parses the same line back
|
|
48
|
+
* out of the child's output. One definition keeps both sides from drifting.
|
|
49
|
+
*/
|
|
50
|
+
export const NEEDS_HUMAN_MARKER = 'dsht-verify-needs-human:';
|
|
51
|
+
/** Render the line the child logs and the parent parses.
|
|
52
|
+
* @param request - What the host is waiting for.
|
|
53
|
+
* @returns The marker line.
|
|
54
|
+
*/
|
|
55
|
+
export function needsHumanLine(request) {
|
|
56
|
+
return `${NEEDS_HUMAN_MARKER}${JSON.stringify(request)}`;
|
|
57
|
+
}
|
|
58
|
+
/** Read that line back, ignoring every other line the child prints.
|
|
59
|
+
* @param line - One child output line.
|
|
60
|
+
* @returns The request, or undefined when the line is not this marker.
|
|
61
|
+
*/
|
|
62
|
+
export function parseNeedsHumanLine(line) {
|
|
63
|
+
const at = line.indexOf(NEEDS_HUMAN_MARKER);
|
|
64
|
+
if (at === -1)
|
|
65
|
+
return undefined;
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(line.slice(at + NEEDS_HUMAN_MARKER.length));
|
|
68
|
+
if (typeof parsed.kind !== 'string' || typeof parsed.text !== 'string')
|
|
69
|
+
return undefined;
|
|
70
|
+
return { kind: parsed.kind, text: parsed.text };
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
package/dist/cost/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Cost domain: price tables, record folding, the folded ledger, the scanner and its controller. */
|
|
2
2
|
export { CostController } from './controller.ts';
|
|
3
3
|
export type { CostHost } from './controller.ts';
|
|
4
|
-
export { CostLedger
|
|
4
|
+
export { CostLedger } from './ledger.ts';
|
|
5
5
|
export { loadPrices } from './config.ts';
|
|
6
6
|
export { candidates, canonicalModel, chargeFor, costDay, DEFAULT_PRICES, isUncorrectedSeed, priceAt, PRICES_REVISION, PRICING_ENGINE_VERSION, pricesDigest, pricesFrom } from './pricing.ts';
|
|
7
7
|
export { costRecords, foldSamples } from './records.ts';
|
package/dist/cost/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Cost domain: price tables, record folding, the folded ledger, the scanner and its controller. */
|
|
2
2
|
export { CostController } from "./controller.js";
|
|
3
|
-
export { CostLedger
|
|
3
|
+
export { CostLedger } from "./ledger.js";
|
|
4
4
|
export { loadPrices } from "./config.js";
|
|
5
5
|
export { candidates, canonicalModel, chargeFor, costDay, DEFAULT_PRICES, isUncorrectedSeed, priceAt, PRICES_REVISION, PRICING_ENGINE_VERSION, pricesDigest, pricesFrom } from "./pricing.js";
|
|
6
6
|
export { costRecords, foldSamples } from "./records.js";
|
package/dist/cost/ledger.d.ts
CHANGED
package/dist/cost/ledger.js
CHANGED
package/dist/json.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Plain JSON shapes and the strict readers for them.
|
|
2
|
+
*
|
|
3
|
+
* A dependency-free leaf shared by the wire, the features and the UI contract: the wire validates
|
|
4
|
+
* what it decodes with these, and the UI reads the host summaries it is handed. Typing those
|
|
5
|
+
* summaries per feature is what will eventually let the UI stop reading raw objects.
|
|
6
|
+
*/
|
|
7
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
8
|
+
[key: string]: Json;
|
|
9
|
+
};
|
|
10
|
+
export type ObjectValue = {
|
|
11
|
+
[key: string]: Json;
|
|
12
|
+
};
|
|
13
|
+
/** Require an object from a decoded wire message. */
|
|
14
|
+
export declare function object(value: unknown): ObjectValue;
|
|
15
|
+
/** Require a string field rather than silently accepting protocol drift. */
|
|
16
|
+
export declare function string(value: unknown): string;
|
|
17
|
+
/** Require an array field from the server. */
|
|
18
|
+
export declare function array(value: unknown): Json[];
|
package/dist/json.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Require an object from a decoded wire message. */
|
|
2
|
+
export function object(value) {
|
|
3
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
4
|
+
throw new Error('Expected a JSON object from the server');
|
|
5
|
+
}
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
/** Require a string field rather than silently accepting protocol drift. */
|
|
9
|
+
export function string(value) {
|
|
10
|
+
if (typeof value !== 'string')
|
|
11
|
+
throw new Error('Expected a string from the server');
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
/** Require an array field from the server. */
|
|
15
|
+
export function array(value) {
|
|
16
|
+
if (!Array.isArray(value))
|
|
17
|
+
throw new Error('Expected an array from the server');
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The composer's `@` mention grammar.
|
|
2
|
+
*
|
|
3
|
+
* Both the session domain (validating host candidates) and the composer (finding the unfinished
|
|
4
|
+
* token and encoding a choice) need the same syntax, so it lives in its own leaf.
|
|
5
|
+
*/
|
|
6
|
+
/** A path relative to the remote session's working directory. */
|
|
7
|
+
export interface FileReference {
|
|
8
|
+
path: string;
|
|
9
|
+
kind: 'file' | 'directory';
|
|
10
|
+
}
|
|
11
|
+
/** Find the unfinished reference at the end of the draft; email addresses do not trigger it.
|
|
12
|
+
* @param text - Complete composer draft.
|
|
13
|
+
* @returns The replaceable token and host query, or undefined outside a reference.
|
|
14
|
+
*/
|
|
15
|
+
export declare function activeReference(text: string): {
|
|
16
|
+
prefix: string;
|
|
17
|
+
query: string;
|
|
18
|
+
quoted: boolean;
|
|
19
|
+
} | undefined;
|
|
20
|
+
/** Encode a candidate in Harness prompt syntax; directories keep completion open.
|
|
21
|
+
* @param candidate - Remote path and entry kind.
|
|
22
|
+
* @param quoted - Preserve an explicitly opened quote.
|
|
23
|
+
* @returns Mention text, or undefined for paths the mention grammar cannot encode.
|
|
24
|
+
*/
|
|
25
|
+
export declare function fileMention(candidate: FileReference, quoted?: boolean): string | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Find the unfinished reference at the end of the draft; email addresses do not trigger it.
|
|
2
|
+
* @param text - Complete composer draft.
|
|
3
|
+
* @returns The replaceable token and host query, or undefined outside a reference.
|
|
4
|
+
*/
|
|
5
|
+
export function activeReference(text) {
|
|
6
|
+
const quoted = /(?:^|\s)(@"([^"]*))$/u.exec(text);
|
|
7
|
+
if (quoted)
|
|
8
|
+
return { prefix: quoted[1], query: quoted[2], quoted: true };
|
|
9
|
+
const plain = /(?:^|\s)(@([^\s"]*))$/u.exec(text);
|
|
10
|
+
if (plain)
|
|
11
|
+
return { prefix: plain[1], query: plain[2], quoted: false };
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Encode a candidate in Harness prompt syntax; directories keep completion open.
|
|
15
|
+
* @param candidate - Remote path and entry kind.
|
|
16
|
+
* @param quoted - Preserve an explicitly opened quote.
|
|
17
|
+
* @returns Mention text, or undefined for paths the mention grammar cannot encode.
|
|
18
|
+
*/
|
|
19
|
+
export function fileMention(candidate, quoted = false) {
|
|
20
|
+
const path = candidate.path + (candidate.kind === 'directory' ? '/' : '');
|
|
21
|
+
if (/[\u0000-\u001f\u007f-\u009f"]/u.test(path))
|
|
22
|
+
return undefined;
|
|
23
|
+
if (!quoted && !/\s/u.test(path))
|
|
24
|
+
return `@${path}`;
|
|
25
|
+
return `@"${path}${candidate.kind === 'file' ? '"' : ''}`;
|
|
26
|
+
}
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
/** What the session domain reads from the connection the controller owns. */
|
|
2
|
-
import type {
|
|
3
|
-
import type { ObjectValue } from '../transport/wire.ts';
|
|
2
|
+
import type { Json } from '../transport/wire.ts';
|
|
4
3
|
/** Read-only connection facts and actions the session controller needs. */
|
|
5
4
|
export interface ConnectionView {
|
|
6
|
-
/** Projection store of the current generation. */
|
|
7
|
-
telemetryView(): Telemetry;
|
|
8
|
-
/** Cached host running flag for one session, or undefined when never reported. */
|
|
9
|
-
runningFor(sessionId: string): boolean | undefined;
|
|
10
|
-
/** When this client first observed the session, for the elapsed-time fallback. */
|
|
11
|
-
observedAt(sessionId: string): number | undefined;
|
|
12
|
-
/** Record an observation start for a session this client just opened. */
|
|
13
|
-
observe(sessionId: string): void;
|
|
14
5
|
/** Fail the current generation, so the controller reopens a snapshot. */
|
|
15
6
|
fail(error: Error): void;
|
|
16
7
|
/** Answer one retained host waterfall through the event-result endpoint. */
|
|
17
|
-
reply(
|
|
8
|
+
reply(eventId: string, outcome: Json): Promise<void>;
|
|
18
9
|
}
|