@cspeach/cli 1.1.29 → 1.1.31
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/.cspeach/project.json +4 -0
- package/README.md +11 -1
- package/dist/agent/loop.js +127 -11
- package/dist/auth/auth-file.js +2 -2
- package/dist/cli.js +39 -13
- package/dist/commands/config-set.js +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/logout.js +1 -1
- package/dist/commands/plan-continue.js +1 -1
- package/dist/commands/plan-gate.js +8 -1
- package/dist/commands/plan-resume.js +24 -18
- package/dist/commands/plan-startup.js +49 -6
- package/dist/commands/team-discover.js +195 -0
- package/dist/commands/team-plan.js +585 -0
- package/dist/commands/team.js +652 -46
- package/dist/commands/whoami.js +1 -1
- package/dist/config/paths.js +15 -1
- package/dist/journey/chain.js +728 -0
- package/dist/journey/clean-core.js +213 -0
- package/dist/journey/flow.js +166 -0
- package/dist/journey/run.js +435 -0
- package/dist/journey/session-deps.js +77 -0
- package/dist/journey/trigger.js +153 -0
- package/dist/lane/app-chain.js +9 -7
- package/dist/lane/chain-token.js +9 -5
- package/dist/lane/roots.js +97 -41
- package/dist/one-shot.js +21 -1
- package/dist/projects/clean-core-confirm.js +192 -0
- package/dist/projects/detail-path.js +9 -4
- package/dist/projects/extract-clean-core.js +82 -0
- package/dist/projects/migration.js +4 -105
- package/dist/projects/plan-lane.js +67 -17
- package/dist/projects/plan-sources.js +13 -7
- package/dist/projects/project-folder.js +131 -0
- package/dist/projects/promote-command.js +39 -0
- package/dist/projects/save-command.js +115 -15
- package/dist/projects/saved-lines.js +17 -3
- package/dist/projects/sort-command.js +499 -0
- package/dist/projects/sort-core.js +248 -0
- package/dist/projects/validate.js +2 -1
- package/dist/projects/workspace-retire.js +48 -0
- package/dist/projects/workspace.js +182 -120
- package/dist/register/atc-file.js +9 -1
- package/dist/register/discover.js +901 -0
- package/dist/register/findings.js +82 -0
- package/dist/register/measure.js +60 -1
- package/dist/register/offer.js +125 -0
- package/dist/register/picture.js +25 -0
- package/dist/register/plan-import.js +325 -0
- package/dist/register/records.js +57 -0
- package/dist/register/status-file/estate-parts.js +168 -0
- package/dist/register/status-file/plan-parts.js +68 -0
- package/dist/register/status-file/progress-chart.js +110 -0
- package/dist/register/status-file/register-map.js +221 -0
- package/dist/register/status-file/register-model.js +341 -0
- package/dist/register/status-file/register-parts.js +555 -0
- package/dist/register/status-file/register-status-file.js +189 -0
- package/dist/register/status-file/team-css.js +5 -0
- package/dist/register/status-file-sync.js +22 -0
- package/dist/register/status-html.js +19 -0
- package/dist/register/store.js +40 -2
- package/dist/register/workbooks.js +124 -0
- package/dist/register/write-whole.js +22 -0
- package/dist/register/xlsx.js +127 -0
- package/dist/register/xref.js +117 -0
- package/dist/renderer/startup-lines.js +11 -0
- package/dist/renderer/tool-labels.js +3 -1
- package/dist/renderer/tool-widget.js +15 -1
- package/dist/repl/at-completer.js +6 -2
- package/dist/repl/at-picker.js +1 -1
- package/dist/repl/journey-entry.js +25 -0
- package/dist/repl.js +158 -39
- package/dist/session/backfill.js +342 -0
- package/dist/session/project-match.js +63 -0
- package/dist/session/resume.js +72 -24
- package/dist/session/schema.js +3 -1
- package/dist/session/store.js +96 -51
- package/dist/skill-catalog.js +2 -2
- package/dist/skills/bundled-skills.js +45 -45
- package/dist/tools/_filesystem-shared.js +190 -21
- package/dist/tools/_team-work-path.js +133 -0
- package/dist/tools/approval.js +7 -1
- package/dist/tools/filesystem/extract-xlsx.js +27 -3
- package/dist/tools/filesystem/file-edit.js +12 -4
- package/dist/tools/filesystem/file-read.js +8 -4
- package/dist/tools/filesystem/file-write.js +12 -4
- package/dist/tools/filesystem/glob.js +33 -8
- package/dist/tools/filesystem/grep.js +9 -4
- package/dist/tools/filesystem/read-document.js +6 -3
- package/dist/tools/project/cca_merge.js +8 -2
- package/dist/tools/project/team_estate.js +91 -0
- package/dist/tools/project/team_findings.js +145 -0
- package/dist/tools/project/upgrade_merge_progress.js +8 -2
- package/dist/tools/sap-read.js +21 -4
- package/dist/tools/sap-write.js +203 -5
- package/dist/tools/subagent/read_agent.js +3 -0
- package/dist/tools/tool-loading.js +2 -2
- package/dist/tools/transport-fit-check.js +117 -0
- package/dist/tools/transport-fit.js +15 -7
- package/dist/tools/transport.js +57 -2
- package/dist/ui/app.js +2 -2
- package/dist/ui/approval-modal.js +74 -6
- package/dist/ui/widgets/confirm-card.js +68 -5
- package/dist/util/open-in-browser.js +143 -0
- package/package.json +120 -120
package/dist/tools/sap-write.js
CHANGED
|
@@ -34,7 +34,10 @@ import { getLook } from '../renderer/look.js';
|
|
|
34
34
|
import { getEffectiveBodyWidth } from '../renderer/tty.js';
|
|
35
35
|
import { verifySyntax, verifyActive } from './verify.js';
|
|
36
36
|
import { hasRecordedSyntaxErrorsByName, clearSyntaxErrorsAfterActivation } from './syntax-state.js';
|
|
37
|
+
import { teamFixOffers, cspeachWritesThisSession } from '../register/offer.js';
|
|
38
|
+
import { isJourneyActive } from '../journey/flow.js';
|
|
37
39
|
import { appendPending, finalizeToolCall } from '../session/pending.js';
|
|
40
|
+
import { checkTransportFit, reusedRequestRefusal } from './transport-fit-check.js';
|
|
38
41
|
import { auditLog } from '@cspeach/sap-client';
|
|
39
42
|
import { ERR } from '../errors/codes.js';
|
|
40
43
|
import { interceptUpdateMethod, CancelledByUserError, } from './update-method-intercept.js';
|
|
@@ -42,7 +45,8 @@ import { resolveWriteTransport } from './transport-resolution.js';
|
|
|
42
45
|
// F-L2-1: statusline-only wire — records the transport a write ACTUALLY
|
|
43
46
|
// stamped so the `tr` segment stops reading `none` for plan-supplied
|
|
44
47
|
// transports. Never touches the session transport pointer.
|
|
45
|
-
import { noteWriteTransport } from '../repl/current-transport.js';
|
|
48
|
+
import { noteWriteTransport, setCurrentTransport } from '../repl/current-transport.js';
|
|
49
|
+
import { markTransportCreatedThisSession } from './transport.js';
|
|
46
50
|
import { presentSafetyConfirmation } from '../repl/safety-confirm.js';
|
|
47
51
|
import { isSafetyConfirmEnabled } from '../repl/safety-mode-state.js';
|
|
48
52
|
// ── Transcript visibility helpers (D16-display + tour slide-7, 2026-06-12) ──
|
|
@@ -118,6 +122,21 @@ function chainActivation(w) {
|
|
|
118
122
|
return { state: 'active' };
|
|
119
123
|
return w.stillInactive ? { state: 'still_inactive' } : { state: 'unverified' };
|
|
120
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* 1.1.31 — the write itself activated and verified an object of the joined team
|
|
127
|
+
* project: CSPeach wrote it, so the fix is recorded for the developer (rehearsal
|
|
128
|
+
* 2026-10-07: the refactor's write activated the class; no separate sap_activate
|
|
129
|
+
* ran, so the hook in sap_activate alone never fired).
|
|
130
|
+
*/
|
|
131
|
+
/** Guided clean-core journey on: the fix lines in solo words (register/offer.ts). */
|
|
132
|
+
const journeyWording = (ctx) => (isJourneyActive(ctx.session) ? { wording: 'journey' } : {});
|
|
133
|
+
function recordOwnFixAfterWrite(ctx, type, name, transport, w) {
|
|
134
|
+
if (!w?.success || !w.verified)
|
|
135
|
+
return;
|
|
136
|
+
const written = new Map([[String(name).trim().toUpperCase(), String(transport ?? '').trim().toUpperCase()]]);
|
|
137
|
+
for (const line of teamFixOffers([{ type, name }], { written, ...journeyWording(ctx) }))
|
|
138
|
+
ctx.chunkEmitter?.emit('info', line);
|
|
139
|
+
}
|
|
121
140
|
function activatedByWrite(w) {
|
|
122
141
|
if (!w)
|
|
123
142
|
return {};
|
|
@@ -316,6 +335,7 @@ registerTool({
|
|
|
316
335
|
newSource: oldSource !== undefined ? args.source : undefined,
|
|
317
336
|
},
|
|
318
337
|
});
|
|
338
|
+
recordOwnFixAfterWrite(ctx, args.type, args.name, resolved.owning ?? resolved.transport, writeActivation);
|
|
319
339
|
return {
|
|
320
340
|
content: JSON.stringify({
|
|
321
341
|
written: true,
|
|
@@ -525,6 +545,7 @@ registerTool({
|
|
|
525
545
|
activation: chainActivation(writeActivation),
|
|
526
546
|
card: { object: args.class_name, what: `method ${args.method_name}`, transport: resolved.transport },
|
|
527
547
|
});
|
|
548
|
+
recordOwnFixAfterWrite(ctx, 'CLAS', args.class_name, resolved.owning ?? resolved.transport, writeActivation);
|
|
528
549
|
return {
|
|
529
550
|
content: JSON.stringify({
|
|
530
551
|
updated: true,
|
|
@@ -672,7 +693,14 @@ registerTool({
|
|
|
672
693
|
name: 'sap_create_object',
|
|
673
694
|
description: 'Create a new ABAP object shell. '
|
|
674
695
|
+ 'Requires approval_id. No snapshot needed (new object). '
|
|
675
|
-
+ 'Write source separately via sap_set_source, then call sap_activate.'
|
|
696
|
+
+ 'Write source separately via sap_set_source, then call sap_activate. '
|
|
697
|
+
+ 'NEW PACKAGE (type DEVC): do NOT call sap_transport_create first. Without `transport`, '
|
|
698
|
+
+ 'this tool asks SAP what the new package needs and creates its first transport itself, '
|
|
699
|
+
+ 'then the package — ONE approval card covers both; the card text reads '
|
|
700
|
+
+ "\"Create package NAME and its transport '<text>'\" (<text> = the description argument, ≤60 ASCII chars) and the approval object is the package name. "
|
|
701
|
+
+ 'Optional DEVC params: description, softwareComponent (default HOME), transportLayer '
|
|
702
|
+
+ '(default: the system\'s layer), applicationComponent, superPackage, responsible '
|
|
703
|
+
+ '(default: you, upper case). A $-local package needs no transport.',
|
|
676
704
|
isMutating: true,
|
|
677
705
|
requiresSap: true,
|
|
678
706
|
input_schema: {
|
|
@@ -689,6 +717,12 @@ registerTool({
|
|
|
689
717
|
serviceDefinition: { type: 'string', description: 'SRVB ONLY — REQUIRED when type=SRVB: name of the activated service definition (SRVD) to bind, e.g. ZUI_MYSERVICE.' },
|
|
690
718
|
bindingType: { type: 'string', description: 'SRVB ONLY (optional): binding protocol. Default ODATA.' },
|
|
691
719
|
bindingVersion: { type: 'string', description: 'SRVB ONLY (optional): binding version. Default V4 (OData V4 UI). Use V2 for OData V2.' },
|
|
720
|
+
// DEVC-only (§10a): package attributes; ignored for other types.
|
|
721
|
+
softwareComponent: { type: 'string', description: 'DEVC ONLY (optional): software component. Default HOME.' },
|
|
722
|
+
transportLayer: { type: 'string', description: 'DEVC ONLY (optional): transport layer. When omitted: used if the SAP transport check names one; otherwise the create is refused and the layer must be named.' },
|
|
723
|
+
applicationComponent: { type: 'string', description: 'DEVC ONLY (optional): application component, e.g. FI-AR.' },
|
|
724
|
+
superPackage: { type: 'string', description: 'DEVC ONLY (optional): parent package.' },
|
|
725
|
+
responsible: { type: 'string', description: 'DEVC ONLY (optional): responsible user. Default: the logged-on user (upper case).' },
|
|
692
726
|
},
|
|
693
727
|
required: ['name', 'type', 'package', 'description', 'approval_id'],
|
|
694
728
|
},
|
|
@@ -721,7 +755,17 @@ registerTool({
|
|
|
721
755
|
}
|
|
722
756
|
}
|
|
723
757
|
// 3-tier fallback: explicit arg → session current-transport → undefined
|
|
724
|
-
|
|
758
|
+
let effectiveTransport = args.transport ?? ctx.currentTransport?.get() ?? undefined;
|
|
759
|
+
// ── 1.1.31: does the chosen request fit the package? (see transport-fit-check.ts)
|
|
760
|
+
// A package create decides its own transport below (§10a), so only objects.
|
|
761
|
+
const isDevc = String(args.type ?? '').toUpperCase().split('/')[0].trim() === 'DEVC';
|
|
762
|
+
if (!isDevc) {
|
|
763
|
+
const fit = await checkTransportFit(ctx.adt, {
|
|
764
|
+
type: args.type, name: args.name, pkg: args.package, transport: effectiveTransport,
|
|
765
|
+
});
|
|
766
|
+
if (!fit.ok)
|
|
767
|
+
return { content: JSON.stringify(fit.refusal), is_error: true };
|
|
768
|
+
}
|
|
725
769
|
// ── WAL: record pending entry ────────────────────────────────────────────
|
|
726
770
|
await appendPending(ctx.session, {
|
|
727
771
|
tool_use_id: toolUseId,
|
|
@@ -729,6 +773,48 @@ registerTool({
|
|
|
729
773
|
args: { name: args.name, type: args.type, package: args.package },
|
|
730
774
|
started_at: new Date().toISOString(),
|
|
731
775
|
});
|
|
776
|
+
// ── §10a: a new package and its first transport, under this ONE approval ─
|
|
777
|
+
let devcExtras = {};
|
|
778
|
+
let devcTransportCreated;
|
|
779
|
+
if (isDevc) {
|
|
780
|
+
let flow;
|
|
781
|
+
try {
|
|
782
|
+
flow = await prepareNewPackage(args, ctx);
|
|
783
|
+
}
|
|
784
|
+
catch (err) {
|
|
785
|
+
// A throw (network, auth, …) must still close the pending entry and
|
|
786
|
+
// reach the person in plain words, never as a raw exception.
|
|
787
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
788
|
+
const answered = typeof err?.status === 'number';
|
|
789
|
+
const message = answered
|
|
790
|
+
? `SAP refused to create the transport: ${msg}`
|
|
791
|
+
: `Could not reach SAP to create the transport: ${msg}`;
|
|
792
|
+
await finalizeToolCall(ctx.session, toolUseId, message, true, Date.now() - startedAt);
|
|
793
|
+
return {
|
|
794
|
+
content: JSON.stringify({ error: 'transport_create_failed', message, next: 'Next: check the connection to SAP and try again.' }),
|
|
795
|
+
is_error: true,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
if ('refused' in flow) {
|
|
799
|
+
await finalizeToolCall(ctx.session, toolUseId, flow.refused.message, true, Date.now() - startedAt);
|
|
800
|
+
return { content: JSON.stringify(flow.refused), is_error: true };
|
|
801
|
+
}
|
|
802
|
+
effectiveTransport = flow.corrNr;
|
|
803
|
+
devcTransportCreated = flow.created;
|
|
804
|
+
if (flow.created) {
|
|
805
|
+
// Same auto-attach as sap_transport_create: later creates in the new
|
|
806
|
+
// package pick this request up through the 3-tier fallback.
|
|
807
|
+
setCurrentTransport(flow.created);
|
|
808
|
+
markTransportCreatedThisSession(flow.created);
|
|
809
|
+
}
|
|
810
|
+
devcExtras = {
|
|
811
|
+
softwareComponent: args.softwareComponent,
|
|
812
|
+
transportLayer: flow.layer,
|
|
813
|
+
applicationComponent: args.applicationComponent,
|
|
814
|
+
superPackage: args.superPackage ? String(args.superPackage).toUpperCase() : undefined,
|
|
815
|
+
responsible: args.responsible,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
732
818
|
// ── Create ───────────────────────────────────────────────────────────────
|
|
733
819
|
// createObject(objtype, name, packageName, description, transport?, extras?)
|
|
734
820
|
let result;
|
|
@@ -737,6 +823,7 @@ registerTool({
|
|
|
737
823
|
serviceDefinition: args.serviceDefinition,
|
|
738
824
|
bindingType: args.bindingType,
|
|
739
825
|
bindingVersion: args.bindingVersion,
|
|
826
|
+
...devcExtras,
|
|
740
827
|
});
|
|
741
828
|
await auditLog('createObject', args.type, args.name, 'success', Date.now() - startedAt);
|
|
742
829
|
}
|
|
@@ -744,14 +831,14 @@ registerTool({
|
|
|
744
831
|
const errStr = String(err);
|
|
745
832
|
await finalizeToolCall(ctx.session, toolUseId, errStr, true, Date.now() - startedAt);
|
|
746
833
|
return {
|
|
747
|
-
content: JSON.stringify({ error: 'create_failed', detail: errStr }),
|
|
834
|
+
content: JSON.stringify({ error: 'create_failed', detail: errStr, ...orphanRequest(devcTransportCreated) }),
|
|
748
835
|
is_error: true,
|
|
749
836
|
};
|
|
750
837
|
}
|
|
751
838
|
if (result.status !== 'created') {
|
|
752
839
|
await finalizeToolCall(ctx.session, toolUseId, JSON.stringify(result), true, Date.now() - startedAt);
|
|
753
840
|
return {
|
|
754
|
-
content: JSON.stringify({ error: 'create_failed', detail: result.error }),
|
|
841
|
+
content: JSON.stringify({ error: 'create_failed', detail: result.error, ...orphanRequest(devcTransportCreated) }),
|
|
755
842
|
is_error: true,
|
|
756
843
|
};
|
|
757
844
|
}
|
|
@@ -759,6 +846,22 @@ registerTool({
|
|
|
759
846
|
// the 3-tier effectiveTransport above is what SAP stamped.
|
|
760
847
|
noteWriteTransport(effectiveTransport);
|
|
761
848
|
await finalizeToolCall(ctx.session, toolUseId, JSON.stringify(result), false, Date.now() - startedAt);
|
|
849
|
+
if (isDevc) {
|
|
850
|
+
const pkgName = result.objectName || String(args.name).toUpperCase();
|
|
851
|
+
return {
|
|
852
|
+
content: JSON.stringify({
|
|
853
|
+
created: true,
|
|
854
|
+
object: pkgName,
|
|
855
|
+
type: 'DEVC',
|
|
856
|
+
...(effectiveTransport ? { transport: effectiveTransport } : {}),
|
|
857
|
+
...(devcTransportCreated ? { transport_created: true } : {}),
|
|
858
|
+
message: effectiveTransport
|
|
859
|
+
? `Created package ${pkgName} on request ${effectiveTransport}.`
|
|
860
|
+
: `Created package ${pkgName} (no transport needed).`,
|
|
861
|
+
next_step: 'create_objects_in_the_package_on_this_transport',
|
|
862
|
+
}),
|
|
863
|
+
};
|
|
864
|
+
}
|
|
762
865
|
return {
|
|
763
866
|
content: JSON.stringify({
|
|
764
867
|
created: true,
|
|
@@ -770,6 +873,90 @@ registerTool({
|
|
|
770
873
|
};
|
|
771
874
|
},
|
|
772
875
|
});
|
|
876
|
+
/**
|
|
877
|
+
* §10a — when the package create fails after the flow already made a request,
|
|
878
|
+
* say so: the request exists, empty, and can be used on the next try.
|
|
879
|
+
*/
|
|
880
|
+
function orphanRequest(created) {
|
|
881
|
+
if (!created)
|
|
882
|
+
return {};
|
|
883
|
+
return {
|
|
884
|
+
transport_created: created,
|
|
885
|
+
message: `Request ${created} was created first and is still there, empty. `
|
|
886
|
+
+ 'It is now the session transport, so the next try uses it; or ask your Basis team to delete it.',
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* §10a — decide the transport for a brand-new package the way ADT does:
|
|
891
|
+
* 1. `$`-local package → no transport;
|
|
892
|
+
* 2. explicit `transport` → that one;
|
|
893
|
+
* 3. otherwise POST transportchecks with the package's own entry (a layer only
|
|
894
|
+
* when the caller named one); a refusal stops here. The layer is then the
|
|
895
|
+
* caller's ?? the one CTS resolved (PDEVCLASS); neither → refused in plain
|
|
896
|
+
* words, nothing created (never a value-help guess). No recording needed → no transport;
|
|
897
|
+
* else create the request from the package entry (REF = package URI,
|
|
898
|
+
* layer on the URL).
|
|
899
|
+
* Runs under the approval the caller already verified — nothing is asked twice.
|
|
900
|
+
*/
|
|
901
|
+
async function prepareNewPackage(args, ctx) {
|
|
902
|
+
const name = String(args.name ?? '').trim().toUpperCase();
|
|
903
|
+
const explicitLayer = args.transportLayer ? String(args.transportLayer) : undefined;
|
|
904
|
+
if (name.startsWith('$'))
|
|
905
|
+
return { layer: explicitLayer };
|
|
906
|
+
if (args.transport)
|
|
907
|
+
return { corrNr: String(args.transport), layer: explicitLayer };
|
|
908
|
+
let layer = explicitLayer;
|
|
909
|
+
const uri = '/sap/bc/adt/packages/' + encodeURIComponent(name.toLowerCase());
|
|
910
|
+
const check = await ctx.adt.transportCheck({
|
|
911
|
+
pgmid: 'R3TR',
|
|
912
|
+
object: 'DEVC',
|
|
913
|
+
objectName: name,
|
|
914
|
+
devclass: name,
|
|
915
|
+
superPackage: args.superPackage ? String(args.superPackage).toUpperCase() : undefined,
|
|
916
|
+
operation: 'I',
|
|
917
|
+
uri,
|
|
918
|
+
transportLayer: layer,
|
|
919
|
+
});
|
|
920
|
+
if (!check.ok) {
|
|
921
|
+
return {
|
|
922
|
+
refused: {
|
|
923
|
+
error: 'transport_check_refused',
|
|
924
|
+
message: check.error ?? 'SAP refused the transport check with no message.',
|
|
925
|
+
},
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
// The layer is the caller's, or the one CTS resolved for this package
|
|
929
|
+
// (PDEVCLASS in the check's answer). Never a guess from the value help: it
|
|
930
|
+
// lists every layer on the box, and its order says nothing about this package.
|
|
931
|
+
layer = layer ?? (check.transportLayer || undefined);
|
|
932
|
+
if (!layer) {
|
|
933
|
+
return {
|
|
934
|
+
refused: {
|
|
935
|
+
error: 'transport_layer_unknown',
|
|
936
|
+
message: `SAP's transport check did not name a transport layer for package ${name}, so CSPeach did not create it.`,
|
|
937
|
+
next: 'Next: ask which transport layer the package belongs on (your Basis team knows), then ask again naming that transport layer.',
|
|
938
|
+
},
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
if (!check.requestRequired)
|
|
942
|
+
return { layer };
|
|
943
|
+
const text = args.description ? String(args.description) : `Clean core: ${name}`;
|
|
944
|
+
const tr = await ctx.adt.transportCreate(text, name, { ref: uri, transportLayer: layer });
|
|
945
|
+
if (tr.status === 'error' || !tr.transportNumber) {
|
|
946
|
+
return {
|
|
947
|
+
refused: {
|
|
948
|
+
error: 'transport_create_failed',
|
|
949
|
+
message: `SAP refused to create the transport: ${tr.error ?? 'no transport number came back'}`,
|
|
950
|
+
next: 'Next: check the package and its transport layer, or ask your Basis team.',
|
|
951
|
+
},
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
// 1.1.31 — SAP may hand back the user's newest open request instead of a
|
|
955
|
+
// new one; that request may be other work, so it is never used silently.
|
|
956
|
+
if (tr.status !== 'created')
|
|
957
|
+
return { refused: reusedRequestRefusal(tr) };
|
|
958
|
+
return { corrNr: tr.transportNumber, layer, created: tr.transportNumber };
|
|
959
|
+
}
|
|
773
960
|
// ── sap_set_class_include ─────────────────────────────────────────────────────
|
|
774
961
|
//
|
|
775
962
|
// Signature: setClassInclude(className, includeType, source, transport?)
|
|
@@ -1314,6 +1501,7 @@ registerTool({
|
|
|
1314
1501
|
// still inactive with syntax errors" incident.
|
|
1315
1502
|
const stillInactive = [];
|
|
1316
1503
|
const unverifiable = [];
|
|
1504
|
+
const verified = [];
|
|
1317
1505
|
for (const obj of args.objects) {
|
|
1318
1506
|
const v = await verifyActive(ctx.adt, obj.name, obj.type);
|
|
1319
1507
|
if (v.verifyError) {
|
|
@@ -1325,6 +1513,7 @@ registerTool({
|
|
|
1325
1513
|
else if (activationResults.find((r) => r.name === obj.name && r.type === obj.type)?.success) {
|
|
1326
1514
|
// Final review A M-2 — activated AND verified: the source compiles.
|
|
1327
1515
|
clearSyntaxErrorsAfterActivation(obj.name);
|
|
1516
|
+
verified.push({ name: obj.name, type: obj.type });
|
|
1328
1517
|
}
|
|
1329
1518
|
}
|
|
1330
1519
|
const overallSuccess = !anyError && stillInactive.length === 0 && unverifiable.length === 0;
|
|
@@ -1342,6 +1531,15 @@ registerTool({
|
|
|
1342
1531
|
// successful write op. Without this, Rule 8 counts a failed activate
|
|
1343
1532
|
// as if it had run, inflating the "batch in progress" count and
|
|
1344
1533
|
// confusing the user about how many real writes have happened.
|
|
1534
|
+
// 2026-10-06 — an object of the team project this laptop joined. 1.1.31:
|
|
1535
|
+
// when CSPeach itself wrote its source in this session, the fix is recorded
|
|
1536
|
+
// for the developer (only the next check proves it); otherwise one line
|
|
1537
|
+
// offers the claim.
|
|
1538
|
+
if (overallSuccess) {
|
|
1539
|
+
const written = cspeachWritesThisSession(ctx.session?.toolCalls);
|
|
1540
|
+
for (const line of teamFixOffers(verified, { written, ...journeyWording(ctx) }))
|
|
1541
|
+
ctx.chunkEmitter?.emit('info', line);
|
|
1542
|
+
}
|
|
1345
1543
|
return {
|
|
1346
1544
|
content: JSON.stringify({
|
|
1347
1545
|
success: overallSuccess,
|
|
@@ -78,6 +78,9 @@ export async function readAgentHandler(args, ctx) {
|
|
|
78
78
|
// The session model, never a cheaper one (owner decision 11); effort low.
|
|
79
79
|
const model = ctx.session.model;
|
|
80
80
|
const childSession = newSession(`reader-${Date.now()}-${Math.floor(Math.random() * 1e9).toString(16)}`, ctx.session.sap_system ?? null, READER_SKILL, model);
|
|
81
|
+
// Sessions/folders (Task 6) — a child keeps the parent's folder and id.
|
|
82
|
+
childSession.project_dir = ctx.session.project_dir ?? null;
|
|
83
|
+
childSession.project_id = ctx.session.project_id ?? null;
|
|
81
84
|
// Only what a read needs. No previewHook (no writes), no chunkEmitter /
|
|
82
85
|
// todoEmitter (the child never paints the parent UI), no pendingDispatch /
|
|
83
86
|
// currentTransport (the child never changes parent REPL state).
|
|
@@ -56,8 +56,8 @@ export const SKILL_STARTERS = Object.freeze({
|
|
|
56
56
|
// Both flag-gated (LOCAL_BUILD_TOOLS / STANDALONE_FILE_TOOLS): skipped when off.
|
|
57
57
|
'abap-app': ['app_lane_resolve', 'read_document'],
|
|
58
58
|
'abap-atc-fix': ['sap_atc_run'],
|
|
59
|
-
'abap-cca': ['sap_atc_run', 'sap_usage_references', 'sap_api_state'],
|
|
60
|
-
'abap-clean-core': ['sap_api_state', 'sap_atc_run', 'sap_usage_references'],
|
|
59
|
+
'abap-cca': ['team_estate', 'sap_atc_run', 'sap_usage_references', 'sap_api_state'],
|
|
60
|
+
'abap-clean-core': ['sap_api_state', 'sap_atc_run', 'sap_usage_references', 'team_findings'],
|
|
61
61
|
'abap-data-model': ['sap_create_domain', 'sap_create_data_element'],
|
|
62
62
|
'abap-document': ['sap_usage_references'],
|
|
63
63
|
'abap-handover': ['sap_usage_references'],
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 1.1.31 — does the chosen request fit the package a new object goes into?
|
|
3
|
+
*
|
|
4
|
+
* Live 2026-10-07 (S/4HANA): a package with no transport layer is local. A
|
|
5
|
+
* create in it on a transportable request (target CL5) failed with HTTP 403
|
|
6
|
+
* CTS_WBO_API 047 "Request S4HK903753 is not a local request". Eclipse avoids
|
|
7
|
+
* this by asking POST /sap/bc/adt/cts/transportchecks first: the answer says
|
|
8
|
+
* whether a request is needed and lists the open requests that fit.
|
|
9
|
+
*
|
|
10
|
+
* This asks the same check before sap_create_object (non-package) sends the
|
|
11
|
+
* create:
|
|
12
|
+
* - no request needed (local package) and one was chosen → refused. The
|
|
13
|
+
* transport is never dropped silently, and nothing falls back to a local
|
|
14
|
+
* request: the person decides.
|
|
15
|
+
* - a request is needed and SAP lists fitting ones, but the chosen request
|
|
16
|
+
* (or task) is not among them → refused, naming the ones SAP listed.
|
|
17
|
+
* Live 2026-10-07: a Z package with NO layer still answers "request
|
|
18
|
+
* required", and lists only LOCAL requests (S4HK903755 was listed for
|
|
19
|
+
* ZLOCAL, the transportable S4HK903753 was not). No layer in the answer +
|
|
20
|
+
* the chosen request not listed → the local-package wording, then the list.
|
|
21
|
+
* - SAP lists none → no verdict (it may list only the user's own requests);
|
|
22
|
+
* the create goes ahead and SAP decides.
|
|
23
|
+
* - the check itself fails (endpoint missing on an older system, an error
|
|
24
|
+
* answer, a throw) → no verdict, the create goes ahead as before. This keeps
|
|
25
|
+
* older systems working; the reason goes to a CSPEACH_DEBUG line only.
|
|
26
|
+
*/
|
|
27
|
+
import { toObjectUrl, localPackageMessage } from '@cspeach/sap-client';
|
|
28
|
+
/** At most this many fitting requests are named in a refusal. */
|
|
29
|
+
export const MAX_FITTING_SHOWN = 5;
|
|
30
|
+
function debug(msg) {
|
|
31
|
+
if (process.env['CSPEACH_DEBUG'] !== '1')
|
|
32
|
+
return;
|
|
33
|
+
process.stderr.write(`[transport-fit-check] ${msg}\n`);
|
|
34
|
+
}
|
|
35
|
+
export async function checkTransportFit(adt, o) {
|
|
36
|
+
const pkg = String(o.pkg ?? '').trim().toUpperCase();
|
|
37
|
+
const transport = String(o.transport ?? '').trim().toUpperCase();
|
|
38
|
+
// No request chosen: nothing to fit. A $ package is local by name.
|
|
39
|
+
if (!transport || !pkg || pkg.startsWith('$'))
|
|
40
|
+
return { ok: true };
|
|
41
|
+
if (typeof adt.transportCheck !== 'function')
|
|
42
|
+
return { ok: true };
|
|
43
|
+
const object = String(o.type ?? '').toUpperCase().split('/')[0].trim();
|
|
44
|
+
const objectName = String(o.name ?? '').trim().toUpperCase();
|
|
45
|
+
let uri;
|
|
46
|
+
try {
|
|
47
|
+
uri = toObjectUrl(object, objectName).toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
debug(`no object URI for ${object} ${objectName}, no fit check`);
|
|
51
|
+
return { ok: true };
|
|
52
|
+
}
|
|
53
|
+
let check;
|
|
54
|
+
try {
|
|
55
|
+
check = await adt.transportCheck({ pgmid: 'R3TR', object, objectName, devclass: pkg, operation: 'I', uri });
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
debug(`check failed, create goes ahead: ${(err instanceof Error ? err.message : String(err)).slice(0, 200)}`);
|
|
59
|
+
return { ok: true };
|
|
60
|
+
}
|
|
61
|
+
if (!check.ok) {
|
|
62
|
+
debug(`check gave no answer, create goes ahead: ${(check.error ?? '').slice(0, 200)}`);
|
|
63
|
+
return { ok: true };
|
|
64
|
+
}
|
|
65
|
+
if (!check.requestRequired) {
|
|
66
|
+
// Local only when SAP also names no layer for the package (PDEVCLASS):
|
|
67
|
+
// a "no recording" answer next to a layer is not understood, so no verdict.
|
|
68
|
+
if (check.transportLayer) {
|
|
69
|
+
debug(`no recording needed but package ${pkg} has layer ${check.transportLayer}, no verdict`);
|
|
70
|
+
return { ok: true };
|
|
71
|
+
}
|
|
72
|
+
return { ok: false, refusal: { error: 'package_local', package_local: true, message: localPackageMessage(pkg) } };
|
|
73
|
+
}
|
|
74
|
+
const requests = check.requests ?? [];
|
|
75
|
+
if (requests.length === 0) {
|
|
76
|
+
debug(`SAP listed no fitting requests for ${pkg}, no verdict`);
|
|
77
|
+
return { ok: true };
|
|
78
|
+
}
|
|
79
|
+
const fits = requests.some((r) => r.number.toUpperCase() === transport || (r.tasks ?? []).some((t) => t.toUpperCase() === transport));
|
|
80
|
+
if (fits)
|
|
81
|
+
return { ok: true };
|
|
82
|
+
const shown = requests.slice(0, MAX_FITTING_SHOWN).map((r) => ({ number: r.number, description: r.text ?? '' }));
|
|
83
|
+
const list = shown.map((r) => (r.description ? `${r.number} ('${r.description}')` : r.number)).join(', ');
|
|
84
|
+
if (!check.transportLayer) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
refusal: {
|
|
88
|
+
error: 'package_local',
|
|
89
|
+
package_local: true,
|
|
90
|
+
message: `${localPackageMessage(pkg)} Request ${transport} is not a local request. SAP lists these local requests for it: ${list}.`,
|
|
91
|
+
fitting_requests: shown,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
refusal: {
|
|
98
|
+
error: 'transport_does_not_fit',
|
|
99
|
+
message: `Request ${transport} does not fit package ${pkg}. SAP lists these open requests for it: ${list}. `
|
|
100
|
+
+ 'Name one of them as the transport for this create, or create a new request for the package.',
|
|
101
|
+
fitting_requests: shown,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 1.1.31 — SAP answered a create with an existing request (the user's newest
|
|
107
|
+
* open one, which may belong to other work). It is never used silently, and
|
|
108
|
+
* never advised to be released.
|
|
109
|
+
*/
|
|
110
|
+
export function reusedRequestRefusal(tr) {
|
|
111
|
+
return {
|
|
112
|
+
error: 'transport_reused',
|
|
113
|
+
message: `SAP handed back an existing request ${tr.transportNumber ?? '(no number)'} ('${tr.existingDescription ?? ''}') `
|
|
114
|
+
+ 'instead of creating a new one; CSPeach did not use it.',
|
|
115
|
+
next: 'Next: create the request for the package in SE09/SE10, or name an existing request that fits.',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* back empty). Strictly best-effort: a failed lookup leaves those fields as
|
|
29
29
|
* SAP returned them, gives no verdict, and writes a CSPEACH_DEBUG line.
|
|
30
30
|
*/
|
|
31
|
+
import { localPackageMessage } from '@cspeach/sap-client';
|
|
31
32
|
const TRKORR = /^[A-Z0-9]{1,20}$/;
|
|
32
33
|
const PACKAGE = /^[A-Z0-9_/$]{1,30}$/;
|
|
33
34
|
const REQUEST_SID = /^([A-Z0-9]{3})K\d+$/;
|
|
@@ -263,29 +264,33 @@ async function requestPackages(adt, requests) {
|
|
|
263
264
|
}
|
|
264
265
|
return out;
|
|
265
266
|
}
|
|
266
|
-
/**
|
|
267
|
+
/**
|
|
268
|
+
* The package's consolidation target, or undefined when unknown / ambiguous.
|
|
269
|
+
* 1.1.31 — `local`: the package row was read and has no transport layer, so
|
|
270
|
+
* SAP treats it as local (said to the person, not only in a debug line).
|
|
271
|
+
*/
|
|
267
272
|
async function packageTarget(adt, pkg, sid) {
|
|
268
273
|
const name = pkg.toUpperCase().trim();
|
|
269
274
|
if (!PACKAGE.test(name))
|
|
270
|
-
return
|
|
275
|
+
return {};
|
|
271
276
|
const dev = await rows(adt, `SELECT DEVCLASS, PDEVCLASS FROM TDEVC WHERE DEVCLASS = '${name}'`, 1, 'TDEVC');
|
|
272
277
|
const layer = (dev?.[0]?.['PDEVCLASS'] ?? '').trim();
|
|
273
278
|
if (!layer || !PACKAGE.test(layer)) {
|
|
274
279
|
if (dev)
|
|
275
280
|
debug(`package ${name} has no transport layer, no ranking`);
|
|
276
|
-
return
|
|
281
|
+
return dev && dev.length > 0 && !layer ? { local: true } : {};
|
|
277
282
|
}
|
|
278
283
|
let routes = await rows(adt, `SELECT VERSION, INTSYS, CONSYS FROM TCERELE WHERE TRANSLAYER = '${layer}'`, 50, 'TCERELE');
|
|
279
284
|
if (!routes || routes.length === 0) {
|
|
280
285
|
if (routes)
|
|
281
286
|
debug(`layer ${layer} has no consolidation route, no ranking`);
|
|
282
|
-
return
|
|
287
|
+
return {};
|
|
283
288
|
}
|
|
284
289
|
const active = await rows(adt, `SELECT VERSION FROM TCEVERS WHERE ACTIVE = 'A'`, 5, 'TCEVERS');
|
|
285
290
|
// Review M6 — the read failed: a route of an inactive version could pass as
|
|
286
291
|
// the target, so give no verdict (an empty answer still means "unfiltered").
|
|
287
292
|
if (active === null)
|
|
288
|
-
return
|
|
293
|
+
return {};
|
|
289
294
|
const versions = new Set((active ?? []).map((r) => (r['VERSION'] ?? '').trim()).filter(Boolean));
|
|
290
295
|
if (versions.size > 0)
|
|
291
296
|
routes = routes.filter((r) => versions.has((r['VERSION'] ?? '').trim()));
|
|
@@ -297,7 +302,7 @@ async function packageTarget(adt, pkg, sid) {
|
|
|
297
302
|
const targets = [...new Set(routes.map((r) => (r['CONSYS'] ?? '').trim()).filter((t) => t.length > 0))];
|
|
298
303
|
if (targets.length !== 1)
|
|
299
304
|
debug(`layer ${layer} routes to ${targets.length} targets, no ranking`);
|
|
300
|
-
return targets.length === 1 ? targets[0] :
|
|
305
|
+
return targets.length === 1 ? { target: targets[0] } : {};
|
|
301
306
|
}
|
|
302
307
|
/**
|
|
303
308
|
* E070-TRFUNCTION request codes (re-review 4). The list endpoint's
|
|
@@ -399,7 +404,10 @@ export async function enrichTransportList(rawAdt, transports, pkg) {
|
|
|
399
404
|
}
|
|
400
405
|
if (!pkg)
|
|
401
406
|
return { transports: out };
|
|
402
|
-
const
|
|
407
|
+
const resolved = await packageTarget(adt, pkg, systemId(numbers));
|
|
408
|
+
if (resolved.local)
|
|
409
|
+
return { transports: out, packageLocal: true, packageNote: localPackageMessage(pkg) };
|
|
410
|
+
const target = resolved.target;
|
|
403
411
|
if (!target)
|
|
404
412
|
return { transports: out };
|
|
405
413
|
// Review I4 — only a workbench request (TRFUNCTION 'K') can take a
|
package/dist/tools/transport.js
CHANGED
|
@@ -19,6 +19,7 @@ import { presentSafetyConfirmation } from '../repl/safety-confirm.js';
|
|
|
19
19
|
import { isSafetyConfirmEnabled } from '../repl/safety-mode-state.js';
|
|
20
20
|
import { setCurrentTransport, noteConfirmedTransport } from '../repl/current-transport.js';
|
|
21
21
|
import { enrichTransportList } from './transport-fit.js';
|
|
22
|
+
import { reusedRequestRefusal } from './transport-fit-check.js';
|
|
22
23
|
// ── Session ledger: transports CREATED by sap_transport_create this session ──
|
|
23
24
|
//
|
|
24
25
|
// Orphan-hint support (third junk transport this month, 2026-06-12 — D15/D20
|
|
@@ -100,7 +101,9 @@ registerTool({
|
|
|
100
101
|
type: 'string',
|
|
101
102
|
description: 'Target package (DEVCLASS) for the transport — typically the same package the '
|
|
102
103
|
+ 'objects will live in. Required by SAP\'s CreateCorrectionRequest endpoint. '
|
|
103
|
-
+ 'Empty / missing target_package on this endpoint commonly causes HTTP 400.'
|
|
104
|
+
+ 'Empty / missing target_package on this endpoint commonly causes HTTP 400. '
|
|
105
|
+
+ 'The package must already exist: for a NEW package use sap_create_object type DEVC, '
|
|
106
|
+
+ 'which creates the package and its first transport together.',
|
|
104
107
|
},
|
|
105
108
|
approval_id: {
|
|
106
109
|
type: 'string',
|
|
@@ -193,6 +196,38 @@ registerTool({
|
|
|
193
196
|
+ `(${err instanceof Error ? err.message : String(err)})`);
|
|
194
197
|
}
|
|
195
198
|
}
|
|
199
|
+
// ── Gate 0b: the target package must exist (§10a) ───────────────────────
|
|
200
|
+
// A request for a package that is not in the system has no transport
|
|
201
|
+
// target — SAP answers HTTP 500. A new package gets its first transport
|
|
202
|
+
// from sap_create_object DEVC (check → create from the package's own
|
|
203
|
+
// entry). Read-only lookup, before the approval gate like Gate 0.
|
|
204
|
+
// Best-effort: a failed lookup never blocks the create.
|
|
205
|
+
if (args.target_package) {
|
|
206
|
+
const pkg = String(args.target_package).trim().toUpperCase();
|
|
207
|
+
let missing = false;
|
|
208
|
+
try {
|
|
209
|
+
const isPkg = (hits) => hits.some((h) => (h.name ?? '').toUpperCase() === pkg && (!h.type || h.type.toUpperCase().startsWith('DEVC')));
|
|
210
|
+
let hits = await ctx.adt.searchObject(pkg, 'DEVC/K', 50);
|
|
211
|
+
// A type filter the system reads differently must never invent a
|
|
212
|
+
// "missing" package: an empty filtered answer is asked again unfiltered.
|
|
213
|
+
if (Array.isArray(hits) && hits.length === 0)
|
|
214
|
+
hits = await ctx.adt.searchObject(pkg, undefined, 50);
|
|
215
|
+
missing = Array.isArray(hits) && !isPkg(hits);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
missing = false;
|
|
219
|
+
}
|
|
220
|
+
if (missing) {
|
|
221
|
+
return {
|
|
222
|
+
content: JSON.stringify({
|
|
223
|
+
error: 'package_missing',
|
|
224
|
+
message: `Package ${pkg} does not exist yet. Create it with sap_create_object DEVC — `
|
|
225
|
+
+ 'CSPeach creates the package and its transport together.',
|
|
226
|
+
}),
|
|
227
|
+
is_error: true,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
196
231
|
// ── Gate 1: Approval ────────────────────────────────────────────────────
|
|
197
232
|
const verify = await verifyAndSpendApprovalId(args.approval_id, args.description, 'create');
|
|
198
233
|
if (!verify.ok) {
|
|
@@ -215,7 +250,24 @@ registerTool({
|
|
|
215
250
|
// transportCreate(description, targetPackage?)
|
|
216
251
|
try {
|
|
217
252
|
const result = await ctx.adt.transportCreate(args.description, args.target_package);
|
|
253
|
+
if (result.status === 'error') {
|
|
254
|
+
// §10a: SAP's own sentence in plain words, audited as the error it is.
|
|
255
|
+
await auditLog('transportCreate', 'CTS', result.transportNumber ?? 'UNKNOWN', 'error', Date.now() - startedAt);
|
|
256
|
+
return {
|
|
257
|
+
content: JSON.stringify({
|
|
258
|
+
error: 'transport_create_failed',
|
|
259
|
+
message: `SAP refused to create the transport: ${result.error ?? 'no message'}`,
|
|
260
|
+
next: 'Next: check the package and its transport layer, or ask your Basis team.',
|
|
261
|
+
}),
|
|
262
|
+
is_error: true,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
218
265
|
await auditLog('transportCreate', 'CTS', result.transportNumber ?? 'UNKNOWN', 'success', Date.now() - startedAt);
|
|
266
|
+
// 1.1.31 — a request SAP handed back is refused in plain words; the
|
|
267
|
+
// advice is to create one for the package or name one that fits.
|
|
268
|
+
if (result.status === 'reused') {
|
|
269
|
+
return { content: JSON.stringify(reusedRequestRefusal(result)), is_error: true };
|
|
270
|
+
}
|
|
219
271
|
if (result.status !== 'created' || !result.transportNumber) {
|
|
220
272
|
return {
|
|
221
273
|
content: JSON.stringify({ error: 'transport_create_failed', detail: result.error }),
|
|
@@ -371,7 +423,8 @@ registerTool({
|
|
|
371
423
|
+ 'recommend a request with contents "other_work" or "not_checked" for reuse; list a '
|
|
372
424
|
+ '"not_checked" one as "not checked", never as a clean fit. A description that seems to name '
|
|
373
425
|
+ 'other work may only lower a request in your answer; it never overrides `suggested: true`. '
|
|
374
|
-
+ 'When no request is suggested, offer to create a new transport.'
|
|
426
|
+
+ 'When no request is suggested, offer to create a new transport. '
|
|
427
|
+
+ '`package_local: true` means the package has no transport layer: show `package_note` to the person and offer no request.',
|
|
375
428
|
isMutating: false,
|
|
376
429
|
requiresSap: true,
|
|
377
430
|
input_schema: {
|
|
@@ -405,6 +458,8 @@ registerTool({
|
|
|
405
458
|
filter_status: result.filterStatus,
|
|
406
459
|
count: enriched.transports.length,
|
|
407
460
|
...(enriched.packageTarget ? { package: String(args.package).toUpperCase(), package_target: enriched.packageTarget } : {}),
|
|
461
|
+
// 1.1.31 — a package with no transport layer is local: say so plainly.
|
|
462
|
+
...(enriched.packageLocal ? { package: String(args.package).toUpperCase(), package_local: true, package_note: enriched.packageNote } : {}),
|
|
408
463
|
transports: enriched.transports,
|
|
409
464
|
// Round 5 item 3 — sap-client says when the list may be incomplete.
|
|
410
465
|
...(result.note ? { note: result.note } : {}),
|
package/dist/ui/app.js
CHANGED
|
@@ -581,7 +581,7 @@ export function App({ chunkEmitter, onSubmit, onSlashTrigger, onHistoryTrigger,
|
|
|
581
581
|
cardRows = estimateAskCardRows(askQuestionPending, true, uiWidth(columns));
|
|
582
582
|
// C1 — the confirm card's real height (facts + choices) for the spec §2 cap.
|
|
583
583
|
if (occupant === 'confirm' && confirmSlot.pending)
|
|
584
|
-
cardRows = estimateConfirmCardRows(confirmSlot.pending);
|
|
584
|
+
cardRows = estimateConfirmCardRows(confirmSlot.pending, uiWidth(columns), rows - 6);
|
|
585
585
|
const paletteRows = lineResolution ? PALETTE_ROWS : 0;
|
|
586
586
|
// P2 — the checklist's WANTED height (header + rows); liveBudget folds it.
|
|
587
587
|
const showChecklist = todos.length > 0 && !planClosed;
|
|
@@ -610,7 +610,7 @@ export function App({ chunkEmitter, onSubmit, onSlashTrigger, onHistoryTrigger,
|
|
|
610
610
|
}
|
|
611
611
|
else if (occupant === 'confirm' && confirmSlot.pending) {
|
|
612
612
|
const req = confirmSlot.pending;
|
|
613
|
-
slot = (_jsx(ConfirmCard, { request: req, width: uiWidth(columns), onResolve: (r) => confirmSlot.finish(req, r), onInterruptTurn: () => { onInterrupt?.(); } }, confirmSlot.seq));
|
|
613
|
+
slot = (_jsx(ConfirmCard, { request: req, width: uiWidth(columns), maxRows: rows - 6, onResolve: (r) => confirmSlot.finish(req, r), onInterruptTurn: () => { onInterrupt?.(); } }, confirmSlot.seq));
|
|
614
614
|
}
|
|
615
615
|
else if (occupant === 'ask' && askQuestionPending) {
|
|
616
616
|
const req = askQuestionPending;
|