@cspeach/cli 1.1.1 → 1.1.2
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/loop.js +11 -0
- package/dist/approvals/jwt.js +12 -1
- package/dist/cost/session-cost.js +28 -0
- package/dist/renderer/abap-inline.js +2 -1
- package/dist/renderer/syntax.js +7 -1
- package/dist/repl/current-transport.js +23 -1
- package/dist/repl/post-turn-status.js +9 -6
- package/dist/repl.js +4 -0
- package/dist/skills/bundled-skills.js +41 -41
- package/dist/tools/fiori/fe-scaffold.js +25 -0
- package/dist/tools/fiori/smoke/assertions.js +8 -2
- package/dist/tools/fiori/smoke/driver.js +6 -0
- package/dist/tools/fiori/smoke/run-smoke.js +69 -2
- package/dist/tools/fiori/tools.js +4 -2
- package/dist/tools/sap-write.js +18 -0
- package/dist/tools/todo.js +11 -3
- package/dist/ui/app.js +1 -1
- package/dist/ui/sap-state-store.js +1 -1
- package/dist/ui/status-line.js +2 -2
- package/dist/ui/todo-panel.js +27 -6
- package/fiori/zlas-inspection-lr/package.json +26 -0
- package/fiori/zlas-inspection-lr/ui5-local.yaml +45 -0
- package/fiori/zlas-inspection-lr/ui5.yaml +41 -0
- package/fiori/zlas-inspection-lr/webapp/Component.js +12 -0
- package/fiori/zlas-inspection-lr/webapp/i18n/i18n.properties +9 -0
- package/fiori/zlas-inspection-lr/webapp/index.html +35 -0
- package/fiori/zlas-inspection-lr/webapp/manifest.json +145 -0
- package/package.json +2 -2
package/dist/agent/loop.js
CHANGED
|
@@ -19,6 +19,8 @@ import { turnStatusEmitter } from '../ui/turn-status-emitter.js';
|
|
|
19
19
|
// two-line dispatch renderer; re-add if a future in-place spinner returns)
|
|
20
20
|
import { buildRetryCapPausePayload, buildSkippedSiblingResults } from './retry-cap.js';
|
|
21
21
|
import { appendCostLine, buildEntry as buildCostEntry } from '../cost/cost-log.js';
|
|
22
|
+
import { computeSessionCost } from '../cost/session-cost.js';
|
|
23
|
+
import { globalStore } from '../ui/sap-state-store.js';
|
|
22
24
|
import { lastAssistantIsAwaitingAnswer } from '../session/awaiting-answer.js';
|
|
23
25
|
import { enrichUserMessage } from '../router/intent-extractor.js';
|
|
24
26
|
import { resetRule8State, shouldGateRule8 } from '../repl/rule8-detector.js';
|
|
@@ -698,6 +700,11 @@ export async function runTurn(params) {
|
|
|
698
700
|
session.usage.cache_creation_input_tokens =
|
|
699
701
|
(session.usage.cache_creation_input_tokens ?? 0)
|
|
700
702
|
+ (msgUsage.cache_creation_input_tokens ?? 0);
|
|
703
|
+
// F-L2-2 — live spend. The store's 200ms trailing rate limiter
|
|
704
|
+
// absorbs stream-event volume; no extra throttling here.
|
|
705
|
+
if (params.pushSpend) {
|
|
706
|
+
globalStore.update({ dollarSpendToday: computeSessionCost(session) });
|
|
707
|
+
}
|
|
701
708
|
}
|
|
702
709
|
// New message begins — reset the per-message cumulative-output scratch.
|
|
703
710
|
messageOutputTokensSoFar = 0;
|
|
@@ -720,6 +727,10 @@ export async function runTurn(params) {
|
|
|
720
727
|
if (inc > 0) {
|
|
721
728
|
session.usage.output_tokens += inc;
|
|
722
729
|
messageOutputTokensSoFar = deltaUsage.output_tokens;
|
|
730
|
+
// F-L2-2 — live spend (see message_start above).
|
|
731
|
+
if (params.pushSpend) {
|
|
732
|
+
globalStore.update({ dollarSpendToday: computeSessionCost(session) });
|
|
733
|
+
}
|
|
723
734
|
}
|
|
724
735
|
}
|
|
725
736
|
}
|
package/dist/approvals/jwt.js
CHANGED
|
@@ -76,9 +76,20 @@ export async function verifyAndSpendApprovalId(jwt, expectedObject, expectedOp)
|
|
|
76
76
|
// signature-verification failure code. Mismatch reasons (nonce, object,
|
|
77
77
|
// op) fall out of the post-verify checks below and are returned
|
|
78
78
|
// verbatim — no catch involved.
|
|
79
|
+
//
|
|
80
|
+
// F-L2-13: normalize the token at the tool boundary. An approval_id that
|
|
81
|
+
// arrives wrapped in stray whitespace (a model-emitted argument with a
|
|
82
|
+
// trailing newline, a copy/paste with leading spaces) is a structurally
|
|
83
|
+
// valid JWT that jose rejects as malformed — the user then sees
|
|
84
|
+
// APPROVAL_INVALID / invalid_signature, which reads as "your token is
|
|
85
|
+
// forged" and offers no way forward. Trimming here covers all 10
|
|
86
|
+
// tool-boundary callers (sap-write.ts, transport.ts) at one site. Nonce
|
|
87
|
+
// bookkeeping keys on the decoded `payload.nonce`, never on the raw
|
|
88
|
+
// string, so trimming cannot desynchronize single-use accounting.
|
|
89
|
+
const token = String(jwt ?? '').trim();
|
|
79
90
|
let payload;
|
|
80
91
|
try {
|
|
81
|
-
const verified = await jwtVerify(
|
|
92
|
+
const verified = await jwtVerify(token, sessionKey);
|
|
82
93
|
payload = verified.payload;
|
|
83
94
|
}
|
|
84
95
|
catch (err) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped cost total — the single source of truth for the statusline
|
|
3
|
+
* `$` segment and the printed status footer's session line.
|
|
4
|
+
*
|
|
5
|
+
* F-L2-2 (2026-08-11): this computation was inlined in
|
|
6
|
+
* `repl/post-turn-status.ts`, the POST-turn chokepoint. That meant the
|
|
7
|
+
* statusline spend only ever moved between turns — during a long turn (the
|
|
8
|
+
* exact moment a user wants to watch the meter) it sat frozen. Extracted
|
|
9
|
+
* here verbatim so the agent loop can call it mid-stream (after each
|
|
10
|
+
* `message_start` / `message_delta` usage mutation) and push the running
|
|
11
|
+
* total to the store.
|
|
12
|
+
*
|
|
13
|
+
* Honest-semantics caveat, carried over unchanged from the original site:
|
|
14
|
+
* the total is priced at `session.model` over LIFETIME `session.usage`. On a
|
|
15
|
+
* session with model-tiered turns (A4 modelOverride) that is a mixed-model
|
|
16
|
+
* approximation — the per-turn cost JSONL records the model that actually
|
|
17
|
+
* ran. Same caveat as the footer's session line.
|
|
18
|
+
*/
|
|
19
|
+
import { computeCost } from './pricing.js';
|
|
20
|
+
/** USD cost of the session's lifetime usage, priced at `session.model`. */
|
|
21
|
+
export function computeSessionCost(session) {
|
|
22
|
+
return computeCost(session.model, {
|
|
23
|
+
input: session.usage.input_tokens,
|
|
24
|
+
output: session.usage.output_tokens,
|
|
25
|
+
cacheRead: session.usage.cache_read_input_tokens,
|
|
26
|
+
cacheCreate: session.usage.cache_creation_input_tokens ?? 0,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* ```abap → existing ABAP regex logic (unchanged)
|
|
8
8
|
* ```cds → cli-highlight CDS grammar (via syntax.ts)
|
|
9
9
|
* ```bdef → cli-highlight BDEF grammar (via syntax.ts)
|
|
10
|
+
* ```ddl → cli-highlight CDS grammar (ddl is a CDS alias; via syntax.ts)
|
|
10
11
|
* ```<any other or empty> → pass through (no highlight)
|
|
11
12
|
*
|
|
12
13
|
* The function is renamed `applyCodeHighlight`. A deprecation re-export
|
|
@@ -64,7 +65,7 @@ export function applyCodeHighlight(text) {
|
|
|
64
65
|
// dependency for the hottest fenced-block type.
|
|
65
66
|
out.push(highlightAbapBlock(body));
|
|
66
67
|
}
|
|
67
|
-
else if (lang === 'cds' || lang === 'bdef') {
|
|
68
|
+
else if (lang === 'cds' || lang === 'bdef' || lang === 'ddl') {
|
|
68
69
|
// cli-highlight grammars via the Task 5 dispatcher.
|
|
69
70
|
out.push(highlightCodeBlock(body, lang));
|
|
70
71
|
}
|
package/dist/renderer/syntax.js
CHANGED
|
@@ -47,7 +47,13 @@ const hljs = createRequire(require.resolve('cli-highlight'))('highlight.js');
|
|
|
47
47
|
hljs.registerLanguage('abap', () => abapGrammar);
|
|
48
48
|
hljs.registerLanguage('cds', () => cdsGrammar);
|
|
49
49
|
hljs.registerLanguage('bdef', () => bdefGrammar);
|
|
50
|
-
|
|
50
|
+
// `ddl` is an alias of the CDS grammar: ```ddl fences carry CDS DDL source
|
|
51
|
+
// (the abap-rap templates emit them). Registering it on the same hljs
|
|
52
|
+
// instance also silences marked-terminal's "Could not find the language
|
|
53
|
+
// 'ddl'" warning, since marked-terminal calls cli-highlight directly with
|
|
54
|
+
// the raw fence lang and never goes through highlightCodeBlock().
|
|
55
|
+
hljs.registerLanguage('ddl', () => cdsGrammar);
|
|
56
|
+
const SUPPORTED = new Set(['abap', 'cds', 'bdef', 'ddl']);
|
|
51
57
|
/**
|
|
52
58
|
* Highlight a code block body (without fence markers).
|
|
53
59
|
*
|
|
@@ -15,12 +15,23 @@
|
|
|
15
15
|
* Module-scope singleton per CLI process. Tests must call clearCurrentTransport()
|
|
16
16
|
* in beforeEach to avoid leakage across Vitest test files.
|
|
17
17
|
*
|
|
18
|
-
* Task 9 (ux-wave1) — this setter is ALSO
|
|
18
|
+
* Task 9 (ux-wave1) — this setter is ALSO a statusline wire for the
|
|
19
19
|
* `tr <TRKORR>` segment: every path that changes the session transport
|
|
20
20
|
* (sap_transport create success, owning-transport adoption in the create
|
|
21
21
|
* guard, /transport slash command) funnels through set/clear here, so the
|
|
22
22
|
* globalStore push lives HERE rather than scattered across tool handlers.
|
|
23
23
|
* Store writes are harmless in classic mode (nothing subscribes there).
|
|
24
|
+
*
|
|
25
|
+
* F-L2-1 (2026-08-11) — that wire alone is not enough, so this module now
|
|
26
|
+
* carries a SECOND, write-side wire: noteWriteTransport(). The session flow
|
|
27
|
+
* above is the only writer of `store.transport`, but a per-write `corrNr`
|
|
28
|
+
* coming from a prompt or a plan never travels through it — live evidence: a
|
|
29
|
+
* whole session's writes stamped S4HK903635 while the statusline read
|
|
30
|
+
* `tr none`. noteWriteTransport records what a write ACTUALLY stamped into a
|
|
31
|
+
* SEPARATE store field (`lastWriteTransport`) that the statusline falls back
|
|
32
|
+
* to. It deliberately does NOT touch the session pointer below: that pointer
|
|
33
|
+
* feeds the 3-tier write-transport fallback, and moving it would change write
|
|
34
|
+
* behaviour, not just the display.
|
|
24
35
|
*/
|
|
25
36
|
import { globalStore } from '../ui/sap-state-store.js';
|
|
26
37
|
let currentTransport = null;
|
|
@@ -35,6 +46,17 @@ export function clearCurrentTransport() {
|
|
|
35
46
|
currentTransport = null;
|
|
36
47
|
globalStore.update({ transport: null });
|
|
37
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Statusline-only wire (F-L2-1): record the transport actually stamped on the
|
|
51
|
+
* last successful write. Never touches the session transport pointer — see the
|
|
52
|
+
* module doc. No-op on a falsy trkorr (local / $TMP objects write without one,
|
|
53
|
+
* and blanking the display there would lose the last real datapoint).
|
|
54
|
+
*/
|
|
55
|
+
export function noteWriteTransport(trkorr) {
|
|
56
|
+
if (!trkorr)
|
|
57
|
+
return;
|
|
58
|
+
globalStore.update({ lastWriteTransport: trkorr });
|
|
59
|
+
}
|
|
38
60
|
/**
|
|
39
61
|
* Handles the `/transport [<trkorr>|clear]` slash command. Returns user-facing
|
|
40
62
|
* status text. Mirrors the shape of handleSafetyModeCommand in safety-mode-state.ts.
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* primary per-turn status surface.
|
|
19
19
|
*/
|
|
20
20
|
import { computeCost } from '../cost/pricing.js';
|
|
21
|
+
import { computeSessionCost } from '../cost/session-cost.js';
|
|
21
22
|
import { globalStore } from '../ui/sap-state-store.js';
|
|
22
23
|
import { printStatusFooter, formatStatusFooter } from '../renderer/status-footer.js';
|
|
23
24
|
import { shouldUseInk } from '../renderer/tty.js';
|
|
@@ -43,12 +44,9 @@ export function pushPostTurnStatus(params) {
|
|
|
43
44
|
cacheCreate: (session.usage.cache_creation_input_tokens ?? 0) - snapshot.cacheCreate,
|
|
44
45
|
};
|
|
45
46
|
const turnCost = computeCost(turnModel ?? session.model, turnCostTokens);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
cacheRead: session.usage.cache_read_input_tokens,
|
|
50
|
-
cacheCreate: session.usage.cache_creation_input_tokens ?? 0,
|
|
51
|
-
});
|
|
47
|
+
// F-L2-2: extracted to cost/session-cost.ts so the agent loop can push the
|
|
48
|
+
// SAME total mid-stream. This call site is unchanged in behaviour.
|
|
49
|
+
const sessionCost = computeSessionCost(session);
|
|
52
50
|
// Task 9 (ux-wave1) — statusline spend wire. Honest semantics note: the
|
|
53
51
|
// store field is named `dollarSpendToday` (legacy sidebar naming, kept to
|
|
54
52
|
// avoid rename churn) but the value written is SESSION-scoped — the same
|
|
@@ -56,6 +54,11 @@ export function pushPostTurnStatus(params) {
|
|
|
56
54
|
// lifetime session.usage at session.model rates (mixed-model turns are an
|
|
57
55
|
// approximation, same caveat as the footer's session line). There is no
|
|
58
56
|
// day-spanning ledger; the statusline shows session spend.
|
|
57
|
+
//
|
|
58
|
+
// F-L2-2 (2026-08-11): the agent loop now pushes the same value mid-stream
|
|
59
|
+
// so the segment ticks DURING a turn, not only between turns. This push
|
|
60
|
+
// stays as the authoritative end-of-turn settle (the loop's pushes are
|
|
61
|
+
// gated to REPL turns; this one runs on every path that reaches here).
|
|
59
62
|
globalStore.update({ dollarSpendToday: sessionCost });
|
|
60
63
|
const statusPayload = {
|
|
61
64
|
sessionId: session.id,
|
package/dist/repl.js
CHANGED
|
@@ -822,6 +822,7 @@ export async function runRepl(opts) {
|
|
|
822
822
|
skill: rr.skill,
|
|
823
823
|
ctx: { adt, sapAlias: selectedAlias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: replPreviewHook, chunkEmitter, todoEmitter, currentTransport: currentTransportAccessor, pendingDispatch: pendingDispatchAccessor },
|
|
824
824
|
chunkEmitter,
|
|
825
|
+
pushSpend: true, // F-L2-2 — top-level REPL turn: live statusline spend
|
|
825
826
|
});
|
|
826
827
|
}
|
|
827
828
|
finally {
|
|
@@ -1254,6 +1255,7 @@ export async function runRepl(opts) {
|
|
|
1254
1255
|
skill: skill,
|
|
1255
1256
|
ctx: { adt, sapAlias: selectedAlias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: replPreviewHook, chunkEmitter, todoEmitter, currentTransport: currentTransportAccessor, pendingDispatch: pendingDispatchAccessor },
|
|
1256
1257
|
chunkEmitter,
|
|
1258
|
+
pushSpend: true, // F-L2-2 — top-level REPL turn: live statusline spend
|
|
1257
1259
|
signal: currentTurnAbort.signal,
|
|
1258
1260
|
suppressSaveHook: planResume !== null || planContinue !== null,
|
|
1259
1261
|
// A4 — undefined on every non-plan turn (and when tiering is off).
|
|
@@ -2270,6 +2272,7 @@ export async function runRepl(opts) {
|
|
|
2270
2272
|
userMessage: rr.prompt,
|
|
2271
2273
|
skill: rr.skill,
|
|
2272
2274
|
ctx: { adt, sapAlias: selectedAlias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: replPreviewHook, chunkEmitter: classicOutputEmitter, todoEmitter, currentTransport: currentTransportAccessor, pendingDispatch: pendingDispatchAccessor },
|
|
2275
|
+
pushSpend: true, // F-L2-2 — top-level REPL turn: live statusline spend
|
|
2273
2276
|
});
|
|
2274
2277
|
continue;
|
|
2275
2278
|
}
|
|
@@ -2797,6 +2800,7 @@ export async function runRepl(opts) {
|
|
|
2797
2800
|
userMessage: prompt,
|
|
2798
2801
|
skill: skill,
|
|
2799
2802
|
ctx: { adt, sapAlias: selectedAlias, session, cwd: process.cwd(), provider, skillSource: provider.skillSource, previewHook: replPreviewHook, chunkEmitter: classicOutputEmitter, todoEmitter, currentTransport: currentTransportAccessor, pendingDispatch: pendingDispatchAccessor },
|
|
2803
|
+
pushSpend: true, // F-L2-2 — top-level REPL turn: live statusline spend
|
|
2800
2804
|
signal: turnAbort.signal,
|
|
2801
2805
|
suppressSaveHook: planResume !== null || planContinue !== null,
|
|
2802
2806
|
// A4 — undefined on every non-plan turn (and when tiering is off).
|