@cspeach/cli 1.1.33 → 1.1.35
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/anthropic-provider.js +2 -0
- package/dist/agent/azure-profile.js +7 -0
- package/dist/agent/connection-profile.js +49 -0
- package/dist/agent/loop.js +182 -39
- package/dist/agent/provider-shape.js +36 -0
- package/dist/agent/providers/ai-hub-provider.js +6 -5
- package/dist/agent/providers/azure-provider.js +125 -0
- package/dist/agent/providers/byok-provider.js +8 -7
- package/dist/agent/providers/factory.js +8 -3
- package/dist/agent/providers/local-provider.js +18 -22
- package/dist/agent/risk-gate.js +202 -0
- package/dist/agent/summarise-via-provider.js +55 -8
- package/dist/agent/tool-dispatch.js +19 -0
- package/dist/agent/turn-error-ux.js +22 -0
- package/dist/auth/api-key.js +10 -5
- package/dist/auth/auth-file.js +35 -6
- package/dist/auth/azure/azure-key.js +21 -0
- package/dist/auth/azure/azure-log.js +45 -0
- package/dist/auth/azure/cache-store.js +171 -0
- package/dist/auth/azure/clear.js +28 -0
- package/dist/auth/azure/loopback.js +192 -0
- package/dist/auth/azure/msal-client.js +41 -0
- package/dist/auth/azure/sign-in.js +219 -0
- package/dist/auth/azure/startup-sign-in.js +64 -0
- package/dist/auth/azure/token-provider.js +116 -0
- package/dist/auth/key-resolver.js +258 -0
- package/dist/auth/me.js +11 -5
- package/dist/cli.js +21 -2
- package/dist/commands/compact.js +12 -1
- package/dist/commands/config-set.js +74 -3
- package/dist/commands/config-show.js +25 -0
- package/dist/commands/cost.js +19 -6
- package/dist/commands/help.js +2 -2
- package/dist/commands/login-azure.js +33 -0
- package/dist/commands/login.js +56 -8
- package/dist/commands/logout.js +34 -10
- package/dist/commands/plan-boundary.js +4 -4
- package/dist/commands/plan-model-tier.js +8 -3
- package/dist/commands/usage.js +6 -0
- package/dist/commands/whoami.js +113 -9
- package/dist/config/loader.js +5 -0
- package/dist/connection/azure-auth.js +135 -0
- package/dist/connection/azure-errors.js +103 -0
- package/dist/connection/azure-facts.js +21 -0
- package/dist/connection/azure-gate.js +40 -0
- package/dist/connection/azure-last-call.js +5 -0
- package/dist/connection/azure-turn-error.js +25 -0
- package/dist/connection/cache.js +70 -0
- package/dist/connection/fetch.js +33 -0
- package/dist/connection/guard.js +94 -0
- package/dist/connection/limit-gate.js +26 -0
- package/dist/connection/local-usage.js +71 -0
- package/dist/connection/login-step.js +62 -0
- package/dist/connection/startup.js +67 -0
- package/dist/connection/state.js +68 -0
- package/dist/connection/types.js +1 -0
- package/dist/cost/cost-log.js +3 -1
- package/dist/cost/credits-wire.js +1 -1
- package/dist/cost/credits.js +4 -0
- package/dist/cost/display-mode.js +7 -0
- package/dist/cost/router-spend.js +3 -0
- package/dist/cost/session-cost.js +4 -0
- package/dist/doctor/checks/azure.js +100 -0
- package/dist/doctor/checks/credits.js +4 -3
- package/dist/doctor/checks/usage-reporting.js +22 -0
- package/dist/doctor/run.js +7 -0
- package/dist/lib/graceful-exit.js +17 -0
- package/dist/licence/licence-check.js +205 -0
- package/dist/licence/paths.js +6 -0
- package/dist/licence/turn-gate.js +66 -0
- package/dist/models/model-identity.js +65 -0
- package/dist/models/resolve.js +8 -0
- package/dist/models/server-config.js +3 -2
- package/dist/one-shot.js +86 -2
- package/dist/projects/answer-blockers.js +25 -12
- package/dist/renderer/change-card.js +28 -4
- package/dist/renderer/footer-line.js +7 -1
- package/dist/renderer/notices.js +27 -13
- package/dist/renderer/progress-chatter.js +3 -1
- package/dist/renderer/status-footer.js +13 -6
- package/dist/renderer/tool-labels.js +8 -2
- package/dist/renderer/verify-chain.js +41 -18
- package/dist/renderer/wrap-rows.js +87 -0
- package/dist/repl/ask-or-no.js +18 -0
- package/dist/repl/builtin-commands.js +10 -2
- package/dist/repl/post-turn-status.js +3 -2
- package/dist/repl/safety-confirm.js +47 -2
- package/dist/repl/session-spend-line.js +4 -1
- package/dist/repl/startup-account.js +51 -0
- package/dist/repl.js +217 -48
- package/dist/router/byok-classifier.js +41 -14
- package/dist/router/classifier.js +5 -16
- package/dist/router/router-target.js +41 -0
- package/dist/sap/connection-manager.js +15 -0
- package/dist/sap/onboarding.js +70 -64
- package/dist/sap/session-end.js +66 -0
- package/dist/session/audit-export.js +11 -4
- package/dist/skills/manifest-client.js +37 -4
- package/dist/skills/skills-cache.js +115 -0
- package/dist/skills/source-authed.js +276 -0
- package/dist/tools/ask-question.js +1 -1
- package/dist/tools/filesystem/glob.js +12 -3
- package/dist/tools/filesystem/grep.js +14 -3
- package/dist/tools/sap-read.js +22 -22
- package/dist/tools/sap-write.js +249 -18
- package/dist/tools/snapshot.js +26 -0
- package/dist/tools/tool-loading.js +9 -10
- package/dist/ui/app.js +19 -9
- package/dist/ui/approval-modal.js +37 -5
- package/dist/ui/live-area.js +14 -1
- package/dist/ui/widgets/ask-card.js +183 -107
- package/dist/ui/widgets/coaching-picker.js +22 -1
- package/dist/ui/widgets/confirm-card.js +102 -76
- package/dist/usage/usage-queue.js +323 -0
- package/dist/usage/usage-reporter.js +270 -0
- package/package.json +4 -3
- package/dist/skills/bundled-skills.js +0 -286
- package/dist/skills/source-bundled.js +0 -20
- package/dist/skills/source-manifest.js +0 -36
|
@@ -29,6 +29,8 @@ const RULE_LABEL = {
|
|
|
29
29
|
RULE_9_TRANSPORT_ISOLATION: 'RULE 9 — transport isolation',
|
|
30
30
|
RULE_10_VERIFY_AFTER_WRITE: 'RULE 10 — verify after write',
|
|
31
31
|
CREDENTIAL_HANDOVER: 'SAP login handed to a local process',
|
|
32
|
+
RISKY_COMMAND: 'risky command (known pattern)',
|
|
33
|
+
CREDENTIAL_FILE_READ: 'credential file read',
|
|
32
34
|
};
|
|
33
35
|
/** Piece 2 §5 — plain-words heading: what could go wrong. No rule codes, no tool names. */
|
|
34
36
|
export const SAFETY_HEADING = {
|
|
@@ -39,6 +41,8 @@ export const SAFETY_HEADING = {
|
|
|
39
41
|
RULE_9_TRANSPORT_ISOLATION: 'This change may land in a transport shared with other work',
|
|
40
42
|
RULE_10_VERIFY_AFTER_WRITE: 'This change is not proven until it is checked',
|
|
41
43
|
CREDENTIAL_HANDOVER: 'Your SAP login will be handed to a local program',
|
|
44
|
+
RISKY_COMMAND: 'This command matches a known risky pattern',
|
|
45
|
+
CREDENTIAL_FILE_READ: 'This reads a file that may hold passwords or keys',
|
|
42
46
|
};
|
|
43
47
|
/** Bottom-edge note: which rule is asking. */
|
|
44
48
|
export const SAFETY_FOOTNOTE = {
|
|
@@ -49,6 +53,8 @@ export const SAFETY_FOOTNOTE = {
|
|
|
49
53
|
RULE_9_TRANSPORT_ISOLATION: 'Forge Rule 9',
|
|
50
54
|
RULE_10_VERIFY_AFTER_WRITE: 'Forge Rule 10',
|
|
51
55
|
CREDENTIAL_HANDOVER: 'Credential handover',
|
|
56
|
+
RISKY_COMMAND: 'Risky command check',
|
|
57
|
+
CREDENTIAL_FILE_READ: 'Credential file check',
|
|
52
58
|
};
|
|
53
59
|
/** Rule 7 covers both overwrite and delete; the delete says so. */
|
|
54
60
|
export function safetyHeading(a) {
|
|
@@ -103,8 +109,40 @@ function whatLabel(a) {
|
|
|
103
109
|
return fallback;
|
|
104
110
|
return toolActivityLabel(tool, args);
|
|
105
111
|
}
|
|
112
|
+
/** The risk gate's two rules — their subject is a model-written command or path. */
|
|
113
|
+
function isRiskRule(a) {
|
|
114
|
+
return a.rule === 'RISKY_COMMAND' || a.rule === 'CREDENTIAL_FILE_READ';
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Fix round 1 (F9) — the most of a risky command or path the card shows. A
|
|
118
|
+
* longer one shows its head and its tail with the hidden length between them
|
|
119
|
+
* (fix round 2, L2: a command padded past the cap cannot hide its target on
|
|
120
|
+
* the classic card, which has no scrollback copy). Ink also writes the whole
|
|
121
|
+
* text to scrollback first, like the Rule 8 summary. Never redacted.
|
|
122
|
+
*/
|
|
123
|
+
export const RISK_SUBJECT_MAX = 300;
|
|
124
|
+
const RISK_SUBJECT_HEAD = 200;
|
|
125
|
+
const RISK_SUBJECT_TAIL = 80;
|
|
126
|
+
/** The subject as shown on a card: display-cleaned (F8), cut to head + tail past RISK_SUBJECT_MAX (F9, L2). */
|
|
127
|
+
function riskSubject(name) {
|
|
128
|
+
const c = clean(name);
|
|
129
|
+
if (c.length <= RISK_SUBJECT_MAX)
|
|
130
|
+
return c;
|
|
131
|
+
const hidden = c.length - RISK_SUBJECT_HEAD - RISK_SUBJECT_TAIL;
|
|
132
|
+
return `${c.slice(0, RISK_SUBJECT_HEAD)} ${getGlyphs().more} (+${hidden} chars) ${c.slice(-RISK_SUBJECT_TAIL)}`;
|
|
133
|
+
}
|
|
106
134
|
/** What / Where / Undo rows (plain text; the card paints them dim). */
|
|
107
135
|
export function safetyFacts(a) {
|
|
136
|
+
// Security batch item 4 — the risk gate's cards: the exact command or path,
|
|
137
|
+
// why it was flagged, and that it runs on this computer.
|
|
138
|
+
if (isRiskRule(a)) {
|
|
139
|
+
return [
|
|
140
|
+
`What ${a.rule === 'RISKY_COMMAND' ? 'Run' : 'Read'} ${riskSubject(a.object.name)}`,
|
|
141
|
+
...boundedWhatLines(whatLines(a)).map((t) => ` ${t}`),
|
|
142
|
+
'Where this computer',
|
|
143
|
+
`Undo ${a.rule === 'RISKY_COMMAND' ? 'Cannot be undone' : 'Once read, its text goes to the model'}`,
|
|
144
|
+
];
|
|
145
|
+
}
|
|
108
146
|
const what = a.rule === 'CREDENTIAL_HANDOVER'
|
|
109
147
|
? `Hand the SAP login for ${clean(a.object.name)} to a local program`
|
|
110
148
|
: whatLabel(a);
|
|
@@ -162,6 +200,10 @@ async function confirmOnCards(action) {
|
|
|
162
200
|
// The card keeps the header and the last lines; the whole text lands above it.
|
|
163
201
|
emitTrace(what.join('\n'));
|
|
164
202
|
}
|
|
203
|
+
if (isRiskRule(action) && clean(action.object.name).length > RISK_SUBJECT_MAX) {
|
|
204
|
+
// F9 — the card shows the cut; the whole (cleaned) command lands above it.
|
|
205
|
+
emitTrace(clean(action.object.name));
|
|
206
|
+
}
|
|
165
207
|
const first = await askConfirm({
|
|
166
208
|
id: 'safety-confirm',
|
|
167
209
|
tag: release ? 'CANNOT BE UNDONE' : 'SAFETY CHECK',
|
|
@@ -260,14 +302,17 @@ function renderCard(action) {
|
|
|
260
302
|
lines.push('');
|
|
261
303
|
lines.push(`${chalk.yellow(getGlyphs().attention)} ${chalk.yellow.bold(RULE_LABEL[action.rule])}`);
|
|
262
304
|
lines.push(` ${chalk.dim('op: ')} ${action.op}`);
|
|
263
|
-
|
|
305
|
+
// F8 — a risk card's subject is model-written: cleaned (no terminal escapes)
|
|
306
|
+
// and cut (F9). Other rules print exactly as before.
|
|
307
|
+
const risk = isRiskRule(action);
|
|
308
|
+
lines.push(` ${chalk.dim('object: ')} ${action.object.type} ${risk ? riskSubject(action.object.name) : action.object.name}`);
|
|
264
309
|
lines.push(` ${chalk.dim('transport:')} ${transport}`);
|
|
265
310
|
if (action.rule === 'RULE_8_BATCH_PLAN' && typeof action.batch_count === 'number') {
|
|
266
311
|
lines.push(` ${chalk.dim('batch: ')} ${action.batch_count} objects this turn`);
|
|
267
312
|
}
|
|
268
313
|
// what_will_happen may include embedded newlines (e.g. Rule 8 plan summary).
|
|
269
314
|
// Indent the continuation lines so they align under the header field.
|
|
270
|
-
const whatLines = action.what_will_happen.split('\n');
|
|
315
|
+
const whatLines = risk ? action.what_will_happen.split('\n').map(clean) : action.what_will_happen.split('\n');
|
|
271
316
|
if (whatLines.length === 1) {
|
|
272
317
|
lines.push(` ${chalk.dim('what: ')} ${whatLines[0]}`);
|
|
273
318
|
}
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
* credits existed: ` spend: $1.37`.
|
|
7
7
|
*/
|
|
8
8
|
import { formatCost } from '../cost/pricing.js';
|
|
9
|
-
import { creditsActive, formatCredits, CREDITS_PENDING } from '../cost/credits.js';
|
|
9
|
+
import { creditsActive, tokensOnlyActive, formatCredits, CREDITS_PENDING } from '../cost/credits.js';
|
|
10
|
+
import { AZURE_INVOICE_LINE } from '../cost/display-mode.js';
|
|
10
11
|
/** The spend VALUE in the current display mode (piece 2: goodbye block). */
|
|
11
12
|
export function formatSessionSpendValue(sessionCostUsd, credits) {
|
|
13
|
+
if (tokensOnlyActive())
|
|
14
|
+
return AZURE_INVOICE_LINE;
|
|
12
15
|
if (!creditsActive())
|
|
13
16
|
return formatCost(sessionCostUsd);
|
|
14
17
|
if (!credits)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { formatFailure, formatInfo } from '../renderer/notice-shape.js';
|
|
2
|
+
export async function runStartupAccount(deps, io) {
|
|
3
|
+
let licenceCheckedAtMs;
|
|
4
|
+
try {
|
|
5
|
+
const cfgEarly = await deps.loadConfig();
|
|
6
|
+
if (cfgEarly.llm.mode === 'managed') {
|
|
7
|
+
const authFile = await deps.readAuthFile();
|
|
8
|
+
const startupKey = authFile ? (await deps.resolveApiKey()).key : null;
|
|
9
|
+
if (authFile && startupKey) {
|
|
10
|
+
const meResult = await deps.fetchMe({ proxyUrl: cfgEarly.proxy_url, apiKey: startupKey });
|
|
11
|
+
if (!meResult.ok) {
|
|
12
|
+
if (meResult.status === 401) {
|
|
13
|
+
io.closeBrand();
|
|
14
|
+
io.print(formatFailure({ what: 'Your CSPeach sign-in is no longer valid', next: 'cspeach login' }));
|
|
15
|
+
return { exit: 1 };
|
|
16
|
+
}
|
|
17
|
+
io.notice(formatInfo(`Could not refresh your account (${meResult.error})`));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
io.showProfile(meResult.profile);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const { key: startupKey } = await deps.resolveApiKey();
|
|
26
|
+
if (startupKey) {
|
|
27
|
+
const licence = await deps.runLicenceCheck({ proxyUrl: cfgEarly.proxy_url, apiKey: startupKey }, { headers: await deps.licenceCheckHeaders(cfgEarly.llm.mode).catch(() => ({})) });
|
|
28
|
+
licenceCheckedAtMs = (deps.now ?? Date.now)();
|
|
29
|
+
if (!licence.allow) {
|
|
30
|
+
io.closeBrand();
|
|
31
|
+
io.print(licence.message);
|
|
32
|
+
return { exit: 1 };
|
|
33
|
+
}
|
|
34
|
+
if (licence.verdict === 'unknown') {
|
|
35
|
+
io.notice(formatInfo(licence.line));
|
|
36
|
+
}
|
|
37
|
+
else if (licence.profile) {
|
|
38
|
+
io.showProfile(licence.profile);
|
|
39
|
+
}
|
|
40
|
+
if (licence.verdict === 'ok') {
|
|
41
|
+
try {
|
|
42
|
+
deps.startSkillsWarmup?.(cfgEarly, licence.verdict);
|
|
43
|
+
}
|
|
44
|
+
catch { /* never blocks start-up */ }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch { /* banner is best-effort — never block REPL startup */ }
|
|
50
|
+
return licenceCheckedAtMs === undefined ? { exit: null } : { exit: null, licenceCheckedAtMs };
|
|
51
|
+
}
|
package/dist/repl.js
CHANGED
|
@@ -27,7 +27,12 @@ import { applyResumedSkill, isShortFollowUp } from './router/resume-routing.js';
|
|
|
27
27
|
import { readableInterruptReason, isUserStopReason } from './session/interrupt-reason.js';
|
|
28
28
|
import { getStore } from './auth/api-key.js';
|
|
29
29
|
import { readAuthFile } from './auth/auth-file.js';
|
|
30
|
+
import { resolveApiKey } from './auth/key-resolver.js';
|
|
30
31
|
import { fetchMe } from './auth/me.js';
|
|
32
|
+
import { runLicenceCheck, licenceCheckHeaders } from './licence/licence-check.js';
|
|
33
|
+
import { LicenceTurnGate } from './licence/turn-gate.js';
|
|
34
|
+
import { runStartupAccount } from './repl/startup-account.js';
|
|
35
|
+
import { startSkillsCacheWarmup } from './skills/source-authed.js';
|
|
31
36
|
import { formatLoginBanner, signInFragment } from './ui/login-banner.js';
|
|
32
37
|
import { printStartupBanner, printKeytarFallbackBanner, keychainFallbackNotice } from './renderer/banners.js';
|
|
33
38
|
import { paint } from './renderer/theme.js';
|
|
@@ -39,15 +44,18 @@ import { AdtClient, snapshots } from '@cspeach/sap-client';
|
|
|
39
44
|
import { makeSessionId, saveSession, sessionSaved } from './session/store.js';
|
|
40
45
|
import { newSession } from './session/schema.js';
|
|
41
46
|
import { resolveModelRole } from './models/resolve.js';
|
|
47
|
+
import { familyOf, planTierModelFor, getActiveDeployments } from './models/model-identity.js';
|
|
42
48
|
import { loadServerModelConfig, loadCustomerModelConfig, fetchToolLoadingMode } from './models/server-config.js';
|
|
43
49
|
import { prefetchRouterPrompt } from './router/router-prompt.js';
|
|
50
|
+
import { profileForMode } from './agent/connection-profile.js';
|
|
44
51
|
import { repinResumedSessionModel } from './session/repin-model.js';
|
|
45
52
|
import { computeCost } from './cost/pricing.js';
|
|
46
53
|
import { formatSessionSpendValue } from './repl/session-spend-line.js';
|
|
47
54
|
import { formatGoodbye } from './renderer/goodbye.js';
|
|
48
|
-
import { globalCredits } from './cost/credits.js';
|
|
55
|
+
import { globalCredits, tokensOnlyActive } from './cost/credits.js';
|
|
49
56
|
import { initCredits } from './cost/credits-wire.js';
|
|
50
57
|
import { createProviderForMode } from './agent/providers/factory.js';
|
|
58
|
+
import { startUsageFlushAtStartup } from './usage/usage-reporter.js';
|
|
51
59
|
import { assertModeLicensed, CspeachLicenseError } from './agent/providers/license-gate.js';
|
|
52
60
|
import { runTurn } from './agent/loop.js';
|
|
53
61
|
import { IdleKeepalive } from './agent/cache-keepalive.js';
|
|
@@ -136,6 +144,7 @@ import { maybeExpandNumberSelection, lastAssistantText } from './repl/numbered-m
|
|
|
136
144
|
import { attachEarlyLineBuffer, noopEarlyLineBuffer } from './repl/early-line-buffer.js';
|
|
137
145
|
import { createRestoreGuard } from './repl/restore-guard.js';
|
|
138
146
|
import { gracefulExit } from './lib/graceful-exit.js';
|
|
147
|
+
import { endSapSessionAfterTurn } from './sap/session-end.js';
|
|
139
148
|
import { snapshotTurnStart, pushPostTurnStatus, seedFooterSpend } from './repl/post-turn-status.js';
|
|
140
149
|
import { looksLikeCspeachShellCommand, CSPEACH_SHELL_NUDGE } from './repl/cspeach-shell-detect.js';
|
|
141
150
|
import { applyLocalBuildConfig, applyStandaloneFileTools } from './tools/local-build.js';
|
|
@@ -294,7 +303,8 @@ function existingStreamDir(sessionId) {
|
|
|
294
303
|
}
|
|
295
304
|
function printGoodbye(session) {
|
|
296
305
|
const userTurns = session.messages.filter((m) => m.role === 'user' && typeof m.content === 'string').length;
|
|
297
|
-
|
|
306
|
+
// Azure: no price for the customer's deployment — never consult one.
|
|
307
|
+
const sessionCost = tokensOnlyActive() ? 0 : computeCost(session.model, {
|
|
298
308
|
input: session.usage.input_tokens ?? 0,
|
|
299
309
|
output: session.usage.output_tokens ?? 0,
|
|
300
310
|
cacheRead: session.usage.cache_read_input_tokens ?? 0,
|
|
@@ -457,46 +467,51 @@ export async function runRepl(opts) {
|
|
|
457
467
|
}
|
|
458
468
|
}
|
|
459
469
|
catch { /* startup banner is best-effort */ }
|
|
460
|
-
// Customer-onboarding (Task 9)
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
470
|
+
// Customer-onboarding (Task 9) + security batch item 1.
|
|
471
|
+
// Managed: today's call exactly — only when auth.json exists, the key is
|
|
472
|
+
// resolved only then, the same request, and a 401 exits. Falls through to the
|
|
473
|
+
// legacy keytar flow below when the file is absent (back-compat for
|
|
474
|
+
// pre-`cspeach login` users).
|
|
475
|
+
// byok / ai-hub / local: the shared licence check — server verdict, 72-hour
|
|
476
|
+
// offline grace, licence.json — whether or not auth.json exists. It IS this
|
|
477
|
+
// start-up /v1/me call (one request, the same 5 s budget), never a second one.
|
|
478
|
+
// The block lives in repl/startup-account.ts (behaviour-identical lift, driven
|
|
479
|
+
// by repl/__tests__/startup-account.test.ts).
|
|
464
480
|
let signIn;
|
|
465
481
|
let signedInAs;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (
|
|
472
|
-
|
|
473
|
-
earlyBrand?.close();
|
|
474
|
-
console.log(formatFailure({ what: 'Your CSPeach sign-in is no longer valid', next: 'cspeach login' }));
|
|
475
|
-
return 1;
|
|
476
|
-
}
|
|
477
|
-
const note = formatInfo(`Could not refresh your account (${meResult.error})`);
|
|
478
|
-
if (getLook() === 'panels')
|
|
479
|
-
startupNotices.push(note);
|
|
480
|
-
else
|
|
481
|
-
console.log(note.trimEnd());
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
signedInAs = meResult.profile.email;
|
|
485
|
-
const banner = formatLoginBanner(meResult.profile);
|
|
486
|
-
if (getLook() === 'panels') {
|
|
487
|
-
signIn = signInFragment(meResult.profile);
|
|
488
|
-
if (banner.warningLine)
|
|
489
|
-
startupNotices.push(formatAttention({ text: banner.warningLine }));
|
|
490
|
-
}
|
|
491
|
-
else {
|
|
492
|
-
console.log(banner.line);
|
|
493
|
-
if (banner.warningLine)
|
|
494
|
-
console.log(formatAttention({ text: banner.warningLine }).trimEnd());
|
|
495
|
-
}
|
|
496
|
-
}
|
|
482
|
+
const showProfile = (profile) => {
|
|
483
|
+
signedInAs = profile.email;
|
|
484
|
+
const banner = formatLoginBanner(profile);
|
|
485
|
+
if (getLook() === 'panels') {
|
|
486
|
+
signIn = signInFragment(profile);
|
|
487
|
+
if (banner.warningLine)
|
|
488
|
+
startupNotices.push(formatAttention({ text: banner.warningLine }));
|
|
497
489
|
}
|
|
498
|
-
|
|
499
|
-
|
|
490
|
+
else {
|
|
491
|
+
console.log(banner.line);
|
|
492
|
+
if (banner.warningLine)
|
|
493
|
+
console.log(formatAttention({ text: banner.warningLine }).trimEnd());
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
const startupAccount = await runStartupAccount({
|
|
497
|
+
loadConfig, readAuthFile, resolveApiKey: () => resolveApiKey(), fetchMe, runLicenceCheck, licenceCheckHeaders,
|
|
498
|
+
// Final review finding 1 — fill the skills cache for offline use. Fire-and-forget.
|
|
499
|
+
startSkillsWarmup: (c, verdict) => { void startSkillsCacheWarmup(c, verdict); },
|
|
500
|
+
}, {
|
|
501
|
+
print: (line) => console.log(line),
|
|
502
|
+
notice: (note) => {
|
|
503
|
+
if (getLook() === 'panels')
|
|
504
|
+
startupNotices.push(note);
|
|
505
|
+
else
|
|
506
|
+
console.log(note.trimEnd());
|
|
507
|
+
},
|
|
508
|
+
showProfile,
|
|
509
|
+
closeBrand: () => earlyBrand?.close(),
|
|
510
|
+
});
|
|
511
|
+
if (startupAccount.exit === 1)
|
|
512
|
+
return 1;
|
|
513
|
+
// Read by the between-turns recheck (Task 6): the 4-hour clock starts here.
|
|
514
|
+
const licenceCheckedAtMs = startupAccount.licenceCheckedAtMs;
|
|
500
515
|
earlyBrand?.finish(signIn);
|
|
501
516
|
const store = await getStore();
|
|
502
517
|
const keychainMissing = !(await store.isKeychainAvailable());
|
|
@@ -601,8 +616,55 @@ export async function runRepl(opts) {
|
|
|
601
616
|
fetchToolLoadingMode(cfg, async () => apiKey ?? '', { timeoutMs: 1500 }),
|
|
602
617
|
// 2026-10-02 — BYOK / ai-hub route on the laptop: prefetch the signed
|
|
603
618
|
// router prompt (public manifest, no prompt data) under the same cap.
|
|
604
|
-
cfg.llm?.mode
|
|
619
|
+
profileForMode(cfg.llm?.mode).router === 'direct' ? prefetchRouterPrompt({ timeoutMs: 1500 }) : Promise.resolve(),
|
|
605
620
|
]);
|
|
621
|
+
// Azure tenant mode (Task 4) — the company's connection settings, before
|
|
622
|
+
// the session model is pinned (resolveModelRole reads the deployments).
|
|
623
|
+
// Cached connection.json, refreshed under the same 1.5 s cap; a changed
|
|
624
|
+
// endpoint is shown and asked about. A refusal is shown now and the gate
|
|
625
|
+
// refuses turns — never a fall-back to managed.
|
|
626
|
+
if (cfg.llm.mode === 'azure') {
|
|
627
|
+
const { refreshAzureConnection } = await import('./connection/startup.js');
|
|
628
|
+
const { confirm } = await import('./repl/themed-prompts.js');
|
|
629
|
+
const { askOrNo } = await import('./repl/ask-or-no.js');
|
|
630
|
+
const note = (line) => {
|
|
631
|
+
if (getLook() === 'panels')
|
|
632
|
+
startupNotices.push(line.endsWith('\n') ? line : `${line}\n`);
|
|
633
|
+
else
|
|
634
|
+
console.log(line.trimEnd());
|
|
635
|
+
};
|
|
636
|
+
// The "endpoint changed" rows must be on screen BEFORE the question, so
|
|
637
|
+
// they are held here and printed straight away when a question opens;
|
|
638
|
+
// anything left over joins the start-up notices.
|
|
639
|
+
const held = [];
|
|
640
|
+
const az = await refreshAzureConnection({
|
|
641
|
+
proxyUrl: cfg.proxy_url, apiKey: apiKey ?? null, interactive: true,
|
|
642
|
+
print: (line) => { held.push(line); },
|
|
643
|
+
confirm: async (message) => {
|
|
644
|
+
for (const line of held.splice(0))
|
|
645
|
+
console.log(line.trimEnd());
|
|
646
|
+
// Ctrl+C here is a no (pending kept), never an unhandled ExitPromptError.
|
|
647
|
+
return askOrNo((m) => confirm({ message: m, default: false }))(message);
|
|
648
|
+
},
|
|
649
|
+
});
|
|
650
|
+
for (const line of held)
|
|
651
|
+
note(line);
|
|
652
|
+
if (!az.verdict.allow)
|
|
653
|
+
note(az.verdict.message);
|
|
654
|
+
else if (az.cached) {
|
|
655
|
+
// Task 6b — the Microsoft sign-in, before the first turn: asks first,
|
|
656
|
+
// then the browser. Esc / Ctrl+C during it stops it at once (the
|
|
657
|
+
// listener closes). Its own lines (the address to copy when no browser
|
|
658
|
+
// opens) print straight away, never held for after the sign-in.
|
|
659
|
+
const { azureSignInAtStart } = await import('./auth/azure/startup-sign-in.js');
|
|
660
|
+
const { loginOpener } = await import('./util/open-in-browser.js');
|
|
661
|
+
const s = await azureSignInAtStart(az.cached.connection, {
|
|
662
|
+
ask: askOrNo((message) => confirm({ message, default: true })), print: (line) => console.log(line.trimEnd()), open: loginOpener(),
|
|
663
|
+
});
|
|
664
|
+
if (!s.ok)
|
|
665
|
+
note(s.message);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
606
668
|
// Credits (2026-09-16) — resolve ONCE, before any cost figure can render.
|
|
607
669
|
// A managed customer is billed in credits at a multiplier we do not
|
|
608
670
|
// disclose, so the dollar figures (our Anthropic COGS) must never reach
|
|
@@ -815,6 +877,41 @@ export async function runRepl(opts) {
|
|
|
815
877
|
throw new Error('No API key configured');
|
|
816
878
|
return key;
|
|
817
879
|
});
|
|
880
|
+
// Security batch item 1 — licence re-check between turns (byok / ai-hub /
|
|
881
|
+
// local, every 4 hours from the start-up check). Managed: never calls.
|
|
882
|
+
const licenceGate = new LicenceTurnGate({
|
|
883
|
+
mode: cfg.llm.mode,
|
|
884
|
+
proxyUrl: cfg.proxy_url,
|
|
885
|
+
getKey: async () => (await store.get()) ?? null,
|
|
886
|
+
// No start-up check ran (no key yet, e.g. pasted at the prompt below):
|
|
887
|
+
// non-managed checks on the very first turn. Managed never reads it.
|
|
888
|
+
lastCheckAtMs: licenceCheckedAtMs ?? (cfg.llm.mode === 'managed' ? Date.now() : -Infinity),
|
|
889
|
+
...(cfg.llm.mode === 'azure' ? {
|
|
890
|
+
extra: async () => (await import('./connection/azure-gate.js')).azureGateCheck(),
|
|
891
|
+
// Plan D13 — never a prompt between turns (no inquirer inside the Ink
|
|
892
|
+
// UI): a changed endpoint or tenant is held as pending and the `extra`
|
|
893
|
+
// gate refuses with "…waiting for you to confirm it · start cspeach to
|
|
894
|
+
// review it". Nothing is printed here; the gate's refusal is the notice.
|
|
895
|
+
// So on this 4-hourly path the cache-write-failure line and the ignored
|
|
896
|
+
// env-override lines are dropped (they show again at the next start).
|
|
897
|
+
onRecheck: async () => {
|
|
898
|
+
const { refreshAzureConnection } = await import('./connection/startup.js');
|
|
899
|
+
await refreshAzureConnection({
|
|
900
|
+
proxyUrl: cfg.proxy_url, apiKey: (await store.get()) ?? null, interactive: false,
|
|
901
|
+
print: () => { }, confirm: async () => false,
|
|
902
|
+
});
|
|
903
|
+
},
|
|
904
|
+
} : {}),
|
|
905
|
+
});
|
|
906
|
+
// Security batch item 3 — send the usage counts still waiting from earlier
|
|
907
|
+
// runs. Non-managed only (a managed seat makes no call); fire-and-forget:
|
|
908
|
+
// never awaited, never throws into start-up.
|
|
909
|
+
if (cfg.llm.mode !== 'managed') {
|
|
910
|
+
try {
|
|
911
|
+
startUsageFlushAtStartup(cfg);
|
|
912
|
+
}
|
|
913
|
+
catch { /* best effort */ }
|
|
914
|
+
}
|
|
818
915
|
// Task 15 — keep the prompt cache warm while the user is idle between
|
|
819
916
|
// turns: armed after a turn, disarmed on the next submit and on exit;
|
|
820
917
|
// `[keepalive] idle_pings` per idle period (managed / BYOK only).
|
|
@@ -1148,6 +1245,16 @@ export async function runRepl(opts) {
|
|
|
1148
1245
|
// older turns via Haiku so subsequent turns ingest ~5k tokens of
|
|
1149
1246
|
// context instead of 500k+. Saves \$3-8 per follow-up turn.
|
|
1150
1247
|
if (trimmed === '/compact') {
|
|
1248
|
+
// Security batch item 1 — /compact makes a model call: gated like a turn.
|
|
1249
|
+
{
|
|
1250
|
+
const g = await licenceGate.beforeTurn();
|
|
1251
|
+
if (!g.allow) {
|
|
1252
|
+
chunkEmitter.emit('chunk', '\n' + g.message + '\n');
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
if (g.line)
|
|
1256
|
+
chunkEmitter.emit('chunk', formatInfo(g.line));
|
|
1257
|
+
}
|
|
1151
1258
|
const { runCompact, formatCompactResult, buildSummarisationPrompt, serialiseForSummariser } = await import('./commands/compact.js');
|
|
1152
1259
|
const { summariseViaProvider } = await import('./agent/summarise-via-provider.js');
|
|
1153
1260
|
const { loadConfig } = await import('./config/loader.js');
|
|
@@ -1156,7 +1263,7 @@ export async function runRepl(opts) {
|
|
|
1156
1263
|
return await summariseViaProvider(provider, buildSummarisationPrompt(), serialiseForSummariser(toSummarise),
|
|
1157
1264
|
// model-governance step 2d — compact model resolves env > local >
|
|
1158
1265
|
// server > built-in (== COMPACTION_MODEL when nothing set).
|
|
1159
|
-
{ model: resolveModelRole('compact', cfg), maxTokens: 4000, headers: { 'X-CSForge-Skill': 'compact', 'X-CSPeach-Model-Role': 'compact' } });
|
|
1266
|
+
{ model: resolveModelRole('compact', cfg), maxTokens: 4000, headers: { 'X-CSForge-Skill': 'compact', 'X-CSPeach-Model-Role': 'compact' }, sessionId: session.id });
|
|
1160
1267
|
};
|
|
1161
1268
|
try {
|
|
1162
1269
|
chunkEmitter.emit('chunk', '\n' + formatInfo('Compacting older turns into a summary…'));
|
|
@@ -1219,6 +1326,15 @@ export async function runRepl(opts) {
|
|
|
1219
1326
|
chunkEmitter.emit('chunk', '\n' + formatInfo(`Rerouting "${preview}" to /${rr.skill}`));
|
|
1220
1327
|
if (rr.warning)
|
|
1221
1328
|
chunkEmitter.emit('chunk', chalk.dim(` (${rr.warning})\n`));
|
|
1329
|
+
{
|
|
1330
|
+
const g = await licenceGate.beforeTurn();
|
|
1331
|
+
if (!g.allow) {
|
|
1332
|
+
chunkEmitter.emit('chunk', '\n' + g.message + '\n');
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
if (g.line)
|
|
1336
|
+
chunkEmitter.emit('chunk', formatInfo(g.line));
|
|
1337
|
+
}
|
|
1222
1338
|
// Re-review 1 — its own AbortController, wired like the main path, so
|
|
1223
1339
|
// Ctrl+C and `/exit` stop a rerouted turn the same way.
|
|
1224
1340
|
const rerouteAbort = new AbortController();
|
|
@@ -1576,6 +1692,20 @@ export async function runRepl(opts) {
|
|
|
1576
1692
|
session.last_object = extractedObject ?? previousObject ?? null;
|
|
1577
1693
|
return;
|
|
1578
1694
|
}
|
|
1695
|
+
// Security batch item 1 — never mid-turn: checked here, before the turn
|
|
1696
|
+
// and before any plan-resume work (run lease, object-list grant,
|
|
1697
|
+
// re-audit, phase context, plan_in_flight). A plan step refused here
|
|
1698
|
+
// never starts; the plan file keeps its state, so `/abap-plan --resume`
|
|
1699
|
+
// continues later.
|
|
1700
|
+
{
|
|
1701
|
+
const g = await licenceGate.beforeTurn();
|
|
1702
|
+
if (!g.allow) {
|
|
1703
|
+
chunkEmitter.emit('chunk', '\n' + g.message + '\n');
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
if (g.line)
|
|
1707
|
+
chunkEmitter.emit('chunk', formatInfo(g.line));
|
|
1708
|
+
}
|
|
1579
1709
|
// B3 (2026-06-06) — `/abap-plan --resume @plan-file`. Token-free
|
|
1580
1710
|
// pre-work (load envelope, pick next eligible phase, print the
|
|
1581
1711
|
// tracker board, inline the phase's rule files) happens in
|
|
@@ -1724,7 +1854,7 @@ export async function runRepl(opts) {
|
|
|
1724
1854
|
const { beginPlanPhaseContext, makePhaseBoundarySummariser } = await import('./commands/plan-boundary.js');
|
|
1725
1855
|
await beginPlanPhaseContext({
|
|
1726
1856
|
session,
|
|
1727
|
-
summarise: makePhaseBoundarySummariser(provider, resolveModelRole('compact', cfg)),
|
|
1857
|
+
summarise: makePhaseBoundarySummariser(provider, resolveModelRole('compact', cfg), session.id),
|
|
1728
1858
|
log: (line) => chunkEmitter.emit('chunk', line + '\n'),
|
|
1729
1859
|
});
|
|
1730
1860
|
}
|
|
@@ -1752,7 +1882,9 @@ export async function runRepl(opts) {
|
|
|
1752
1882
|
delegateTo: planResume.nextDelegateTo,
|
|
1753
1883
|
// model-governance step 2d — the session default now resolves
|
|
1754
1884
|
// env > local > server > built-in (== cfg.default_model when nothing set).
|
|
1755
|
-
defaultModel: resolveModelRole('session_default', cfg),
|
|
1885
|
+
defaultModel: familyOf(resolveModelRole('session_default', cfg), provider),
|
|
1886
|
+
// Azure tenant mode (Task 2) — undefined (today's Sonnet) with no connection table.
|
|
1887
|
+
tierModel: planTierModelFor(getActiveDeployments()),
|
|
1756
1888
|
});
|
|
1757
1889
|
if (choice.model) {
|
|
1758
1890
|
planModelOverride = choice.model;
|
|
@@ -1987,6 +2119,9 @@ export async function runRepl(opts) {
|
|
|
1987
2119
|
planSubagentDispatches = getAndResetPlanPhaseSubagentDispatches();
|
|
1988
2120
|
}
|
|
1989
2121
|
}
|
|
2122
|
+
// 2026-10-13 — end the stateful SAP session between turns, so a lock
|
|
2123
|
+
// a failed write left behind does not outlive the turn (SM12).
|
|
2124
|
+
await endSapSessionAfterTurn(adt);
|
|
1990
2125
|
}
|
|
1991
2126
|
// B3 — persist the plan revision from the resume turn's manifest
|
|
1992
2127
|
// block (runs even after a turn error: partial assistant content is
|
|
@@ -2178,7 +2313,7 @@ export async function runRepl(opts) {
|
|
|
2178
2313
|
session,
|
|
2179
2314
|
config: { ...cfg.compact, auto_threshold_tokens: resolveAutoCompactThreshold(cfg) },
|
|
2180
2315
|
queued: pendingDispatchAccessor.get() !== null,
|
|
2181
|
-
summarise: makeAutoCompactSummariser(provider, resolveModelRole('compact', cfg)),
|
|
2316
|
+
summarise: makeAutoCompactSummariser(provider, resolveModelRole('compact', cfg), session.id),
|
|
2182
2317
|
emit: (line) => chunkEmitter.emit('chunk', line + '\n'),
|
|
2183
2318
|
});
|
|
2184
2319
|
}
|
|
@@ -3074,6 +3209,16 @@ export async function runRepl(opts) {
|
|
|
3074
3209
|
// Summarises older turns via Haiku 4.5 so subsequent turns ingest ~5k
|
|
3075
3210
|
// tokens of context instead of 500k+. Backs up session pre-mutation.
|
|
3076
3211
|
if (trimmed === '/compact') {
|
|
3212
|
+
// Security batch item 1 — /compact makes a model call: gated like a turn.
|
|
3213
|
+
{
|
|
3214
|
+
const g = await licenceGate.beforeTurn();
|
|
3215
|
+
if (!g.allow) {
|
|
3216
|
+
console.log(g.message);
|
|
3217
|
+
continue;
|
|
3218
|
+
}
|
|
3219
|
+
if (g.line)
|
|
3220
|
+
console.log(formatInfo(g.line).trimEnd());
|
|
3221
|
+
}
|
|
3077
3222
|
const { runCompact, formatCompactResult, buildSummarisationPrompt, serialiseForSummariser } = await import('./commands/compact.js');
|
|
3078
3223
|
const { summariseViaProvider } = await import('./agent/summarise-via-provider.js');
|
|
3079
3224
|
const { loadConfig } = await import('./config/loader.js');
|
|
@@ -3082,7 +3227,7 @@ export async function runRepl(opts) {
|
|
|
3082
3227
|
return await summariseViaProvider(provider, buildSummarisationPrompt(), serialiseForSummariser(toSummarise),
|
|
3083
3228
|
// model-governance step 2d — compact model resolves env > local >
|
|
3084
3229
|
// server > built-in (== COMPACTION_MODEL when nothing set).
|
|
3085
|
-
{ model: resolveModelRole('compact', cfg), maxTokens: 4000, headers: { 'X-CSForge-Skill': 'compact', 'X-CSPeach-Model-Role': 'compact' } });
|
|
3230
|
+
{ model: resolveModelRole('compact', cfg), maxTokens: 4000, headers: { 'X-CSForge-Skill': 'compact', 'X-CSPeach-Model-Role': 'compact' }, sessionId: session.id });
|
|
3086
3231
|
};
|
|
3087
3232
|
try {
|
|
3088
3233
|
console.log('\n' + formatInfo('Compacting older turns into a summary…').trimEnd());
|
|
@@ -3154,6 +3299,15 @@ export async function runRepl(opts) {
|
|
|
3154
3299
|
console.log(formatInfo(`Rerouting "${preview}" to /${rr.skill}`).trimEnd());
|
|
3155
3300
|
if (rr.warning)
|
|
3156
3301
|
console.log(chalk.dim(` (${rr.warning})`));
|
|
3302
|
+
{
|
|
3303
|
+
const g = await licenceGate.beforeTurn();
|
|
3304
|
+
if (!g.allow) {
|
|
3305
|
+
console.log(g.message);
|
|
3306
|
+
continue;
|
|
3307
|
+
}
|
|
3308
|
+
if (g.line)
|
|
3309
|
+
console.log(formatInfo(g.line).trimEnd());
|
|
3310
|
+
}
|
|
3157
3311
|
// A turn error prints the same stderr block as the normal turn path.
|
|
3158
3312
|
await runRerouteTurn({
|
|
3159
3313
|
run: () => runTurn({
|
|
@@ -3574,6 +3728,17 @@ export async function runRepl(opts) {
|
|
|
3574
3728
|
session.last_object = extractedObject ?? previousObject ?? null;
|
|
3575
3729
|
continue;
|
|
3576
3730
|
}
|
|
3731
|
+
// Security batch item 1 — checked before the turn and before any
|
|
3732
|
+
// plan-resume work (classic mirror of the Ink path).
|
|
3733
|
+
{
|
|
3734
|
+
const g = await licenceGate.beforeTurn();
|
|
3735
|
+
if (!g.allow) {
|
|
3736
|
+
console.log(g.message);
|
|
3737
|
+
continue;
|
|
3738
|
+
}
|
|
3739
|
+
if (g.line)
|
|
3740
|
+
console.log(formatInfo(g.line).trimEnd());
|
|
3741
|
+
}
|
|
3577
3742
|
// B3 (2026-06-06) — `/abap-plan --resume @plan-file` (classic-mode
|
|
3578
3743
|
// mirror of the Ink path above; see that block for the full design
|
|
3579
3744
|
// note). Token-free pre-work; terminal states end the turn with no
|
|
@@ -3700,7 +3865,7 @@ export async function runRepl(opts) {
|
|
|
3700
3865
|
const { beginPlanPhaseContext, makePhaseBoundarySummariser } = await import('./commands/plan-boundary.js');
|
|
3701
3866
|
await beginPlanPhaseContext({
|
|
3702
3867
|
session,
|
|
3703
|
-
summarise: makePhaseBoundarySummariser(provider, resolveModelRole('compact', cfg)),
|
|
3868
|
+
summarise: makePhaseBoundarySummariser(provider, resolveModelRole('compact', cfg), session.id),
|
|
3704
3869
|
log: (line) => console.log(line),
|
|
3705
3870
|
});
|
|
3706
3871
|
}
|
|
@@ -3723,7 +3888,9 @@ export async function runRepl(opts) {
|
|
|
3723
3888
|
delegateTo: planResume.nextDelegateTo,
|
|
3724
3889
|
// model-governance step 2d — the session default now resolves
|
|
3725
3890
|
// env > local > server > built-in (== cfg.default_model when nothing set).
|
|
3726
|
-
defaultModel: resolveModelRole('session_default', cfg),
|
|
3891
|
+
defaultModel: familyOf(resolveModelRole('session_default', cfg), provider),
|
|
3892
|
+
// Azure tenant mode (Task 2) — undefined (today's Sonnet) with no connection table.
|
|
3893
|
+
tierModel: planTierModelFor(getActiveDeployments()),
|
|
3727
3894
|
});
|
|
3728
3895
|
if (choice.model) {
|
|
3729
3896
|
planModelOverride = choice.model;
|
|
@@ -3954,6 +4121,8 @@ export async function runRepl(opts) {
|
|
|
3954
4121
|
planSubagentDispatches = getAndResetPlanPhaseSubagentDispatches();
|
|
3955
4122
|
}
|
|
3956
4123
|
}
|
|
4124
|
+
// 2026-10-13 — end the stateful SAP session between turns (Ink mirror).
|
|
4125
|
+
await endSapSessionAfterTurn(adt);
|
|
3957
4126
|
}
|
|
3958
4127
|
// B3 — persist the plan revision from the resume turn's manifest block.
|
|
3959
4128
|
// FALLBACK: skipped when the mid-turn onPlanManifest hook already saved it
|
|
@@ -4127,7 +4296,7 @@ export async function runRepl(opts) {
|
|
|
4127
4296
|
session,
|
|
4128
4297
|
config: { ...cfg.compact, auto_threshold_tokens: resolveAutoCompactThreshold(cfg) },
|
|
4129
4298
|
queued: pendingDispatchAccessor.get() !== null,
|
|
4130
|
-
summarise: makeAutoCompactSummariser(provider, resolveModelRole('compact', cfg)),
|
|
4299
|
+
summarise: makeAutoCompactSummariser(provider, resolveModelRole('compact', cfg), session.id),
|
|
4131
4300
|
emit: (line) => console.log(line),
|
|
4132
4301
|
});
|
|
4133
4302
|
}
|