@cspeach/cli 1.1.51 → 1.1.53
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/dist/agent/chat-prompt.js +4 -1
- package/dist/agent/conversation-prompt.js +31 -13
- package/dist/agent/inline-skill-block.js +341 -0
- package/dist/agent/loop.js +304 -49
- package/dist/agent/maybe-build-project-context.js +11 -2
- package/dist/agent/provider-shape.js +8 -4
- package/dist/agent/providers/ai-hub-provider.js +2 -2
- package/dist/agent/providers/azure-provider.js +2 -2
- package/dist/agent/providers/byok-provider.js +2 -2
- package/dist/agent/providers/local-provider.js +2 -2
- package/dist/agent/repair-partial.js +21 -0
- package/dist/agent/skill-block-key.js +134 -0
- package/dist/agent/steering-queue.js +50 -14
- package/dist/agent/system-prompt.js +22 -0
- package/dist/agent/tool-dispatch.js +25 -9
- package/dist/agent/turn-watchdog.js +1 -1
- package/dist/approvals/advisory-render.js +6 -3
- package/dist/approvals/approval-prompt.js +9 -4
- package/dist/approvals/op-labels.js +20 -3
- package/dist/approvals/plan-grant.js +8 -0
- package/dist/approvals/render.js +22 -10
- package/dist/approvals/risk-floor.js +32 -4
- package/dist/approvals/visible-changes.js +50 -0
- package/dist/cli.js +10 -2
- package/dist/commands/compact.js +6 -1
- package/dist/commands/config-set.js +13 -0
- package/dist/commands/config-show.js +16 -4
- package/dist/commands/export-conversation.js +7 -4
- package/dist/commands/finish-check-guard.js +124 -0
- package/dist/commands/plan-chain.js +2 -2
- package/dist/commands/plan-reground.js +27 -4
- package/dist/commands/plan-resume.js +113 -3
- package/dist/commands/plan-startup.js +3 -3
- package/dist/config/loader.js +5 -1
- package/dist/cost/cost-log.js +6 -0
- package/dist/cost/system-block.js +6 -0
- package/dist/docx/doc-meta.js +1 -0
- package/dist/docx/markdown-to-docx.js +2 -0
- package/dist/lane/app-chain.js +13 -6
- package/dist/models/server-config.js +1 -1
- package/dist/one-shot.js +1 -0
- package/dist/project-context/conventions.js +8 -1
- package/dist/project-context/index-files.js +97 -34
- package/dist/project-context/index.js +1 -1
- package/dist/project-context/render.js +38 -4
- package/dist/projects/answer-blockers.js +107 -16
- package/dist/projects/blocker-answers-note.js +89 -0
- package/dist/projects/change-options.js +70 -0
- package/dist/projects/document-save.js +86 -0
- package/dist/projects/extract-design.js +4 -0
- package/dist/projects/extract-estimate.js +4 -0
- package/dist/projects/extract-spec-gap.js +8 -0
- package/dist/projects/index.js +1 -1
- package/dist/projects/plan-objects.js +24 -0
- package/dist/projects/plan-run.js +12 -2
- package/dist/projects/save-command.js +50 -18
- package/dist/projects/yes-no.js +5 -1
- package/dist/renderer/change-card.js +17 -1
- package/dist/renderer/html-entities.js +25 -0
- package/dist/renderer/machine-blocks.js +54 -7
- package/dist/renderer/steering-echo.js +53 -8
- package/dist/renderer/tool-labels.js +5 -1
- package/dist/renderer/tool-widget.js +20 -5
- package/dist/repl/chat-lane.js +15 -3
- package/dist/repl/inline-mode.js +51 -0
- package/dist/repl/leftover.js +90 -0
- package/dist/repl/reroute-turn.js +7 -1
- package/dist/repl/turn-stop.js +64 -0
- package/dist/repl.js +155 -24
- package/dist/router/classifier.js +1 -1
- package/dist/sap/reachability-state.js +7 -0
- package/dist/sap/unreachable.js +2 -2
- package/dist/session/fresh-start.js +4 -0
- package/dist/session/queued-step.js +3 -1
- package/dist/session/user-prompt.js +12 -1
- package/dist/skills/manifest-client.js +3 -2
- package/dist/skills/preamble.js +6 -4
- package/dist/skills/source-authed.js +3 -2
- package/dist/tools/approval.js +49 -8
- package/dist/tools/dispatch-skill.js +1 -1
- package/dist/tools/hand-off.js +49 -7
- package/dist/tools/lane/app-lane-resolve.js +9 -0
- package/dist/tools/method-only-change.js +241 -0
- package/dist/tools/project/spec_answers_record.js +190 -0
- package/dist/tools/rap/skeleton-resolve.js +4 -0
- package/dist/tools/sap-write.js +78 -3
- package/dist/tools/snapshot.js +7 -2
- package/dist/tools/subagent/agent_run.js +14 -0
- package/dist/tools/tool-loading.js +18 -8
- package/dist/tools/transport-fit-check.js +70 -0
- package/dist/tools/transport.js +17 -8
- package/dist/tools/write-pipeline.js +46 -5
- package/dist/ui/app.js +112 -13
- package/dist/ui/approval-modal.js +13 -7
- package/dist/ui/ascii-chars.js +21 -3
- package/dist/ui/footer.js +128 -7
- package/dist/ui/interrupt.js +35 -1
- package/dist/ui/live-area.js +21 -4
- package/dist/ui/queue-box-emitter.js +14 -0
- package/dist/ui/rewind-panel.js +2 -1
- package/dist/ui/session-timeline.js +2 -1
- package/dist/ui/text-input.js +31 -2
- package/dist/ui/widgets/ask-card.js +171 -29
- package/dist/ui/widgets/bar-chart.js +2 -1
- package/dist/ui/widgets/confirm-card.js +2 -1
- package/dist/ui/widgets/dep-graph.js +2 -1
- package/dist/ui/widgets/diff-viewer.js +2 -1
- package/dist/ui/widgets/question-card.js +2 -1
- package/dist/ui/widgets/shortcuts-sheet.js +2 -2
- package/dist/ui/widgets/stack-frames.js +2 -1
- package/dist/util/connect-retry.js +65 -0
- package/package.json +2 -2
|
@@ -13,7 +13,13 @@ export async function runRerouteTurn(p) {
|
|
|
13
13
|
await p.run();
|
|
14
14
|
}
|
|
15
15
|
catch (err) {
|
|
16
|
-
|
|
16
|
+
const stopped = p.signal?.aborted ?? false;
|
|
17
|
+
const line = stopped ? p.stopLine?.() ?? null : null;
|
|
18
|
+
if (line !== null)
|
|
19
|
+
p.print(line);
|
|
20
|
+
else
|
|
21
|
+
await renderTurnError({ err, session: p.session, userInterrupted: stopped, inSession: true, print: p.print });
|
|
22
|
+
p.onError?.();
|
|
17
23
|
}
|
|
18
24
|
finally {
|
|
19
25
|
p.onFinally();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LT-15 Task 5b — how the current dispatch was stopped (Ink REPL only;
|
|
3
|
+
* classic, headless and one-shot have no Esc brake).
|
|
4
|
+
*
|
|
5
|
+
* Per dispatch (plan review I4): set by the presses, read by autoDispatch
|
|
6
|
+
* (a queued hop / lane plan / switch is not started after ANY stop) and by
|
|
7
|
+
* the leftover runner's settle, then reset in handleSubmit's finally right
|
|
8
|
+
* after that settle, and at a restore's end. Never reset at controller
|
|
9
|
+
* creation, so a stale stop can never touch the next hop or turn.
|
|
10
|
+
*
|
|
11
|
+
* `press` is the Esc brake. It reuses what Ctrl+C already does — the SAME
|
|
12
|
+
* `currentTurnAbort.abort()` (deps.abortTurn) or, in the post-turn window,
|
|
13
|
+
* the SAME pending cancel (deps.cancelPending) — and never touches Ctrl+C's
|
|
14
|
+
* press count (review C1): after an Esc, one Ctrl+C is still Ctrl+C's FIRST
|
|
15
|
+
* press, never the force-exit.
|
|
16
|
+
*/
|
|
17
|
+
import { steeringSnapshot } from '../agent/steering-queue.js';
|
|
18
|
+
import { decideEscape, brakeLine, brakeEndLine } from '../ui/interrupt.js';
|
|
19
|
+
export function createTurnStop(deps) {
|
|
20
|
+
let braked = false;
|
|
21
|
+
let ctrlC = false;
|
|
22
|
+
const waiting = deps.waiting ?? (() => steeringSnapshot().length);
|
|
23
|
+
const decide = (ui) => decideEscape({
|
|
24
|
+
...ui,
|
|
25
|
+
turnActive: deps.turnActive(),
|
|
26
|
+
busy: deps.busy(),
|
|
27
|
+
restoreActive: deps.restoreActive(),
|
|
28
|
+
phase: deps.phase(),
|
|
29
|
+
canChain: deps.canChain(),
|
|
30
|
+
alreadyStopped: braked || ctrlC || deps.exitRequested(),
|
|
31
|
+
});
|
|
32
|
+
const brake = () => {
|
|
33
|
+
braked = true;
|
|
34
|
+
deps.print(brakeLine(waiting() > 0));
|
|
35
|
+
deps.abortTurn();
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
stopFromCard() {
|
|
39
|
+
// The card itself is the overlay that owned the key: decide as if
|
|
40
|
+
// nothing else were open. Only a running turn is stopped.
|
|
41
|
+
if (decide({ overlayOpen: false, bufferCount: 0, fuzzyOpen: false }) !== 'brake')
|
|
42
|
+
return 'none';
|
|
43
|
+
brake();
|
|
44
|
+
return 'brake';
|
|
45
|
+
},
|
|
46
|
+
press(ui) {
|
|
47
|
+
const action = decide(ui);
|
|
48
|
+
if (action === 'brake') {
|
|
49
|
+
brake();
|
|
50
|
+
}
|
|
51
|
+
else if (action === 'cancel-pending') {
|
|
52
|
+
braked = true;
|
|
53
|
+
deps.cancelPending();
|
|
54
|
+
}
|
|
55
|
+
return action;
|
|
56
|
+
},
|
|
57
|
+
noteCtrlCStop() { ctrlC = true; },
|
|
58
|
+
get braked() { return braked; },
|
|
59
|
+
get stopped() { return braked || ctrlC; },
|
|
60
|
+
get brakedOnly() { return braked && !ctrlC; },
|
|
61
|
+
endLine() { return brakeEndLine(waiting() > 0); },
|
|
62
|
+
reset() { braked = false; ctrlC = false; },
|
|
63
|
+
};
|
|
64
|
+
}
|
package/dist/repl.js
CHANGED
|
@@ -30,6 +30,7 @@ import { createDispatchSlot, queuedStepView } from './session/queued-step.js';
|
|
|
30
30
|
import { continueSkillFor } from './repl/continue-skill.js';
|
|
31
31
|
import { applyResumedSkill } from './router/resume-routing.js';
|
|
32
32
|
import { ACK_PHRASES, wantsContinuation, isConversationalAck, decideLane, turnStatusLabel, applyLaneTools, chatFallbackOrigin } from './repl/chat-lane.js';
|
|
33
|
+
import { inlineActive, inlineServed, isChatLaneProcess } from './repl/inline-mode.js';
|
|
33
34
|
import { CHAT_SKILL } from './agent/chat-prompt.js';
|
|
34
35
|
import { setAssistantStyle } from './agent/style-state.js';
|
|
35
36
|
import { setAssistantLevel } from './agent/level-state.js';
|
|
@@ -45,7 +46,7 @@ import { startSkillsCacheWarmup } from './skills/source-authed.js';
|
|
|
45
46
|
import { formatLoginBanner, signInFragment } from './ui/login-banner.js';
|
|
46
47
|
import { printStartupBanner, printKeytarFallbackBanner, keychainFallbackNotice } from './renderer/banners.js';
|
|
47
48
|
import { paint } from './renderer/theme.js';
|
|
48
|
-
import {
|
|
49
|
+
import { classifyMidTurnLine, midTurnSlashHint } from './renderer/steering-echo.js';
|
|
49
50
|
import { runAddSapWizard } from './sap/onboarding.js';
|
|
50
51
|
import { getConnection } from './sap/connection-manager.js';
|
|
51
52
|
import { unreachableCode, unreachableMessage } from './sap/unreachable.js';
|
|
@@ -154,6 +155,9 @@ import { getEffectiveWriteMode, runModeCommand } from './repl/mode-cycle.js';
|
|
|
154
155
|
import { maybeExpandNumberSelection, lastAssistantText } from './repl/numbered-menu.js';
|
|
155
156
|
import { attachEarlyLineBuffer, noopEarlyLineBuffer } from './repl/early-line-buffer.js';
|
|
156
157
|
import { createRestoreGuard } from './repl/restore-guard.js';
|
|
158
|
+
import { createLeftoverRunner, takeUnsentReport, parseSubmitted } from './repl/leftover.js';
|
|
159
|
+
import { createTurnStop } from './repl/turn-stop.js';
|
|
160
|
+
import { requestQueueToBox } from './ui/queue-box-emitter.js';
|
|
157
161
|
import { gracefulExit } from './lib/graceful-exit.js';
|
|
158
162
|
import { endSapSessionAfterTurn } from './sap/session-end.js';
|
|
159
163
|
import { snapshotTurnStart, pushPostTurnStatus, seedFooterSpend } from './repl/post-turn-status.js';
|
|
@@ -201,6 +205,7 @@ import './tools/project/team_findings.js';
|
|
|
201
205
|
import './tools/project/team_estate.js';
|
|
202
206
|
import './tools/project/rules_propose.js';
|
|
203
207
|
import './tools/project/team_lane_move.js';
|
|
208
|
+
import './tools/project/spec_answers_record.js';
|
|
204
209
|
import './tools/subagent/_background-shared.js';
|
|
205
210
|
import './tools/subagent/background_run.js';
|
|
206
211
|
import './tools/subagent/monitor_emit.js';
|
|
@@ -1079,13 +1084,47 @@ export async function runRepl(opts) {
|
|
|
1079
1084
|
: pendingCancelLine(phase, exiting, queuedWorkLabel(pendingDispatchAccessor.get()));
|
|
1080
1085
|
chunkEmitter.emit('chunk', '\n' + formatInfo(line));
|
|
1081
1086
|
};
|
|
1087
|
+
// LT-15 Feature 4 — messages still waiting when a dispatch ends: run them
|
|
1088
|
+
// as the next turn (normal end), put them back in the box (Ctrl+C, an
|
|
1089
|
+
// error), let a queued hop read them first, or report them at exit.
|
|
1090
|
+
// Flags are per dispatch: set below, read + reset by settle() in
|
|
1091
|
+
// handleSubmit's finally and at a restore's end (plan review I4).
|
|
1092
|
+
const leftover = createLeftoverRunner({
|
|
1093
|
+
isBusy: () => turnStatusEmitter.isBusy(),
|
|
1094
|
+
isDone: () => done,
|
|
1095
|
+
// F4 — prose: never parsed as a slash command or @-shortcut; it goes
|
|
1096
|
+
// through routing like a fresh prompt (review M5), as the person's turn.
|
|
1097
|
+
submit: (text) => { void handleSubmit(text, { prose: true }); },
|
|
1098
|
+
toBox: () => { requestQueueToBox(); },
|
|
1099
|
+
print: (line) => chunkEmitter.emit('chunk', '\n' + line),
|
|
1100
|
+
schedule: (fn) => { setImmediate(fn); },
|
|
1101
|
+
});
|
|
1102
|
+
// LT-15 Task 5b — how this dispatch was stopped (Esc brake or Ctrl+C).
|
|
1103
|
+
// Per dispatch: read by autoDispatch (nothing queued starts after a stop)
|
|
1104
|
+
// and by leftover.settle, reset right after that settle and at a
|
|
1105
|
+
// restore's end (plan review I4). The brake reuses Ctrl+C's own abort and
|
|
1106
|
+
// pending cancel and never touches interruptCount (review C1).
|
|
1107
|
+
const turnStop = createTurnStop({
|
|
1108
|
+
turnActive: () => currentTurnAbort !== null,
|
|
1109
|
+
busy: () => turnStatusEmitter.isBusy(),
|
|
1110
|
+
restoreActive: () => restoreGuard.active,
|
|
1111
|
+
phase: () => cancelPhase(),
|
|
1112
|
+
canChain: () => canChainNow(),
|
|
1113
|
+
exitRequested: () => exitAfterTurn,
|
|
1114
|
+
abortTurn: () => { currentTurnAbort?.abort(); },
|
|
1115
|
+
cancelPending: () => { announcePendingCancel(false); },
|
|
1116
|
+
print: (line) => chunkEmitter.emit('chunk', '\n' + formatInfo(line)),
|
|
1117
|
+
});
|
|
1082
1118
|
/** Auto-dispatch of the next plan phase, unless a post-turn cancel stopped it. */
|
|
1083
1119
|
// Task 13 fix round — `planHop` travels WITH the command into handleSubmit,
|
|
1084
1120
|
// so a refused submit can never leave a stale hop mark behind.
|
|
1085
1121
|
const autoDispatch = (cmd, planHop = false, queued = null) => {
|
|
1086
1122
|
// Round 4 item 9b — the exit is already under way: start nothing new.
|
|
1087
1123
|
// Final review B-I2 — `/exit` typed mid-turn (exitAfterTurn) stops it too.
|
|
1088
|
-
|
|
1124
|
+
// LT-15 Task 5b (D4, 5a re-review) — nothing queued starts after a stop
|
|
1125
|
+
// of this dispatch (Esc or Ctrl+C); the D11 chat fallback still re-runs
|
|
1126
|
+
// the person's own message (review M6).
|
|
1127
|
+
const decision = autoDispatchDecision({ done, exitRequested: exitAfterTurn, cancelPending: pendingCancel.pending, stopped: turnStop.stopped, chatFallback: queued?.by === 'chat_fallback' }, cmd);
|
|
1089
1128
|
if (decision.kind === 'skip')
|
|
1090
1129
|
return;
|
|
1091
1130
|
if (decision.kind === 'not-started') {
|
|
@@ -1098,6 +1137,8 @@ export async function runRepl(opts) {
|
|
|
1098
1137
|
const notice = queued ? null : autoRoutedNotice(cmd);
|
|
1099
1138
|
if (notice)
|
|
1100
1139
|
chunkEmitter.emit('chunk', formatInfo(notice));
|
|
1140
|
+
// LT-15 D3 — the hop goes first; a waiting message is read at its round 0.
|
|
1141
|
+
leftover.noteDispatchScheduled();
|
|
1101
1142
|
setImmediate(() => { void handleSubmit(cmd, { planHop, queued }); });
|
|
1102
1143
|
};
|
|
1103
1144
|
// BUG-2b — the actual dispatch. Wrapped by handleSubmit (below) which
|
|
@@ -1108,6 +1149,9 @@ export async function runRepl(opts) {
|
|
|
1108
1149
|
const trimmed = line.trim();
|
|
1109
1150
|
if (!trimmed)
|
|
1110
1151
|
return;
|
|
1152
|
+
// LT-15 F4 — a replayed waiting message is prose, also when the D11
|
|
1153
|
+
// chat fallback re-runs it (the flag rides on the fallback origin).
|
|
1154
|
+
const prose = submitOpts?.prose === true || submitOpts?.queued?.prose === true;
|
|
1111
1155
|
// Round 6 review I2 — the resumed skill is offered to the FIRST submit
|
|
1112
1156
|
// only (slash commands included), then cleared.
|
|
1113
1157
|
const resumedSkill = resumeKeepsSkill ? session.skill ?? null : null;
|
|
@@ -1116,7 +1160,10 @@ export async function runRepl(opts) {
|
|
|
1116
1160
|
// the Footer has already persisted this submission (flattened), so
|
|
1117
1161
|
// the consecutive-dup guard makes this a no-op instead of the old
|
|
1118
1162
|
// double append.
|
|
1119
|
-
|
|
1163
|
+
// LT-15 F4 — a replayed waiting message is already in history (the
|
|
1164
|
+
// Footer stored each one when it was typed).
|
|
1165
|
+
if (!prose)
|
|
1166
|
+
appendHistoryEntry(trimmed);
|
|
1120
1167
|
// Phase A** (2026-05-16) — echo the user's prompt into Body so it
|
|
1121
1168
|
// appears in the conversation history. Plus a dim divider above
|
|
1122
1169
|
// the echo so consecutive turns have a clear visual break (closes
|
|
@@ -1362,7 +1409,8 @@ export async function runRepl(opts) {
|
|
|
1362
1409
|
provider,
|
|
1363
1410
|
userMessage: rr.prompt,
|
|
1364
1411
|
skill: rr.skill,
|
|
1365
|
-
ctx: { adt, sapAlias: selectedAlias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: inkPreview, chunkEmitter, todoEmitter, currentTransport: currentTransportAccessor, pendingDispatch: pendingDispatchAccessor },
|
|
1412
|
+
ctx: { adt, sapAlias: selectedAlias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: inkPreview, chunkEmitter, todoEmitter, currentTransport: currentTransportAccessor, pendingDispatch: pendingDispatchAccessor, inlineSkills: inlineActive({ skills: cfg.assistant.skills, lane: laneSetting, interactive: !!process.stdin.isTTY, served: inlineServed(cfg.llm?.mode) }), chatLaneProcess: isChatLaneProcess(laneSetting, !!process.stdin.isTTY), turnBy: lastTurnBy },
|
|
1413
|
+
...(signedInAs ? { operator: signedInAs } : {}), // change options: Prepared for
|
|
1366
1414
|
chunkEmitter,
|
|
1367
1415
|
pushSpend: true, // F-L2-2 — top-level REPL turn: live statusline spend
|
|
1368
1416
|
autoCompact: true, // Task 19 — top-level REPL turn: auto-compact gate at turn end
|
|
@@ -1371,6 +1419,11 @@ export async function runRepl(opts) {
|
|
|
1371
1419
|
session,
|
|
1372
1420
|
print: (line) => chunkEmitter.emit('chunk', line + '\n'),
|
|
1373
1421
|
signal: rerouteAbort.signal,
|
|
1422
|
+
// LT-15 review I5 — an error end puts waiting text back in the box.
|
|
1423
|
+
onError: () => { if (!rerouteAbort.signal.aborted)
|
|
1424
|
+
leftover.noteError(); },
|
|
1425
|
+
// LT-15 Task 5b — an Esc brake prints one dim line, not the block.
|
|
1426
|
+
stopLine: () => (turnStop.brakedOnly ? formatInfo(turnStop.endLine()).trimEnd() : null),
|
|
1374
1427
|
onFinally: () => {
|
|
1375
1428
|
turnStatusEmitter.stop();
|
|
1376
1429
|
currentTurnAbort = null;
|
|
@@ -1393,6 +1446,9 @@ export async function runRepl(opts) {
|
|
|
1393
1446
|
todoEmitter.emit('update', []);
|
|
1394
1447
|
clearAppFlow(session); // Task 7 — /new ends an /abap-app flow
|
|
1395
1448
|
clearJourneyFlow(session); // Task 3 — and a clean-core journey
|
|
1449
|
+
// LT-15 Task 4 — a message still waiting belongs to the old
|
|
1450
|
+
// conversation: back into the box (or reported), never carried over.
|
|
1451
|
+
leftover.toBoxOrReport();
|
|
1396
1452
|
// 2026-10-22 — and the conversation history: the next prompt sends none of it.
|
|
1397
1453
|
startFreshConversation(session);
|
|
1398
1454
|
idleKeepalive.disarm(); // no idle cache ping for the old history
|
|
@@ -1465,7 +1521,9 @@ export async function runRepl(opts) {
|
|
|
1465
1521
|
// an item in a 2+-item list in the LAST assistant message.
|
|
1466
1522
|
let effectiveInput = trimmed;
|
|
1467
1523
|
{
|
|
1468
|
-
|
|
1524
|
+
// LT-15 Task 4 fix round 1 — never for a replayed waiting message: a
|
|
1525
|
+
// "2" typed mid-turn did not answer the menu of the turn that just ended.
|
|
1526
|
+
const expansion = prose ? null : maybeExpandNumberSelection(trimmed, lastAssistantText(session.messages));
|
|
1469
1527
|
if (expansion) {
|
|
1470
1528
|
chunkEmitter.emit('chunk', chalk.dim(`${expansion.echo}\n`));
|
|
1471
1529
|
effectiveInput = expansion.text;
|
|
@@ -1490,7 +1548,9 @@ export async function runRepl(opts) {
|
|
|
1490
1548
|
return;
|
|
1491
1549
|
}
|
|
1492
1550
|
effectiveInput = door.input;
|
|
1493
|
-
|
|
1551
|
+
// LT-15 F4 — a replayed waiting message is prose: never a slash command
|
|
1552
|
+
// or an @-shortcut (`/sap/bc/adt/x is slow` reaches the model as text).
|
|
1553
|
+
const parsed = parseSubmitted(effectiveInput, prose);
|
|
1494
1554
|
// D35 (2026-06-11) — explicit slash naming a skill that isn't in the
|
|
1495
1555
|
// catalog: print the notice ("did you mean" / builtin-with-args) and
|
|
1496
1556
|
// end the turn. Slash input must NEVER fall through to the classifier
|
|
@@ -1580,7 +1640,7 @@ export async function runRepl(opts) {
|
|
|
1580
1640
|
// Round 6 item 4c / review I2 — after a resume, a short follow-up
|
|
1581
1641
|
// ("continue", a number) stays in the session's skill; anything else
|
|
1582
1642
|
// is classified (applyResumedSkill below).
|
|
1583
|
-
const continuedSkill = wantsContinuation({ trimmed, sessionSkill: session.skill, awaitingSkillAnswer: session.awaitingSkillAnswer, resumedSkill });
|
|
1643
|
+
const continuedSkill = wantsContinuation({ trimmed, sessionSkill: session.skill, awaitingSkillAnswer: session.awaitingSkillAnswer, resumedSkill, lastChangeOptions: session.lastChangeOptions ?? null });
|
|
1584
1644
|
if (continuedSkill) {
|
|
1585
1645
|
chunkEmitter.emit('chunk', formatRoutingLine({ kind: 'continuing', skill: continuedSkill }));
|
|
1586
1646
|
skill = continuedSkill;
|
|
@@ -1611,7 +1671,7 @@ export async function runRepl(opts) {
|
|
|
1611
1671
|
sap_system: selectedAlias,
|
|
1612
1672
|
last_skill: session.last_skill ?? null,
|
|
1613
1673
|
last_object: extractedObject ?? session.last_object ?? null,
|
|
1614
|
-
}, { signal: routingCtl.signal });
|
|
1674
|
+
}, { signal: routingCtl.signal, prose });
|
|
1615
1675
|
}
|
|
1616
1676
|
catch (err) {
|
|
1617
1677
|
if (!routingCtl.signal.aborted) {
|
|
@@ -2056,7 +2116,9 @@ export async function runRepl(opts) {
|
|
|
2056
2116
|
provider,
|
|
2057
2117
|
userMessage: prompt,
|
|
2058
2118
|
skill: skill,
|
|
2059
|
-
|
|
2119
|
+
// Stage 1A — inline is decided here, at every submit, from today's inputs (never cached).
|
|
2120
|
+
ctx: { adt, sapAlias: selectedAlias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: inkPreview, chunkEmitter, todoEmitter, currentTransport: currentTransportAccessor, pendingDispatch: pendingDispatchAccessor, inlineSkills: inlineActive({ skills: cfg.assistant.skills, lane: laneSetting, interactive: !!process.stdin.isTTY, served: inlineServed(cfg.llm?.mode) }), chatLaneProcess: isChatLaneProcess(laneSetting, !!process.stdin.isTTY), turnBy: lastTurnBy },
|
|
2121
|
+
...(signedInAs ? { operator: signedInAs } : {}), // change options: Prepared for
|
|
2060
2122
|
chunkEmitter,
|
|
2061
2123
|
pushSpend: true, // F-L2-2 — top-level REPL turn: live statusline spend
|
|
2062
2124
|
explicitSlash: parsed.skill !== undefined, // Task 7 R11 — typed or queued slash command
|
|
@@ -2134,7 +2196,7 @@ export async function runRepl(opts) {
|
|
|
2134
2196
|
// never throw ChatUnavailableError again.
|
|
2135
2197
|
laneSetting = 'skills';
|
|
2136
2198
|
sapStateStore.update({ chatLane: false });
|
|
2137
|
-
pendingDispatchAccessor.set(trimmed, chatFallbackOrigin());
|
|
2199
|
+
pendingDispatchAccessor.set(trimmed, chatFallbackOrigin(prose));
|
|
2138
2200
|
}
|
|
2139
2201
|
else {
|
|
2140
2202
|
// 2026-09-24 — print the turn-error block INTO the conversation (it
|
|
@@ -2144,13 +2206,25 @@ export async function runRepl(opts) {
|
|
|
2144
2206
|
// "type continue" (the partial turn is already saved).
|
|
2145
2207
|
const { renderTurnError } = await import('./agent/turn-error-ux.js');
|
|
2146
2208
|
turnCancelledByUser = currentTurnAbort?.signal.aborted ?? false;
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2209
|
+
// LT-15 review I5 — an error end (not a stop): waiting text goes back
|
|
2210
|
+
// into the box, nothing runs by itself.
|
|
2211
|
+
if (!turnCancelledByUser)
|
|
2212
|
+
leftover.noteError();
|
|
2213
|
+
if (turnCancelledByUser && turnStop.brakedOnly) {
|
|
2214
|
+
// LT-15 Task 5b — an Esc brake: one dim line in place of the
|
|
2215
|
+
// "type continue" block. The loop already saved the turn as
|
|
2216
|
+
// interrupted, and a journey stays paused.
|
|
2217
|
+
chunkEmitter.emit('chunk', '\n' + formatInfo(turnStop.endLine()));
|
|
2218
|
+
}
|
|
2219
|
+
else {
|
|
2220
|
+
await renderTurnError({
|
|
2221
|
+
err,
|
|
2222
|
+
session,
|
|
2223
|
+
userInterrupted: turnCancelledByUser,
|
|
2224
|
+
inSession: true,
|
|
2225
|
+
print: (line) => chunkEmitter.emit('chunk', line + '\n'),
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2154
2228
|
}
|
|
2155
2229
|
}
|
|
2156
2230
|
finally {
|
|
@@ -2276,6 +2350,8 @@ export async function runRepl(opts) {
|
|
|
2276
2350
|
tokensUsed: 0,
|
|
2277
2351
|
model: session.model,
|
|
2278
2352
|
emit: (line) => chunkEmitter.emit('chunk', line + '\n'),
|
|
2353
|
+
...(signedInAs ? { operator: signedInAs } : {}),
|
|
2354
|
+
sessionId: session.id,
|
|
2279
2355
|
});
|
|
2280
2356
|
}
|
|
2281
2357
|
else if (r.kind === 'none' && !(await import('./sap/reachability-state.js')).isSapUnreachableNoted()) {
|
|
@@ -2443,21 +2519,23 @@ export async function runRepl(opts) {
|
|
|
2443
2519
|
chunkEmitter.emit('chunk', midTurnSlashHint(kind.command));
|
|
2444
2520
|
return;
|
|
2445
2521
|
}
|
|
2446
|
-
|
|
2522
|
+
// LT-15 Feature 1 (D5) — no scrollback receipt; the App's queue row shows it.
|
|
2523
|
+
if (t.length > 0)
|
|
2447
2524
|
enqueueSteering(t);
|
|
2448
|
-
chunkEmitter.emit('chunk', steeringEchoLine(t));
|
|
2449
|
-
}
|
|
2450
2525
|
return;
|
|
2451
2526
|
}
|
|
2452
2527
|
idleKeepalive.disarm(); // Task 15 — a submit ends the idle period
|
|
2453
2528
|
turnStatusEmitter.reserve();
|
|
2454
2529
|
postTurn = false;
|
|
2455
2530
|
postTurnMayChain = false;
|
|
2531
|
+
// LT-15 review I5 — a dispatch that threw ended by an error.
|
|
2532
|
+
let threw = true;
|
|
2456
2533
|
try {
|
|
2457
2534
|
// Round 4 item 9b — the exit path waits (capped) for this to finish.
|
|
2458
2535
|
const run = dispatchSubmit(line, submitOpts);
|
|
2459
2536
|
dispatchTail = run;
|
|
2460
2537
|
await run;
|
|
2538
|
+
threw = false;
|
|
2461
2539
|
}
|
|
2462
2540
|
finally {
|
|
2463
2541
|
turnStatusEmitter.release();
|
|
@@ -2479,6 +2557,14 @@ export async function runRepl(opts) {
|
|
|
2479
2557
|
// the submit ended in an exit (merge: UI round-3 /exit mid-turn).
|
|
2480
2558
|
if (!done)
|
|
2481
2559
|
idleKeepalive.armAfterTurn(session);
|
|
2560
|
+
// LT-15 Feature 4 — after release(): messages still waiting run as the
|
|
2561
|
+
// next turn (on the next tick, drained there), go back into the box,
|
|
2562
|
+
// wait for a queued hop, or are left for the exit report.
|
|
2563
|
+
if (threw)
|
|
2564
|
+
leftover.noteError();
|
|
2565
|
+
leftover.settle({ exitRequested: exitAfterTurn, braked: turnStop.braked });
|
|
2566
|
+
// LT-15 Task 5b (I4) — the stop belongs to this dispatch only.
|
|
2567
|
+
turnStop.reset();
|
|
2482
2568
|
}
|
|
2483
2569
|
};
|
|
2484
2570
|
// Fix round 3 (item 5) — `/exit` / `/quit` typed while a turn runs:
|
|
@@ -2495,6 +2581,9 @@ export async function runRepl(opts) {
|
|
|
2495
2581
|
cancelAnnouncedPhase = null;
|
|
2496
2582
|
pendingCancel.clear();
|
|
2497
2583
|
interruptCount = 0;
|
|
2584
|
+
// LT-15 Feature 4 — a restore's end decides like a normal turn end.
|
|
2585
|
+
leftover.settle({ exitRequested: exitAfterTurn, braked: turnStop.braked });
|
|
2586
|
+
turnStop.reset();
|
|
2498
2587
|
},
|
|
2499
2588
|
exit: () => { done = true; if (resolveDone)
|
|
2500
2589
|
resolveDone(); },
|
|
@@ -2532,6 +2621,10 @@ export async function runRepl(opts) {
|
|
|
2532
2621
|
let idleInterruptAt = 0;
|
|
2533
2622
|
const onInterrupt = () => {
|
|
2534
2623
|
const now = Date.now();
|
|
2624
|
+
// LT-15 D1 — Ctrl+C during a dispatch: waiting messages go back into the
|
|
2625
|
+
// box at this dispatch's end; nothing runs by itself.
|
|
2626
|
+
if (turnStatusEmitter.isBusy())
|
|
2627
|
+
leftover.noteCtrlC();
|
|
2535
2628
|
const action = decideInterrupt({
|
|
2536
2629
|
turnActive: currentTurnAbort !== null,
|
|
2537
2630
|
// Review I3 — routing / prep in flight without a controller yet is
|
|
@@ -2545,18 +2638,30 @@ export async function runRepl(opts) {
|
|
|
2545
2638
|
});
|
|
2546
2639
|
if (action === 'abort-turn') {
|
|
2547
2640
|
interruptCount++;
|
|
2548
|
-
|
|
2641
|
+
turnStop.noteCtrlCStop(); // LT-15 Task 5b — nothing queued starts after it
|
|
2642
|
+
// After an Esc the stop line is already on screen: not repeated (C1).
|
|
2643
|
+
if (!turnStop.braked)
|
|
2644
|
+
chunkEmitter.emit('chunk', '\n' + formatInfo('Stopping the turn · saving the work done so far'));
|
|
2549
2645
|
currentTurnAbort.abort();
|
|
2646
|
+
return 'stopped'; // LT-15 D1 — App puts the waiting text back in the box now
|
|
2550
2647
|
}
|
|
2551
2648
|
else if (action === 'cancel-pending') {
|
|
2552
2649
|
interruptCount++;
|
|
2650
|
+
turnStop.noteCtrlCStop();
|
|
2553
2651
|
announcePendingCancel(false);
|
|
2652
|
+
return 'stopped';
|
|
2554
2653
|
}
|
|
2555
2654
|
else if (action === 'force-exit') {
|
|
2556
2655
|
// Second Ctrl+C during the same turn — give up and exit.
|
|
2557
2656
|
chunkEmitter.emit('chunk', '\n' + formatInfo('Exiting · second Ctrl+C'));
|
|
2558
2657
|
unmount();
|
|
2559
2658
|
process.stdout.write('\x1b[?25h'); // restore cursor
|
|
2659
|
+
// LT-15 Feature 4 — say how many waiting messages were never sent.
|
|
2660
|
+
{
|
|
2661
|
+
const unsent = takeUnsentReport();
|
|
2662
|
+
if (unsent)
|
|
2663
|
+
console.log(unsent.trimEnd());
|
|
2664
|
+
}
|
|
2560
2665
|
printGoodbye(session);
|
|
2561
2666
|
// Final review B M-2 — through gracefulExit (fetch drain, capped).
|
|
2562
2667
|
// Re-review item 3 — mark the session done first, so nothing queued
|
|
@@ -2576,6 +2681,18 @@ export async function runRepl(opts) {
|
|
|
2576
2681
|
resolveDone();
|
|
2577
2682
|
}
|
|
2578
2683
|
};
|
|
2684
|
+
// LT-15 Task 5b — Esc while working (the App calls this only while busy
|
|
2685
|
+
// and when no card, held draft or Ctrl+R search owns the Esc). The brake
|
|
2686
|
+
// decides with decideEscape and reuses Ctrl+C's own stop.
|
|
2687
|
+
const onBrake = (ui) => {
|
|
2688
|
+
turnStop.press(ui);
|
|
2689
|
+
};
|
|
2690
|
+
// LT-15 Revision 2 — Esc / [c] on the approval card (or Esc / Cancel on
|
|
2691
|
+
// the plan gate card) is the same safe stop as the Esc brake. The card
|
|
2692
|
+
// still answers cancel-turn, so nothing from it is written or minted.
|
|
2693
|
+
const onCardStop = () => {
|
|
2694
|
+
turnStop.stopFromCard();
|
|
2695
|
+
};
|
|
2579
2696
|
// Wave1 live-fix (Bug 2) — bracketed paste for the Ink path. Windows
|
|
2580
2697
|
// Terminal delivers pasted newlines as `\r`, and a `\r`-only chunk
|
|
2581
2698
|
// parses as key.return (ink parse-keypress.js:145-148) → every pasted
|
|
@@ -2640,6 +2757,8 @@ export async function runRepl(opts) {
|
|
|
2640
2757
|
sapAlias: selectedAlias,
|
|
2641
2758
|
onInterrupt,
|
|
2642
2759
|
onExitRequest,
|
|
2760
|
+
onBrake,
|
|
2761
|
+
onCardStop,
|
|
2643
2762
|
}), {
|
|
2644
2763
|
// Bug 2 (wave1 live-fix) — Ink reads from the paste guard, not
|
|
2645
2764
|
// process.stdin directly. The guard satisfies Ink's full stdin
|
|
@@ -2718,6 +2837,13 @@ export async function runRepl(opts) {
|
|
|
2718
2837
|
if (exitTail === 'timeout') {
|
|
2719
2838
|
console.log(formatAttention({ text: 'Post-turn work did not finish', fact: 'the session file may be incomplete' }).trimEnd());
|
|
2720
2839
|
}
|
|
2840
|
+
// LT-15 Feature 4 — `/exit` (or the idle exit) with messages still waiting:
|
|
2841
|
+
// they are not sent; say how many.
|
|
2842
|
+
{
|
|
2843
|
+
const unsent = takeUnsentReport();
|
|
2844
|
+
if (unsent)
|
|
2845
|
+
console.log(unsent.trimEnd());
|
|
2846
|
+
}
|
|
2721
2847
|
printGoodbye(session);
|
|
2722
2848
|
return 0;
|
|
2723
2849
|
}
|
|
@@ -3404,7 +3530,8 @@ export async function runRepl(opts) {
|
|
|
3404
3530
|
provider,
|
|
3405
3531
|
userMessage: rr.prompt,
|
|
3406
3532
|
skill: rr.skill,
|
|
3407
|
-
ctx: { adt, sapAlias: selectedAlias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: replPreviewHook, chunkEmitter: classicOutputEmitter, todoEmitter, currentTransport: currentTransportAccessor, pendingDispatch: pendingDispatchAccessor },
|
|
3533
|
+
ctx: { adt, sapAlias: selectedAlias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: replPreviewHook, chunkEmitter: classicOutputEmitter, todoEmitter, currentTransport: currentTransportAccessor, pendingDispatch: pendingDispatchAccessor, inlineSkills: inlineActive({ skills: cfg.assistant.skills, lane: laneSetting, interactive: !pipedStdin, served: inlineServed(cfg.llm?.mode) }), chatLaneProcess: isChatLaneProcess(laneSetting, !pipedStdin), turnBy: lastTurnBy },
|
|
3534
|
+
...(signedInAs ? { operator: signedInAs } : {}), // change options: Prepared for
|
|
3408
3535
|
pushSpend: true, // F-L2-2 — top-level REPL turn: live statusline spend
|
|
3409
3536
|
autoCompact: true, // Task 19 — top-level REPL turn: auto-compact gate at turn end
|
|
3410
3537
|
}),
|
|
@@ -3712,7 +3839,7 @@ export async function runRepl(opts) {
|
|
|
3712
3839
|
continue;
|
|
3713
3840
|
}
|
|
3714
3841
|
// Round 6 item 4c / review I2 — see the Ink path.
|
|
3715
|
-
const continuedSkill = wantsContinuation({ trimmed, sessionSkill: session.skill, awaitingSkillAnswer: session.awaitingSkillAnswer, resumedSkill });
|
|
3842
|
+
const continuedSkill = wantsContinuation({ trimmed, sessionSkill: session.skill, awaitingSkillAnswer: session.awaitingSkillAnswer, resumedSkill, lastChangeOptions: session.lastChangeOptions ?? null });
|
|
3716
3843
|
if (continuedSkill) {
|
|
3717
3844
|
console.log(formatRoutingLine({ kind: 'continuing', skill: continuedSkill }).trimEnd());
|
|
3718
3845
|
skill = continuedSkill;
|
|
@@ -4146,7 +4273,9 @@ export async function runRepl(opts) {
|
|
|
4146
4273
|
provider,
|
|
4147
4274
|
userMessage: prompt,
|
|
4148
4275
|
skill: skill,
|
|
4149
|
-
|
|
4276
|
+
// Stage 1A — inline is decided here, at every submit, from today's inputs (never cached).
|
|
4277
|
+
ctx: { adt, sapAlias: selectedAlias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: replPreviewHook, chunkEmitter: classicOutputEmitter, todoEmitter, currentTransport: currentTransportAccessor, pendingDispatch: pendingDispatchAccessor, inlineSkills: inlineActive({ skills: cfg.assistant.skills, lane: laneSetting, interactive: !pipedStdin, served: inlineServed(cfg.llm?.mode) }), chatLaneProcess: isChatLaneProcess(laneSetting, !pipedStdin), turnBy: lastTurnBy },
|
|
4278
|
+
...(signedInAs ? { operator: signedInAs } : {}), // change options: Prepared for
|
|
4150
4279
|
pushSpend: true, // F-L2-2 — top-level REPL turn: live statusline spend
|
|
4151
4280
|
explicitSlash: parsed.skill !== undefined, // Task 7 R11 — typed or queued slash command
|
|
4152
4281
|
signal: turnAbort.signal,
|
|
@@ -4340,6 +4469,8 @@ export async function runRepl(opts) {
|
|
|
4340
4469
|
tokensUsed: 0,
|
|
4341
4470
|
model: session.model,
|
|
4342
4471
|
emit: (line) => console.log(line),
|
|
4472
|
+
...(signedInAs ? { operator: signedInAs } : {}),
|
|
4473
|
+
sessionId: session.id,
|
|
4343
4474
|
});
|
|
4344
4475
|
}
|
|
4345
4476
|
else if (r.kind === 'none' && !(await import('./sap/reachability-state.js')).isSapUnreachableNoted()) {
|
|
@@ -157,7 +157,7 @@ export async function classifyPrompt(prompt, context, opts = {}) {
|
|
|
157
157
|
// before classification; this guard is the backstop that turns any
|
|
158
158
|
// future regression into a loud error instead of a silent misroute.
|
|
159
159
|
// Fires BEFORE config/key loading so no network call is ever made.
|
|
160
|
-
if (prompt.trimStart().startsWith('/')) {
|
|
160
|
+
if (!opts.prose && prompt.trimStart().startsWith('/')) {
|
|
161
161
|
throw new Error('slash command reached the classifier — explicit /commands must be routed before classification (CLI bug, please report)');
|
|
162
162
|
}
|
|
163
163
|
const cfg = await loadConfig();
|
|
@@ -7,6 +7,13 @@ let unreachable = null;
|
|
|
7
7
|
const CODE_RE = /\b(ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|ENETUNREACH|ECONNRESET|EAI_AGAIN)\b/;
|
|
8
8
|
export const SAP_UNREACHABLE_DIRECTIVE = 'SAP is not reachable from this machine right now. Stop here: do not retry SAP tools and do not write the plan manifest. '
|
|
9
9
|
+ 'End the turn with one short sentence. CSPeach pauses the build and runs this step again when the network is back.';
|
|
10
|
+
/**
|
|
11
|
+
* LT-14 (1A fix wave) — outside a plan step, the note that rides on a SAP
|
|
12
|
+
* tool error when SAP did not answer. Before, the model got the bare error
|
|
13
|
+
* and invented network advice for the person. No pause, no flag.
|
|
14
|
+
*/
|
|
15
|
+
export const SAP_UNREACHABLE_NOTE = 'SAP did not answer from this machine. Do not guess why or give the person network advice. '
|
|
16
|
+
+ "After three such failures in a row, stop and ask; offer 'SAP is reachable again; try once more' and 'Stop for now; I'll pick up later'.";
|
|
10
17
|
export function noteSapUnreachable(code) {
|
|
11
18
|
unreachable = code;
|
|
12
19
|
}
|
package/dist/sap/unreachable.js
CHANGED
|
@@ -26,6 +26,6 @@ export function unreachableMessage(alias, host, port, code) {
|
|
|
26
26
|
return (
|
|
27
27
|
// 2026-10-04: never assume a VPN — many users sit on the company network.
|
|
28
28
|
`Can't reach SAP system ${alias} (${host}:${port}) from this machine — ${code}. ` +
|
|
29
|
-
'Check network access to the SAP system
|
|
30
|
-
'
|
|
29
|
+
'Check network access to the SAP system. ' +
|
|
30
|
+
'For work that does not need SAP, run with --no-sap.');
|
|
31
31
|
}
|
|
@@ -10,6 +10,10 @@ export function startFreshConversation(session) {
|
|
|
10
10
|
// Word export (final-review m-4): the once-per-object save offers and the
|
|
11
11
|
// export records describe the cleared conversation; they go with it.
|
|
12
12
|
clearConversationRecords(session);
|
|
13
|
+
// Change options (1.1.53): the last document goes too; a level reply then routes as today.
|
|
14
|
+
session.lastChangeOptions = null;
|
|
15
|
+
// Task 16: a pending blocker-form note describes the cleared conversation.
|
|
16
|
+
delete session.blockerAnswersNote;
|
|
13
17
|
}
|
|
14
18
|
/** The one line /new prints. */
|
|
15
19
|
export function freshStartText(lane) {
|
|
@@ -45,5 +45,7 @@ export function queuedStepView(origin) {
|
|
|
45
45
|
if (!line)
|
|
46
46
|
return { echo: false, line: null };
|
|
47
47
|
// D11 — the chat-fallback line is dim; painted after shownUpTo flattened it.
|
|
48
|
-
|
|
48
|
+
// LT-4 (1A fix wave, owner ruling): the hand-off arrow line is dim too, kept in scrollback.
|
|
49
|
+
const dim = origin.by === 'chat_fallback' || origin.by === 'hand_off';
|
|
50
|
+
return { echo: false, line: dim ? paint('dim', line) : line };
|
|
49
51
|
}
|
|
@@ -26,8 +26,19 @@ export function isUserPrompt(m) {
|
|
|
26
26
|
* (`<session_context>` from the intent extractor / SAP system info,
|
|
27
27
|
* `<project_context>` from CSPEACH_PROJECT_CONTEXT). The user never typed
|
|
28
28
|
* them, so a prompt preview must never show them (fix round 3, item 2).
|
|
29
|
+
* Stage 1A (M5) — `<skill>`, the playbook block an inline turn appends. The
|
|
30
|
+
* order matters: `<session_context>` first, so the bare `<skill>` word in a
|
|
31
|
+
* `<playbook>` line goes with the block it sits in — `skill` stays last.
|
|
32
|
+
* Shared with the conversation export (commands/export-conversation.ts).
|
|
29
33
|
*/
|
|
30
|
-
const INJECTED_CONTEXT_TAGS = [
|
|
34
|
+
export const INJECTED_CONTEXT_TAGS = [
|
|
35
|
+
'session_context', 'project_context',
|
|
36
|
+
// The /export docx conversation list (main, 2026-10-23): one list for both readers.
|
|
37
|
+
'journey', 'lane_decision', 'system_switch', 'sap_system', 'sap_connection',
|
|
38
|
+
// Task 16 (F-M): the blocker form's note (it rides inside <session_context>).
|
|
39
|
+
'blocker_answers',
|
|
40
|
+
'skill',
|
|
41
|
+
];
|
|
31
42
|
/**
|
|
32
43
|
* One-line preview of what the user typed: injected context blocks removed,
|
|
33
44
|
* `<attached file="x">…</attached>` expansions shown as the original `@x`
|
|
@@ -4,6 +4,7 @@ import { sha512 } from '@noble/hashes/sha512';
|
|
|
4
4
|
import { bytesToHex, concatBytes } from '@noble/hashes/utils';
|
|
5
5
|
import { MANIFEST_PUBLIC_KEYS } from './signing-public-key.js';
|
|
6
6
|
import { canonicalise } from './canonical.js';
|
|
7
|
+
import { fetchWithConnectRetry } from '../util/connect-retry.js';
|
|
7
8
|
// @noble/ed25519 v2 requires an ed25519 sha512 hook.
|
|
8
9
|
ed.etc.sha512Sync = (...m) => sha512(concatBytes(...m));
|
|
9
10
|
/**
|
|
@@ -39,7 +40,7 @@ export class ManifestClient {
|
|
|
39
40
|
async fetchManifest() {
|
|
40
41
|
let r;
|
|
41
42
|
try {
|
|
42
|
-
r = await
|
|
43
|
+
r = await fetchWithConnectRetry(this.manifestUrl, () => ({ signal: AbortSignal.timeout(10_000) }));
|
|
43
44
|
}
|
|
44
45
|
catch (e) {
|
|
45
46
|
throw new ManifestUnavailableError(`manifest fetch failed (${this.manifestUrl}): ${e.message}`);
|
|
@@ -99,7 +100,7 @@ export class ManifestClient {
|
|
|
99
100
|
async fetchSkillBody(name, bodyUrl, opts) {
|
|
100
101
|
let r;
|
|
101
102
|
try {
|
|
102
|
-
r = await
|
|
103
|
+
r = await fetchWithConnectRetry(bodyUrl, () => ({ signal: AbortSignal.timeout(10_000) }));
|
|
103
104
|
}
|
|
104
105
|
catch (e) {
|
|
105
106
|
throw new Error(`skill body fetch failed (${bodyUrl}): ${e.message}`);
|
package/dist/skills/preamble.js
CHANGED
|
@@ -52,8 +52,10 @@ take precedence if a skill's instruction conflicts with them.
|
|
|
52
52
|
revise and re-submit.
|
|
53
53
|
|
|
54
54
|
## Transport discipline
|
|
55
|
-
- All writes go into the
|
|
56
|
-
for a transport number per change.
|
|
55
|
+
- All writes go into the session transport once one is set. Do not ask
|
|
56
|
+
the user for a transport number per change. When a create says a
|
|
57
|
+
transport is needed, ask the user once: a new session transport
|
|
58
|
+
(recommended) or one of the open requests for the package.
|
|
57
59
|
- Never use \$TMP unless the user explicitly requests local-only work.
|
|
58
60
|
|
|
59
61
|
## Escalate, don't fail silently
|
|
@@ -70,6 +72,6 @@ take precedence if a skill's instruction conflicts with them.
|
|
|
70
72
|
|
|
71
73
|
## One skill per turn
|
|
72
74
|
- If the user's request is better served by a different skill, call
|
|
73
|
-
|
|
74
|
-
skill yourself.
|
|
75
|
+
dispatch_skill after the person has picked the command. Do not try to do
|
|
76
|
+
the work of another skill yourself.
|
|
75
77
|
`;
|