arc-1 0.9.12 → 0.9.14
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/adt/abapgit.d.ts +10 -0
- package/dist/adt/abapgit.d.ts.map +1 -1
- package/dist/adt/abapgit.js +18 -1
- package/dist/adt/abapgit.js.map +1 -1
- package/dist/adt/client.d.ts +5 -0
- package/dist/adt/client.d.ts.map +1 -1
- package/dist/adt/client.js +16 -3
- package/dist/adt/client.js.map +1 -1
- package/dist/adt/ddic-xml.d.ts +9 -0
- package/dist/adt/ddic-xml.d.ts.map +1 -1
- package/dist/adt/ddic-xml.js +4 -1
- package/dist/adt/ddic-xml.js.map +1 -1
- package/dist/adt/transport.d.ts +10 -2
- package/dist/adt/transport.d.ts.map +1 -1
- package/dist/adt/transport.js +56 -4
- package/dist/adt/transport.js.map +1 -1
- package/dist/cache/inactive-list-cache.d.ts +4 -4
- package/dist/cache/inactive-list-cache.d.ts.map +1 -1
- package/dist/cache/inactive-list-cache.js +19 -12
- package/dist/cache/inactive-list-cache.js.map +1 -1
- package/dist/context/grep.d.ts +3 -1
- package/dist/context/grep.d.ts.map +1 -1
- package/dist/context/grep.js +83 -1
- package/dist/context/grep.js.map +1 -1
- package/dist/handlers/intent.d.ts.map +1 -1
- package/dist/handlers/intent.js +111 -29
- package/dist/handlers/intent.js.map +1 -1
- package/dist/handlers/schemas.d.ts +2 -1
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +7 -3
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +7 -1
- package/dist/handlers/tools.js.map +1 -1
- package/dist/server/server.d.ts +1 -1
- package/dist/server/server.js +1 -1
- package/dist/server/stateless-client-store.d.ts +6 -5
- package/dist/server/stateless-client-store.d.ts.map +1 -1
- package/dist/server/stateless-client-store.js +16 -6
- package/dist/server/stateless-client-store.js.map +1 -1
- package/package.json +1 -1
package/dist/handlers/intent.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* responses. Internal details (stack traces, SAP XML) are NOT
|
|
10
10
|
* leaked to the LLM — only user-friendly error messages.
|
|
11
11
|
*/
|
|
12
|
-
import { checkRepo as abapGitCheckRepo, createBranch as abapGitCreateBranch, createRepo as abapGitCreateRepo, getExternalInfo as abapGitGetExternalInfo, listRepos as abapGitListRepos, pullRepo as abapGitPullRepo, pushRepo as abapGitPushRepo, stageRepo as abapGitStageRepo, switchBranch as abapGitSwitchBranch, unlinkRepo as abapGitUnlinkRepo, } from '../adt/abapgit.js';
|
|
12
|
+
import { checkRepo as abapGitCheckRepo, createBranch as abapGitCreateBranch, createRepo as abapGitCreateRepo, enforceRepoPackageAllowed as abapGitEnforceRepoPackage, getExternalInfo as abapGitGetExternalInfo, listRepos as abapGitListRepos, pullRepo as abapGitPullRepo, pushRepo as abapGitPushRepo, stageRepo as abapGitStageRepo, switchBranch as abapGitSwitchBranch, unlinkRepo as abapGitUnlinkRepo, } from '../adt/abapgit.js';
|
|
13
13
|
import { buildSiblingExtensionFinding, classifyCdsImpact, deriveSiblingStem, isSiblingNameMatch, } from '../adt/cds-impact.js';
|
|
14
14
|
import { diffMethodSets, extractMethodNameFromClause, findSectionAnchor, insertMethodPair, moveMethodDefinition, removeMethodPair, spliceClassDefinition, spliceMethodSignature, } from '../adt/class-structure.js';
|
|
15
15
|
import { findDefinition, findInterfaceImplementersViaSeoMetaRel, findReferences, findWhereUsed, getCompletion, getWhereUsedScope, } from '../adt/codeintel.js';
|
|
@@ -916,9 +916,10 @@ export async function handleToolCall(client, config, toolName, args, authInfo, _
|
|
|
916
916
|
return requestContext.run({ requestId: reqId, user, tool: toolName }, async () => {
|
|
917
917
|
try {
|
|
918
918
|
let result;
|
|
919
|
+
const cacheSecurity = buildCacheSecurityContext(authInfo, isPerUserClient);
|
|
919
920
|
switch (toolName) {
|
|
920
921
|
case 'SAPRead':
|
|
921
|
-
result = await handleSAPRead(client, args, cachingLayer);
|
|
922
|
+
result = await handleSAPRead(client, args, cachingLayer, cacheSecurity);
|
|
922
923
|
break;
|
|
923
924
|
case 'SAPSearch':
|
|
924
925
|
result = await handleSAPSearch(client, args);
|
|
@@ -927,10 +928,10 @@ export async function handleToolCall(client, config, toolName, args, authInfo, _
|
|
|
927
928
|
result = await handleSAPQuery(client, args);
|
|
928
929
|
break;
|
|
929
930
|
case 'SAPWrite':
|
|
930
|
-
result = await handleSAPWrite(client, args, config, cachingLayer);
|
|
931
|
+
result = await handleSAPWrite(client, args, config, cachingLayer, cacheSecurity);
|
|
931
932
|
break;
|
|
932
933
|
case 'SAPActivate':
|
|
933
|
-
result = await handleSAPActivate(client, args, cachingLayer);
|
|
934
|
+
result = await handleSAPActivate(client, args, cachingLayer, cacheSecurity);
|
|
934
935
|
break;
|
|
935
936
|
case 'SAPNavigate':
|
|
936
937
|
result = await handleSAPNavigate(client, args);
|
|
@@ -948,7 +949,7 @@ export async function handleToolCall(client, config, toolName, args, authInfo, _
|
|
|
948
949
|
result = await handleSAPGit(client, args, authInfo);
|
|
949
950
|
break;
|
|
950
951
|
case 'SAPContext':
|
|
951
|
-
result = await handleSAPContext(client, args, cachingLayer);
|
|
952
|
+
result = await handleSAPContext(client, args, cachingLayer, cacheSecurity);
|
|
952
953
|
break;
|
|
953
954
|
case 'SAPManage':
|
|
954
955
|
result = await handleSAPManage(client, config, args, cachingLayer, isPerUserClient);
|
|
@@ -1065,7 +1066,47 @@ const VERSIONED_SOURCE_READ_TYPES = new Set([
|
|
|
1065
1066
|
function inactiveTypeMatches(readType, inactiveType) {
|
|
1066
1067
|
return (inactiveType.split('/')[0] ?? inactiveType).toUpperCase() === readType.toUpperCase();
|
|
1067
1068
|
}
|
|
1068
|
-
|
|
1069
|
+
function nonEmptyString(value) {
|
|
1070
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
1071
|
+
}
|
|
1072
|
+
function resolveCacheUserKey(authInfo) {
|
|
1073
|
+
if (!authInfo)
|
|
1074
|
+
return undefined;
|
|
1075
|
+
const extra = (authInfo.extra ?? {});
|
|
1076
|
+
const issuerOrClient = nonEmptyString(extra.iss) ?? nonEmptyString(authInfo.clientId) ?? 'unknown-auth-source';
|
|
1077
|
+
const namespace = issuerOrClient.toLowerCase();
|
|
1078
|
+
const userName = nonEmptyString(extra.userName);
|
|
1079
|
+
if (userName)
|
|
1080
|
+
return `${namespace}:userName:${userName.toUpperCase()}`;
|
|
1081
|
+
const email = nonEmptyString(extra.email);
|
|
1082
|
+
if (email)
|
|
1083
|
+
return `${namespace}:email:${email.toLowerCase()}`;
|
|
1084
|
+
const sub = nonEmptyString(extra.sub);
|
|
1085
|
+
if (sub)
|
|
1086
|
+
return `${namespace}:sub:${sub}`;
|
|
1087
|
+
const preferredUsername = nonEmptyString(extra.preferred_username);
|
|
1088
|
+
if (preferredUsername)
|
|
1089
|
+
return `${namespace}:preferred_username:${preferredUsername.toLowerCase()}`;
|
|
1090
|
+
return undefined;
|
|
1091
|
+
}
|
|
1092
|
+
function buildCacheSecurityContext(authInfo, isPerUserClient) {
|
|
1093
|
+
if (!isPerUserClient)
|
|
1094
|
+
return { isPerUserClient: false };
|
|
1095
|
+
return {
|
|
1096
|
+
isPerUserClient: true,
|
|
1097
|
+
userKey: resolveCacheUserKey(authInfo),
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
function inactiveListUserKey(client, cacheSecurity) {
|
|
1101
|
+
return cacheSecurity?.isPerUserClient ? cacheSecurity.userKey : client.username;
|
|
1102
|
+
}
|
|
1103
|
+
function invalidateInactiveList(cachingLayer, client, cacheSecurity) {
|
|
1104
|
+
cachingLayer?.inactiveLists.invalidate(inactiveListUserKey(client, cacheSecurity));
|
|
1105
|
+
}
|
|
1106
|
+
function contextCacheForDependencyPayloads(cachingLayer, cacheSecurity) {
|
|
1107
|
+
return cacheSecurity?.isPerUserClient ? undefined : cachingLayer;
|
|
1108
|
+
}
|
|
1109
|
+
async function resolveVersionAndDraftInfo(client, cachingLayer, type, name, requestedVersion, cacheSecurity) {
|
|
1069
1110
|
if (!VERSIONED_SOURCE_READ_TYPES.has(type)) {
|
|
1070
1111
|
return { effectiveVersion: requestedVersion === 'auto' ? 'active' : requestedVersion };
|
|
1071
1112
|
}
|
|
@@ -1073,7 +1114,7 @@ async function resolveVersionAndDraftInfo(client, cachingLayer, type, name, requ
|
|
|
1073
1114
|
if (cachingLayer || requestedVersion !== 'active') {
|
|
1074
1115
|
try {
|
|
1075
1116
|
const inactiveObjects = cachingLayer
|
|
1076
|
-
? await cachingLayer.inactiveLists.getOrFetch(client)
|
|
1117
|
+
? await cachingLayer.inactiveLists.getOrFetch(client, inactiveListUserKey(client, cacheSecurity))
|
|
1077
1118
|
: await client.getInactiveObjects();
|
|
1078
1119
|
const upperName = name.toUpperCase();
|
|
1079
1120
|
draft = inactiveObjects.find((object) => inactiveTypeMatches(type, object.type) && object.name.toUpperCase() === upperName);
|
|
@@ -1103,7 +1144,7 @@ function sourceVersionWarning(effectiveVersion, draft) {
|
|
|
1103
1144
|
}
|
|
1104
1145
|
return undefined;
|
|
1105
1146
|
}
|
|
1106
|
-
async function handleSAPRead(client, args, cachingLayer) {
|
|
1147
|
+
async function handleSAPRead(client, args, cachingLayer, cacheSecurity) {
|
|
1107
1148
|
const type = normalizeObjectType(String(args.type ?? ''));
|
|
1108
1149
|
const name = String(args.name ?? '');
|
|
1109
1150
|
const requestedVersion = (args.version ?? 'active');
|
|
@@ -1126,10 +1167,10 @@ async function handleSAPRead(client, args, cachingLayer) {
|
|
|
1126
1167
|
return textResult(JSON.stringify(sdo, null, 2));
|
|
1127
1168
|
}
|
|
1128
1169
|
if (args.force_refresh === true && cachingLayer && VERSIONED_SOURCE_READ_TYPES.has(type)) {
|
|
1129
|
-
cachingLayer
|
|
1170
|
+
invalidateInactiveList(cachingLayer, client, cacheSecurity);
|
|
1130
1171
|
cachingLayer.invalidate(type, name, 'all');
|
|
1131
1172
|
}
|
|
1132
|
-
const { effectiveVersion, draft } = await resolveVersionAndDraftInfo(client, cachingLayer, type, name, requestedVersion);
|
|
1173
|
+
const { effectiveVersion, draft } = await resolveVersionAndDraftInfo(client, cachingLayer, type, name, requestedVersion, cacheSecurity);
|
|
1133
1174
|
const versionWarning = sourceVersionWarning(effectiveVersion, draft);
|
|
1134
1175
|
// Helper: get source with cache support, returns cache hit status
|
|
1135
1176
|
const cachedGet = async (objType, objName, version, fetcher) => {
|
|
@@ -2580,6 +2621,13 @@ export function buildCreateXml(type, name, pkg, description, properties, languag
|
|
|
2580
2621
|
description,
|
|
2581
2622
|
package: pkg,
|
|
2582
2623
|
messages: messages.length > 0 ? messages : undefined,
|
|
2624
|
+
// Thread the configured language into the body (same spirit as #343).
|
|
2625
|
+
// Live-verified on a4h 7.58: the MSAG handler keys T100.SPRSL by the
|
|
2626
|
+
// BODY adtcore:language — without it the messages are stored under a
|
|
2627
|
+
// BLANK language key (texts never resolve at runtime; ATC/SLIN flags
|
|
2628
|
+
// every number as missing). The sap-language URL param alone does NOT
|
|
2629
|
+
// prevent this.
|
|
2630
|
+
language: masterLanguage,
|
|
2583
2631
|
};
|
|
2584
2632
|
return buildMessageClassXml(params);
|
|
2585
2633
|
}
|
|
@@ -3148,7 +3196,7 @@ async function enforceAllowedPackageForObjectUrl(client, objectUrl, label, accep
|
|
|
3148
3196
|
* PUT; ABAP-specific pre-write steps (lint, RAP preflight, CDS guard) do not apply. Create leaves the
|
|
3149
3197
|
* object inactive — callers follow with SAPActivate (never auto-activated).
|
|
3150
3198
|
*/
|
|
3151
|
-
async function handleServerDrivenObjectWrite(client, action, type, name, args, cachingLayer) {
|
|
3199
|
+
async function handleServerDrivenObjectWrite(client, action, type, name, args, cachingLayer, cacheSecurity) {
|
|
3152
3200
|
// Discovery gate — mirror handleSAPRead's server-driven branch.
|
|
3153
3201
|
if (supportsServerDrivenObject(client.http, type) === false) {
|
|
3154
3202
|
return errorResult(`SAPWrite type=${type} (server-driven object) requires SAP_BASIS 8.16+ (ABAP Platform 2025 / S/4HANA 2025). ` +
|
|
@@ -3159,7 +3207,7 @@ async function handleServerDrivenObjectWrite(client, action, type, name, args, c
|
|
|
3159
3207
|
const blueAccept = serverDrivenBlueContentType(type);
|
|
3160
3208
|
const invalidate = () => {
|
|
3161
3209
|
cachingLayer?.invalidate(type, name, 'all');
|
|
3162
|
-
cachingLayer
|
|
3210
|
+
invalidateInactiveList(cachingLayer, client, cacheSecurity);
|
|
3163
3211
|
};
|
|
3164
3212
|
// SDO source is AFF JSON (not ABAP) — validate it parses before any PUT.
|
|
3165
3213
|
const validateSource = () => {
|
|
@@ -3222,7 +3270,7 @@ async function handleServerDrivenObjectWrite(client, action, type, name, args, c
|
|
|
3222
3270
|
'Supported: create, update, delete (source is AFF JSON) — then SAPActivate to activate.');
|
|
3223
3271
|
}
|
|
3224
3272
|
}
|
|
3225
|
-
async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
3273
|
+
async function handleSAPWrite(client, args, config, cachingLayer, cacheSecurity) {
|
|
3226
3274
|
const action = String(args.action ?? '');
|
|
3227
3275
|
const type = normalizeWriteObjectType(String(args.type ?? ''));
|
|
3228
3276
|
const name = String(args.name ?? '');
|
|
@@ -3262,7 +3310,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
3262
3310
|
// objectBasePath(<sdo>) throws, so this MUST come before the objectUrl computation. Mirrors the
|
|
3263
3311
|
// server-driven branch in handleSAPRead.
|
|
3264
3312
|
if (isServerDrivenObjectType(type)) {
|
|
3265
|
-
return handleServerDrivenObjectWrite(client, action, type, name, args, cachingLayer);
|
|
3313
|
+
return handleServerDrivenObjectWrite(client, action, type, name, args, cachingLayer, cacheSecurity);
|
|
3266
3314
|
}
|
|
3267
3315
|
// For TABL update/delete/edit_method, the existing object may live at /tables/
|
|
3268
3316
|
// (transparent) or /structures/ (DDIC structure). Resolve once via the client's
|
|
@@ -3333,7 +3381,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
3333
3381
|
const invalidateWrittenObject = (objType = type, objName = name) => {
|
|
3334
3382
|
// Source cache is keyed by canonical type (SAPRead collapses TABL/DT, TABL/DS).
|
|
3335
3383
|
cachingLayer?.invalidate(canonicalTablType(objType), objName, 'all');
|
|
3336
|
-
cachingLayer
|
|
3384
|
+
invalidateInactiveList(cachingLayer, client, cacheSecurity);
|
|
3337
3385
|
};
|
|
3338
3386
|
// Helper: enforce allowedPackages for existing objects (update/delete/edit_method/scaffold_rap_handlers).
|
|
3339
3387
|
// Only fetches metadata when package restrictions are configured — no extra HTTP call otherwise.
|
|
@@ -3351,7 +3399,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
3351
3399
|
// surgery call on a draft would splice active-version line ranges into inactive
|
|
3352
3400
|
// source and silently corrupt the draft.
|
|
3353
3401
|
async function fetchClassStructureAndMain(clsName) {
|
|
3354
|
-
const { effectiveVersion } = await resolveVersionAndDraftInfo(client, cachingLayer, 'CLAS', clsName, 'auto');
|
|
3402
|
+
const { effectiveVersion } = await resolveVersionAndDraftInfo(client, cachingLayer, 'CLAS', clsName, 'auto', cacheSecurity);
|
|
3355
3403
|
const structure = await client.getClassStructure(clsName, effectiveVersion);
|
|
3356
3404
|
const main = cachingLayer
|
|
3357
3405
|
? (await cachingLayer.getSource('CLAS', clsName, (ifNoneMatch) => client.getClass(clsName, undefined, { ifNoneMatch, version: effectiveVersion }), { version: effectiveVersion })).source
|
|
@@ -3740,7 +3788,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
3740
3788
|
// splice against stale content (and frequently "method not found").
|
|
3741
3789
|
// Use the standard inactive-list lookup to pick the right version —
|
|
3742
3790
|
// same auto-resolution semantics SAPRead exposes via `version='auto'`.
|
|
3743
|
-
const { effectiveVersion } = await resolveVersionAndDraftInfo(client, cachingLayer, 'CLAS', name, 'auto');
|
|
3791
|
+
const { effectiveVersion } = await resolveVersionAndDraftInfo(client, cachingLayer, 'CLAS', name, 'auto', cacheSecurity);
|
|
3744
3792
|
const fetched = await client.getClass(name, resolvedInclude, { version: effectiveVersion });
|
|
3745
3793
|
currentSource = stripIncludeHeader(fetched.source);
|
|
3746
3794
|
// If the include itself has no draft (only MAIN does), SAP returns the
|
|
@@ -4604,7 +4652,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
4604
4652
|
for (const o of writtenObjects) {
|
|
4605
4653
|
cachingLayer?.invalidate(o.type, o.name, 'all');
|
|
4606
4654
|
}
|
|
4607
|
-
cachingLayer
|
|
4655
|
+
invalidateInactiveList(cachingLayer, client, cacheSecurity);
|
|
4608
4656
|
}
|
|
4609
4657
|
else {
|
|
4610
4658
|
// Flip every written-but-not-yet-activated entry to 'failed', preserving the
|
|
@@ -4824,7 +4872,7 @@ async function runPreWriteSyntaxCheck(client, type, source, objectUrl, config, p
|
|
|
4824
4872
|
}
|
|
4825
4873
|
}
|
|
4826
4874
|
// ─── SAPActivate Handler ─────────────────────────────────────────────
|
|
4827
|
-
async function handleSAPActivate(client, args, cachingLayer) {
|
|
4875
|
+
async function handleSAPActivate(client, args, cachingLayer, cacheSecurity) {
|
|
4828
4876
|
const action = String(args.action ?? 'activate');
|
|
4829
4877
|
const name = String(args.name ?? '');
|
|
4830
4878
|
const version = String(args.version ?? '0001');
|
|
@@ -4850,6 +4898,7 @@ async function handleSAPActivate(client, args, cachingLayer) {
|
|
|
4850
4898
|
if (!name) {
|
|
4851
4899
|
return errorResult('Missing required "name" parameter for publish_srvb action.');
|
|
4852
4900
|
}
|
|
4901
|
+
await enforceAllowedPackageForObjectUrl(client, objectUrlForType('SRVB', name), `Publish of service binding '${name}'`, SERVICEBINDING_V2_CONTENT_TYPE);
|
|
4853
4902
|
const serviceType = await resolveServiceType();
|
|
4854
4903
|
const result = await publishServiceBinding(client.http, client.safety, name, version, serviceType);
|
|
4855
4904
|
if (result.severity === 'ERROR') {
|
|
@@ -4885,6 +4934,7 @@ async function handleSAPActivate(client, args, cachingLayer) {
|
|
|
4885
4934
|
if (!name) {
|
|
4886
4935
|
return errorResult('Missing required "name" parameter for unpublish_srvb action.');
|
|
4887
4936
|
}
|
|
4937
|
+
await enforceAllowedPackageForObjectUrl(client, objectUrlForType('SRVB', name), `Unpublish of service binding '${name}'`, SERVICEBINDING_V2_CONTENT_TYPE);
|
|
4888
4938
|
const serviceType = await resolveServiceType();
|
|
4889
4939
|
const result = await unpublishServiceBinding(client.http, client.safety, name, version, serviceType);
|
|
4890
4940
|
if (result.severity === 'ERROR') {
|
|
@@ -4971,7 +5021,7 @@ async function handleSAPActivate(client, args, cachingLayer) {
|
|
|
4971
5021
|
for (const object of objects) {
|
|
4972
5022
|
cachingLayer?.invalidate(object.type, object.name, 'all');
|
|
4973
5023
|
}
|
|
4974
|
-
cachingLayer
|
|
5024
|
+
invalidateInactiveList(cachingLayer, client, cacheSecurity);
|
|
4975
5025
|
return textResult(`Successfully activated ${objects.length} objects: ${names}.${statusDetails}`);
|
|
4976
5026
|
}
|
|
4977
5027
|
// On batch failure enrich with per-object inactive-version syntax errors —
|
|
@@ -5037,7 +5087,7 @@ async function handleSAPActivate(client, args, cachingLayer) {
|
|
|
5037
5087
|
const result = await activate(client.http, client.safety, objectUrl, { ...activateOpts, name });
|
|
5038
5088
|
if (result.success) {
|
|
5039
5089
|
cachingLayer?.invalidate(type, name, 'all');
|
|
5040
|
-
cachingLayer
|
|
5090
|
+
invalidateInactiveList(cachingLayer, client, cacheSecurity);
|
|
5041
5091
|
return textResult(`Successfully activated ${type} ${name}.${formatActivationMessages(result)}`);
|
|
5042
5092
|
}
|
|
5043
5093
|
// On failure, try to enrich with the actual compiler errors from the inactive version —
|
|
@@ -5720,6 +5770,11 @@ async function handleSAPGit(client, args, _authInfo) {
|
|
|
5720
5770
|
result = await gctsPullRepo(client.http, client.safety, repoId, String(args.commit ?? '').trim() || undefined);
|
|
5721
5771
|
}
|
|
5722
5772
|
else {
|
|
5773
|
+
// R9: a pull deserializes remote content into the repo's server-bound package, which is
|
|
5774
|
+
// NOT the caller-supplied `package` (abapGit ignores that for an existing repo). Gate the
|
|
5775
|
+
// real binding against the allowlist before writing.
|
|
5776
|
+
const repo = await loadAbapGitRepo(client, repoId);
|
|
5777
|
+
await abapGitEnforceRepoPackage(client.safety, repo.package, client.getPackageHierarchyResolver(), 'SAPGit(action="pull")');
|
|
5723
5778
|
result = await abapGitPullRepo(client.http, client.safety, repoId, {
|
|
5724
5779
|
...(packageName ? { package: packageName } : {}),
|
|
5725
5780
|
...(url ? { url } : {}),
|
|
@@ -5734,6 +5789,9 @@ async function handleSAPGit(client, args, _authInfo) {
|
|
|
5734
5789
|
if (!repoId)
|
|
5735
5790
|
return errorResult('SAPGit(action="push") requires repoId.');
|
|
5736
5791
|
const repo = await loadAbapGitRepo(client, repoId);
|
|
5792
|
+
// R9: push exports the repo's bound-package source to a remote git; gate that package
|
|
5793
|
+
// against the allowlist (the read-side mirror of the pull gate above).
|
|
5794
|
+
await abapGitEnforceRepoPackage(client.safety, repo.package, client.getPackageHierarchyResolver(), 'SAPGit(action="push")');
|
|
5737
5795
|
const staging = Array.isArray(args.objects) && args.objects.length > 0
|
|
5738
5796
|
? { repoKey: repo.key, branchName: repo.branchName, objects: args.objects }
|
|
5739
5797
|
: await abapGitStageRepo(client.http, client.safety, repo);
|
|
@@ -5958,8 +6016,24 @@ async function handleSAPTransport(client, args) {
|
|
|
5958
6016
|
if (!id)
|
|
5959
6017
|
return errorResult('Transport ID is required for "delete" action.');
|
|
5960
6018
|
const recursive = Boolean(args.recursive ?? false);
|
|
5961
|
-
|
|
5962
|
-
|
|
6019
|
+
const removeLockedObjects = Boolean(args.removeLockedObjects ?? false);
|
|
6020
|
+
try {
|
|
6021
|
+
await deleteTransport(client.http, client.safety, id, recursive, removeLockedObjects);
|
|
6022
|
+
}
|
|
6023
|
+
catch (e) {
|
|
6024
|
+
// ADT refuses to delete a request/task that still holds locked objects (e.g. a deleted
|
|
6025
|
+
// object's lingering record). Point the caller at removeLockedObjects instead of a raw [?/009].
|
|
6026
|
+
if (!removeLockedObjects && e instanceof Error && /locked objects/i.test(e.message)) {
|
|
6027
|
+
return errorResult(`${e.message}\n\nThe request still holds locked object(s). ` +
|
|
6028
|
+
`Retry with removeLockedObjects=true to strip them first:\n` +
|
|
6029
|
+
` SAPTransport(action="delete", id="${id}", removeLockedObjects=true)`);
|
|
6030
|
+
}
|
|
6031
|
+
throw e;
|
|
6032
|
+
}
|
|
6033
|
+
const extras = [recursive ? 'recursive' : '', removeLockedObjects ? 'removed locked objects' : '']
|
|
6034
|
+
.filter(Boolean)
|
|
6035
|
+
.join(', ');
|
|
6036
|
+
return textResult(`Deleted transport request: ${id}${extras ? ` (${extras})` : ''}`);
|
|
5963
6037
|
}
|
|
5964
6038
|
case 'reassign': {
|
|
5965
6039
|
const id = String(args.id ?? '');
|
|
@@ -6058,7 +6132,7 @@ function parseSiblingMaxCandidates(value) {
|
|
|
6058
6132
|
const rounded = Math.trunc(parsed);
|
|
6059
6133
|
return Math.min(Math.max(rounded, 1), HARD_MAX_SIBLING_MAX_CANDIDATES);
|
|
6060
6134
|
}
|
|
6061
|
-
async function handleSAPContext(client, args, cachingLayer) {
|
|
6135
|
+
async function handleSAPContext(client, args, cachingLayer, cacheSecurity) {
|
|
6062
6136
|
const action = String(args.action ?? '');
|
|
6063
6137
|
// action="impact" is DDLS-only on the server side — default the type so LLMs
|
|
6064
6138
|
// don't have to supply it redundantly (and don't get a validation retry when
|
|
@@ -6066,12 +6140,19 @@ async function handleSAPContext(client, args, cachingLayer) {
|
|
|
6066
6140
|
const rawType = String(args.type ?? '');
|
|
6067
6141
|
const type = normalizeObjectType(rawType || (action === 'impact' ? 'DDLS' : ''));
|
|
6068
6142
|
const name = String(args.name ?? '');
|
|
6069
|
-
|
|
6143
|
+
// Bound dependency fan-out: a huge maxDeps would fan out unbounded SAP fetches per level
|
|
6144
|
+
// (depth is already capped at 3). Clamp to [1, 100]; non-finite/<1 falls back to the default 20.
|
|
6145
|
+
const rawMaxDeps = Number(args.maxDeps ?? 20);
|
|
6146
|
+
const maxDeps = Number.isFinite(rawMaxDeps) && rawMaxDeps >= 1 ? Math.min(Math.floor(rawMaxDeps), 100) : 20;
|
|
6070
6147
|
const depth = Math.min(Math.max(Number(args.depth ?? 1), 1), 3);
|
|
6071
6148
|
// ─── Reverse dep lookup (pre-warmer only) ─────────────────────────
|
|
6072
6149
|
if (action === 'usages') {
|
|
6073
6150
|
if (!name)
|
|
6074
6151
|
return errorResult('"name" is required for usages action.');
|
|
6152
|
+
if (cacheSecurity?.isPerUserClient) {
|
|
6153
|
+
return errorResult('SAPContext(action="usages") is disabled under principal propagation because it reads the shared warmup index. ' +
|
|
6154
|
+
`Use SAPNavigate(action="references", type="${type || 'CLAS'}", name="${name}") for a live SAP-authorized lookup.`);
|
|
6155
|
+
}
|
|
6075
6156
|
if (!cachingLayer) {
|
|
6076
6157
|
return errorResult('Reverse dependency lookup requires object caching. Cache is disabled (ARC1_CACHE=none). ' +
|
|
6077
6158
|
'Enable caching and run cache warmup to use this feature.');
|
|
@@ -6287,7 +6368,7 @@ async function handleSAPContext(client, args, cachingLayer) {
|
|
|
6287
6368
|
}
|
|
6288
6369
|
case 'DDLS': {
|
|
6289
6370
|
const ddlSource = await cachedGet('DDLS', name, (ifNoneMatch) => client.getDdls(name, { ifNoneMatch }));
|
|
6290
|
-
const cdsResult = await compressCdsContext(client, ddlSource, name, maxDeps, depth, cachingLayer);
|
|
6371
|
+
const cdsResult = await compressCdsContext(client, ddlSource, name, maxDeps, depth, contextCacheForDependencyPayloads(cachingLayer, cacheSecurity));
|
|
6291
6372
|
return textResult(cdsResult.output);
|
|
6292
6373
|
}
|
|
6293
6374
|
default:
|
|
@@ -6295,8 +6376,9 @@ async function handleSAPContext(client, args, cachingLayer) {
|
|
|
6295
6376
|
}
|
|
6296
6377
|
}
|
|
6297
6378
|
// Check dep graph cache — if source hash matches, return cached contracts
|
|
6298
|
-
|
|
6299
|
-
|
|
6379
|
+
const dependencyPayloadCache = contextCacheForDependencyPayloads(cachingLayer, cacheSecurity);
|
|
6380
|
+
if (dependencyPayloadCache) {
|
|
6381
|
+
const cachedGraph = dependencyPayloadCache.getCachedDepGraph(source);
|
|
6300
6382
|
if (cachedGraph) {
|
|
6301
6383
|
const successful = cachedGraph.contracts.filter((c) => c.success);
|
|
6302
6384
|
const failed = cachedGraph.contracts.filter((c) => !c.success);
|
|
@@ -6326,7 +6408,7 @@ async function handleSAPContext(client, args, cachingLayer) {
|
|
|
6326
6408
|
const abaplintVersion = cachedFeatures?.abapRelease
|
|
6327
6409
|
? mapSapReleaseToAbaplintVersion(cachedFeatures.abapRelease)
|
|
6328
6410
|
: undefined;
|
|
6329
|
-
const result = await compressContext(client, source, name, type, maxDeps, depth, abaplintVersion,
|
|
6411
|
+
const result = await compressContext(client, source, name, type, maxDeps, depth, abaplintVersion, dependencyPayloadCache);
|
|
6330
6412
|
return textResult(result.output);
|
|
6331
6413
|
}
|
|
6332
6414
|
function buildCdsUpstream(deps) {
|