@try-works/dsh-recursive-mode 0.4.5 → 0.4.7
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 +15 -4
- package/lib/client/contract.d.ts +10 -0
- package/lib/client/doc-viewer.d.ts +50 -0
- package/lib/client/spec-sheet-view.d.ts +237 -0
- package/lib/client/spec-sheet.d.ts +103 -0
- package/lib/client/use-live.d.ts +17 -1
- package/lib/client.js +988 -14
- package/lib/errors.d.ts +47 -1
- package/lib/index.js +483 -48
- package/lib/recursive_ask.tool.d.ts +93 -16
- package/lib/recursive_closeout.tool.d.ts +10 -0
- package/lib/recursive_init.tool.d.ts +8 -0
- package/lib/recursive_phase.tool.d.ts +8 -0
- package/lib/recursive_scratch.tool.d.ts +10 -0
- package/lib/recursive_worktree.tool.d.ts +16 -0
- package/lib/run-id.d.ts +62 -0
- package/lib/run-spec.d.ts +78 -0
- package/lib/run-start.d.ts +27 -0
- package/package.json +1 -1
- package/src/client/contract.ts +10 -0
- package/src/client/doc-viewer.tsx +131 -12
- package/src/client/slots.ts +12 -0
- package/src/client/spec-sheet-view.ts +325 -0
- package/src/client/spec-sheet.tsx +409 -0
- package/src/client/styles.ts +298 -1
- package/src/client/use-live.ts +23 -2
- package/src/errors.ts +48 -2
- package/src/recursive_ask.tool.ts +217 -41
- package/src/recursive_closeout.tool.ts +53 -35
- package/src/recursive_init.tool.ts +20 -4
- package/src/recursive_phase.tool.ts +22 -2
- package/src/recursive_scratch.tool.ts +17 -1
- package/src/recursive_worktree.tool.ts +27 -2
- package/src/run-id.ts +100 -0
- package/src/run-spec.ts +148 -0
- package/src/run-start.ts +68 -3
package/src/client/styles.ts
CHANGED
|
@@ -14,7 +14,7 @@ const BOARD_CSS = `
|
|
|
14
14
|
/* ===== dsh-recursive Paper theme (run 16) ===== */
|
|
15
15
|
|
|
16
16
|
/* Raw tokens: light (default) + dark + shared, scoped to the board/inspector. */
|
|
17
|
-
.rec-board, .rec-inspector {
|
|
17
|
+
.rec-board, .rec-inspector, .rec-spec {
|
|
18
18
|
/* light */
|
|
19
19
|
--rm3-light-background: #FFFFFF;
|
|
20
20
|
--rm3-light-foreground: #111111;
|
|
@@ -673,6 +673,9 @@ const BOARD_CSS = `
|
|
|
673
673
|
line-height: 1.7;
|
|
674
674
|
padding: 0 6px;
|
|
675
675
|
border-left: 2px solid transparent;
|
|
676
|
+
/* Containing block for the visually-hidden text that carries a task box's tick to a screen reader, so it
|
|
677
|
+
cannot be positioned against the page instead of against its own line. */
|
|
678
|
+
position: relative;
|
|
676
679
|
}
|
|
677
680
|
|
|
678
681
|
.rec-doc-line-current {
|
|
@@ -1097,8 +1100,302 @@ dl.rec-settings-rows {
|
|
|
1097
1100
|
overflow-wrap: anywhere;
|
|
1098
1101
|
}
|
|
1099
1102
|
|
|
1103
|
+
/* ===== Run-start spec sheet (tool.call.toolview key 'recursive_ask') =====
|
|
1104
|
+
The document beside the run-start question. LIGHT is the only theme here: the sheet renders inline in a
|
|
1105
|
+
transcript row, not on the board, so it does not own a data-theme attribute and must not inherit the
|
|
1106
|
+
board's. The body is the ONLY scrolling box (a max-height cap with overflow-y: auto) so the decision
|
|
1107
|
+
controls below it can never be pushed out of reach by a long document. */
|
|
1108
|
+
.rec-spec {
|
|
1109
|
+
box-sizing: border-box;
|
|
1110
|
+
margin: 6px 0;
|
|
1111
|
+
padding: var(--board-space-12);
|
|
1112
|
+
border: 1px solid var(--board-border);
|
|
1113
|
+
border-radius: var(--board-radius-lg);
|
|
1114
|
+
background: var(--board-card);
|
|
1115
|
+
color: var(--board-fg);
|
|
1116
|
+
font-family: var(--board-font-sans);
|
|
1117
|
+
font-size: var(--board-text-sm);
|
|
1118
|
+
/* The one transition this sheet has. It is disabled outright under prefers-reduced-motion below. */
|
|
1119
|
+
transition: border-color 160ms ease;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
.rec-spec-header {
|
|
1123
|
+
display: flex;
|
|
1124
|
+
align-items: baseline;
|
|
1125
|
+
justify-content: space-between;
|
|
1126
|
+
gap: var(--board-space-8);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.rec-spec-title {
|
|
1130
|
+
margin: 0;
|
|
1131
|
+
font-size: var(--board-text-sm);
|
|
1132
|
+
font-weight: var(--board-fw-semibold);
|
|
1133
|
+
letter-spacing: var(--board-tracking-tight);
|
|
1134
|
+
overflow-wrap: anywhere;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.rec-spec-tag {
|
|
1138
|
+
flex: none;
|
|
1139
|
+
font-size: var(--board-text-xs);
|
|
1140
|
+
color: var(--board-muted-fg);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
.rec-spec-path {
|
|
1144
|
+
margin: 2px 0 var(--board-space-8);
|
|
1145
|
+
font-family: var(--board-font-mono);
|
|
1146
|
+
font-size: var(--board-text-xs);
|
|
1147
|
+
color: var(--board-muted-fg);
|
|
1148
|
+
overflow-wrap: anywhere;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.rec-spec-notice {
|
|
1152
|
+
margin: 0 0 var(--board-space-8);
|
|
1153
|
+
padding: var(--board-space-8);
|
|
1154
|
+
border: 1px solid var(--board-border);
|
|
1155
|
+
border-radius: var(--board-radius-md);
|
|
1156
|
+
background: var(--board-muted);
|
|
1157
|
+
font-size: var(--board-text-xs);
|
|
1158
|
+
line-height: 1.5;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/* An UNFILLED template is stated as a warning, never as a neutral fact: the whole point of this seat is that
|
|
1162
|
+
nobody is nudged toward approving a hollow document. */
|
|
1163
|
+
.rec-spec-notice-unfilled {
|
|
1164
|
+
border-color: var(--board-warning);
|
|
1165
|
+
background: color-mix(in srgb, var(--board-warning) 10%, var(--board-card));
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
.rec-spec-unfilled-lead {
|
|
1169
|
+
margin: 0 0 6px;
|
|
1170
|
+
font-weight: var(--board-fw-semibold);
|
|
1171
|
+
color: var(--board-warning);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
.rec-spec-unfilled-why {
|
|
1175
|
+
margin: 0 0 4px;
|
|
1176
|
+
color: var(--board-muted-fg);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
.rec-spec-unfilled-next {
|
|
1180
|
+
margin: 6px 0 0;
|
|
1181
|
+
font-weight: var(--board-fw-medium);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.rec-spec-evidence {
|
|
1185
|
+
margin: 0 0 4px;
|
|
1186
|
+
padding-left: 18px;
|
|
1187
|
+
font-family: var(--board-font-mono);
|
|
1188
|
+
font-size: var(--board-text-xs);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.rec-spec-evidence-item {
|
|
1192
|
+
overflow-wrap: anywhere;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
.rec-spec-notice-filled {
|
|
1196
|
+
border-color: var(--board-success);
|
|
1197
|
+
background: color-mix(in srgb, var(--board-success) 8%, var(--board-card));
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
.rec-spec-notice-error {
|
|
1201
|
+
border-color: var(--board-error);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.rec-spec-question {
|
|
1205
|
+
margin: 0 0 var(--board-space-8);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.rec-spec-question-lead,
|
|
1209
|
+
.rec-spec-question-options,
|
|
1210
|
+
.rec-spec-question-where {
|
|
1211
|
+
margin: 0 0 4px;
|
|
1212
|
+
font-size: var(--board-text-xs);
|
|
1213
|
+
line-height: 1.5;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
.rec-spec-question-where {
|
|
1217
|
+
color: var(--board-muted-fg);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
.rec-spec-decision {
|
|
1221
|
+
margin: 0;
|
|
1222
|
+
font-size: var(--board-text-xs);
|
|
1223
|
+
font-weight: var(--board-fw-medium);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/* The document body: the one bounded scroller. Focusable (tabIndex=0) for the keyboard, and the border
|
|
1227
|
+
marks the focus ring's home rather than relying on a colour change alone. */
|
|
1228
|
+
.rec-spec-body-scroll {
|
|
1229
|
+
max-height: 420px;
|
|
1230
|
+
overflow-y: auto;
|
|
1231
|
+
overscroll-behavior: contain;
|
|
1232
|
+
border: 1px solid var(--board-border);
|
|
1233
|
+
border-radius: var(--board-radius-md);
|
|
1234
|
+
background: var(--board-muted);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
.rec-spec-body-scroll:focus-visible {
|
|
1238
|
+
outline: 2px solid var(--board-info);
|
|
1239
|
+
outline-offset: 2px;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/* The rendered preview inside that scroller (PreviewLines from doc-viewer.tsx). The line blocks carry their
|
|
1243
|
+
own padding so the marks — task boxes, gate readings — line up in a column down the left edge. */
|
|
1244
|
+
.rec-spec-body-scroll .rec-doc-lines {
|
|
1245
|
+
padding: var(--board-space-12);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.rec-spec-body-scroll .rec-doc-line {
|
|
1249
|
+
padding: 0;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/* Raw source is monospace, wrapping, and NOT reflowed: the same characters the preview was built from. */
|
|
1253
|
+
.rec-spec-text {
|
|
1254
|
+
margin: 0;
|
|
1255
|
+
padding: var(--board-space-12);
|
|
1256
|
+
font-family: var(--board-font-mono);
|
|
1257
|
+
font-size: var(--board-text-xs);
|
|
1258
|
+
line-height: 1.55;
|
|
1259
|
+
/* The document is shown VERBATIM: only wrapping is normalised, never the characters. */
|
|
1260
|
+
white-space: pre-wrap;
|
|
1261
|
+
overflow-wrap: anywhere;
|
|
1262
|
+
tab-size: 2;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/* ===== The mode control (preview <-> raw source) =====
|
|
1266
|
+
One real button, so Tab reaches it and Enter/Space press it; aria-pressed carries its state to a screen
|
|
1267
|
+
reader and the role=status line beside it names the mode on screen in words. */
|
|
1268
|
+
.rec-spec-view {
|
|
1269
|
+
display: flex;
|
|
1270
|
+
align-items: center;
|
|
1271
|
+
flex-wrap: wrap;
|
|
1272
|
+
gap: var(--board-space-8);
|
|
1273
|
+
margin: 0 0 var(--board-space-8);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
.rec-spec-view-toggle {
|
|
1277
|
+
flex: none;
|
|
1278
|
+
padding: 4px 12px;
|
|
1279
|
+
font-family: inherit;
|
|
1280
|
+
font-size: var(--board-text-xs);
|
|
1281
|
+
color: var(--board-fg);
|
|
1282
|
+
background: var(--board-card);
|
|
1283
|
+
border: 1px solid var(--board-border);
|
|
1284
|
+
border-radius: 999px;
|
|
1285
|
+
cursor: pointer;
|
|
1286
|
+
white-space: nowrap;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
.rec-spec-view-toggle:hover {
|
|
1290
|
+
background: var(--board-accent);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
.rec-spec-view-toggle:focus-visible {
|
|
1294
|
+
outline: 2px solid var(--board-info);
|
|
1295
|
+
outline-offset: 2px;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/* A pressed toggle says "the verbatim source is what you are reading" — marked, and not by colour alone. */
|
|
1299
|
+
.rec-spec-view-toggle[aria-pressed='true'] {
|
|
1300
|
+
border-color: var(--board-info);
|
|
1301
|
+
color: var(--board-info);
|
|
1302
|
+
font-weight: var(--board-fw-semibold);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
.rec-spec-view-toggle[aria-disabled='true'] {
|
|
1306
|
+
opacity: 0.55;
|
|
1307
|
+
cursor: default;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
.rec-spec-view-state {
|
|
1311
|
+
flex: 1 1 auto;
|
|
1312
|
+
min-width: 0;
|
|
1313
|
+
font-size: var(--board-text-xs);
|
|
1314
|
+
color: var(--board-muted-fg);
|
|
1315
|
+
overflow-wrap: anywhere;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/* ===== Marks the PREVIEW must not hide (shared with the phase-doc viewer) =====
|
|
1319
|
+
The scaffolded Phase 0 template ships unticked boxes and two FAIL gates; a preview that drew them as
|
|
1320
|
+
generic body text would make an unfilled form look like a finished spec. */
|
|
1321
|
+
.rec-doc-sr {
|
|
1322
|
+
position: absolute;
|
|
1323
|
+
width: 1px;
|
|
1324
|
+
height: 1px;
|
|
1325
|
+
margin: -1px;
|
|
1326
|
+
padding: 0;
|
|
1327
|
+
border: 0;
|
|
1328
|
+
overflow: hidden;
|
|
1329
|
+
clip: rect(0 0 0 0);
|
|
1330
|
+
white-space: nowrap;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
.rec-doc-todo {
|
|
1334
|
+
display: flex;
|
|
1335
|
+
align-items: baseline;
|
|
1336
|
+
gap: 8px;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.rec-doc-todo-check {
|
|
1340
|
+
flex: none;
|
|
1341
|
+
font-family: var(--board-font-mono);
|
|
1342
|
+
font-size: 12.5px;
|
|
1343
|
+
line-height: 1.7;
|
|
1344
|
+
letter-spacing: var(--board-tracking-mono);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.rec-doc-todo-check-off {
|
|
1348
|
+
color: var(--board-warning);
|
|
1349
|
+
font-weight: var(--board-fw-semibold);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
.rec-doc-todo-check-on {
|
|
1353
|
+
color: var(--board-success);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/* An unticked box is stated, not merely greyed: the warning colour AND the mark together. */
|
|
1357
|
+
.rec-doc-todo-open .rec-doc-li-text {
|
|
1358
|
+
color: var(--board-fg);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
.rec-doc-todo-done .rec-doc-li-text {
|
|
1362
|
+
color: var(--board-muted-fg);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
.rec-doc-gate {
|
|
1366
|
+
font-family: var(--board-font-mono);
|
|
1367
|
+
font-size: var(--board-text-xs);
|
|
1368
|
+
font-weight: var(--board-fw-semibold);
|
|
1369
|
+
letter-spacing: var(--board-tracking-mono);
|
|
1370
|
+
margin: 4px 0;
|
|
1371
|
+
padding: 3px 8px;
|
|
1372
|
+
border: 1px solid var(--board-border);
|
|
1373
|
+
border-radius: var(--board-radius-md);
|
|
1374
|
+
display: inline-block;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
.rec-doc-gate-fail {
|
|
1378
|
+
color: var(--board-error);
|
|
1379
|
+
border-color: var(--board-error);
|
|
1380
|
+
background: color-mix(in srgb, var(--board-error) 8%, var(--board-card));
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
.rec-doc-gate-pass {
|
|
1384
|
+
color: var(--board-success);
|
|
1385
|
+
border-color: var(--board-success);
|
|
1386
|
+
background: color-mix(in srgb, var(--board-success) 8%, var(--board-card));
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
.rec-spec-empty {
|
|
1390
|
+
margin: 0;
|
|
1391
|
+
padding: var(--board-space-12);
|
|
1392
|
+
color: var(--board-muted-fg);
|
|
1393
|
+
font-size: var(--board-text-xs);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1100
1396
|
@media (prefers-reduced-motion: reduce) {
|
|
1101
1397
|
.rec-card, .rec-back, .rec-close, .rec-theme-toggle { transition: none; }
|
|
1398
|
+
.rec-spec { transition: none; }
|
|
1102
1399
|
}
|
|
1103
1400
|
`
|
|
1104
1401
|
|
package/src/client/use-live.ts
CHANGED
|
@@ -23,15 +23,34 @@ function isEmpty(scope: LiveScope): boolean {
|
|
|
23
23
|
return !scope.sessionId && !scope.cwd
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/** Options for {@link useLiveProjection}. */
|
|
27
|
+
export interface LiveProjectionOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Skip the route entirely (default true).
|
|
30
|
+
*
|
|
31
|
+
* ⚠ THIS EXISTS FOR THE PER-CALL SEATS, NOT THE PANELS. A `tool.call.toolview` entry mounts once per Tool
|
|
32
|
+
* call in the transcript, so a seat that always subscribed would open one `/state` fetch and one SSE
|
|
33
|
+
* stream per call row. A row that has nothing to show passes `enabled: false` and asks nothing. The hook
|
|
34
|
+
* itself stays UNCONDITIONAL at every call site — a conditional hook is the run 13 crash, and the flag is
|
|
35
|
+
* how a caller keeps the call order fixed while making the request conditional.
|
|
36
|
+
*/
|
|
37
|
+
enabled?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
/**
|
|
27
41
|
* Subscribe to the live route for one scope. Returns the current state. The SSE
|
|
28
42
|
* feed pushes full frames on every fs change; a dropped stream (sleep/error)
|
|
29
43
|
* triggers one refetch so the board never serves a stale fold.
|
|
44
|
+
*
|
|
45
|
+
* @param scope - sessionId PRIMARY + cwd fallback hint (host resolves the root).
|
|
46
|
+
* @param options - `enabled: false` clears the snapshot and never fetches.
|
|
30
47
|
*/
|
|
31
|
-
export function useLiveProjection(scope: LiveScope): LiveProjectionSnapshot {
|
|
48
|
+
export function useLiveProjection(scope: LiveScope, options: LiveProjectionOptions = {}): LiveProjectionSnapshot {
|
|
49
|
+
const { enabled = true } = options
|
|
32
50
|
const [state, setState] = useState<LiveRecursiveState | null>(null)
|
|
33
51
|
|
|
34
52
|
useEffect(() => {
|
|
53
|
+
if (!enabled) { setState(null); return }
|
|
35
54
|
// Empty scope = no resolvable session row yet — render nothing, never fetch.
|
|
36
55
|
if (isEmpty(scope)) { setState(null); return }
|
|
37
56
|
// 0.1.18 (LIVE BUG 5): do NOT null the snapshot on scope change. 0.1.16
|
|
@@ -51,7 +70,9 @@ export function useLiveProjection(scope: LiveScope): LiveProjectionSnapshot {
|
|
|
51
70
|
})
|
|
52
71
|
|
|
53
72
|
return () => { disposed = true; disposeEvents() }
|
|
54
|
-
|
|
73
|
+
// `enabled` belongs in the deps: it is read inside the effect, so leaving it out would keep a stale
|
|
74
|
+
// closure's verdict and mount the very stream the flag exists to avoid.
|
|
75
|
+
}, [scope.sessionId, scope.cwd, enabled])
|
|
55
76
|
|
|
56
77
|
return state
|
|
57
78
|
}
|
package/src/errors.ts
CHANGED
|
@@ -65,12 +65,30 @@ export const TOOL_ERRORS = {
|
|
|
65
65
|
problem: 'this gate has no default artifact, so one must be named',
|
|
66
66
|
next: 'pass artifact: <file> so the answer has somewhere durable to land',
|
|
67
67
|
},
|
|
68
|
+
RELAY_ONLY_FOR_RUN_START: {
|
|
69
|
+
code: 'RM1150',
|
|
70
|
+
klass: 'input',
|
|
71
|
+
problem: 'relay applies only to the run-start gate, which is the only gate that asks the user-questions channel',
|
|
72
|
+
next: 'drop relay for this gate and answer it with one of its own labels, or call recursive_ask with gate: run-start when the decision is whether to start the run',
|
|
73
|
+
},
|
|
68
74
|
MISSING_RUN_ID: {
|
|
69
75
|
code: 'RM1101',
|
|
70
76
|
klass: 'input',
|
|
71
77
|
problem: 'runId is required',
|
|
72
78
|
next: 'call recursive_status with no runId to see the latest run id in this workspace',
|
|
73
79
|
},
|
|
80
|
+
/**
|
|
81
|
+
* A run id is the NAME of the run directory and is joined onto the run layer
|
|
82
|
+
* as one path segment, so a path-shaped id is refused before any directory is
|
|
83
|
+
* created. See `run-id.ts` for the rule and for why it is not the runtime that
|
|
84
|
+
* learns to accept a path.
|
|
85
|
+
*/
|
|
86
|
+
BAD_RUN_ID: {
|
|
87
|
+
code: 'RM1107',
|
|
88
|
+
klass: 'input',
|
|
89
|
+
problem: 'runId is not a single directory name',
|
|
90
|
+
next: 'pass the run directory name such as 03-something, then call recursive_init again',
|
|
91
|
+
},
|
|
74
92
|
MISSING_ARTIFACT: {
|
|
75
93
|
code: 'RM1102',
|
|
76
94
|
klass: 'input',
|
|
@@ -144,6 +162,19 @@ export const TOOL_ERRORS = {
|
|
|
144
162
|
problem: 'the run has unresolved delegated work, so this phase cannot lock yet',
|
|
145
163
|
next: 'call recursive_status to see the pending delegation, have the child write its reply.md, then lock again',
|
|
146
164
|
},
|
|
165
|
+
/**
|
|
166
|
+
* THE ORDERING DEFECT, AS A CODE. `recursive_ask gate=run-start` used to raise "start this run or hold?"
|
|
167
|
+
* over a Phase 0 document that was still the scaffold `recursive_init` wrote — placeholder requirements,
|
|
168
|
+
* unchecked lists, `FAIL` gates — and nothing put that document in front of the person either. The owner:
|
|
169
|
+
* *"i was never shown the spec before that so how could i approve if i havent seen it"*. Approving an
|
|
170
|
+
* unfilled template is not a decision about a spec, so the gate refuses to be raised until there is one.
|
|
171
|
+
*/
|
|
172
|
+
RUN_START_SPEC_UNFILLED: {
|
|
173
|
+
code: 'RM4404',
|
|
174
|
+
klass: 'state',
|
|
175
|
+
problem: 'the Phase 0 requirements document is still the unfilled template, so there is no run spec for a person to approve',
|
|
176
|
+
next: 'fill the requirements document in (define the requirement ids and their acceptance criteria, and complete the TODO list) and then call recursive_ask with gate: run-start again',
|
|
177
|
+
},
|
|
147
178
|
|
|
148
179
|
/* 5xxx — the runtime refused an operation it understands. */
|
|
149
180
|
|
|
@@ -153,11 +184,26 @@ export const TOOL_ERRORS = {
|
|
|
153
184
|
problem: 'the run-start gate needs an answer, and this composition mounts no user-questions channel to ask one directly',
|
|
154
185
|
next: 'call recursive_ask with gate: run-start and no answer to surface the question, then retry with answer: ' + '"Start run"',
|
|
155
186
|
},
|
|
187
|
+
/**
|
|
188
|
+
* ⚠ RM5503 USED TO LIE. Its text asserted "so no person was asked" for EVERY cause, because the caller
|
|
189
|
+
* that produced it had already thrown the cause away — and a live session showed the cost: the gate
|
|
190
|
+
* failed 22.9 s into the call with a reason nobody could see, and its `Next:` clause prescribed the very
|
|
191
|
+
* call that had just failed, so no route to start a run remained. The problem statement now claims only
|
|
192
|
+
* what the gate knows (no decision came back), and the cause travels in the `detail` the caller
|
|
193
|
+
* supplies. `RUN_START_ANSWER_UNUSABLE` carries the one case this entry must NOT cover: a person was
|
|
194
|
+
* reached and their answer was not an approval.
|
|
195
|
+
*/
|
|
156
196
|
RUN_START_UNANSWERED: {
|
|
157
197
|
code: 'RM5503',
|
|
158
198
|
klass: 'runtime',
|
|
159
|
-
problem: 'the
|
|
160
|
-
next: '
|
|
199
|
+
problem: 'the run-start question reached no decision: the mounted user-questions channel failed before a person answered it',
|
|
200
|
+
next: 'read the cause named in the detail, fix it and call recursive_ask again, or - when this composition cannot deliver the question at all - call recursive_ask with answer: ' + '"Start run"' + ' and relay=true to record the person\'s explicit approval as a relayed one',
|
|
201
|
+
},
|
|
202
|
+
RUN_START_ANSWER_UNUSABLE: {
|
|
203
|
+
code: 'RM5504',
|
|
204
|
+
klass: 'runtime',
|
|
205
|
+
problem: 'a person was asked to start this run and their answer was not one of the labels the run-start gate offered',
|
|
206
|
+
next: 'call recursive_ask again and have the person choose exactly "Start run" or "Hold"; a skipped or custom answer is not an approval, and no relayed answer can replace a decision the person made',
|
|
161
207
|
},
|
|
162
208
|
RUNTIME_REFUSED: {
|
|
163
209
|
code: 'RM5501',
|