@cspeach/cli 1.1.11 → 1.1.12

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.
@@ -905,19 +905,31 @@ export async function runTurn(params) {
905
905
  // carry the manifest. See ./turn-assistant-text.ts for details.
906
906
  turnStatusEmitter.activity('finishing up');
907
907
  const assistantText = collectTurnAssistantText(session.messages, turnStartMessageCount);
908
- if (!params.suppressSaveHook) {
909
- await maybeOfferSave({
910
- skill: params.skill,
911
- assistantText,
912
- userMessage: userMessageForSave,
913
- tokensUsed: Math.max(0, (session.usage?.output_tokens ?? 0) - turnStartOutputTokens),
914
- // A4 — record the actual turn model if an override ran the stream
915
- // (unreachable today: plan-resume turns suppress this hook, and
916
- // they are the only modelOverride caller — kept honest anyway).
917
- model: params.modelOverride ?? resolveModelRole('session_default', cfg) ?? session.model ?? 'unknown',
918
- emit,
919
- promotedFrom: promotedFromForSave,
920
- });
908
+ // 2026-09-17 — the save hook below asks the user things (save y/N,
909
+ // blocker pickers). Those are not tool dispatches, so nothing paused
910
+ // the status row: it kept showing "finishing up" with a running timer
911
+ // while the user was the one being waited on (Laeeq's dry run). Pause
912
+ // for the whole hook — the row hides while paused — and resume after,
913
+ // so any real work that follows (auto-compact) shows again.
914
+ turnStatusEmitter.pause();
915
+ try {
916
+ if (!params.suppressSaveHook) {
917
+ await maybeOfferSave({
918
+ skill: params.skill,
919
+ assistantText,
920
+ userMessage: userMessageForSave,
921
+ tokensUsed: Math.max(0, (session.usage?.output_tokens ?? 0) - turnStartOutputTokens),
922
+ // A4 — record the actual turn model if an override ran the stream
923
+ // (unreachable today: plan-resume turns suppress this hook, and
924
+ // they are the only modelOverride caller — kept honest anyway).
925
+ model: params.modelOverride ?? resolveModelRole('session_default', cfg) ?? session.model ?? 'unknown',
926
+ emit,
927
+ promotedFrom: promotedFromForSave,
928
+ });
929
+ }
930
+ }
931
+ finally {
932
+ turnStatusEmitter.resume();
921
933
  }
922
934
  // Phase 2 #9 (2026-05-16) — auto-compact end-of-turn hook.
923
935
  // Runs only on the success path (after maybeOfferSave) so an interrupted
@@ -9,7 +9,7 @@ export class CspeachLicenseError extends Error {
9
9
  super(`CSPeach requires a license to run in '${mode}' mode.\n` +
10
10
  `In this mode your prompts go straight to your own LLM — but the CSPeach skills\n` +
11
11
  `and CLI are licensed software. Run \`cspeach login\` to activate your license.\n` +
12
- `Need an access key? Email laeeq.siddique@cremencing.com.`);
12
+ `Need an access key? Email info@cremencing.com.`);
13
13
  this.name = 'CspeachLicenseError';
14
14
  this.mode = mode;
15
15
  }
@@ -21,9 +21,13 @@
21
21
  export function costDisplayMode(cfg, balanceInfo) {
22
22
  if (cfg.llm?.mode !== 'managed')
23
23
  return 'usd';
24
- if (balanceInfo?.metered !== true)
25
- return 'usd';
26
24
  if (cfg.show_cogs === true)
27
25
  return 'usd';
26
+ // 2026-09-17 — a managed account NEVER falls back to dollars. Before, a
27
+ // balance lookup that failed or took over 2.5s at start (slow network on a
28
+ // customer laptop) dropped the display to USD — our cost on their screen,
29
+ // exactly what credits mode exists to prevent. Unknown or unmetered
30
+ // balances render "credits: updating…" until the next good sample.
31
+ void balanceInfo;
28
32
  return 'credits';
29
33
  }
@@ -87,8 +87,9 @@ export async function offerAnswerBlockers(args) {
87
87
  const answers = new Map();
88
88
  for (let i = 0; i < blockers.length; i++) {
89
89
  const b = blockers[i];
90
- args.log('');
91
- args.log(`Blocker ${i + 1}/${blockers.length}: ${b.text}`);
90
+ // 2026-09-17 — the "Blocker i/n: …" line is printed only on the free-text
91
+ // path below. When a picker handles the question it shows the same text
92
+ // itself, and printing it here too showed every blocker twice.
92
93
  // Choices first, where the question has them and a picker is available.
93
94
  // 'write' falls through to the text box; 'skip' leaves the blocker open.
94
95
  let askText = true;
@@ -111,6 +112,8 @@ export async function offerAnswerBlockers(args) {
111
112
  // c.kind === 'write' → askText stays true
112
113
  }
113
114
  if (askText) {
115
+ args.log('');
116
+ args.log(`Blocker ${i + 1}/${blockers.length}: ${b.text}`);
114
117
  const a = (await args.prompt(' Your answer (Enter to skip): ')).trim();
115
118
  if (a.length > 0)
116
119
  answers.set(b.id, a);
@@ -71,7 +71,7 @@ export async function runAddSapWizard() {
71
71
  default: 'basic',
72
72
  });
73
73
  const username = await input({
74
- message: 'SAP username (e.g. S.LAEEQ):',
74
+ message: 'SAP username (e.g. JSMITH):',
75
75
  validate: (v) => v.length > 0 || 'Required',
76
76
  });
77
77
  let certPath;