@itookit/dsht 0.5.1 → 0.6.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.
- package/README.i18n.yaml +2 -2
- package/README.md +10 -4
- package/README.zh.md +12 -6
- package/dist/catalog/controller.d.ts +26 -6
- package/dist/catalog/controller.js +73 -45
- package/dist/catalog/index.d.ts +1 -0
- package/dist/cli/dsht.js +22 -2
- package/dist/cli/startup.js +30 -11
- package/dist/cli/verifier.d.ts +4 -0
- package/dist/cli/verifier.js +28 -5
- package/dist/contracts.d.ts +42 -5
- package/dist/controller/connection-streams.d.ts +22 -0
- package/dist/controller/connection-streams.js +105 -0
- package/dist/controller/connection.d.ts +14 -3
- package/dist/controller/connection.js +40 -69
- package/dist/controller/controller.d.ts +20 -234
- package/dist/controller/controller.js +113 -811
- package/dist/controller/foreground.d.ts +44 -0
- package/dist/controller/foreground.js +79 -0
- package/dist/controller/loop-coordinator.d.ts +48 -0
- package/dist/controller/loop-coordinator.js +647 -0
- package/dist/controller/loop-prompts-schema.d.ts +16 -2
- package/dist/controller/loop-prompts-schema.js +106 -27
- package/dist/controller/loop-prompts.d.ts +17 -2
- package/dist/controller/loop-prompts.generated.js +2 -1
- package/dist/controller/loop-prompts.js +35 -9
- package/dist/controller/loop-protocols.d.ts +3 -1
- package/dist/controller/loop-protocols.js +8 -3
- package/dist/controller/loop-source.d.ts +74 -0
- package/dist/controller/loop-source.js +224 -0
- package/dist/controller/verifier.d.ts +4 -0
- package/dist/cost/controller.d.ts +3 -1
- package/dist/cost/controller.js +26 -7
- package/dist/cost/index.d.ts +1 -1
- package/dist/cost/index.js +1 -1
- package/dist/cost/ledger-files.d.ts +20 -0
- package/dist/cost/ledger-files.js +115 -15
- package/dist/cost/ledger.d.ts +31 -6
- package/dist/cost/ledger.js +74 -22
- package/dist/cost/pricing.d.ts +39 -0
- package/dist/cost/pricing.js +46 -0
- package/dist/cost/scanner.js +1 -0
- package/dist/cost/types.d.ts +9 -3
- package/dist/session/controller.d.ts +23 -35
- package/dist/session/controller.js +113 -363
- package/dist/session/history-reader.d.ts +32 -0
- package/dist/session/history-reader.js +170 -0
- package/dist/session/index.d.ts +1 -1
- package/dist/session/info.d.ts +3 -38
- package/dist/session/info.js +14 -1
- package/dist/session/interactions.d.ts +26 -0
- package/dist/session/interactions.js +75 -0
- package/dist/session/navigator.d.ts +47 -0
- package/dist/session/navigator.js +158 -0
- package/dist/session/prompt-backfill.d.ts +23 -0
- package/dist/session/prompt-backfill.js +88 -0
- package/dist/session/state.d.ts +20 -0
- package/dist/session/state.js +1 -0
- package/dist/session/telemetry.d.ts +15 -6
- package/dist/session/telemetry.js +44 -7
- package/dist/session/transcript.d.ts +5 -1
- package/dist/slash/index.d.ts +1 -1
- package/dist/slash/parse.d.ts +2 -126
- package/dist/slash/registry.d.ts +1 -1
- package/dist/slash/types.d.ts +126 -0
- package/dist/slash/types.js +1 -0
- package/dist/state.d.ts +5 -17
- package/dist/state.js +1 -1
- package/dist/storage/files.d.ts +8 -0
- package/dist/storage/files.js +18 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/transport/client.d.ts +4 -3
- package/dist/transport/client.js +71 -25
- package/dist/ui/app.js +88 -301
- package/dist/ui/chat/shell-view.d.ts +2 -0
- package/dist/ui/chat/shell-view.js +8 -0
- package/dist/ui/chat/use-history-view.d.ts +69 -0
- package/dist/ui/chat/use-history-view.js +123 -0
- package/dist/ui/dialogs/cost.d.ts +6 -0
- package/dist/ui/dialogs/cost.js +5 -1
- package/dist/ui/dialogs/loop.d.ts +5 -4
- package/dist/ui/dialogs/loop.js +14 -6
- package/dist/ui/dialogs/use-panels.d.ts +53 -0
- package/dist/ui/dialogs/use-panels.js +51 -0
- package/dist/ui/input/use-composer.d.ts +35 -0
- package/dist/ui/input/use-composer.js +109 -0
- package/dist/ui/input/use-deferred-lines.d.ts +16 -0
- package/dist/ui/input/use-deferred-lines.js +54 -0
- package/dist/ui/input/use-history-recall.d.ts +20 -0
- package/dist/ui/input/use-history-recall.js +47 -0
- package/loop.yaml +230 -0
- package/package.json +5 -4
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
*
|
|
3
3
|
* Kept apart from `loop-prompts.ts` so the build script can validate a YAML file before the
|
|
4
4
|
* generated module exists: this module imports nothing, while the renderer imports the generated
|
|
5
|
-
* data. The validator is the single copy of the schema — the generator
|
|
5
|
+
* data. The validator is the single copy of the schema — the generator, the runtime loader and the
|
|
6
|
+
* tests all call it, so a file that passes here is the one the renderer will read.
|
|
7
|
+
*
|
|
8
|
+
* Two entry points share one set of rules: `validateLoopPrompts` checks a whole built-in file, and
|
|
9
|
+
* `validateLoopOverlayPrompts` checks a user file that may carry only the records it changes.
|
|
6
10
|
*/
|
|
7
11
|
/** Placeholders every template may use; a record's own `vars` add to these. */
|
|
8
12
|
export const LOOP_PLACEHOLDERS = ['from', 'to', 'score', 'tries', 'step', 'attempt', 'title', 'artifact', 'checks'];
|
|
@@ -16,18 +20,94 @@ export const RESERVED_PROTOCOL_NAMES = ['answer', 'abort', 'stop'];
|
|
|
16
20
|
*/
|
|
17
21
|
export function validateLoopPrompts(source) {
|
|
18
22
|
const errors = [];
|
|
19
|
-
const document = source;
|
|
20
|
-
if (document
|
|
23
|
+
const document = asDocument(source);
|
|
24
|
+
if (document.version !== 1)
|
|
21
25
|
errors.push('version must be 1');
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
errors.push('defaults.tries must be a number');
|
|
26
|
-
const protocols = document?.protocols;
|
|
27
|
-
if (protocols === null || typeof protocols !== 'object' || Object.keys(protocols ?? {}).length === 0) {
|
|
26
|
+
validateDefaults(document.defaults, errors, true);
|
|
27
|
+
const protocols = document.protocols;
|
|
28
|
+
if (protocols === null || typeof protocols !== 'object' || Array.isArray(protocols) || Object.keys(protocols ?? {}).length === 0) {
|
|
28
29
|
errors.push('protocols must be a non-empty mapping');
|
|
29
30
|
return errors;
|
|
30
31
|
}
|
|
32
|
+
validateProtocols(protocols, errors);
|
|
33
|
+
return errors;
|
|
34
|
+
}
|
|
35
|
+
/** Check a user file that layers over the shipped records.
|
|
36
|
+
*
|
|
37
|
+
* It is a whole `loop.yaml` in shape, but partial in content: an overlay that only adds one record
|
|
38
|
+
* or only moves the global defaults is exactly what it is for, so a missing or empty `protocols` is
|
|
39
|
+
* valid here. Every record it does declare is held to the same rules as a shipped one, because the
|
|
40
|
+
* renderer cannot tell the two apart once they are merged.
|
|
41
|
+
* @param source - Parsed overlay file.
|
|
42
|
+
* @returns One message per problem; an empty list means valid.
|
|
43
|
+
*/
|
|
44
|
+
export function validateLoopOverlayPrompts(source) {
|
|
45
|
+
const errors = [];
|
|
46
|
+
const document = asDocument(source);
|
|
47
|
+
// A version is required even from an overlay: a file written for a later shape must fail loudly
|
|
48
|
+
// instead of having its fields read as this shape's, which is how a silent misread becomes a run
|
|
49
|
+
// judged against a rubric nobody wrote.
|
|
50
|
+
if (document.version !== 1)
|
|
51
|
+
errors.push('version must be 1');
|
|
52
|
+
validateDefaults(document.defaults, errors, false);
|
|
53
|
+
const protocols = document.protocols;
|
|
54
|
+
if (protocols === undefined || protocols === null)
|
|
55
|
+
return errors;
|
|
56
|
+
if (typeof protocols !== 'object' || Array.isArray(protocols)) {
|
|
57
|
+
errors.push('protocols must be a mapping');
|
|
58
|
+
return errors;
|
|
59
|
+
}
|
|
60
|
+
validateProtocols(protocols, errors);
|
|
61
|
+
return errors;
|
|
62
|
+
}
|
|
63
|
+
/** A parsed document with every unsafe value normalized to `undefined`.
|
|
64
|
+
* @param source - Anything a caller parsed out of a file.
|
|
65
|
+
* @returns The document, or an empty one for a null, scalar or array root.
|
|
66
|
+
*/
|
|
67
|
+
function asDocument(source) {
|
|
68
|
+
return source !== null && typeof source === 'object' && !Array.isArray(source) ? source : {};
|
|
69
|
+
}
|
|
70
|
+
/** Check the global defaults.
|
|
71
|
+
*
|
|
72
|
+
* A shipped file must state them, because every record falls back to them. An overlay may state
|
|
73
|
+
* neither, one or both, but a value it does state reaches real runs, so it is checked for range
|
|
74
|
+
* even though the shipped file only has to be numeric.
|
|
75
|
+
* @param defaults - The `defaults` field as parsed.
|
|
76
|
+
* @param errors - Collector to append messages to.
|
|
77
|
+
* @param required - Whether this document must carry both numbers.
|
|
78
|
+
*/
|
|
79
|
+
function validateDefaults(defaults, errors, required) {
|
|
80
|
+
if (defaults === undefined || defaults === null) {
|
|
81
|
+
if (required) {
|
|
82
|
+
errors.push('defaults.score must be a number');
|
|
83
|
+
errors.push('defaults.tries must be a number');
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (typeof defaults !== 'object' || Array.isArray(defaults)) {
|
|
88
|
+
errors.push('defaults must be a mapping');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const { score, tries } = defaults;
|
|
92
|
+
if (required) {
|
|
93
|
+
if (!Number.isFinite(score))
|
|
94
|
+
errors.push('defaults.score must be a number');
|
|
95
|
+
if (!Number.isFinite(tries))
|
|
96
|
+
errors.push('defaults.tries must be a number');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (score !== undefined && (!Number.isFinite(score) || score < 0 || score > 10)) {
|
|
100
|
+
errors.push('defaults.score must be a number in 0-10');
|
|
101
|
+
}
|
|
102
|
+
if (tries !== undefined && (!Number.isInteger(tries) || tries < 1)) {
|
|
103
|
+
errors.push('defaults.tries must be a positive integer');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Check every record of one document against the rules the renderer relies on.
|
|
107
|
+
* @param protocols - The `protocols` mapping.
|
|
108
|
+
* @param errors - Collector to append messages to.
|
|
109
|
+
*/
|
|
110
|
+
function validateProtocols(protocols, errors) {
|
|
31
111
|
const runtime = new Set(LOOP_PLACEHOLDERS);
|
|
32
112
|
const reserved = new Set(RESERVED_PROTOCOL_NAMES);
|
|
33
113
|
const placeholders = (text) => [...text.matchAll(/\{\{(\w+)\}\}/g)].map(match => match[1]);
|
|
@@ -36,7 +116,7 @@ export function validateLoopPrompts(source) {
|
|
|
36
116
|
if (reserved.has(kind))
|
|
37
117
|
errors.push(`${at} is a reserved name: ${RESERVED_PROTOCOL_NAMES.join(', ')} belong to /loop itself`);
|
|
38
118
|
// A template may name a runtime value or one of this record's own vars, and nothing else.
|
|
39
|
-
const vars = protocol
|
|
119
|
+
const vars = protocol?.vars;
|
|
40
120
|
if (vars !== undefined && (vars === null || typeof vars !== 'object' || Array.isArray(vars))) {
|
|
41
121
|
errors.push(`${at}.vars must be a mapping of names to strings`);
|
|
42
122
|
}
|
|
@@ -55,15 +135,15 @@ export function validateLoopPrompts(source) {
|
|
|
55
135
|
if (!allowed.has(name))
|
|
56
136
|
errors.push(`${where}: unknown placeholder {{${name}}}`);
|
|
57
137
|
};
|
|
58
|
-
checkPlaceholders(typeof protocol
|
|
59
|
-
if (typeof protocol
|
|
138
|
+
checkPlaceholders(typeof protocol?.title === 'string' ? protocol.title : '', `${at}.title`);
|
|
139
|
+
if (typeof protocol?.title !== 'string' || protocol.title === '')
|
|
60
140
|
errors.push(`${at}.title must be a non-empty string`);
|
|
61
|
-
if (!Number.isInteger(protocol
|
|
141
|
+
if (!Number.isInteger(protocol?.steps) || (protocol?.steps ?? 0) < 1)
|
|
62
142
|
errors.push(`${at}.steps must be a positive integer`);
|
|
63
|
-
if (protocol
|
|
143
|
+
if (protocol?.artifact !== undefined && (typeof protocol.artifact !== 'string' || protocol.artifact === '')) {
|
|
64
144
|
errors.push(`${at}.artifact must be a non-empty string when present`);
|
|
65
145
|
}
|
|
66
|
-
else if (typeof protocol
|
|
146
|
+
else if (typeof protocol?.artifact === 'string') {
|
|
67
147
|
// The file name may use the record's vars — that is how one run per input gets its own file —
|
|
68
148
|
// but never a runtime placeholder: the artifact must not move between rounds.
|
|
69
149
|
for (const name of placeholders(protocol.artifact)) {
|
|
@@ -72,7 +152,7 @@ export function validateLoopPrompts(source) {
|
|
|
72
152
|
}
|
|
73
153
|
}
|
|
74
154
|
}
|
|
75
|
-
if (protocol
|
|
155
|
+
if (protocol?.artifactMarker !== undefined) {
|
|
76
156
|
if (typeof protocol.artifactMarker !== 'string' || protocol.artifactMarker.trim() === '') {
|
|
77
157
|
errors.push(`${at}.artifactMarker must be a non-empty string when present`);
|
|
78
158
|
}
|
|
@@ -84,28 +164,28 @@ export function validateLoopPrompts(source) {
|
|
|
84
164
|
errors.push(`${at}.artifactMarker needs ${at}.artifact: there is no file to look in`);
|
|
85
165
|
}
|
|
86
166
|
}
|
|
87
|
-
if (typeof protocol
|
|
167
|
+
if (typeof protocol?.fallbackLabel !== 'string' || protocol.fallbackLabel === '')
|
|
88
168
|
errors.push(`${at}.fallbackLabel must be a non-empty string`);
|
|
89
|
-
if (protocol
|
|
169
|
+
if (protocol?.standard !== undefined && (typeof protocol.standard !== 'string' || protocol.standard.trim() === '')) {
|
|
90
170
|
errors.push(`${at}.standard must be a non-empty string when present`);
|
|
91
171
|
}
|
|
92
|
-
if (protocol
|
|
172
|
+
if (protocol?.starts !== undefined && protocol.starts !== 'verify' && protocol.starts !== 'work') {
|
|
93
173
|
errors.push(`${at}.starts must be 'verify' or 'work'`);
|
|
94
174
|
}
|
|
95
|
-
if (protocol
|
|
175
|
+
if (protocol?.defaults !== undefined) {
|
|
96
176
|
const { score, tries } = protocol.defaults;
|
|
97
177
|
if (score !== undefined && (!Number.isFinite(score) || score < 0 || score > 10))
|
|
98
178
|
errors.push(`${at}.defaults.score must be a number in 0-10`);
|
|
99
179
|
if (tries !== undefined && (!Number.isInteger(tries) || tries < 1))
|
|
100
180
|
errors.push(`${at}.defaults.tries must be a positive integer`);
|
|
101
181
|
}
|
|
102
|
-
const rounds = protocol
|
|
182
|
+
const rounds = protocol?.rounds;
|
|
103
183
|
if (!Array.isArray(rounds)) {
|
|
104
184
|
errors.push(`${at}.rounds must be a list`);
|
|
105
185
|
}
|
|
106
186
|
else {
|
|
107
|
-
if (rounds.length > 0 && rounds.length !== protocol
|
|
108
|
-
errors.push(`${at}.rounds has ${rounds.length} entries but steps is ${protocol
|
|
187
|
+
if (rounds.length > 0 && rounds.length !== protocol?.steps) {
|
|
188
|
+
errors.push(`${at}.rounds has ${rounds.length} entries but steps is ${protocol?.steps}`);
|
|
109
189
|
}
|
|
110
190
|
rounds.forEach((round, index) => {
|
|
111
191
|
if (typeof round?.title !== 'string' || round.title === '')
|
|
@@ -115,7 +195,7 @@ export function validateLoopPrompts(source) {
|
|
|
115
195
|
});
|
|
116
196
|
}
|
|
117
197
|
for (const key of ['brief', 'followUp']) {
|
|
118
|
-
const lines = protocol[key];
|
|
198
|
+
const lines = protocol?.[key];
|
|
119
199
|
if (!Array.isArray(lines) || lines.length === 0) {
|
|
120
200
|
errors.push(`${at}.${key} must be a non-empty list`);
|
|
121
201
|
continue;
|
|
@@ -128,17 +208,16 @@ export function validateLoopPrompts(source) {
|
|
|
128
208
|
checkPlaceholders(line, `${at}.${key}[${index}]`);
|
|
129
209
|
});
|
|
130
210
|
}
|
|
131
|
-
const checksLines = (protocol
|
|
211
|
+
const checksLines = (protocol?.brief ?? []).filter(line => typeof line === 'string' && line.trim() === '{{checks}}');
|
|
132
212
|
if ((rounds?.length ?? 0) > 0 && checksLines.length !== 1)
|
|
133
213
|
errors.push(`${at}.brief must contain exactly one line that is just {{checks}}`);
|
|
134
214
|
if ((rounds?.length ?? 0) === 0 && checksLines.length > 0)
|
|
135
215
|
errors.push(`${at}.brief uses {{checks}} but defines no rounds`);
|
|
136
|
-
if (protocol
|
|
216
|
+
if (protocol?.verifyFocus !== undefined) {
|
|
137
217
|
if (typeof protocol.verifyFocus !== 'string' || protocol.verifyFocus === '')
|
|
138
218
|
errors.push(`${at}.verifyFocus must be a non-empty string when present`);
|
|
139
219
|
else
|
|
140
220
|
checkPlaceholders(protocol.verifyFocus, `${at}.verifyFocus`);
|
|
141
221
|
}
|
|
142
222
|
}
|
|
143
|
-
return errors;
|
|
144
223
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { LoopSourceInfo } from '../contracts.ts';
|
|
2
|
+
import type { LoopPromptSource } from './loop-prompts-schema.ts';
|
|
1
3
|
export type { LoopPromptSource, LoopProtocolText, LoopRoundText } from './loop-prompts-schema.ts';
|
|
2
4
|
/** Values one render call supplies; a record's `vars` are merged in on top of these. */
|
|
3
5
|
export interface LoopPromptValues {
|
|
@@ -49,7 +51,20 @@ export interface LoopPrompts {
|
|
|
49
51
|
/** One record's declared `vars`, unrendered, so a form can offer them before a run starts. */
|
|
50
52
|
vars(kind: string): Readonly<Record<string, string>>;
|
|
51
53
|
}
|
|
52
|
-
/**
|
|
53
|
-
*
|
|
54
|
+
/** Put the records this process runs in place, before any run or record list reads them.
|
|
55
|
+
*
|
|
56
|
+
* The files are read and merged at startup rather than at module load, so a bad user file can be
|
|
57
|
+
* reported and refused before the client opens, and a run keeps the rubric it started with even if
|
|
58
|
+
* the file is edited underneath it.
|
|
59
|
+
* @param source - Merged records: shipped ones with the user's file layered on top.
|
|
60
|
+
* @param sourceInfo - Where they came from, and what the user's file changed.
|
|
61
|
+
*/
|
|
62
|
+
export declare function installLoopSource(source: LoopPromptSource, sourceInfo: LoopSourceInfo): void;
|
|
63
|
+
/** Where the installed records came from.
|
|
64
|
+
* @returns The installed source info; the compiled-in records are the empty default.
|
|
65
|
+
*/
|
|
66
|
+
export declare function loopSourceInfo(): LoopSourceInfo;
|
|
67
|
+
/** The records in force.
|
|
68
|
+
* @returns Names, lookups and the declared vars of each record, built once per installed source.
|
|
54
69
|
*/
|
|
55
70
|
export declare function loopPrompts(): LoopPrompts;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// GENERATED FILE — do not edit. Edit loop.yaml and run `npm run build:prompts`.
|
|
2
|
-
//
|
|
2
|
+
// The fallback table: loop.yaml is read at runtime, and this is what a package whose file is
|
|
3
|
+
// missing or unreadable starts on. Kept in sync by tests/controller/loop-prompts.test.ts.
|
|
3
4
|
export const LOOP_PROMPTS = {
|
|
4
5
|
"version": 1,
|
|
5
6
|
"defaults": {
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
/** Typed access to the loop prompts and the placeholder renderer.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* The loop record is configuration, not code: the shipped `loop.yaml` is read at startup from beside
|
|
4
|
+
* the package and a user file may be layered over it (`loop-source.ts`), then the merged table is
|
|
5
|
+
* installed here once. `loop-prompts-schema.ts` holds the shape and the rules, and
|
|
6
|
+
* `loop-prompts.generated.ts` is the compiled-in fallback for a package whose file is missing. This
|
|
7
|
+
* module turns one record into the strings a protocol needs, so the dynamic parts (this round's title,
|
|
8
|
+
* its checklist, the record's own vars) are filled here and nowhere else. A template may only use
|
|
9
|
+
* placeholders the caller can supply; anything else throws rather than putting a literal `{{name}}`
|
|
10
|
+
* into a prompt.
|
|
8
11
|
*/
|
|
9
12
|
import { LOOP_PROMPTS } from "./loop-prompts.generated.js";
|
|
10
13
|
const PLACEHOLDER = /\{\{(\w+)\}\}/g;
|
|
11
|
-
|
|
14
|
+
/** The records in force: the shipped file with the user's layered on top, installed once at startup
|
|
15
|
+
* and replaced only by another install, never by a reload while a run is in flight. */
|
|
16
|
+
let SOURCE = LOOP_PROMPTS;
|
|
12
17
|
/** Replace every `{{name}}` in one template list.
|
|
13
18
|
* @param lines - Template lines.
|
|
14
19
|
* @param values - Values keyed by placeholder name.
|
|
@@ -75,10 +80,31 @@ function render(kind, protocol, overrides) {
|
|
|
75
80
|
followUp: runtime => fill(protocol.followUp, values(runtime), kind),
|
|
76
81
|
};
|
|
77
82
|
}
|
|
78
|
-
/** All records, rendered
|
|
83
|
+
/** All records, rendered when the source is installed: a file edited mid-run cannot change a brief. */
|
|
79
84
|
let cache;
|
|
80
|
-
/**
|
|
81
|
-
|
|
85
|
+
/** Where the installed records came from, for the record list to show. */
|
|
86
|
+
let info = { overridden: [], added: [], warnings: [] };
|
|
87
|
+
/** Put the records this process runs in place, before any run or record list reads them.
|
|
88
|
+
*
|
|
89
|
+
* The files are read and merged at startup rather than at module load, so a bad user file can be
|
|
90
|
+
* reported and refused before the client opens, and a run keeps the rubric it started with even if
|
|
91
|
+
* the file is edited underneath it.
|
|
92
|
+
* @param source - Merged records: shipped ones with the user's file layered on top.
|
|
93
|
+
* @param sourceInfo - Where they came from, and what the user's file changed.
|
|
94
|
+
*/
|
|
95
|
+
export function installLoopSource(source, sourceInfo) {
|
|
96
|
+
SOURCE = source;
|
|
97
|
+
info = { ...sourceInfo, overridden: [...sourceInfo.overridden], added: [...sourceInfo.added], warnings: [...sourceInfo.warnings] };
|
|
98
|
+
cache = undefined;
|
|
99
|
+
}
|
|
100
|
+
/** Where the installed records came from.
|
|
101
|
+
* @returns The installed source info; the compiled-in records are the empty default.
|
|
102
|
+
*/
|
|
103
|
+
export function loopSourceInfo() {
|
|
104
|
+
return info;
|
|
105
|
+
}
|
|
106
|
+
/** The records in force.
|
|
107
|
+
* @returns Names, lookups and the declared vars of each record, built once per installed source.
|
|
82
108
|
*/
|
|
83
109
|
export function loopPrompts() {
|
|
84
110
|
if (cache !== undefined)
|
|
@@ -5,7 +5,9 @@ export declare function loopProtocolNames(): string[];
|
|
|
5
5
|
/** Every record `/loop` may run, in file order, with the defaults a run would start from.
|
|
6
6
|
*
|
|
7
7
|
* The list and the run read the same records, so a chooser can show exactly the name, round count,
|
|
8
|
-
* artifact and defaults the runner would use — never a second table that could drift.
|
|
8
|
+
* artifact and defaults the runner would use — never a second table that could drift. A record the
|
|
9
|
+
* user's own file supplied is marked, because an operator who overrode a shipped record can no longer
|
|
10
|
+
* tell the two apart from the name alone.
|
|
9
11
|
* @returns One summary per record.
|
|
10
12
|
*/
|
|
11
13
|
export declare function loopRecords(): LoopRecord[];
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* record without code knowing which one it is.
|
|
7
7
|
*/
|
|
8
8
|
import { LOOP_MARKER, followUpContract, resultContract, verdictBrief } from "./loop-contract.js";
|
|
9
|
-
import { loopPrompts } from "./loop-prompts.js";
|
|
9
|
+
import { loopPrompts, loopSourceInfo } from "./loop-prompts.js";
|
|
10
10
|
import { coversWholeProtocol } from "./loop.js";
|
|
11
11
|
/** Names `/loop` may run, in file order. */
|
|
12
12
|
export function loopProtocolNames() {
|
|
@@ -15,10 +15,14 @@ export function loopProtocolNames() {
|
|
|
15
15
|
/** Every record `/loop` may run, in file order, with the defaults a run would start from.
|
|
16
16
|
*
|
|
17
17
|
* The list and the run read the same records, so a chooser can show exactly the name, round count,
|
|
18
|
-
* artifact and defaults the runner would use — never a second table that could drift.
|
|
18
|
+
* artifact and defaults the runner would use — never a second table that could drift. A record the
|
|
19
|
+
* user's own file supplied is marked, because an operator who overrode a shipped record can no longer
|
|
20
|
+
* tell the two apart from the name alone.
|
|
19
21
|
* @returns One summary per record.
|
|
20
22
|
*/
|
|
21
23
|
export function loopRecords() {
|
|
24
|
+
const { overridden, added } = loopSourceInfo();
|
|
25
|
+
const mine = new Set([...overridden, ...added]);
|
|
22
26
|
return loopPrompts().names.flatMap(name => {
|
|
23
27
|
const text = loopPrompts().find(name);
|
|
24
28
|
if (text === undefined)
|
|
@@ -26,7 +30,8 @@ export function loopRecords() {
|
|
|
26
30
|
return [{ name, title: text.title, steps: text.steps,
|
|
27
31
|
...(text.artifact === undefined ? {} : { artifact: text.artifact }),
|
|
28
32
|
defaultScore: text.defaultScore, defaultTries: text.defaultTries,
|
|
29
|
-
vars: loopPrompts().vars(name)
|
|
33
|
+
vars: loopPrompts().vars(name),
|
|
34
|
+
...(mine.has(name) ? { fromFile: true } : {}) }];
|
|
30
35
|
});
|
|
31
36
|
}
|
|
32
37
|
/** One record's declared variables, so a caller can offer or validate them before a run starts.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { LoopSourceInfo } from '../contracts.ts';
|
|
2
|
+
import type { LoopProtocolText, LoopPromptSource } from './loop-prompts-schema.ts';
|
|
3
|
+
/** A user file: a whole document in shape, any part of it in content. */
|
|
4
|
+
export interface LoopOverlaySource {
|
|
5
|
+
readonly version: number;
|
|
6
|
+
/** Global defaults this file moves; each field it omits keeps the shipped value. */
|
|
7
|
+
readonly defaults?: {
|
|
8
|
+
readonly score?: number;
|
|
9
|
+
readonly tries?: number;
|
|
10
|
+
};
|
|
11
|
+
/** Records this file adds or replaces, keyed by the name `/loop` takes. */
|
|
12
|
+
readonly protocols?: Readonly<Record<string, LoopProtocolText>>;
|
|
13
|
+
}
|
|
14
|
+
/** The records one process runs, and where they came from. */
|
|
15
|
+
export interface LoopSourceLoad {
|
|
16
|
+
/** The merged table the renderer reads: shipped records, then the user's on top. */
|
|
17
|
+
source: LoopPromptSource;
|
|
18
|
+
/** Which files were read, what the user's file changed, and any note for the operator. */
|
|
19
|
+
info: LoopSourceInfo;
|
|
20
|
+
}
|
|
21
|
+
/** Where the records come from for one process. */
|
|
22
|
+
export interface LoopSourceOptions {
|
|
23
|
+
/** Overlay to read instead of `<configDirectory>/loop.yaml`, from `--loop-file`/`DSHT_LOOP_FILE`. */
|
|
24
|
+
overlayFile?: string;
|
|
25
|
+
/** Configuration directory holding `loop.yaml` when no explicit file is given. */
|
|
26
|
+
configDirectory: string;
|
|
27
|
+
/** State directory holding the override stamp; omit to skip drift reporting. */
|
|
28
|
+
stateDirectory?: string;
|
|
29
|
+
/** Shipped file to read; defaults to the `loop.yaml` beside the package entry point. */
|
|
30
|
+
builtinFile?: string;
|
|
31
|
+
/** Table used when the shipped file cannot be read; defaults to the compiled-in one. */
|
|
32
|
+
fallback?: LoopPromptSource;
|
|
33
|
+
}
|
|
34
|
+
/** Path of the `loop.yaml` shipped beside this module.
|
|
35
|
+
*
|
|
36
|
+
* The path is relative to the module, so it resolves both in the source tree (`src/controller/`) and
|
|
37
|
+
* in the published build (`dist/controller/`), exactly as the version read in `cli/dsht.tsx` does.
|
|
38
|
+
* @returns Absolute path to the shipped record file.
|
|
39
|
+
*/
|
|
40
|
+
export declare function shippedLoopFile(): string;
|
|
41
|
+
/** The overlay file a process reads, if it reads one at all.
|
|
42
|
+
*
|
|
43
|
+
* An explicit file wins over the configuration directory: it is how a script or a second checkout
|
|
44
|
+
* points at another set of records without moving the one the interactive client uses.
|
|
45
|
+
* @param options - Resolved options.
|
|
46
|
+
* @returns Absolute path of the overlay to try.
|
|
47
|
+
*/
|
|
48
|
+
export declare function loopOverlayFile(options: LoopSourceOptions): string;
|
|
49
|
+
/** Layer one user file over the shipped records.
|
|
50
|
+
*
|
|
51
|
+
* A record is taken whole: replacing one does not merge its fields with the shipped copy, because a
|
|
52
|
+
* record is a single prompt contract and a half-new brief with a half-old round table is a protocol
|
|
53
|
+
* nobody wrote. Records the file does not name are untouched, which is what lets a shipped fix reach
|
|
54
|
+
* an install that customises something else.
|
|
55
|
+
* @param builtin - Shipped records.
|
|
56
|
+
* @param overlay - Parsed user file.
|
|
57
|
+
* @returns The merged table and the names the user file replaced or added.
|
|
58
|
+
*/
|
|
59
|
+
export declare function mergeLoopSource(builtin: LoopPromptSource, overlay: LoopOverlaySource): {
|
|
60
|
+
source: LoopPromptSource;
|
|
61
|
+
overridden: string[];
|
|
62
|
+
added: string[];
|
|
63
|
+
};
|
|
64
|
+
/** Read the merged records for one process.
|
|
65
|
+
*
|
|
66
|
+
* The shipped file falls back to the compiled-in table with a warning, because a packaging mistake
|
|
67
|
+
* should not stop the client; a user file does not, because an invalid override the operator wrote is
|
|
68
|
+
* a mistake they can fix and a silent fallback would run the shipped records while they believe their
|
|
69
|
+
* own are in force. An absent overlay is ordinary: most installs have none.
|
|
70
|
+
* @param options - Resolution options.
|
|
71
|
+
* @returns The merged records, their sources and the notes to show the operator.
|
|
72
|
+
* @throws when the overlay exists but is not a valid loop file.
|
|
73
|
+
*/
|
|
74
|
+
export declare function loadLoopSource(options: LoopSourceOptions): Promise<LoopSourceLoad>;
|