wowbagger 0.1.0-alpha.1

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/LICENSE +201 -0
  3. package/README.md +464 -0
  4. package/adapters/claude-code/entrypoint.js +19 -0
  5. package/adapters/claude-code/wowbagger-adapter.json +25 -0
  6. package/adapters/codex/entrypoint.js +11 -0
  7. package/adapters/codex/wowbagger-adapter.json +25 -0
  8. package/adapters/opencode/entrypoint.js +11 -0
  9. package/adapters/opencode/wowbagger-adapter.json +25 -0
  10. package/bin/wowbagger.js +7 -0
  11. package/package.json +51 -0
  12. package/skills/wowbagger/SKILL.md +136 -0
  13. package/src/adapter/approval.js +135 -0
  14. package/src/adapter/bootstrap.js +43 -0
  15. package/src/adapter/context.js +34 -0
  16. package/src/adapter/core-probe.js +231 -0
  17. package/src/adapter/describe.js +383 -0
  18. package/src/adapter/entrypoint-main.js +335 -0
  19. package/src/adapter/entrypoint-path.js +103 -0
  20. package/src/adapter/handoff.js +124 -0
  21. package/src/adapter/instructions.js +106 -0
  22. package/src/adapter/invoke.js +294 -0
  23. package/src/adapter/limits.js +26 -0
  24. package/src/adapter/manifest.js +93 -0
  25. package/src/adapter/messages.js +15 -0
  26. package/src/adapter/paths.js +88 -0
  27. package/src/adapter/process-outcome.js +1116 -0
  28. package/src/adapter/schema-helpers.js +60 -0
  29. package/src/claim-capabilities.js +54 -0
  30. package/src/claim-coordinator.js +85 -0
  31. package/src/claim-journal.js +236 -0
  32. package/src/claim-operations.js +138 -0
  33. package/src/claim-publication.js +739 -0
  34. package/src/claim-request.js +140 -0
  35. package/src/claim-store.js +198 -0
  36. package/src/cli.js +1130 -0
  37. package/src/dependencies.js +3 -0
  38. package/src/git-reconciliation.js +62 -0
  39. package/src/ledger.js +296 -0
  40. package/src/mint.js +32 -0
  41. package/src/mutation.js +1979 -0
  42. package/src/namespace.js +35 -0
  43. package/src/ready.js +85 -0
  44. package/src/request.js +246 -0
  45. package/src/schema-migration.js +300 -0
  46. package/src/validate.js +1208 -0
@@ -0,0 +1,383 @@
1
+ import { validateAdapterManifest } from './manifest.js';
2
+ import { CORE_COMMAND_ORDER } from './core-probe.js';
3
+ import {
4
+ hasExactMembers, isAllBoolean, isNonEmptyString, isNonNegativeSafeInteger, isPositiveSafeInteger, sameJson,
5
+ } from './schema-helpers.js';
6
+
7
+ // Opaque-ID syntax used for the describe request's `request_id` (contract
8
+ // section 3: "The request ID uses the safe opaque-ID syntax").
9
+ const SAFE_ID_PATTERN = /^[A-Za-z0-9._-]{1,128}$/;
10
+
11
+ // The version 2 manifest array is exactly [2] (contract section 3): a
12
+ // manifest or dynamic result advertising an unregistered future version has
13
+ // no schema or handler to select, so it is refused rather than negotiated.
14
+ export const ADAPTER_CONTRACT_VERSION = 2;
15
+ const SUPPORTED_ADAPTER_CONTRACT_VERSIONS = Object.freeze([ADAPTER_CONTRACT_VERSION]);
16
+
17
+ const INSTRUCTION_INPUT_MODES = new Set(['none', 'host-provided', 'configured-relative-paths']);
18
+ const WORKSPACE_SELECTION_MODES = new Set(['none', 'guarded-relative']);
19
+ const PLATFORM_NAMES = Object.freeze(['darwin', 'linux', 'win32']);
20
+ const PLATFORM_STATUSES = new Set(['supported', 'unsupported', 'unverified']);
21
+
22
+ // The dependent execution flags in the §3.2 capability table.
23
+ const EXECUTION_DEPENDENT_FLAGS = Object.freeze([
24
+ 'arguments_array', 'stdio', 'process_tree_containment', 'orphan_detection',
25
+ 'timeout_enforcement', 'stdout_limit', 'stderr_limit',
26
+ ]);
27
+
28
+ function refuse(error_code, detail) {
29
+ return { ok: false, error_code, detail };
30
+ }
31
+
32
+ function isVersionArray(value) {
33
+ return Array.isArray(value)
34
+ && value.length > 0
35
+ && value.every(isPositiveSafeInteger)
36
+ && value.every((version, index) => index === 0 || value[index - 1] < version);
37
+ }
38
+
39
+ function isCommandArray(value) {
40
+ if (!Array.isArray(value)) {
41
+ return false;
42
+ }
43
+ for (let index = 0; index < value.length; index += 1) {
44
+ const command = value[index];
45
+ if (!CORE_COMMAND_ORDER.includes(command)) {
46
+ return false;
47
+ }
48
+ if (index > 0 && CORE_COMMAND_ORDER.indexOf(value[index - 1]) >= CORE_COMMAND_ORDER.indexOf(command)) {
49
+ return false;
50
+ }
51
+ }
52
+ return new Set(value).size === value.length;
53
+ }
54
+
55
+ function isValidPlatformMap(value) {
56
+ return hasExactMembers(value, PLATFORM_NAMES)
57
+ && PLATFORM_NAMES.every((name) => PLATFORM_STATUSES.has(value[name]));
58
+ }
59
+
60
+ // ---- describe request schema (contract section 3) ----
61
+
62
+ function describeRequestIssue(request) {
63
+ if (!hasExactMembers(request, ['bootstrap_wire_version', 'supported_adapter_contract_versions', 'request_id'])) {
64
+ return 'members';
65
+ }
66
+ if (!isPositiveSafeInteger(request.bootstrap_wire_version)) {
67
+ return 'bootstrap_wire_version';
68
+ }
69
+ if (!isVersionArray(request.supported_adapter_contract_versions)) {
70
+ return 'supported_adapter_contract_versions';
71
+ }
72
+ if (typeof request.request_id !== 'string' || !SAFE_ID_PATTERN.test(request.request_id)) {
73
+ return 'request_id';
74
+ }
75
+ return null;
76
+ }
77
+
78
+ // ---- static manifest value checks (section 3.1), on top of Task 3's
79
+ // structural validateAdapterManifest ----
80
+
81
+ function manifestValueIssue(manifest) {
82
+ if (manifest.adapter_manifest_version !== 1) {
83
+ return 'adapter_manifest_version';
84
+ }
85
+ if (!isNonEmptyString(manifest.adapter_id)) {
86
+ return 'adapter_id';
87
+ }
88
+ if (!isNonEmptyString(manifest.adapter_version)) {
89
+ return 'adapter_version';
90
+ }
91
+ if (!sameJson(manifest.adapter_contract_versions, SUPPORTED_ADAPTER_CONTRACT_VERSIONS)) {
92
+ return 'adapter_contract_versions';
93
+ }
94
+ if (manifest.bootstrap_wire_version !== 1) {
95
+ return 'bootstrap_wire_version';
96
+ }
97
+ if (!isPositiveSafeInteger(manifest.required_core_contract_version)) {
98
+ return 'required_core_contract_version';
99
+ }
100
+ if (!isValidPlatformMap(manifest.platforms)) {
101
+ return 'platforms';
102
+ }
103
+ return null;
104
+ }
105
+
106
+ // ---- dynamic describe result schema (section 3.2) ----
107
+
108
+ function commandExecutionIssue(execution) {
109
+ if (!hasExactMembers(execution, [
110
+ 'supported', 'arguments_array', 'shell', 'stdio', 'process_tree_containment',
111
+ 'orphan_detection', 'timeout_enforcement', 'stdout_limit', 'stderr_limit',
112
+ ]) || !isAllBoolean(execution)) {
113
+ return 'command_execution';
114
+ }
115
+ return null;
116
+ }
117
+
118
+ function filesystemIssue(filesystem) {
119
+ if (!hasExactMembers(filesystem, ['workspace_selection', 'no_follow_resolution', 'stable_identity', 'component_walk'])
120
+ || !WORKSPACE_SELECTION_MODES.has(filesystem.workspace_selection)
121
+ || typeof filesystem.no_follow_resolution !== 'boolean'
122
+ || typeof filesystem.stable_identity !== 'boolean'
123
+ || typeof filesystem.component_walk !== 'boolean') {
124
+ return 'filesystem';
125
+ }
126
+ return null;
127
+ }
128
+
129
+ function modelTransportIssue(transport) {
130
+ if (!hasExactMembers(transport, ['available', 'protocol'])
131
+ || typeof transport.available !== 'boolean'
132
+ || !isNonEmptyString(transport.protocol)) {
133
+ return 'model_transport';
134
+ }
135
+ return null;
136
+ }
137
+
138
+ function instructionInputIssue(input) {
139
+ if (!hasExactMembers(input, ['mode', 'max_sources', 'max_bytes'])
140
+ || !INSTRUCTION_INPUT_MODES.has(input.mode)
141
+ || !isNonNegativeSafeInteger(input.max_sources)
142
+ || !isNonNegativeSafeInteger(input.max_bytes)) {
143
+ return 'instruction_input';
144
+ }
145
+ return null;
146
+ }
147
+
148
+ function handoffIssue(handoff) {
149
+ if (!hasExactMembers(handoff, ['supported', 'persistence'])
150
+ || typeof handoff.supported !== 'boolean'
151
+ || handoff.persistence !== 'explicit-only') {
152
+ return 'handoff';
153
+ }
154
+ return null;
155
+ }
156
+
157
+ // `host.trusted_approval` MAY be absent to declare mutations unavailable.
158
+ // Version 1 has exactly one authority label.
159
+ function trustedApprovalIssue(host) {
160
+ if (!Object.hasOwn(host, 'trusted_approval')) {
161
+ return null;
162
+ }
163
+ const approval = host.trusted_approval;
164
+ if (!hasExactMembers(approval, ['supported', 'sources'])
165
+ || typeof approval.supported !== 'boolean'
166
+ || !sameJson(approval.sources, ['consumer'])) {
167
+ return 'trusted_approval';
168
+ }
169
+ return null;
170
+ }
171
+
172
+ function integrationMechanismsIssue(mechanisms) {
173
+ if (!hasExactMembers(mechanisms, ['hooks', 'slash_commands', 'mcp', 'daemon']) || !isAllBoolean(mechanisms)) {
174
+ return 'integration_mechanisms';
175
+ }
176
+ return null;
177
+ }
178
+
179
+ function hostCapabilitiesIssue(host) {
180
+ if (!hasExactMembers(host, [
181
+ 'command_execution', 'filesystem', 'model_transport', 'instruction_input', 'handoff', 'integration_mechanisms',
182
+ ], ['trusted_approval'])) {
183
+ return 'members';
184
+ }
185
+ return commandExecutionIssue(host.command_execution)
186
+ || filesystemIssue(host.filesystem)
187
+ || modelTransportIssue(host.model_transport)
188
+ || instructionInputIssue(host.instruction_input)
189
+ || handoffIssue(host.handoff)
190
+ || trustedApprovalIssue(host)
191
+ || integrationMechanismsIssue(host.integration_mechanisms);
192
+ }
193
+
194
+ function coreSectionIssue(core) {
195
+ if (!hasExactMembers(core, ['required_core_contract_version', 'commands'])
196
+ || !isPositiveSafeInteger(core.required_core_contract_version)
197
+ || !isCommandArray(core.commands)) {
198
+ return 'core';
199
+ }
200
+ return null;
201
+ }
202
+
203
+ function optionalFeaturesIssue(optionalFeatures) {
204
+ if (!hasExactMembers(optionalFeatures, ['claims', 'policy']) || !isAllBoolean(optionalFeatures)) {
205
+ return 'optional_features';
206
+ }
207
+ return null;
208
+ }
209
+
210
+ function limitsSectionIssue(limits) {
211
+ if (!hasExactMembers(limits, [
212
+ 'max_request_bytes', 'max_context_bytes', 'max_stdout_bytes', 'max_stderr_bytes', 'max_timeout_ms',
213
+ ])) {
214
+ return 'limits';
215
+ }
216
+ for (const member of ['max_request_bytes', 'max_context_bytes', 'max_stdout_bytes', 'max_stderr_bytes']) {
217
+ if (!isNonNegativeSafeInteger(limits[member])) {
218
+ return `limits.${member}`;
219
+ }
220
+ }
221
+ if (!isPositiveSafeInteger(limits.max_timeout_ms)) {
222
+ return 'limits.max_timeout_ms';
223
+ }
224
+ return null;
225
+ }
226
+
227
+ // The §3.2 cross-field capability invariants: a contradictory describe
228
+ // result is `invalid-describe-result` even when every individual field has
229
+ // the right type.
230
+ function validateExecutionCapabilities(dynamic) {
231
+ const execution = dynamic.host.command_execution;
232
+ if (execution.shell !== false) {
233
+ return refuse('invalid-describe-result', 'host.command_execution.shell');
234
+ }
235
+ if (execution.supported === true) {
236
+ const missing = EXECUTION_DEPENDENT_FLAGS.find((flag) => execution[flag] !== true);
237
+ if (missing) {
238
+ return refuse('invalid-describe-result', `host.command_execution.${missing}`);
239
+ }
240
+ const badLimit = Object.entries(dynamic.limits).find(([, value]) => !isPositiveSafeInteger(value));
241
+ if (badLimit) {
242
+ return refuse('invalid-describe-result', `limits.${badLimit[0]}`);
243
+ }
244
+ } else {
245
+ const contradicted = EXECUTION_DEPENDENT_FLAGS.find((flag) => execution[flag] !== false);
246
+ if (contradicted) {
247
+ return refuse('invalid-describe-result', `host.command_execution.${contradicted}`);
248
+ }
249
+ if (dynamic.core.commands.length !== 0) {
250
+ return refuse('invalid-describe-result', 'core.commands');
251
+ }
252
+ }
253
+ return { ok: true };
254
+ }
255
+
256
+ function filesystemInvariantIssue(dynamic) {
257
+ const filesystem = dynamic.host.filesystem;
258
+ const guarded = filesystem.workspace_selection === 'guarded-relative';
259
+ const flags = ['no_follow_resolution', 'stable_identity', 'component_walk'];
260
+ const bad = flags.find((flag) => filesystem[flag] !== guarded);
261
+ return bad ? `host.filesystem.${bad}` : null;
262
+ }
263
+
264
+ function instructionInvariantIssue(dynamic) {
265
+ const instruction = dynamic.host.instruction_input;
266
+ if (instruction.mode === 'none') {
267
+ if (instruction.max_sources !== 0) {
268
+ return 'host.instruction_input.max_sources';
269
+ }
270
+ if (instruction.max_bytes !== 0) {
271
+ return 'host.instruction_input.max_bytes';
272
+ }
273
+ return null;
274
+ }
275
+ return instruction.max_sources === 0 || instruction.max_bytes === 0 ? 'host.instruction_input' : null;
276
+ }
277
+
278
+ function dynamicDescribeIssue(dynamic) {
279
+ if (!hasExactMembers(dynamic, [
280
+ 'ok', 'bootstrap_wire_version', 'selected_adapter_contract_version', 'adapter_id', 'adapter_version',
281
+ 'core', 'host', 'optional_features', 'limits', 'platforms',
282
+ ])) {
283
+ return 'members';
284
+ }
285
+ if (dynamic.ok !== true) {
286
+ return 'ok';
287
+ }
288
+ if (dynamic.bootstrap_wire_version !== 1) {
289
+ return 'bootstrap_wire_version';
290
+ }
291
+ if (dynamic.selected_adapter_contract_version !== ADAPTER_CONTRACT_VERSION) {
292
+ return 'selected_adapter_contract_version';
293
+ }
294
+ if (!isNonEmptyString(dynamic.adapter_id)) {
295
+ return 'adapter_id';
296
+ }
297
+ if (!isNonEmptyString(dynamic.adapter_version)) {
298
+ return 'adapter_version';
299
+ }
300
+ const schemaIssue = coreSectionIssue(dynamic.core)
301
+ || hostCapabilitiesIssue(dynamic.host)
302
+ || optionalFeaturesIssue(dynamic.optional_features)
303
+ || limitsSectionIssue(dynamic.limits);
304
+ if (schemaIssue) {
305
+ return schemaIssue;
306
+ }
307
+ const capabilities = validateExecutionCapabilities(dynamic);
308
+ if (!capabilities.ok) {
309
+ return capabilities.detail;
310
+ }
311
+ const invariantIssue = filesystemInvariantIssue(dynamic) || instructionInvariantIssue(dynamic);
312
+ if (invariantIssue) {
313
+ return invariantIssue;
314
+ }
315
+ return isValidPlatformMap(dynamic.platforms) ? null : 'platforms';
316
+ }
317
+
318
+ // describeAdapter(request, manifest, dynamic) implements the bootstrap
319
+ // negotiation of contract section 3: schema-validate the request, the
320
+ // static manifest, and the dynamic result in turn, select a shared adapter
321
+ // contract version, then cross-check static and dynamic identity, version,
322
+ // selected contract, required core version, and the platform map, in the
323
+ // error-precedence order given in section 3.3.
324
+ export function describeAdapter(request, manifest, dynamic) {
325
+ const requestIssue = describeRequestIssue(request);
326
+ if (requestIssue) {
327
+ return refuse('invalid-describe-request', requestIssue);
328
+ }
329
+
330
+ const manifestResult = validateAdapterManifest(manifest);
331
+ if (!manifestResult.ok) {
332
+ return refuse('invalid-adapter-manifest', manifestResult.detail);
333
+ }
334
+ const manifestIssue = manifestValueIssue(manifest);
335
+ if (manifestIssue) {
336
+ return refuse('invalid-adapter-manifest', manifestIssue);
337
+ }
338
+
339
+ if (request.bootstrap_wire_version !== manifest.bootstrap_wire_version) {
340
+ return refuse('unsupported-bootstrap-wire-version', { received: request.bootstrap_wire_version });
341
+ }
342
+
343
+ const selected = SUPPORTED_ADAPTER_CONTRACT_VERSIONS.find(
344
+ (version) => request.supported_adapter_contract_versions.includes(version)
345
+ && manifest.adapter_contract_versions.includes(version),
346
+ );
347
+ if (selected === undefined) {
348
+ return refuse('unsupported-adapter-contract-version', {
349
+ client: request.supported_adapter_contract_versions,
350
+ adapter: manifest.adapter_contract_versions,
351
+ });
352
+ }
353
+
354
+ const dynamicIssue = dynamicDescribeIssue(dynamic);
355
+ if (dynamicIssue) {
356
+ return refuse('invalid-describe-result', dynamicIssue);
357
+ }
358
+
359
+ if (dynamic.adapter_id !== manifest.adapter_id) {
360
+ return refuse('adapter-identity-mismatch', { manifest: manifest.adapter_id, describe: dynamic.adapter_id });
361
+ }
362
+ if (dynamic.adapter_version !== manifest.adapter_version) {
363
+ return refuse('adapter-version-mismatch', {
364
+ manifest: manifest.adapter_version, describe: dynamic.adapter_version,
365
+ });
366
+ }
367
+ if (dynamic.selected_adapter_contract_version !== selected) {
368
+ return refuse('adapter-contract-selection-mismatch', {
369
+ expected: selected, describe: dynamic.selected_adapter_contract_version,
370
+ });
371
+ }
372
+ if (dynamic.core.required_core_contract_version !== manifest.required_core_contract_version) {
373
+ return refuse('required-core-contract-version-mismatch', {
374
+ manifest: manifest.required_core_contract_version,
375
+ describe: dynamic.core.required_core_contract_version,
376
+ });
377
+ }
378
+ if (!sameJson(manifest.platforms, dynamic.platforms)) {
379
+ return refuse('adapter-platform-mismatch', { manifest: manifest.platforms, describe: dynamic.platforms });
380
+ }
381
+
382
+ return { ok: true, result: dynamic };
383
+ }