@bridge_gpt/mcp-server 0.2.18 → 0.2.20
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/CONDUCTOR.md +75 -0
- package/README.md +8 -5
- package/build/agent-capabilities/probe-context.js +13 -3
- package/build/agent-capabilities/probes.js +262 -11
- package/build/agent-capabilities/reporter.js +1 -0
- package/build/agents.generated.js +1 -1
- package/build/backend-warnings.js +44 -0
- package/build/claude-settings.js +129 -0
- package/build/commands.generated.js +5 -3
- package/build/conductor/bridge-api-client.js +7 -7
- package/build/conductor/cli.js +65 -12
- package/build/conductor/deny-enforcement-preflight.js +96 -0
- package/build/conductor/doctor.js +183 -2
- package/build/conductor/epic-reconcile.js +9 -1
- package/build/conductor/epic-runtime.js +403 -43
- package/build/conductor/epic-state.js +7 -0
- package/build/conductor/errors.js +115 -3
- package/build/conductor/event-accessors.js +28 -10
- package/build/conductor/local-merge.js +458 -95
- package/build/conductor/merge-ledger.js +6 -4
- package/build/conductor/pr-ci-producer.js +17 -2
- package/build/conductor/producer-ledger.js +1 -1
- package/build/conductor/store.js +161 -18
- package/build/conductor/supervisor-merge.js +32 -5
- package/build/conductor/taxonomy.js +8 -0
- package/build/conductor/tools.js +28 -6
- package/build/conductor/worker-ledger-cli.js +244 -0
- package/build/conductor-bin.js +1884 -6917
- package/build/doctor.js +8 -0
- package/build/estimate-epic.js +84 -0
- package/build/executor/cli.js +229 -0
- package/build/executor/credentials.js +65 -0
- package/build/executor/deps.js +117 -0
- package/build/executor/env.js +79 -0
- package/build/executor/heartbeat.js +59 -0
- package/build/executor/http-client.js +131 -0
- package/build/executor/index.js +10 -0
- package/build/executor/job-errors.js +55 -0
- package/build/executor/job-log-registry.js +110 -0
- package/build/executor/job-runner.js +822 -0
- package/build/executor/job-types.js +60 -0
- package/build/executor/merge-job.js +229 -0
- package/build/executor/observation.js +123 -0
- package/build/executor/permissions.js +79 -0
- package/build/executor/preflight.js +144 -0
- package/build/executor/process.js +81 -0
- package/build/executor/prompt-spec.js +235 -0
- package/build/executor/results.js +134 -0
- package/build/executor/resume-pre-spawn.js +179 -0
- package/build/executor/runner.js +98 -0
- package/build/executor/terminal-mutation.js +34 -0
- package/build/executor/test-clock.js +109 -0
- package/build/executor/types.js +18 -0
- package/build/executor/verdict-artifact.js +53 -0
- package/build/executor/viewer-tabs.js +78 -0
- package/build/executor/watch-cli.js +113 -0
- package/build/executor/worker-command.js +106 -0
- package/build/executor/worker-finalization.js +177 -0
- package/build/executor/worker-log.js +92 -0
- package/build/executor/worktree-gc.js +134 -0
- package/build/executor/worktree-inspection.js +86 -0
- package/build/executor/worktree.js +103 -0
- package/build/index.js +14534 -10416
- package/build/mcp-invoke.js +19 -3
- package/build/mcp-provisioning.js +31 -25
- package/build/mcp-registration-doctor.js +27 -7
- package/build/mcp-server-invocation.js +152 -0
- package/build/pipelines.generated.js +16 -20
- package/build/readme.generated.js +1 -1
- package/build/sfcc/client.js +192 -50
- package/build/sfcc/ocapi-write-faults.js +94 -0
- package/build/sfcc/permissions.js +7 -22
- package/build/sfcc/reads-site-preference.js +52 -19
- package/build/sfcc/register.js +9 -0
- package/build/sfcc/write-grants.js +80 -0
- package/build/sfcc/write-guard.js +39 -0
- package/build/sfcc/write-result.js +47 -0
- package/build/sfcc/write-tool-common.js +85 -0
- package/build/sfcc/writes-custom-object-def.js +141 -0
- package/build/sfcc/writes-object-attribute-payloads.js +97 -0
- package/build/sfcc/writes-site-preference-payloads.js +59 -0
- package/build/sfcc/writes-site-preference.js +96 -0
- package/build/sfcc/writes-system-object-payloads.js +213 -0
- package/build/sfcc/writes-system-object.js +348 -0
- package/build/sfcc/writes.js +66 -0
- package/build/start-tickets-conductor.js +25 -93
- package/build/start-tickets-prereqs.js +152 -1
- package/build/start-tickets.js +96 -158
- package/build/version.generated.js +1 -1
- package/build/visual-diff-worker.js +313 -0
- package/build/visual-diff.js +632 -0
- package/build/worktree-core.js +202 -0
- package/package.json +8 -4
- package/pipelines/idea-to-ticket.json +7 -0
- package/pipelines/review-ticket.json +5 -18
- package/public/css/main.min.css +1612 -107
- package/public/css/main.min.css.map +1 -1
- package/public/js/main.min.js +10267 -1
- package/public/js/main.min.js.map +1 -1
- package/smoke-test/SMOKE-TEST.md +2 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SFCC system-object metadata write tools (BAPI-583).
|
|
3
|
+
*
|
|
4
|
+
* Live OCAPI write surface mirroring `reads-system-object.ts`. Implements six
|
|
5
|
+
* sandbox-only write operations:
|
|
6
|
+
* system_object_attribute_definition_create — PUT /system_object_definitions/{type}/attribute_definitions/{id}
|
|
7
|
+
* system_object_attribute_definition_update — PATCH /system_object_definitions/{type}/attribute_definitions/{id}
|
|
8
|
+
* system_object_attribute_group_create — PUT /system_object_definitions/{type}/attribute_groups/{id}
|
|
9
|
+
* system_object_attribute_group_update — PATCH /system_object_definitions/{type}/attribute_groups/{id}
|
|
10
|
+
* system_object_attribute_assign_to_group — PUT /system_object_definitions/{type}/attribute_groups/{group}/attribute_definitions/{def}
|
|
11
|
+
* custom_preference_definition_create — PUT /system_object_definitions/{SitePreferences|OrganizationPreferences}/attribute_definitions/{id}
|
|
12
|
+
*
|
|
13
|
+
* Every handler follows the BAPI-582 foundation write recipe: parse inputs →
|
|
14
|
+
* `rejectIfNotSandboxForWrite` → build a decoupled payload → `ocapiPut`/`ocapiPatch`
|
|
15
|
+
* → `formatOcapiWriteToolResult` (which echoes the OCAPI write-grant JSON on 403).
|
|
16
|
+
*/
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { ocapiPut, ocapiPatch } from "./client.js";
|
|
19
|
+
import { withSfccGate } from "./tool-wrapper.js";
|
|
20
|
+
import { rejectIfNotSandboxForWrite } from "./write-guard.js";
|
|
21
|
+
import { formatOcapiWriteToolResult } from "./write-result.js";
|
|
22
|
+
import { objectAttributeDefinitionCreateBodySchema, objectAttributeDefinitionPatchBodySchema, attributeGroupPatchBodySchema, localizedStringSchema, SfccWritePayloadFault, buildObjectAttributeDefinitionCreatePayload, buildObjectAttributeDefinitionPatchPayload, buildAttributeGroupPutPayload, buildAttributeGroupPatchPayload, buildEmptyRelationPayload, } from "./writes-system-object-payloads.js";
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Annotations (destructive OCAPI writes against a developer sandbox)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
const WRITE_ANNOTATIONS = {
|
|
27
|
+
readOnlyHint: false,
|
|
28
|
+
destructiveHint: true,
|
|
29
|
+
idempotentHint: false,
|
|
30
|
+
openWorldHint: true,
|
|
31
|
+
};
|
|
32
|
+
function textResult(text) {
|
|
33
|
+
return { content: [{ type: "text", text }] };
|
|
34
|
+
}
|
|
35
|
+
/** Safe 400 envelope for a Zod validation failure — no stacks, no raw thrown strings. */
|
|
36
|
+
function zodValidationEnvelope(err) {
|
|
37
|
+
return textResult(JSON.stringify({
|
|
38
|
+
error: "VALIDATION_ERROR",
|
|
39
|
+
status: 400,
|
|
40
|
+
message: "Invalid SFCC write tool input.",
|
|
41
|
+
issues: err.issues,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
/** Safe 400 envelope for a local payload-shape fault, in OCAPI-fault vocabulary. */
|
|
45
|
+
function payloadFaultEnvelope(fault) {
|
|
46
|
+
return textResult(JSON.stringify({
|
|
47
|
+
error: "OCAPI_WRITE_ERROR",
|
|
48
|
+
status: fault.status,
|
|
49
|
+
fault: { type: fault.faultType, message: fault.message },
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
/** Safe 500 envelope — never includes stacks, secrets, headers, or raw error text. */
|
|
53
|
+
function unexpectedEnvelope() {
|
|
54
|
+
return textResult(JSON.stringify({
|
|
55
|
+
error: "INTERNAL_ERROR",
|
|
56
|
+
status: 500,
|
|
57
|
+
message: "Unexpected SFCC write tool failure",
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
/** Map a pre-transport error (parse or payload-build) to its safe envelope. */
|
|
61
|
+
function preTransportErrorEnvelope(err) {
|
|
62
|
+
if (err instanceof z.ZodError)
|
|
63
|
+
return zodValidationEnvelope(err);
|
|
64
|
+
if (err instanceof SfccWritePayloadFault)
|
|
65
|
+
return payloadFaultEnvelope(err);
|
|
66
|
+
return unexpectedEnvelope();
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// OCAPI path builders (every dynamic segment URL-encoded)
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
function encodedSegment(segment) {
|
|
72
|
+
return encodeURIComponent(segment);
|
|
73
|
+
}
|
|
74
|
+
function attributeDefinitionPath(objectType, attributeId) {
|
|
75
|
+
return `/system_object_definitions/${encodedSegment(objectType)}/attribute_definitions/${encodedSegment(attributeId)}`;
|
|
76
|
+
}
|
|
77
|
+
function attributeGroupPath(objectType, groupId) {
|
|
78
|
+
return `/system_object_definitions/${encodedSegment(objectType)}/attribute_groups/${encodedSegment(groupId)}`;
|
|
79
|
+
}
|
|
80
|
+
function attributeGroupAssignmentPath(objectType, groupId, attributeId) {
|
|
81
|
+
return (`/system_object_definitions/${encodedSegment(objectType)}` +
|
|
82
|
+
`/attribute_groups/${encodedSegment(groupId)}` +
|
|
83
|
+
`/attribute_definitions/${encodedSegment(attributeId)}`);
|
|
84
|
+
}
|
|
85
|
+
/** Map a preference scope to the OCAPI system object that backs preference attributes. */
|
|
86
|
+
function preferenceObjectTypeForScope(scope) {
|
|
87
|
+
return scope === "site" ? "SitePreferences" : "OrganizationPreferences";
|
|
88
|
+
}
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Input schemas
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
/** Optional sandbox instance context. Writes are sandbox-only (see write-guard). */
|
|
93
|
+
const instanceSchema = z
|
|
94
|
+
.string()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("Sandbox-only: omit (defaults to sandbox) or pass \"sandbox\".");
|
|
97
|
+
const createAttributeDefinitionInput = z.object({
|
|
98
|
+
object_type: z.string().describe("System object type, e.g. \"Product\"."),
|
|
99
|
+
attribute_id: z.string().describe("Attribute id (URL id); the body id must match it."),
|
|
100
|
+
definition: objectAttributeDefinitionCreateBodySchema.describe("OCAPI ObjectAttributeDefinition create body (requires value_type)."),
|
|
101
|
+
instance: instanceSchema,
|
|
102
|
+
});
|
|
103
|
+
const updateAttributeDefinitionInput = z.object({
|
|
104
|
+
object_type: z.string().describe("System object type."),
|
|
105
|
+
attribute_id: z.string().describe("Attribute id (URL id)."),
|
|
106
|
+
patch: objectAttributeDefinitionPatchBodySchema.describe("Partial ObjectAttributeDefinition body (>=1 field)."),
|
|
107
|
+
instance: instanceSchema,
|
|
108
|
+
});
|
|
109
|
+
const createAttributeGroupInput = z.object({
|
|
110
|
+
object_type: z.string().describe("System object type."),
|
|
111
|
+
group_id: z.string().describe("Attribute group id (URL id)."),
|
|
112
|
+
display_name: localizedStringSchema.describe("Localized group display name."),
|
|
113
|
+
internal: z.boolean().describe("Whether the group is internal (BM-only)."),
|
|
114
|
+
instance: instanceSchema,
|
|
115
|
+
});
|
|
116
|
+
const updateAttributeGroupInput = z.object({
|
|
117
|
+
object_type: z.string().describe("System object type."),
|
|
118
|
+
group_id: z.string().describe("Attribute group id (URL id)."),
|
|
119
|
+
patch: attributeGroupPatchBodySchema.describe("Partial group body (display_name and/or internal; >=1 field)."),
|
|
120
|
+
instance: instanceSchema,
|
|
121
|
+
});
|
|
122
|
+
const assignAttributeToGroupInput = z.object({
|
|
123
|
+
object_type: z.string().describe("System object type."),
|
|
124
|
+
group_id: z.string().describe("Attribute group id."),
|
|
125
|
+
attribute_id: z.string().describe("Attribute definition id to assign."),
|
|
126
|
+
instance: instanceSchema,
|
|
127
|
+
});
|
|
128
|
+
const createCustomPreferenceDefinitionInput = z.object({
|
|
129
|
+
preference_scope: z
|
|
130
|
+
.enum(["site", "organization"])
|
|
131
|
+
.describe("site → SitePreferences; organization → OrganizationPreferences."),
|
|
132
|
+
preference_id: z.string().describe("Preference (attribute) id."),
|
|
133
|
+
definition: objectAttributeDefinitionCreateBodySchema.describe("OCAPI ObjectAttributeDefinition create body (requires value_type). " +
|
|
134
|
+
"Set default_value for booleans (else they resolve to null)."),
|
|
135
|
+
instance: instanceSchema,
|
|
136
|
+
});
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Handlers
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
function buildCreateAttributeDefinitionHandler(gateDeps) {
|
|
141
|
+
return withSfccGate(gateDeps, async (args, credentials) => {
|
|
142
|
+
let path;
|
|
143
|
+
let body;
|
|
144
|
+
try {
|
|
145
|
+
const parsed = createAttributeDefinitionInput.parse(args);
|
|
146
|
+
const guard = rejectIfNotSandboxForWrite(parsed.instance);
|
|
147
|
+
if (guard)
|
|
148
|
+
return guard;
|
|
149
|
+
path = attributeDefinitionPath(parsed.object_type, parsed.attribute_id);
|
|
150
|
+
body = buildObjectAttributeDefinitionCreatePayload(parsed.attribute_id, parsed.definition);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
return preTransportErrorEnvelope(err);
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const result = await ocapiPut(path, body, credentials);
|
|
157
|
+
return formatOcapiWriteToolResult(result, "PUT", path);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return unexpectedEnvelope();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function buildUpdateAttributeDefinitionHandler(gateDeps) {
|
|
165
|
+
return withSfccGate(gateDeps, async (args, credentials) => {
|
|
166
|
+
let path;
|
|
167
|
+
let body;
|
|
168
|
+
try {
|
|
169
|
+
const parsed = updateAttributeDefinitionInput.parse(args);
|
|
170
|
+
const guard = rejectIfNotSandboxForWrite(parsed.instance);
|
|
171
|
+
if (guard)
|
|
172
|
+
return guard;
|
|
173
|
+
path = attributeDefinitionPath(parsed.object_type, parsed.attribute_id);
|
|
174
|
+
body = buildObjectAttributeDefinitionPatchPayload(parsed.attribute_id, parsed.patch);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
return preTransportErrorEnvelope(err);
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
// ocapiPatch performs the GET-then-If-Match round trip; 409/412 flow
|
|
181
|
+
// back through formatOcapiWriteToolResult rather than becoming generic errors.
|
|
182
|
+
const result = await ocapiPatch(path, body, credentials);
|
|
183
|
+
return formatOcapiWriteToolResult(result, "PATCH", path);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return unexpectedEnvelope();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function buildCreateAttributeGroupHandler(gateDeps) {
|
|
191
|
+
return withSfccGate(gateDeps, async (args, credentials) => {
|
|
192
|
+
let path;
|
|
193
|
+
let body;
|
|
194
|
+
try {
|
|
195
|
+
const parsed = createAttributeGroupInput.parse(args);
|
|
196
|
+
const guard = rejectIfNotSandboxForWrite(parsed.instance);
|
|
197
|
+
if (guard)
|
|
198
|
+
return guard;
|
|
199
|
+
path = attributeGroupPath(parsed.object_type, parsed.group_id);
|
|
200
|
+
body = buildAttributeGroupPutPayload({
|
|
201
|
+
display_name: parsed.display_name,
|
|
202
|
+
internal: parsed.internal,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
return preTransportErrorEnvelope(err);
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const result = await ocapiPut(path, body, credentials);
|
|
210
|
+
return formatOcapiWriteToolResult(result, "PUT", path);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return unexpectedEnvelope();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function buildUpdateAttributeGroupHandler(gateDeps) {
|
|
218
|
+
return withSfccGate(gateDeps, async (args, credentials) => {
|
|
219
|
+
let path;
|
|
220
|
+
let body;
|
|
221
|
+
try {
|
|
222
|
+
const parsed = updateAttributeGroupInput.parse(args);
|
|
223
|
+
const guard = rejectIfNotSandboxForWrite(parsed.instance);
|
|
224
|
+
if (guard)
|
|
225
|
+
return guard;
|
|
226
|
+
path = attributeGroupPath(parsed.object_type, parsed.group_id);
|
|
227
|
+
body = buildAttributeGroupPatchPayload(parsed.patch);
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
return preTransportErrorEnvelope(err);
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const result = await ocapiPatch(path, body, credentials);
|
|
234
|
+
return formatOcapiWriteToolResult(result, "PATCH", path);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return unexpectedEnvelope();
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function buildAssignAttributeToGroupHandler(gateDeps) {
|
|
242
|
+
return withSfccGate(gateDeps, async (args, credentials) => {
|
|
243
|
+
let path;
|
|
244
|
+
try {
|
|
245
|
+
const parsed = assignAttributeToGroupInput.parse(args);
|
|
246
|
+
const guard = rejectIfNotSandboxForWrite(parsed.instance);
|
|
247
|
+
if (guard)
|
|
248
|
+
return guard;
|
|
249
|
+
path = attributeGroupAssignmentPath(parsed.object_type, parsed.group_id, parsed.attribute_id);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
return preTransportErrorEnvelope(err);
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const result = await ocapiPut(path, buildEmptyRelationPayload(), credentials);
|
|
256
|
+
return formatOcapiWriteToolResult(result, "PUT", path);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return unexpectedEnvelope();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function buildCreateCustomPreferenceDefinitionHandler(gateDeps) {
|
|
264
|
+
return withSfccGate(gateDeps, async (args, credentials) => {
|
|
265
|
+
let path;
|
|
266
|
+
let body;
|
|
267
|
+
try {
|
|
268
|
+
const parsed = createCustomPreferenceDefinitionInput.parse(args);
|
|
269
|
+
const guard = rejectIfNotSandboxForWrite(parsed.instance);
|
|
270
|
+
if (guard)
|
|
271
|
+
return guard;
|
|
272
|
+
const objectType = preferenceObjectTypeForScope(parsed.preference_scope);
|
|
273
|
+
path = attributeDefinitionPath(objectType, parsed.preference_id);
|
|
274
|
+
body = buildObjectAttributeDefinitionCreatePayload(parsed.preference_id, parsed.definition);
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
return preTransportErrorEnvelope(err);
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
const result = await ocapiPut(path, body, credentials);
|
|
281
|
+
return formatOcapiWriteToolResult(result, "PUT", path);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return unexpectedEnvelope();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// Registration
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
/** Concrete write tool names registered by this module. */
|
|
292
|
+
export const SYSTEM_OBJECT_WRITE_TOOL_NAMES = [
|
|
293
|
+
"system_object_attribute_definition_create",
|
|
294
|
+
"system_object_attribute_definition_update",
|
|
295
|
+
"system_object_attribute_group_create",
|
|
296
|
+
"system_object_attribute_group_update",
|
|
297
|
+
"system_object_attribute_assign_to_group",
|
|
298
|
+
"custom_preference_definition_create",
|
|
299
|
+
];
|
|
300
|
+
/**
|
|
301
|
+
* Register the six SFCC system-object write tools. Called from the write seam
|
|
302
|
+
* (`registerSfccWriteTools` in writes.ts) — no direct `register.ts`/`index.ts` edits.
|
|
303
|
+
*/
|
|
304
|
+
export function registerSystemObjectWriteTools(registerTool, deps) {
|
|
305
|
+
const { gateDeps } = deps;
|
|
306
|
+
registerTool("system_object_attribute_definition_create", {
|
|
307
|
+
description: "Create a system-object attribute definition (sandbox-only, destructive). " +
|
|
308
|
+
"PUT .../attribute_definitions/{id}; body id must equal URL id and system must be false. " +
|
|
309
|
+
"Returns 201. HTTP 403 echoes the OCAPI write-grant JSON.",
|
|
310
|
+
inputSchema: createAttributeDefinitionInput,
|
|
311
|
+
annotations: WRITE_ANNOTATIONS,
|
|
312
|
+
}, buildCreateAttributeDefinitionHandler(gateDeps));
|
|
313
|
+
registerTool("system_object_attribute_definition_update", {
|
|
314
|
+
description: "Update a system-object attribute definition (sandbox-only, destructive). " +
|
|
315
|
+
"PATCH .../attribute_definitions/{id} via the ETag GET-then-If-Match round trip. " +
|
|
316
|
+
"Returns 200; surfaces 409/412 on ETag conflicts. HTTP 403 echoes the write-grant JSON.",
|
|
317
|
+
inputSchema: updateAttributeDefinitionInput,
|
|
318
|
+
annotations: WRITE_ANNOTATIONS,
|
|
319
|
+
}, buildUpdateAttributeDefinitionHandler(gateDeps));
|
|
320
|
+
registerTool("system_object_attribute_group_create", {
|
|
321
|
+
description: "Create a system-object attribute group (sandbox-only, destructive). " +
|
|
322
|
+
"PUT .../attribute_groups/{id} with a minimal body (display_name + internal). " +
|
|
323
|
+
"HTTP 403 echoes the OCAPI write-grant JSON.",
|
|
324
|
+
inputSchema: createAttributeGroupInput,
|
|
325
|
+
annotations: WRITE_ANNOTATIONS,
|
|
326
|
+
}, buildCreateAttributeGroupHandler(gateDeps));
|
|
327
|
+
registerTool("system_object_attribute_group_update", {
|
|
328
|
+
description: "Update a system-object attribute group (sandbox-only, destructive). " +
|
|
329
|
+
"PATCH .../attribute_groups/{id} via the ETag round trip (display_name and/or internal). " +
|
|
330
|
+
"HTTP 403 echoes the OCAPI write-grant JSON.",
|
|
331
|
+
inputSchema: updateAttributeGroupInput,
|
|
332
|
+
annotations: WRITE_ANNOTATIONS,
|
|
333
|
+
}, buildUpdateAttributeGroupHandler(gateDeps));
|
|
334
|
+
registerTool("system_object_attribute_assign_to_group", {
|
|
335
|
+
description: "Assign an attribute definition to an attribute group (sandbox-only, destructive). " +
|
|
336
|
+
"PUT .../attribute_groups/{group}/attribute_definitions/{def} with an empty body. " +
|
|
337
|
+
"HTTP 403 echoes the OCAPI write-grant JSON.",
|
|
338
|
+
inputSchema: assignAttributeToGroupInput,
|
|
339
|
+
annotations: WRITE_ANNOTATIONS,
|
|
340
|
+
}, buildAssignAttributeToGroupHandler(gateDeps));
|
|
341
|
+
registerTool("custom_preference_definition_create", {
|
|
342
|
+
description: "Create a custom preference definition (sandbox-only, destructive). " +
|
|
343
|
+
"PUT .../{SitePreferences|OrganizationPreferences}/attribute_definitions/{id}. " +
|
|
344
|
+
"Booleans resolve to null unless default_value is set. HTTP 403 echoes the write-grant JSON.",
|
|
345
|
+
inputSchema: createCustomPreferenceDefinitionInput,
|
|
346
|
+
annotations: WRITE_ANNOTATIONS,
|
|
347
|
+
}, buildCreateCustomPreferenceDefinitionHandler(gateDeps));
|
|
348
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SFCC write-tool registration aggregator (BAPI-581 / BAPI-582 foundation,
|
|
3
|
+
* populated by BAPI-584).
|
|
4
|
+
*
|
|
5
|
+
* This is the registration seam that write-surface tickets register through.
|
|
6
|
+
* `register.ts` wires this behind the existing `includeReadTools` SFCC gate, so
|
|
7
|
+
* the write tools become default-on with the reads without touching
|
|
8
|
+
* `register.ts` or `index.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Each write handler follows the foundation recipe:
|
|
11
|
+
* 1. Parse inputs.
|
|
12
|
+
* 2. Call `rejectIfNotSandboxForWrite` (write-guard.ts) BEFORE payload
|
|
13
|
+
* construction or any OCAPI mutation.
|
|
14
|
+
* 3. Build the SFCC payload in a tool-specific payload helper.
|
|
15
|
+
* 4. Call `ocapiPut` / `ocapiPatch` / `ocapiPatchDirect` (client.ts).
|
|
16
|
+
* 5. Route the result through `formatOcapiWriteToolResult` (write-result.ts).
|
|
17
|
+
*/
|
|
18
|
+
import { registerSystemObjectWriteTools, SYSTEM_OBJECT_WRITE_TOOL_NAMES, } from "./writes-system-object.js";
|
|
19
|
+
import { registerSfccCustomObjectDefWriteTools, CUSTOM_OBJECT_DEF_WRITE_TOOL_NAMES, } from "./writes-custom-object-def.js";
|
|
20
|
+
import { registerSitePreferenceWriteTools, SITE_PREFERENCE_WRITE_TOOL_NAMES, } from "./writes-site-preference.js";
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Tool name registry
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Write tool names registered by this seam (centralized inventory). Extended by
|
|
26
|
+
* each write-surface ticket — BAPI-583 adds the six system-object write tools,
|
|
27
|
+
* BAPI-584 adds the custom-object attribute-definition and site-preference value
|
|
28
|
+
* write tools.
|
|
29
|
+
*/
|
|
30
|
+
export const SFCC_WRITE_TOOL_NAMES = [
|
|
31
|
+
...SYSTEM_OBJECT_WRITE_TOOL_NAMES,
|
|
32
|
+
...CUSTOM_OBJECT_DEF_WRITE_TOOL_NAMES,
|
|
33
|
+
...SITE_PREFERENCE_WRITE_TOOL_NAMES,
|
|
34
|
+
];
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Registration
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
/**
|
|
39
|
+
* Register all SFCC write tools through the host's registerTool wrapper.
|
|
40
|
+
*
|
|
41
|
+
* Registration is delegated to per-surface write modules. Each write handler
|
|
42
|
+
* MUST:
|
|
43
|
+
* 1. Parse inputs.
|
|
44
|
+
* 2. Call `rejectIfNotSandboxForWrite` (write-guard.ts) BEFORE payload
|
|
45
|
+
* construction or any OCAPI mutation.
|
|
46
|
+
* 3. Build the SFCC payload in a tool-specific payload helper.
|
|
47
|
+
* 4. Call `ocapiPut` or `ocapiPatch` (client.ts) for transport.
|
|
48
|
+
* 5. Route the result through `formatOcapiWriteToolResult` (write-result.ts).
|
|
49
|
+
*
|
|
50
|
+
* Each handler is additionally call-time gated by `withSfccGate` and
|
|
51
|
+
* sandbox-guarded, so registering them here (default-on with the reads) does
|
|
52
|
+
* not weaken the per-call safety model.
|
|
53
|
+
*
|
|
54
|
+
* BAPI-583 registers the system-object write surface here; BAPI-584 adds the
|
|
55
|
+
* custom-object attribute-definition and site-preference value write surfaces
|
|
56
|
+
* alongside it. Later write-surface tickets add their modules the same way,
|
|
57
|
+
* without touching `register.ts`/`index.ts`.
|
|
58
|
+
*/
|
|
59
|
+
export function registerSfccWriteTools(registerTool, deps) {
|
|
60
|
+
// System-object attribute/group definition writes (BAPI-583).
|
|
61
|
+
registerSystemObjectWriteTools(registerTool, { gateDeps: deps.gateDeps });
|
|
62
|
+
// Custom-object attribute-definition create/update (PUT / ETag-PATCH).
|
|
63
|
+
registerSfccCustomObjectDefWriteTools(registerTool, { gateDeps: deps.gateDeps });
|
|
64
|
+
// Site-preference value set (direct PATCH of a flat c_ map).
|
|
65
|
+
registerSitePreferenceWriteTools(registerTool, { gateDeps: deps.gateDeps });
|
|
66
|
+
}
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* metadata, or the shell command string.
|
|
21
21
|
*/
|
|
22
22
|
import { randomBytes } from "node:crypto";
|
|
23
|
-
import path from "node:path";
|
|
24
23
|
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { mergeClaudeSettingsWithCommandHook, detectExistingPreToolUseMatcher, provisionClaudeSettingsForWorktree, DEFAULT_PRE_TOOL_USE_MATCHER, } from "./claude-settings.js";
|
|
25
25
|
import { normalizeDeclaredTouchedFiles } from "./conductor/file-scope-guard.js";
|
|
26
26
|
import { resolveProfiles } from "./mcp-profile.js";
|
|
27
27
|
import { resolveStartTicketsRepoName } from "./start-tickets-repo.js";
|
|
@@ -116,6 +116,10 @@ export async function createStartTicketsConductorContext(options, agent, deps) {
|
|
|
116
116
|
supervisorMode,
|
|
117
117
|
agentName: agent.name,
|
|
118
118
|
cliFile: resolveBinPath("conductor-bin.js"),
|
|
119
|
+
// Capture the conductor process's Node executable so dispatched workers run
|
|
120
|
+
// conductor-bin.js under THIS runtime (which owns the ledger), never the
|
|
121
|
+
// worker's own Node (BAPI-527).
|
|
122
|
+
conductorNodePath: deps.execPath ?? process.execPath,
|
|
119
123
|
hookBinPath: resolveBinPath("conductor-claude-hook-bin.js"),
|
|
120
124
|
};
|
|
121
125
|
if (options.epic) {
|
|
@@ -167,6 +171,10 @@ export function buildConductorWorkerEnv(context, worker, parentEnv) {
|
|
|
167
171
|
BAPI_CONDUCTOR_GATE_NAME: context.gateName,
|
|
168
172
|
BAPI_CONDUCTOR_SUPERVISOR_MODE: context.supervisorMode,
|
|
169
173
|
BAPI_CONDUCTOR_CLI_FILE: context.cliFile,
|
|
174
|
+
// BAPI-527: the CLI file + Node runtime are a PAIR. The worker runs
|
|
175
|
+
// conductor-bin.js under this captured conductor Node for every worker-side
|
|
176
|
+
// ledger op, so the worker Node never loads better-sqlite3. Non-secret.
|
|
177
|
+
CONDUCTOR_NODE_PATH: context.conductorNodePath,
|
|
170
178
|
};
|
|
171
179
|
if (context.repoName) {
|
|
172
180
|
env.BAPI_CONDUCTOR_REPO_NAME = context.repoName;
|
|
@@ -205,8 +213,10 @@ export const CONDUCTOR_HOOK_LIFECYCLE_EVENTS = [
|
|
|
205
213
|
"SessionEnd",
|
|
206
214
|
"Notification",
|
|
207
215
|
];
|
|
208
|
-
|
|
209
|
-
|
|
216
|
+
// BAPI-534: the broad PreToolUse matcher and generic hook-merge machinery now
|
|
217
|
+
// live in `claude-settings.ts` (shared with the executor deny-layer path).
|
|
218
|
+
// Re-exported so existing importers of this name keep resolving.
|
|
219
|
+
export { DEFAULT_PRE_TOOL_USE_MATCHER };
|
|
210
220
|
/** Shell-quote a path for embedding in a hook command string. */
|
|
211
221
|
function shellQuotePath(value) {
|
|
212
222
|
// Single-quote for POSIX safety; embedded single quotes are escaped. Hook
|
|
@@ -224,55 +234,15 @@ export function resolveConductorHookCommand(env, hookBinPath, execPath = process
|
|
|
224
234
|
}
|
|
225
235
|
return `${shellQuotePath(execPath)} ${shellQuotePath(hookBinPath)}`;
|
|
226
236
|
}
|
|
227
|
-
function asHookEntries(value) {
|
|
228
|
-
return Array.isArray(value) ? value : [];
|
|
229
|
-
}
|
|
230
|
-
/** Does this event already register `command` as a command hook? */
|
|
231
|
-
function entriesContainCommand(entries, command) {
|
|
232
|
-
return entries.some((entry) => Array.isArray(entry?.hooks) &&
|
|
233
|
-
entry.hooks.some((h) => h && h.type === "command" && h.command === command));
|
|
234
|
-
}
|
|
235
237
|
/**
|
|
236
|
-
* Merge conductor lifecycle hooks into an existing Claude settings object
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
* `
|
|
238
|
+
* Merge conductor lifecycle hooks into an existing Claude settings object.
|
|
239
|
+
* BAPI-534: a thin wrapper over the generic {@link mergeClaudeSettingsWithCommandHook}
|
|
240
|
+
* bound to the conductor lifecycle events — preserving the exported signature and
|
|
241
|
+
* behavior (idempotent; `PreToolUse` only when `enablePreToolUse`; mirrors any
|
|
242
|
+
* existing matcher style; never removes existing settings/hooks).
|
|
240
243
|
*/
|
|
241
244
|
export function mergeClaudeSettingsWithConductorHook(settings, command, options = {}) {
|
|
242
|
-
|
|
243
|
-
? settings.hooks
|
|
244
|
-
: {};
|
|
245
|
-
const hooks = { ...existingHooks };
|
|
246
|
-
const events = [...CONDUCTOR_HOOK_LIFECYCLE_EVENTS];
|
|
247
|
-
if (options.enablePreToolUse) {
|
|
248
|
-
events.push("PreToolUse");
|
|
249
|
-
}
|
|
250
|
-
for (const event of events) {
|
|
251
|
-
const entries = asHookEntries(hooks[event]);
|
|
252
|
-
if (entriesContainCommand(entries, command)) {
|
|
253
|
-
// Idempotent: already registered for this event.
|
|
254
|
-
hooks[event] = entries;
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
const newEntry = { hooks: [{ type: "command", command }] };
|
|
258
|
-
if (event === "PreToolUse") {
|
|
259
|
-
newEntry.matcher = options.preToolUseMatcher ?? DEFAULT_PRE_TOOL_USE_MATCHER;
|
|
260
|
-
}
|
|
261
|
-
hooks[event] = [...entries, newEntry];
|
|
262
|
-
}
|
|
263
|
-
return { ...settings, hooks };
|
|
264
|
-
}
|
|
265
|
-
/** Detect an existing PreToolUse matcher to mirror, if any. */
|
|
266
|
-
function detectExistingPreToolUseMatcher(settings) {
|
|
267
|
-
const hooks = settings.hooks;
|
|
268
|
-
if (hooks === null || typeof hooks !== "object" || Array.isArray(hooks))
|
|
269
|
-
return undefined;
|
|
270
|
-
const entries = asHookEntries(hooks.PreToolUse);
|
|
271
|
-
for (const entry of entries) {
|
|
272
|
-
if (typeof entry?.matcher === "string")
|
|
273
|
-
return entry.matcher;
|
|
274
|
-
}
|
|
275
|
-
return undefined;
|
|
245
|
+
return mergeClaudeSettingsWithCommandHook(settings, command, CONDUCTOR_HOOK_LIFECYCLE_EVENTS, options);
|
|
276
246
|
}
|
|
277
247
|
/**
|
|
278
248
|
* Provision the conductor Claude hook into ONE worktree's
|
|
@@ -281,52 +251,14 @@ function detectExistingPreToolUseMatcher(settings) {
|
|
|
281
251
|
* pretty-printed JSON, mirroring any existing PreToolUse matcher.
|
|
282
252
|
*/
|
|
283
253
|
export async function provisionConductorHookForWorktree(worktreePath, command, options, deps) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
raw = await deps.readFile(settingsPath);
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
// Missing file -> treat as empty settings.
|
|
293
|
-
raw = null;
|
|
294
|
-
}
|
|
295
|
-
if (raw !== null) {
|
|
296
|
-
try {
|
|
297
|
-
const parsed = JSON.parse(raw);
|
|
298
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
299
|
-
return {
|
|
300
|
-
ok: false,
|
|
301
|
-
reason: "malformed",
|
|
302
|
-
error: "existing .claude/settings.local.json is not a JSON object",
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
existing = parsed;
|
|
306
|
-
}
|
|
307
|
-
catch {
|
|
308
|
-
// Malformed JSON: fail the row rather than clobber the user's file. The
|
|
309
|
-
// error is generic — it never echoes the file contents.
|
|
310
|
-
return {
|
|
311
|
-
ok: false,
|
|
312
|
-
reason: "malformed",
|
|
313
|
-
error: "existing .claude/settings.local.json contains invalid JSON",
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
const merged = mergeClaudeSettingsWithConductorHook(existing, command, {
|
|
254
|
+
// BAPI-534: delegate the malformed-safe read/merge/write to the shared
|
|
255
|
+
// `provisionClaudeSettingsForWorktree`, supplying a conductor-hook merge
|
|
256
|
+
// closure. The returned `{ ok, reason, error }` shape is identical to
|
|
257
|
+
// ConductorHookProvisionResult, so the mapping is a pass-through.
|
|
258
|
+
return provisionClaudeSettingsForWorktree(worktreePath, (existing) => mergeClaudeSettingsWithConductorHook(existing, command, {
|
|
318
259
|
enablePreToolUse: options.enablePreToolUse,
|
|
319
260
|
preToolUseMatcher: options.preToolUseMatcher ?? detectExistingPreToolUseMatcher(existing),
|
|
320
|
-
});
|
|
321
|
-
try {
|
|
322
|
-
await deps.mkdir(claudeDir, { recursive: true });
|
|
323
|
-
await deps.writeFile(settingsPath, `${JSON.stringify(merged, null, 2)}\n`);
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
// Best-effort: a write/mkdir failure must not block the actual work.
|
|
327
|
-
return { ok: false, reason: "io", error: "failed to write .claude/settings.local.json" };
|
|
328
|
-
}
|
|
329
|
-
return { ok: true };
|
|
261
|
+
}), deps);
|
|
330
262
|
}
|
|
331
263
|
/**
|
|
332
264
|
* Provision conductor identity across rows. Attaches `runId` to EVERY row (so
|