@cspeach/cli 1.1.44 → 1.1.46
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 +47 -0
- package/dist/agent/chat-unavailable.js +9 -0
- package/dist/agent/level-state.js +11 -0
- package/dist/agent/level.js +60 -0
- package/dist/agent/loop.js +110 -2
- package/dist/agent/parallel-write-guard.js +2 -0
- package/dist/agent/provider-shape.js +6 -2
- package/dist/agent/providers/ai-hub-provider.js +3 -1
- package/dist/agent/providers/azure-provider.js +3 -1
- package/dist/agent/providers/byok-provider.js +3 -1
- package/dist/agent/providers/local-provider.js +3 -1
- package/dist/agent/style-state.js +11 -0
- package/dist/agent/style.js +59 -0
- package/dist/agent/system-prompt.js +24 -3
- package/dist/agent/tool-dispatch.js +55 -0
- package/dist/approvals/jwt.js +5 -2
- package/dist/approvals/op-labels.js +39 -4
- package/dist/approvals/render.js +36 -9
- package/dist/approvals/risk-floor.js +14 -3
- package/dist/cli.js +8 -0
- package/dist/commands/config-set.js +59 -1
- package/dist/commands/config-show.js +1 -0
- package/dist/commands/export-docx.js +66 -0
- package/dist/commands/help.js +28 -2
- package/dist/config/loader.js +51 -1
- package/dist/docx/doc-meta.js +47 -0
- package/dist/docx/markdown-to-docx.js +250 -0
- package/dist/docx/write-docx.js +54 -0
- package/dist/one-shot.js +11 -0
- package/dist/projects/document-save.js +62 -0
- package/dist/projects/document-skills.js +9 -0
- package/dist/renderer/footer-line.js +6 -1
- package/dist/renderer/progress-chatter.js +9 -2
- package/dist/renderer/startup-lines.js +6 -2
- package/dist/renderer/tool-labels.js +5 -2
- package/dist/renderer/tool-widget.js +5 -1
- package/dist/repl/builtin-commands.js +4 -1
- package/dist/repl/chat-command.js +64 -0
- package/dist/repl/chat-lane.js +135 -0
- package/dist/repl/level-command.js +79 -0
- package/dist/repl/style-command.js +67 -0
- package/dist/repl.js +187 -105
- package/dist/sap/profile-features.js +5 -0
- package/dist/session/audit-export.js +50 -2
- package/dist/session/queued-step.js +5 -1
- package/dist/session/schema.js +1 -0
- package/dist/session/store.js +4 -0
- package/dist/tools/approval.js +59 -1
- package/dist/tools/hand-off.js +86 -0
- package/dist/tools/release-texts.js +9 -0
- package/dist/tools/sap-read.js +6 -2
- package/dist/tools/sap-run.js +163 -0
- package/dist/tools/tool-loading.js +11 -7
- package/dist/tools/transport.js +248 -22
- package/dist/tools/write-mode.js +2 -0
- package/dist/ui/approval-modal.js +11 -3
- package/dist/ui/sap-state-store.js +1 -0
- package/dist/ui/session-timeline.js +1 -1
- package/dist/ui/widgets/ask-card.js +2 -1
- package/package.json +3 -3
package/dist/tools/transport.js
CHANGED
|
@@ -7,14 +7,21 @@
|
|
|
7
7
|
* transportList(user?, status?) → Promise<TransportListResult> line 1048
|
|
8
8
|
* TransportListResult { filterUser, filterStatus, transports: TransportEntry[] }
|
|
9
9
|
* TransportEntry { transportNumber, description?, owner?, status?, type?, target? }
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* transportRead(number) → Promise<TransportRequestDoc> (Part 3)
|
|
11
|
+
* transportRelease(number) → Promise<TransportReleaseResult> (Part 3)
|
|
12
|
+
* status 'released' only on SAP's verdict for every step AND a status read back of R/N.
|
|
12
13
|
*/
|
|
13
14
|
import { bindGrantTransport, isPlanGrantInScope } from '../approvals/plan-grant.js';
|
|
14
15
|
import { registerTool } from './index.js';
|
|
15
16
|
import { verifyAndSpendApprovalId } from '../approvals/jwt.js';
|
|
16
|
-
import { auditLog } from '@cspeach/sap-client';
|
|
17
|
+
import { auditLog, SapNotFoundError, isModifiable, knownObjectCount, allObjectNames } from '@cspeach/sap-client';
|
|
18
|
+
import { typeCode } from '../approvals/risk-floor.js';
|
|
19
|
+
import { spillToolResult } from './result-spill.js';
|
|
20
|
+
import { notModifiableText } from './release-texts.js';
|
|
21
|
+
import { RELEASE_CHECK_LINE } from '../approvals/op-labels.js';
|
|
17
22
|
import { ERR } from '../errors/codes.js';
|
|
23
|
+
import { presentSafetyConfirmation } from '../repl/safety-confirm.js';
|
|
24
|
+
import { isSafetyConfirmEnabled } from '../repl/safety-mode-state.js';
|
|
18
25
|
import { setCurrentTransport, noteConfirmedTransport } from '../repl/current-transport.js';
|
|
19
26
|
import { enrichTransportList } from './transport-fit.js';
|
|
20
27
|
import { reusedRequestRefusal } from './transport-fit-check.js';
|
|
@@ -472,40 +479,259 @@ registerTool({
|
|
|
472
479
|
}
|
|
473
480
|
},
|
|
474
481
|
});
|
|
482
|
+
// ── sap_transport_read ───────────────────────────────────────────────────────
|
|
483
|
+
//
|
|
484
|
+
// Part 3 (D16) — one transport request as SAP's transport organizer shows it.
|
|
485
|
+
// Read only, no approval.
|
|
486
|
+
/** The 404 text, never SAP's generic "Object not found". */
|
|
487
|
+
function notFoundText(n) {
|
|
488
|
+
return `Transport request ${n} does not exist.`;
|
|
489
|
+
}
|
|
490
|
+
/** Gate 0 of the release and the whole of the read: SAP's own document, or the refusal envelope. */
|
|
491
|
+
async function readRequest(ctx, asked) {
|
|
492
|
+
try {
|
|
493
|
+
return { doc: await ctx.adt.transportRead(asked) };
|
|
494
|
+
}
|
|
495
|
+
catch (err) {
|
|
496
|
+
const body = err instanceof SapNotFoundError
|
|
497
|
+
? { error: 'not_found', detail: notFoundText(asked) }
|
|
498
|
+
: { error: 'transport_read_failed', detail: err instanceof Error ? err.message : String(err) };
|
|
499
|
+
return { refusal: { content: JSON.stringify(body), is_error: true } };
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
registerTool({
|
|
503
|
+
name: 'sap_transport_read',
|
|
504
|
+
description: 'Read one transport request: status, owner, target, its tasks and how many objects it holds. '
|
|
505
|
+
+ 'A task number answers its request with that task. Read only.',
|
|
506
|
+
isMutating: false,
|
|
507
|
+
requiresSap: true,
|
|
508
|
+
requires: 'cts.requests',
|
|
509
|
+
input_schema: {
|
|
510
|
+
type: 'object',
|
|
511
|
+
properties: {
|
|
512
|
+
transport: { type: 'string', description: 'Transport request or task number (e.g. DEVK900123)' },
|
|
513
|
+
},
|
|
514
|
+
required: ['transport'],
|
|
515
|
+
},
|
|
516
|
+
handler: async (args, ctx) => {
|
|
517
|
+
const asked = String(args.transport ?? '').trim().toUpperCase();
|
|
518
|
+
const read = await readRequest(ctx, asked);
|
|
519
|
+
if ('refusal' in read)
|
|
520
|
+
return read.refusal;
|
|
521
|
+
const { doc } = read;
|
|
522
|
+
const objectCount = knownObjectCount(doc);
|
|
523
|
+
const notes = [];
|
|
524
|
+
if (doc.kind === 'task')
|
|
525
|
+
notes.push(`${asked} is a task of ${doc.number}.`);
|
|
526
|
+
if (objectCount === null)
|
|
527
|
+
notes.push(`CSPeach could not count the objects of ${doc.number}.`);
|
|
528
|
+
return {
|
|
529
|
+
content: JSON.stringify({
|
|
530
|
+
kind: doc.kind,
|
|
531
|
+
request: doc.number,
|
|
532
|
+
description: doc.description,
|
|
533
|
+
owner: doc.owner,
|
|
534
|
+
type: doc.type,
|
|
535
|
+
status: doc.status,
|
|
536
|
+
status_text: doc.statusText,
|
|
537
|
+
target: doc.target,
|
|
538
|
+
tasks: doc.tasks.map((t) => ({
|
|
539
|
+
number: t.number, owner: t.owner, status: t.status, status_text: t.statusText, objects: t.objectCount,
|
|
540
|
+
})),
|
|
541
|
+
object_count: objectCount,
|
|
542
|
+
can_release: isModifiable(doc.status) && doc.actions.includes('newreleasejobs'),
|
|
543
|
+
...(notes.length ? { note: notes.join(' ') } : {}),
|
|
544
|
+
}),
|
|
545
|
+
};
|
|
546
|
+
},
|
|
547
|
+
});
|
|
475
548
|
// ── sap_transport_release ────────────────────────────────────────────────────
|
|
476
549
|
//
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
550
|
+
// Part 3 (D10–D13, D17) — every open task first, then the request, on SAP's
|
|
551
|
+
// own release verdict (sap-client transportRelease). Gates, in order:
|
|
552
|
+
// 0. read the request: missing, a task number, not modifiable → refused;
|
|
553
|
+
// 0b. the request's objects (its tasks' too) against the inactive list →
|
|
554
|
+
// refused (SAP would abort with abortrelinacobj; its consistency check
|
|
555
|
+
// does not see inactive objects);
|
|
556
|
+
// 1. the approval (op release, type CTS) — spent only after 0/0b passed;
|
|
557
|
+
// 2. the safety card (Rule 9) when safety mode is on.
|
|
558
|
+
// The envelope carries every step's verdict, SAP's texts and raw reports, what
|
|
559
|
+
// was never sent, the status read back, and that the release is not yet proven
|
|
560
|
+
// live. sap-client writes the audit rows (one per step and one closing row);
|
|
561
|
+
// the tool writes none of its own, except when sap-client threw before any.
|
|
562
|
+
/** Verdicts that mean "SAP would release it if something were ignored" (D11). */
|
|
563
|
+
const IGNORE_VERDICTS = new Set(['relwithignlock', 'relObjigchkatc', 'relwithignwarning']);
|
|
564
|
+
const NOT_YET_PROVEN = "Built from SAP's own release interface and tested against recorded answers; not yet run in a live release. "
|
|
565
|
+
+ 'Ask the user to check the transport in SE09 after the release.';
|
|
483
566
|
registerTool({
|
|
484
567
|
name: 'sap_transport_release',
|
|
485
|
-
description: '
|
|
486
|
-
+ '
|
|
568
|
+
description: 'Release a transport request: every open task first, then the request. The verdict comes from SAP\'s own release '
|
|
569
|
+
+ 'report, step by step, and the request status is read back; an HTTP answer alone is never taken as released. '
|
|
570
|
+
+ 'Stops at the first step SAP does not release; never ignores locks, object-check errors or warnings (release '
|
|
571
|
+
+ 'those in SE09 or Eclipse). Built from SAP\'s own release interface and tested against recorded answers; not '
|
|
572
|
+
+ 'yet run in a live release, so ask the user to check the transport in SE09 afterwards. Cannot be undone. '
|
|
573
|
+
+ 'Requires approval_id from a request_approval change of op `release`, type CTS (one approval per release; '
|
|
574
|
+
+ 'never auto-approved).',
|
|
487
575
|
isMutating: true,
|
|
488
576
|
requiresSap: true,
|
|
577
|
+
requires: 'cts.requests',
|
|
489
578
|
input_schema: {
|
|
490
579
|
type: 'object',
|
|
491
580
|
properties: {
|
|
492
581
|
transport: {
|
|
493
582
|
type: 'string',
|
|
494
|
-
description: 'Transport request number (e.g. DEVK900123)',
|
|
583
|
+
description: 'Transport request number to release (e.g. DEVK900123); not a task number',
|
|
495
584
|
},
|
|
496
585
|
approval_id: {
|
|
497
586
|
type: 'string',
|
|
498
|
-
description: 'JWT from request_approval (
|
|
587
|
+
description: 'JWT from request_approval (a change of op release, type CTS)',
|
|
499
588
|
},
|
|
500
589
|
},
|
|
501
|
-
required: ['transport'],
|
|
590
|
+
required: ['transport', 'approval_id'],
|
|
591
|
+
},
|
|
592
|
+
handler: async (args, ctx) => {
|
|
593
|
+
const asked = String(args.transport ?? '').trim().toUpperCase();
|
|
594
|
+
// ── Gate 0: SAP's own facts, read fresh (D10, D12) ──────────────────────
|
|
595
|
+
const read = await readRequest(ctx, asked);
|
|
596
|
+
if ('refusal' in read)
|
|
597
|
+
return read.refusal;
|
|
598
|
+
const { doc } = read;
|
|
599
|
+
if (doc.kind === 'task') {
|
|
600
|
+
return {
|
|
601
|
+
content: JSON.stringify({
|
|
602
|
+
error: 'task_number',
|
|
603
|
+
message: `${asked} is a task of ${doc.number}. Give the request number; CSPeach releases the tasks first, then the request.`,
|
|
604
|
+
}),
|
|
605
|
+
is_error: true,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
if (!isModifiable(doc.status)) {
|
|
609
|
+
return {
|
|
610
|
+
content: JSON.stringify({
|
|
611
|
+
error: 'not_modifiable',
|
|
612
|
+
message: notModifiableText(doc),
|
|
613
|
+
}),
|
|
614
|
+
is_error: true,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
// ── Gate 0b: inactive objects of the request (T14) ──────────────────────
|
|
618
|
+
let warning;
|
|
619
|
+
let inactiveNames;
|
|
620
|
+
try {
|
|
621
|
+
inactiveNames = new Set((await ctx.adt.inactiveObjects()).map((o) => String(o.name ?? '').trim().toUpperCase()));
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
warning = 'CSPeach could not read the inactive list; SAP aborts a release that holds inactive objects.';
|
|
625
|
+
}
|
|
626
|
+
if (inactiveNames) {
|
|
627
|
+
const blocked = allObjectNames(doc).filter((n) => inactiveNames.has(n));
|
|
628
|
+
if (blocked.length > 0) {
|
|
629
|
+
return {
|
|
630
|
+
content: JSON.stringify({
|
|
631
|
+
error: 'release_blocked_inactive',
|
|
632
|
+
objects: blocked,
|
|
633
|
+
message: `These objects of ${doc.number} are inactive: ${blocked.join(', ')}. `
|
|
634
|
+
+ 'Activate them (sap_activate), then ask again. SAP would abort the release.',
|
|
635
|
+
}),
|
|
636
|
+
is_error: true,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
// ── Gate 1: Approval (op release, type CTS) ─────────────────────────────
|
|
641
|
+
const verify = await verifyAndSpendApprovalId(args.approval_id, args.transport, 'release');
|
|
642
|
+
if (!verify.ok) {
|
|
643
|
+
return {
|
|
644
|
+
content: JSON.stringify({ error: ERR.APPROVAL_INVALID, reason: verify.reason }),
|
|
645
|
+
is_error: true,
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (typeCode(verify.payload?.type) !== 'CTS') {
|
|
649
|
+
return {
|
|
650
|
+
content: JSON.stringify({ error: ERR.APPROVAL_INVALID, reason: 'type_mismatch' }),
|
|
651
|
+
is_error: true,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const startedAt = Date.now();
|
|
655
|
+
// ── Gate 2: Safety confirmation (Rule 9 — a release cannot be undone) ───
|
|
656
|
+
if (isSafetyConfirmEnabled()) {
|
|
657
|
+
const openTasks = doc.tasks.filter((t) => isModifiable(t.status)).map((t) => t.number);
|
|
658
|
+
const objects = knownObjectCount(doc);
|
|
659
|
+
const c = await presentSafetyConfirmation({
|
|
660
|
+
rule: 'RULE_9_TRANSPORT_RELEASE',
|
|
661
|
+
op: 'transport_release',
|
|
662
|
+
object: { type: 'TRANSPORT', name: doc.number },
|
|
663
|
+
what_will_happen: `Releases ${openTasks.length} task(s)${openTasks.length ? ` (${openTasks.join(', ')})` : ''} then request ${doc.number} `
|
|
664
|
+
+ `(${objects === null ? 'an uncounted number of' : objects} objects, target ${doc.target || 'none'}). `
|
|
665
|
+
+ `Cannot be undone; the request may move to other systems on the route. ${RELEASE_CHECK_LINE}`,
|
|
666
|
+
rollback_available: false,
|
|
667
|
+
transport: doc.number,
|
|
668
|
+
args: { ...args },
|
|
669
|
+
});
|
|
670
|
+
if (!c.confirmed) {
|
|
671
|
+
return {
|
|
672
|
+
content: JSON.stringify({ error: 'cancelled_by_user', reason: c.reason }),
|
|
673
|
+
is_error: true,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
// ── Release: tasks, then the request, on SAP's verdict ──────────────────
|
|
678
|
+
let r;
|
|
679
|
+
try {
|
|
680
|
+
r = await ctx.adt.transportRelease(doc.number);
|
|
681
|
+
}
|
|
682
|
+
catch (err) {
|
|
683
|
+
// sap-client threw before its first step (its own read): it wrote no row.
|
|
684
|
+
await auditLog('transportRelease', 'CTS', doc.number, 'error', Date.now() - startedAt);
|
|
685
|
+
return {
|
|
686
|
+
content: JSON.stringify({ error: 'transport_release_failed', detail: err instanceof Error ? err.message : String(err) }),
|
|
687
|
+
is_error: true,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
const finalStatus = r.finalStatus?.status;
|
|
691
|
+
// Never `released: true` beside a status that is not R or N.
|
|
692
|
+
const released = r.status === 'released' && (finalStatus === 'R' || finalStatus === 'N');
|
|
693
|
+
let note;
|
|
694
|
+
switch (r.status) {
|
|
695
|
+
case 'released':
|
|
696
|
+
// Worded from `released`, never from r.status alone: "Released." only beside released: true.
|
|
697
|
+
note = released
|
|
698
|
+
? "Released. SAP's answer for each step is in steps[] and sap_answer[]."
|
|
699
|
+
: `SAP reported every step released, but the request reads status ${finalStatus || 'unknown'}. Check it in SE09.`;
|
|
700
|
+
break;
|
|
701
|
+
case 'stopped': {
|
|
702
|
+
const last = r.steps[r.steps.length - 1];
|
|
703
|
+
note = `${r.error ?? ''} Nothing was sent after that step.`.trim();
|
|
704
|
+
if (last && IGNORE_VERDICTS.has(last.verdict)) {
|
|
705
|
+
note += ' CSPeach does not release with anything ignored. Release it in SE09 or Eclipse if that is what you want.';
|
|
706
|
+
}
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
case 'refused':
|
|
710
|
+
note = r.refusal ?? 'Nothing was sent.';
|
|
711
|
+
break;
|
|
712
|
+
default:
|
|
713
|
+
note = r.error ?? `The release ended with status ${r.status}.`;
|
|
714
|
+
}
|
|
715
|
+
const detail = r.refusal ?? r.error;
|
|
716
|
+
const stopStep = r.status === 'stopped' ? r.steps[r.steps.length - 1] : undefined;
|
|
717
|
+
const envelope = {
|
|
718
|
+
released,
|
|
719
|
+
status: r.status,
|
|
720
|
+
// Near the top so a spilled preview (and the audit row read from it) keeps the stopping step.
|
|
721
|
+
...(stopStep ? { stopped_at: stopStep.number, stop_verdict: stopStep.verdict } : {}),
|
|
722
|
+
transport_number: r.transportNumber,
|
|
723
|
+
final_status: finalStatus,
|
|
724
|
+
not_sent: r.notSent ?? [],
|
|
725
|
+
note,
|
|
726
|
+
...(detail ? { error_detail: detail } : {}),
|
|
727
|
+
...(warning ? { warning } : {}),
|
|
728
|
+
not_yet_proven: NOT_YET_PROVEN,
|
|
729
|
+
steps: r.steps.map(({ rawReports: _raw, ...s }) => s),
|
|
730
|
+
sap_answer: r.steps.map((s) => ({ step: s.number, raw: s.rawReports ?? '' })),
|
|
731
|
+
};
|
|
732
|
+
// Pretty-printed with the facts first: a spilled preview keeps the verdict
|
|
733
|
+
// lines (a one-line JSON would be cut at the preview's line limit).
|
|
734
|
+
const failed = r.status === 'stopped' || r.status === 'error' || r.status === 'refused';
|
|
735
|
+
return spillToolResult({ content: JSON.stringify(envelope, null, 2), ...(failed ? { is_error: true } : {}) }, ctx, 'report', { label: `release ${r.transportNumber}` });
|
|
502
736
|
},
|
|
503
|
-
handler: async (args) => ({
|
|
504
|
-
content: JSON.stringify({
|
|
505
|
-
error: 'release_unavailable',
|
|
506
|
-
transport: args.transport,
|
|
507
|
-
message: RELEASE_UNAVAILABLE,
|
|
508
|
-
}),
|
|
509
|
-
is_error: true,
|
|
510
|
-
}),
|
|
511
737
|
});
|
package/dist/tools/write-mode.js
CHANGED
|
@@ -23,6 +23,8 @@ export const WRITE_TOOLS = new Set([
|
|
|
23
23
|
'sap_transport_release',
|
|
24
24
|
// Final review I2 — SAP's RAP generator creates the skeleton objects.
|
|
25
25
|
'sap_generate_objects',
|
|
26
|
+
// Part 3 — a run executes code on the SAP system; advisory mode never runs it.
|
|
27
|
+
'sap_run',
|
|
26
28
|
]);
|
|
27
29
|
export function isWriteTool(name) {
|
|
28
30
|
return WRITE_TOOLS.has(name);
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useRef, useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
4
4
|
import { riskReasonText, shownRiskReasons } from '../approvals/risk-floor.js';
|
|
5
|
-
import { verbLine, viewAffordance, toSourceLines, affectsLine } from '../approvals/op-labels.js';
|
|
5
|
+
import { verbLine, viewAffordance, toSourceLines, affectsLine, releaseLines } from '../approvals/op-labels.js';
|
|
6
6
|
// BUG-1 (2026-07-05 live smoke): the interactive approval modal mounted
|
|
7
7
|
// DEAD — every keypress ignored — when it appeared after a guarded
|
|
8
8
|
// /abap-plan turn. Root cause is identical to the one AskCard
|
|
@@ -39,10 +39,18 @@ export function riskRows(effectiveRisk, width) {
|
|
|
39
39
|
* decision input too: its "Affects:" text wraps onto up to this many rows.
|
|
40
40
|
*/
|
|
41
41
|
export const AFFECTS_MAX_ROWS = 2;
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* The rows under the verb line: "Affects: …" (none when the change names no
|
|
44
|
+
* process), then — Part 3 D12 — a release card's fact lines, each wrapping
|
|
45
|
+
* onto up to AFFECTS_MAX_ROWS rows. Any other change: exactly as before.
|
|
46
|
+
*/
|
|
43
47
|
export function affectsRows(change, width) {
|
|
48
|
+
const cols = Math.max(1, width - MODAL_CHROME_COLS);
|
|
44
49
|
const line = affectsLine(change);
|
|
45
|
-
return
|
|
50
|
+
return [
|
|
51
|
+
...(line ? wrapRows(line, cols, AFFECTS_MAX_ROWS) : []),
|
|
52
|
+
...releaseLines(change).flatMap((l) => wrapRows(l, cols, AFFECTS_MAX_ROWS)),
|
|
53
|
+
];
|
|
46
54
|
}
|
|
47
55
|
/**
|
|
48
56
|
* The collapsed card's rendered height at `width` (the live-area budget; the
|
|
@@ -20,6 +20,7 @@ const DEFAULT_STATE = {
|
|
|
20
20
|
creditsMode: false, creditsSessionTotal: 0, creditsBalance: null,
|
|
21
21
|
creditsUnitLabel: 'credits', creditsPending: false,
|
|
22
22
|
standalone: false,
|
|
23
|
+
chatLane: false,
|
|
23
24
|
};
|
|
24
25
|
/**
|
|
25
26
|
* Rate limiter: calls `fn` at most once every `minIntervalMs`. Trailing-only:
|
|
@@ -14,7 +14,7 @@ const MUTATING_TOOLS = new Set([
|
|
|
14
14
|
'sap_set_source', 'sap_update_method', 'sap_delete_object', 'sap_activate',
|
|
15
15
|
'sap_create_object', 'sap_transport_release', 'sap_message_maintain',
|
|
16
16
|
'sap_number_range_intervals', 'sap_service_binding_publish',
|
|
17
|
-
'sap_service_binding_unpublish',
|
|
17
|
+
'sap_service_binding_unpublish', 'sap_run',
|
|
18
18
|
]);
|
|
19
19
|
export function toTimelineEntry(call) {
|
|
20
20
|
// args shape depends on the tool; extract defensively.
|
|
@@ -178,6 +178,7 @@ export function toCardQuestions(request) {
|
|
|
178
178
|
options: freeText ? [] : request.choices,
|
|
179
179
|
otherRow: false,
|
|
180
180
|
defaultValue: request.defaultValue,
|
|
181
|
+
cursorValue: request.cursorValue,
|
|
181
182
|
keys: request.keys,
|
|
182
183
|
}];
|
|
183
184
|
}
|
|
@@ -323,7 +324,7 @@ export function AskCard({ request, width, showDescriptions, maxRows, onResolveV1
|
|
|
323
324
|
// Focus slots: 0..n-1 = questions; n = the Review step (2+ questions,
|
|
324
325
|
// reachable only once every question has an answer).
|
|
325
326
|
const [focus, setFocus] = useState(0);
|
|
326
|
-
const [cursors, setCursors] = useState(() => questions.map((qq) => Math.max(0, qq.options.findIndex((o) => o.value === qq.defaultValue))));
|
|
327
|
+
const [cursors, setCursors] = useState(() => questions.map((qq) => Math.max(0, qq.options.findIndex((o) => o.value === (qq.defaultValue ?? qq.cursorValue)))));
|
|
327
328
|
const [ticks, setTicks] = useState(() => questions.map(() => new Set()));
|
|
328
329
|
const [drafts, setDrafts] = useState(() => questions.map(() => ''));
|
|
329
330
|
const [answers, setAnswers] = useState(() => questions.map(() => null));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cspeach/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.46",
|
|
4
4
|
"description": "CSPeach — AI-assisted ABAP development CLI. Direct SAP access, signed skills, safety gates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abap",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@anthropic-ai/sdk": "^0.90.0",
|
|
55
55
|
"@azure/msal-node": "6.0.1",
|
|
56
56
|
"@cspeach/register-core": "0.4.0",
|
|
57
|
-
"@cspeach/sap-client": "0.1.
|
|
57
|
+
"@cspeach/sap-client": "0.1.29",
|
|
58
58
|
"@iarna/toml": "^2.2.5",
|
|
59
59
|
"@inkjs/ui": "^2.0.0",
|
|
60
60
|
"@inquirer/prompts": "^7.0.0",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"clipboardy": "^5.3.1",
|
|
75
75
|
"cross-spawn": "^7.0.6",
|
|
76
76
|
"diff": "^9.0.0",
|
|
77
|
+
"docx": "^9.0.2",
|
|
77
78
|
"ejs": "3.1.10",
|
|
78
79
|
"fuse.js": "^7.0.0",
|
|
79
80
|
"ink": "^5.0.0",
|
|
@@ -104,7 +105,6 @@
|
|
|
104
105
|
"@types/turndown": "^5.0.5",
|
|
105
106
|
"@ui5/builder": "4.3.2",
|
|
106
107
|
"@ui5/fs": "4.0.6",
|
|
107
|
-
"docx": "^9.0.2",
|
|
108
108
|
"husky": "^9.1.7",
|
|
109
109
|
"ink-testing-library": "^4.0.0",
|
|
110
110
|
"pdf-lib": "^1.17.1",
|