vouchington-tooling 0.29.0 → 0.31.0
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/README.md +69 -19
- package/bin/node-guard.mjs +9 -0
- package/bin/vouchington-mcp.mjs +20 -0
- package/bin/vouchington.mjs +18 -0
- package/dist/agent-blackboard/feedback-outbox-counts.d.mts +5 -0
- package/dist/agent-blackboard/feedback-outbox-counts.mjs +11 -0
- package/dist/agent-blackboard/feedback-outbox-directory.d.mts +2 -0
- package/dist/agent-blackboard/feedback-outbox-directory.mjs +16 -0
- package/dist/agent-blackboard/feedback-outbox.mjs +5 -7
- package/dist/agent-blackboard/index.d.mts +1 -0
- package/dist/agent-blackboard/index.mjs +1 -0
- package/dist/api-contract-discovery/header-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/protocol-body-status.d.mts +2 -0
- package/dist/api-contract-discovery/protocol-body-status.mjs +4 -0
- package/dist/api-contract-discovery/protocol-callback-argument-bindings.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-callback-argument-bindings.mjs +33 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.mjs +103 -0
- package/dist/api-contract-discovery/protocol-callback-mutations.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-callback-mutations.mjs +42 -0
- package/dist/api-contract-discovery/protocol-callback-registration.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-callback-registration.mjs +27 -0
- package/dist/api-contract-discovery/protocol-callback-values.d.mts +17 -0
- package/dist/api-contract-discovery/protocol-callback-values.mjs +114 -0
- package/dist/api-contract-discovery/protocol-context-alias.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-context-alias.mjs +20 -0
- package/dist/api-contract-discovery/protocol-contract-extraction.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-contract-extraction.mjs +87 -0
- package/dist/api-contract-discovery/protocol-contract-keys.d.mts +1 -0
- package/dist/api-contract-discovery/protocol-contract-keys.mjs +8 -0
- package/dist/api-contract-discovery/protocol-contract-registry.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-contract-registry.mjs +133 -0
- package/dist/api-contract-discovery/protocol-executable-path.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-executable-path.mjs +64 -0
- package/dist/api-contract-discovery/protocol-execution-path.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-execution-path.mjs +16 -0
- package/dist/api-contract-discovery/protocol-http-association.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-http-association.mjs +97 -0
- package/dist/api-contract-discovery/protocol-http-body-kinds.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-body-kinds.mjs +19 -0
- package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +6 -0
- package/dist/api-contract-discovery/protocol-http-context-callers.mjs +34 -0
- package/dist/api-contract-discovery/protocol-http-context.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-http-context.mjs +108 -0
- package/dist/api-contract-discovery/protocol-http-emission.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-emission.mjs +76 -0
- package/dist/api-contract-discovery/protocol-http-implicit-taint.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-http-implicit-taint.mjs +26 -0
- package/dist/api-contract-discovery/protocol-http-registration.d.mts +12 -0
- package/dist/api-contract-discovery/protocol-http-registration.mjs +53 -0
- package/dist/api-contract-discovery/protocol-marker-analysis.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-marker-analysis.mjs +50 -0
- package/dist/api-contract-discovery/protocol-opaque-callback.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-opaque-callback.mjs +107 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.mjs +72 -0
- package/dist/api-contract-discovery/protocol-platform-mutation-targets.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-platform-mutation-targets.mjs +89 -0
- package/dist/api-contract-discovery/protocol-platform-writes.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-platform-writes.mjs +92 -0
- package/dist/api-contract-discovery/protocol-requested-keys.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-requested-keys.mjs +45 -0
- package/dist/api-contract-discovery/protocol-sse-callers.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-sse-callers.mjs +67 -0
- package/dist/api-contract-discovery/protocol-sse-emission.d.mts +11 -0
- package/dist/api-contract-discovery/protocol-sse-emission.mjs +61 -0
- package/dist/api-contract-discovery/protocol-sse-feasible-status.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-sse-feasible-status.mjs +24 -0
- package/dist/api-contract-discovery/protocol-sse-raw-writes.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-raw-writes.mjs +70 -0
- package/dist/api-contract-discovery/protocol-sse-status.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-sse-status.mjs +84 -0
- package/dist/api-contract-discovery/protocol-sse-write-access.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-write-access.mjs +42 -0
- package/dist/api-contract-discovery/protocol-sse-write-helpers.d.mts +6 -0
- package/dist/api-contract-discovery/protocol-sse-write-helpers.mjs +66 -0
- package/dist/api-contract-discovery/protocol-sse-write-mutations.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-write-mutations.mjs +36 -0
- package/dist/api-contract-discovery/protocol-sse-write-resolution.d.mts +14 -0
- package/dist/api-contract-discovery/protocol-sse-write-resolution.mjs +79 -0
- package/dist/api-contract-discovery/protocol-status-dominance.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-status-dominance.mjs +35 -0
- package/dist/api-contract-discovery/protocol-write-receiver.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-write-receiver.mjs +79 -0
- package/dist/api-contract-discovery/query-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.mjs +30 -0
- package/dist/api-contract-discovery/registered-route-catalog.mjs +11 -7
- package/dist/api-contract-discovery/registered-route-factory-returns.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-factory-returns.mjs +29 -0
- package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +5 -1
- package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +79 -35
- package/dist/api-contract-discovery/registered-route-runtime-parameters.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-runtime-parameters.mjs +5 -0
- package/dist/api-contract-discovery/registered-route-terminal-error.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-terminal-error.mjs +98 -0
- package/dist/api-contract-discovery/request-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/response-contract-implicit.mjs +28 -10
- package/dist/api-contract-discovery/response-contract-registry.mjs +14 -1
- package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-route-analysis.mjs +22 -4
- package/dist/api-contract-discovery/response-contract-status.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-status.mjs +10 -8
- package/dist/api-contract-discovery/response-contract-types.d.mts +7 -0
- package/dist/cli/commands/agent-blackboard-flags.d.mts +4 -0
- package/dist/cli/commands/agent-blackboard-flags.mjs +34 -0
- package/dist/cli/commands/agent-blackboard.mjs +27 -35
- package/dist/cli/index.d.mts +2 -0
- package/dist/cli/index.mjs +12 -15
- package/dist/cli/parse-forwarded-commands.d.mts +0 -3
- package/dist/cli/parse-forwarded-commands.mjs +0 -2
- package/dist/cli/parse.d.mts +0 -3
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +2 -6
- package/dist/contract-schema/contract-schema-bounded-array.d.mts +1 -3
- package/dist/contract-schema/contract-schema-bounded-array.mjs +5 -0
- package/dist/contract-schema/contract-schema-object-tuple.mjs +7 -5
- package/dist/contract-schema/contract-schema-type-extractor.mjs +13 -0
- package/dist/contract-schema/contract-schema-validator.mjs +2 -2
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +0 -1
- package/dist/mcp-server/dispatch.d.mts +4 -1
- package/dist/mcp-server/dispatch.mjs +6 -4
- package/dist/mcp-server/handlers-outbox.mjs +2 -7
- package/dist/mcp-server/harness.test-helpers.d.mts +1 -0
- package/dist/mcp-server/harness.test-helpers.mjs +1 -1
- package/dist/mcp-server/main.d.mts +5 -0
- package/dist/mcp-server/main.mjs +8 -0
- package/dist/mcp-server/run.d.mts +2 -0
- package/dist/mcp-server/run.mjs +4 -1
- package/dist/mcp-server/sdk.d.mts +11 -2
- package/dist/mcp-server/sdk.mjs +37 -5
- package/dist/mcp-server/server.mjs +3 -2
- package/dist/mcp-server/tools.mjs +3 -2
- package/dist/mcp-server/worktree.d.mts +17 -10
- package/dist/mcp-server/worktree.mjs +48 -31
- package/dist/openapi-document/build-openapi-catalog-sse.d.mts +4 -0
- package/dist/openapi-document/build-openapi-catalog-sse.mjs +27 -0
- package/dist/openapi-document/build-openapi-document.mjs +5 -2
- package/dist/openapi-document/build-openapi-response.d.mts +2 -2
- package/dist/openapi-document/build-openapi-response.mjs +33 -3
- package/dist/openapi-document/build-openapi-sse-events.d.mts +11 -0
- package/dist/openapi-document/build-openapi-sse-events.mjs +46 -0
- package/dist/openapi-document/contract-schema-intersection-merge.mjs +11 -11
- package/dist/openapi-document/contract-schema-to-openapi.mjs +3 -3
- package/dist/openapi-document/openapi-route-helpers.mjs +1 -3
- package/dist/openapi-document/openapi-types.d.mts +3 -0
- package/dist/openapi-document/operation-types.d.mts +7 -0
- package/docs/mcp-server.md +83 -25
- package/package.json +11 -9
- package/scripts/build.mjs +2 -0
- package/skills/blackboard/SKILL.md +66 -4
- package/skills/manifest.json +19 -19
- package/dist/agent-harness-config/apply.d.mts +0 -4
- package/dist/agent-harness-config/apply.mjs +0 -72
- package/dist/agent-harness-config/cli.d.mts +0 -10
- package/dist/agent-harness-config/cli.mjs +0 -100
- package/dist/agent-harness-config/index.d.mts +0 -4
- package/dist/agent-harness-config/index.mjs +0 -3
- package/dist/agent-harness-config/merge-json.d.mts +0 -5
- package/dist/agent-harness-config/merge-json.mjs +0 -62
- package/dist/agent-harness-config/merge-toml.d.mts +0 -6
- package/dist/agent-harness-config/merge-toml.mjs +0 -132
- package/dist/agent-harness-config/paths.d.mts +0 -3
- package/dist/agent-harness-config/paths.mjs +0 -117
- package/dist/agent-harness-config/policy.d.mts +0 -14
- package/dist/agent-harness-config/policy.mjs +0 -51
- package/dist/agent-harness-config/prerequisites.d.mts +0 -2
- package/dist/agent-harness-config/prerequisites.mjs +0 -67
- package/dist/agent-harness-config/toml-value.d.mts +0 -6
- package/dist/agent-harness-config/toml-value.mjs +0 -69
- package/dist/agent-harness-config/types.d.mts +0 -90
- package/dist/agent-harness-config/types.mjs +0 -2
- package/docs/agent-harness-config.md +0 -134
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { catalogResponse } from './openapi-route-helpers.mjs';
|
|
2
|
+
/** Keeps a cataloged stream unavailable until its extracted variants describe SSE. */
|
|
3
|
+
export function retainCatalogSseResponse(kind, variants, rendered) {
|
|
4
|
+
if (kind !== 'sse' ||
|
|
5
|
+
variants.some((variant) => variant.mediaType === 'text/event-stream' &&
|
|
6
|
+
variant.mediaTypeKnowledge !== 'unknown' &&
|
|
7
|
+
variant.bodyKind !== 'none'))
|
|
8
|
+
return rendered;
|
|
9
|
+
const reason = [rendered.unavailableReason, catalogResponse('sse').unavailableReason]
|
|
10
|
+
.filter(Boolean)
|
|
11
|
+
.join('; ');
|
|
12
|
+
const existing = rendered.responses[200];
|
|
13
|
+
return {
|
|
14
|
+
responses: {
|
|
15
|
+
...rendered.responses,
|
|
16
|
+
200: {
|
|
17
|
+
...existing,
|
|
18
|
+
description: existing?.description ?? 'OK',
|
|
19
|
+
content: { ...existing?.content, 'text/event-stream': { schema: {} } },
|
|
20
|
+
'x-schema-unavailable': true,
|
|
21
|
+
'x-schema-unavailable-reason': reason,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
unavailable: true,
|
|
25
|
+
unavailableReason: reason,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { queryParameters } from './build-openapi-query.mjs';
|
|
2
2
|
import { buildOperationRequestBody } from './build-openapi-request.mjs';
|
|
3
3
|
import { buildOperationResponse } from './build-openapi-response.mjs';
|
|
4
|
+
import { retainCatalogSseResponse } from './build-openapi-catalog-sse.mjs';
|
|
4
5
|
import { createComponentRegistry } from './component-registry.mjs';
|
|
5
6
|
import { responseStatusCodesForContract, routeShape, } from './operation-types.mjs';
|
|
6
7
|
import { registerOpenApiErrorResponse } from './openapi-error-response.mjs';
|
|
@@ -112,10 +113,12 @@ function assertFixedNoContent(catalogRoute, variants, groupKey) {
|
|
|
112
113
|
throw new Error(`OpenAPI fixed-no-content metadata conflicts with extracted response variants for ${groupKey}`);
|
|
113
114
|
}
|
|
114
115
|
function renderOperation(catalogRoute, variants, registry) {
|
|
115
|
-
if (catalogRoute?.kind === '
|
|
116
|
+
if (catalogRoute?.kind === 'fixed-no-content')
|
|
116
117
|
return catalogResponse(catalogRoute.kind, catalogRoute.fixedStatus);
|
|
117
118
|
if (variants)
|
|
118
|
-
return buildOperationResponse(variants, registry);
|
|
119
|
+
return retainCatalogSseResponse(catalogRoute?.kind, variants, buildOperationResponse(variants, registry));
|
|
120
|
+
if (catalogRoute?.kind === 'sse')
|
|
121
|
+
return catalogResponse('sse');
|
|
119
122
|
if (catalogRoute?.kind === 'error-only')
|
|
120
123
|
return catalogResponse('error-only');
|
|
121
124
|
return catalogResponse('ordinary');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ComponentRegistry } from './component-registry.mts';
|
|
2
2
|
import { type ResponseContract } from './operation-types.mts';
|
|
3
|
-
import type {
|
|
3
|
+
import type { OpenApiResponse, OpenApiSchema } from './openapi-types.mts';
|
|
4
4
|
export declare function buildOperationResponse(variants: ResponseContract[], registry: ComponentRegistry): {
|
|
5
|
-
responses: Record<number,
|
|
5
|
+
responses: Record<number, OpenApiResponse>;
|
|
6
6
|
unavailable: boolean;
|
|
7
7
|
unavailableReason?: string;
|
|
8
8
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { STATUS_CODES } from 'node:http';
|
|
2
|
+
import { collectSseEventSchemas, renderSseEvents, } from './build-openapi-sse-events.mjs';
|
|
2
3
|
import { nodeToOpenApi } from './contract-schema-to-openapi.mjs';
|
|
3
4
|
import { responseStatusCodesForContract } from './operation-types.mjs';
|
|
4
5
|
export function buildOperationResponse(variants, registry) {
|
|
@@ -7,6 +8,8 @@ export function buildOperationResponse(variants, registry) {
|
|
|
7
8
|
const operationFailures = [];
|
|
8
9
|
for (const contract of variants) {
|
|
9
10
|
const converted = convertContract(contract, registry);
|
|
11
|
+
const events = collectSseEventSchemas(contract, registry);
|
|
12
|
+
operationFailures.push(...events.failureReasons);
|
|
10
13
|
if (converted.failureReason)
|
|
11
14
|
operationFailures.push(converted.failureReason);
|
|
12
15
|
if (contract.statusKnowledge === 'unknown' && !converted.failureReason)
|
|
@@ -17,6 +20,8 @@ export function buildOperationResponse(variants, registry) {
|
|
|
17
20
|
if (converted.failureReason)
|
|
18
21
|
reasons.push(converted.failureReason);
|
|
19
22
|
bodyless.set(status, reasons);
|
|
23
|
+
if (contract.includeDefaultError)
|
|
24
|
+
addDefaultError(content, status, registry);
|
|
20
25
|
continue;
|
|
21
26
|
}
|
|
22
27
|
const mediaType = mediaTypeFor(contract);
|
|
@@ -25,12 +30,21 @@ export function buildOperationResponse(variants, registry) {
|
|
|
25
30
|
continue;
|
|
26
31
|
}
|
|
27
32
|
const byMedia = content.get(status) ?? new Map();
|
|
28
|
-
const bucket = byMedia.get(mediaType) ?? {
|
|
29
|
-
|
|
33
|
+
const bucket = byMedia.get(mediaType) ?? {
|
|
34
|
+
schemas: [],
|
|
35
|
+
failureReasons: [],
|
|
36
|
+
sseEvents: new Map(),
|
|
37
|
+
};
|
|
38
|
+
bucket.schemas.push(mediaType === 'text/event-stream' ? { type: 'string' } : (converted.schema ?? {}));
|
|
39
|
+
bucket.failureReasons.push(...events.failureReasons);
|
|
30
40
|
if (converted.failureReason)
|
|
31
41
|
bucket.failureReasons.push(converted.failureReason);
|
|
42
|
+
for (const [name, schemas] of events.schemas)
|
|
43
|
+
bucket.sseEvents.set(name, [...(bucket.sseEvents.get(name) ?? []), ...schemas]);
|
|
32
44
|
byMedia.set(mediaType, bucket);
|
|
33
45
|
content.set(status, byMedia);
|
|
46
|
+
if (contract.includeDefaultError)
|
|
47
|
+
addDefaultError(content, status, registry);
|
|
34
48
|
}
|
|
35
49
|
}
|
|
36
50
|
const responses = {};
|
|
@@ -45,6 +59,17 @@ export function buildOperationResponse(variants, registry) {
|
|
|
45
59
|
...(reasons.length > 0 ? { unavailableReason: reasons.join('; ') } : {}),
|
|
46
60
|
};
|
|
47
61
|
}
|
|
62
|
+
function addDefaultError(content, status, registry) {
|
|
63
|
+
const byMedia = content.get(status) ?? new Map();
|
|
64
|
+
const bucket = byMedia.get('application/json') ?? {
|
|
65
|
+
schemas: [],
|
|
66
|
+
failureReasons: [],
|
|
67
|
+
sseEvents: new Map(),
|
|
68
|
+
};
|
|
69
|
+
bucket.schemas.push({ $ref: `#/components/schemas/${registry.refName('ErrorBody')}` });
|
|
70
|
+
byMedia.set('application/json', bucket);
|
|
71
|
+
content.set(status, byMedia);
|
|
72
|
+
}
|
|
48
73
|
function renderStatusResponse(status, byMedia, bodyless, operationFailures) {
|
|
49
74
|
const hasBodyless = bodyless.has(status);
|
|
50
75
|
const conflictReason = byMedia && hasBodyless
|
|
@@ -67,7 +92,12 @@ function renderStatusResponse(status, byMedia, bodyless, operationFailures) {
|
|
|
67
92
|
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
68
93
|
.map(([mediaType, bucket]) => [
|
|
69
94
|
mediaType,
|
|
70
|
-
{
|
|
95
|
+
{
|
|
96
|
+
schema: mergeVariantSchemas(bucket.schemas),
|
|
97
|
+
...(bucket.sseEvents.size
|
|
98
|
+
? { 'x-sse-events': renderSseEvents(bucket.sseEvents) }
|
|
99
|
+
: {}),
|
|
100
|
+
},
|
|
71
101
|
])),
|
|
72
102
|
}
|
|
73
103
|
: {}),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ComponentRegistry } from './component-registry.mts';
|
|
2
|
+
import type { ResponseContract } from './operation-types.mts';
|
|
3
|
+
import type { OpenApiSchema } from './openapi-types.mts';
|
|
4
|
+
export type SseEventSchemas = Map<string, OpenApiSchema[]>;
|
|
5
|
+
export declare function collectSseEventSchemas(contract: ResponseContract, registry: ComponentRegistry): {
|
|
6
|
+
schemas: SseEventSchemas;
|
|
7
|
+
failureReasons: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function renderSseEvents(schemas: SseEventSchemas): Record<string, {
|
|
10
|
+
dataSchema: OpenApiSchema;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { nodeToOpenApi } from './contract-schema-to-openapi.mjs';
|
|
2
|
+
export function collectSseEventSchemas(contract, registry) {
|
|
3
|
+
const schemas = new Map();
|
|
4
|
+
const failureReasons = [];
|
|
5
|
+
if (contract.mediaType !== 'text/event-stream' && !contract.sseEvents)
|
|
6
|
+
return { schemas, failureReasons };
|
|
7
|
+
if (contract.mediaType !== 'text/event-stream' || contract.bodyKind === 'none')
|
|
8
|
+
return {
|
|
9
|
+
schemas,
|
|
10
|
+
failureReasons: ['SSE event metadata requires a text/event-stream response body'],
|
|
11
|
+
};
|
|
12
|
+
if (!contract.sseEvents?.length)
|
|
13
|
+
return { schemas, failureReasons: ['SSE event payload contracts are missing'] };
|
|
14
|
+
for (const { eventName, contract: event } of contract.sseEvents) {
|
|
15
|
+
if (!eventName.trim() || event.schema.root.type === 'unknown') {
|
|
16
|
+
failureReasons.push('SSE event payload or name is not statically known');
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
let schema;
|
|
20
|
+
try {
|
|
21
|
+
schema = nodeToOpenApi(event.schema.root, {
|
|
22
|
+
definitions: event.schema.definitions,
|
|
23
|
+
refName: registry.refName,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
failureReasons.push(String(error));
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
registry.register(event.source, event.schema.definitions);
|
|
31
|
+
const variants = schemas.get(eventName) ?? [];
|
|
32
|
+
variants.push(schema);
|
|
33
|
+
schemas.set(eventName, variants);
|
|
34
|
+
}
|
|
35
|
+
return { schemas, failureReasons };
|
|
36
|
+
}
|
|
37
|
+
export function renderSseEvents(schemas) {
|
|
38
|
+
return Object.fromEntries([...schemas.entries()]
|
|
39
|
+
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
40
|
+
.map(([name, variants]) => {
|
|
41
|
+
const unique = [
|
|
42
|
+
...new Map(variants.map((schema) => [JSON.stringify(schema), schema])).values(),
|
|
43
|
+
].toSorted((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
44
|
+
return [name, { dataSchema: unique.length === 1 ? unique[0] : { anyOf: unique } }];
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
@@ -11,10 +11,10 @@ export function intersectionToOpenApi(node, ctx, nodeToOpenApi) {
|
|
|
11
11
|
if (resolved.some((variant) => variant === undefined)) {
|
|
12
12
|
return { allOf: node.variants.map((variant) => nodeToOpenApi(variant, ctx)) };
|
|
13
13
|
}
|
|
14
|
-
const properties =
|
|
14
|
+
const properties = new Map();
|
|
15
15
|
for (const variant of resolved) {
|
|
16
16
|
for (const [key, property] of Object.entries(variant.properties)) {
|
|
17
|
-
const existing = properties
|
|
17
|
+
const existing = properties.get(key);
|
|
18
18
|
if (existing &&
|
|
19
19
|
hashSchemaNode(existing.schema, ctx) !== hashSchemaNode(property.schema, ctx)) {
|
|
20
20
|
// A member re-declaring a shared key with a strictly narrower type is a common, valid
|
|
@@ -23,33 +23,33 @@ export function intersectionToOpenApi(node, ctx, nodeToOpenApi) {
|
|
|
23
23
|
// narrower type, not a conflict. Prefer whichever side is the narrower one; only two
|
|
24
24
|
// genuinely incompatible schemas (neither a subset of the other) are a real conflict.
|
|
25
25
|
if (isNarrowerVariant(property.schema, existing.schema, ctx)) {
|
|
26
|
-
properties
|
|
26
|
+
properties.set(key, {
|
|
27
27
|
schema: property.schema,
|
|
28
28
|
required: existing.required || property.required,
|
|
29
|
-
};
|
|
29
|
+
});
|
|
30
30
|
continue;
|
|
31
31
|
}
|
|
32
32
|
/* v8 ignore next 6 -- symmetric of the previous branch; covered by order-swapped fixtures */
|
|
33
33
|
if (isNarrowerVariant(existing.schema, property.schema, ctx)) {
|
|
34
|
-
properties
|
|
34
|
+
properties.set(key, {
|
|
35
35
|
schema: existing.schema,
|
|
36
36
|
required: existing.required || property.required,
|
|
37
|
-
};
|
|
37
|
+
});
|
|
38
38
|
continue;
|
|
39
39
|
}
|
|
40
40
|
throw new Error(`Cannot merge intersection: property "${key}" has conflicting schemas across members`);
|
|
41
41
|
}
|
|
42
|
-
properties
|
|
42
|
+
properties.set(key, {
|
|
43
43
|
schema: property.schema,
|
|
44
44
|
required: existing?.required || property.required,
|
|
45
|
-
};
|
|
45
|
+
});
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
const sortedKeys =
|
|
49
|
-
const required = sortedKeys.filter((key) => properties
|
|
48
|
+
const sortedKeys = [...properties.keys()].toSorted();
|
|
49
|
+
const required = sortedKeys.filter((key) => properties.get(key).required);
|
|
50
50
|
return {
|
|
51
51
|
type: 'object',
|
|
52
|
-
properties: Object.fromEntries(sortedKeys.map((key) => [key, nodeToOpenApi(properties
|
|
52
|
+
properties: Object.fromEntries(sortedKeys.map((key) => [key, nodeToOpenApi(properties.get(key).schema, ctx)])),
|
|
53
53
|
...(required.length > 0 ? { required } : {}),
|
|
54
54
|
additionalProperties: false,
|
|
55
55
|
};
|
|
@@ -42,17 +42,17 @@ function tupleToOpenApi(node, ctx) {
|
|
|
42
42
|
}
|
|
43
43
|
function objectToOpenApi(node, ctx) {
|
|
44
44
|
const keys = Object.keys(node.properties).toSorted();
|
|
45
|
-
const properties =
|
|
45
|
+
const properties = new Map();
|
|
46
46
|
const required = [];
|
|
47
47
|
for (const key of keys) {
|
|
48
48
|
const property = node.properties[key];
|
|
49
|
-
properties
|
|
49
|
+
properties.set(key, nodeToOpenApi(property.schema, ctx));
|
|
50
50
|
if (property.required)
|
|
51
51
|
required.push(key);
|
|
52
52
|
}
|
|
53
53
|
return {
|
|
54
54
|
type: 'object',
|
|
55
|
-
properties,
|
|
55
|
+
properties: Object.fromEntries(properties),
|
|
56
56
|
...(required.length > 0 ? { required } : {}),
|
|
57
57
|
additionalProperties: node.additionalProperties === false ? false : nodeToOpenApi(node.additionalProperties, ctx),
|
|
58
58
|
};
|
|
@@ -34,11 +34,9 @@ export function catalogResponse(kind, fixedStatus) {
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
if (kind === 'error-only') {
|
|
37
|
-
const reason = 'registered route has no success response';
|
|
38
37
|
return {
|
|
39
38
|
responses: { 405: { $ref: '#/components/responses/Error' } },
|
|
40
|
-
unavailable:
|
|
41
|
-
unavailableReason: reason,
|
|
39
|
+
unavailable: false,
|
|
42
40
|
};
|
|
43
41
|
}
|
|
44
42
|
const reason = kind === 'sse'
|
|
@@ -39,6 +39,9 @@ export type OpenApiResponse = {
|
|
|
39
39
|
description: string;
|
|
40
40
|
content?: Record<string, {
|
|
41
41
|
schema: OpenApiSchema;
|
|
42
|
+
'x-sse-events'?: Record<string, {
|
|
43
|
+
dataSchema: OpenApiSchema;
|
|
44
|
+
}>;
|
|
42
45
|
}>;
|
|
43
46
|
'x-schema-unavailable'?: true;
|
|
44
47
|
'x-schema-unavailable-reason'?: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ExtractedResponseContract } from '../contract-schema/types.mts';
|
|
1
2
|
import type { ContractSchema } from './contract-schema-types.mts';
|
|
2
3
|
import type { OpenApiQueryContract } from './query-types.mts';
|
|
3
4
|
export type ResponseContract = {
|
|
@@ -11,6 +12,12 @@ export type ResponseContract = {
|
|
|
11
12
|
mediaType?: string;
|
|
12
13
|
mediaTypeKnowledge?: 'known' | 'none' | 'unknown';
|
|
13
14
|
unavailableReason?: string;
|
|
15
|
+
sseEvents?: readonly {
|
|
16
|
+
eventName: string;
|
|
17
|
+
contract: ExtractedResponseContract;
|
|
18
|
+
}[];
|
|
19
|
+
/** Merge the framework error body into this explicit response's JSON variants. */
|
|
20
|
+
includeDefaultError?: boolean;
|
|
14
21
|
};
|
|
15
22
|
export type RequestContract = {
|
|
16
23
|
source: string;
|
package/docs/mcp-server.md
CHANGED
|
@@ -11,30 +11,68 @@ are later phases.
|
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Install the server once per machine, in its own dedicated directory, not in a project or a
|
|
15
|
+
worktree: `pnpm add` installs into the directory it runs in. The server needs these packages
|
|
16
|
+
installed next to `vouchington-tooling`. `agent-blackboard` and `@modelcontextprotocol/sdk` are
|
|
17
|
+
optional peer dependencies, so nothing else in the CLI requires them. Pin exact versions; replace
|
|
18
|
+
the placeholders with the current releases:
|
|
16
19
|
|
|
17
20
|
```bash
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
mkdir -p ~/.local/share/vouchington-mcp
|
|
22
|
+
cd ~/.local/share/vouchington-mcp
|
|
23
|
+
pnpm init
|
|
24
|
+
pnpm add --save-exact vouchington-tooling@<version> agent-blackboard@<version>
|
|
25
|
+
pnpm add --save-exact @modelcontextprotocol/sdk@<version> zod@<version>
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
Launch the server by the absolute path into that directory, for example
|
|
29
|
+
`/abs/node ~/.local/share/vouchington-mcp/node_modules/vouchington-tooling/bin/vouchington-mcp.mjs`
|
|
30
|
+
with `~` expanded to an absolute path (see [Launch](#launch)).
|
|
31
|
+
|
|
32
|
+
`zod` is required by the SDK. Both `agent-blackboard` and the SDK are resolved from the server's own
|
|
33
|
+
install, relative to this package's module, never from the worktree a tool call names, so
|
|
34
|
+
repositories do not need to depend on `agent-blackboard`. When either is missing, `vouchington mcp`
|
|
35
|
+
writes a message naming the package and the install directory to add it in to stderr and exits with
|
|
36
|
+
status 1.
|
|
25
37
|
|
|
26
38
|
The connection comes from `AGENT_BLACKBOARD_URL` and `AGENT_BLACKBOARD_TOKEN` in the server's
|
|
27
39
|
environment. The server never searches for, prints, or mints them.
|
|
28
40
|
|
|
41
|
+
## CLI fallback
|
|
42
|
+
|
|
43
|
+
If MCP is unavailable, use `vouchington agent-blackboard journal append|entries|flush|status` as described
|
|
44
|
+
in the blackboard skill. Record the fallback and delivery status in the journal and tell the human. The CLI resolves its
|
|
45
|
+
client from its own installation, so install `agent-blackboard` alongside that trusted CLI.
|
|
46
|
+
Run the script with a verified absolute `node` and a cleared environment (`env -i`), never through
|
|
47
|
+
its `#!/usr/bin/env node` line, which takes `node` from a possibly repository-controlled `PATH`.
|
|
48
|
+
A missing MCP connection does not waive persistence or readback requirements.
|
|
49
|
+
|
|
29
50
|
## Launch
|
|
30
51
|
|
|
31
52
|
```bash
|
|
32
53
|
vouchington mcp
|
|
33
54
|
```
|
|
34
55
|
|
|
35
|
-
The
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
The stable entry point, for a harness that launches the server without a shell or a `PATH`
|
|
57
|
+
lookup, is this package-relative file:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
node_modules/vouchington-tooling/bin/vouchington-mcp.mjs
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Run it with an absolute Node, for example
|
|
64
|
+
`/abs/node /abs/machine/node_modules/vouchington-tooling/bin/vouchington-mcp.mjs`. It is also the
|
|
65
|
+
`vouchington-mcp` bin (`node_modules/.bin/vouchington-mcp`) and the `vouchington-tooling/mcp-server/bin`
|
|
66
|
+
export. It takes no arguments, skips CLI argument parsing, and is a hand-written plain-JavaScript
|
|
67
|
+
file that a CLI refactor does not move; a test pins the path. Both it and `bin/vouchington.mjs` check
|
|
68
|
+
the Node version first: on Node older than 24 they print one line to stderr, such as
|
|
69
|
+
`vouchington requires Node >=24 (found 18.19.0)`, and exit with status 1.
|
|
70
|
+
|
|
71
|
+
The command takes no arguments and may be launched with any working directory, including one outside
|
|
72
|
+
every git repository (for example `~`). When the working directory is inside a git worktree, that
|
|
73
|
+
worktree's top level is the default for tools called without `worktree`. Otherwise those calls fail
|
|
74
|
+
with a tool error asking for an explicit `worktree`. stdout carries only protocol frames, and
|
|
75
|
+
diagnostics go to stderr.
|
|
38
76
|
|
|
39
77
|
## Tools
|
|
40
78
|
|
|
@@ -113,42 +151,62 @@ instructions shows it to the agent.
|
|
|
113
151
|
|
|
114
152
|
## Worktree validation
|
|
115
153
|
|
|
116
|
-
`worktree` must be an absolute path that, after resolving symlinks,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
154
|
+
`worktree` is checked on every call. It must be an absolute path that, after resolving symlinks,
|
|
155
|
+
equals the top level of a git worktree, that is, what `git rev-parse --show-toplevel` prints when
|
|
156
|
+
run in it. Any git worktree on the machine is accepted, including one of a repository unrelated to
|
|
157
|
+
the launch directory, so the tools can act on every checkout the user can reach. Worktrees created
|
|
158
|
+
after launch are accepted and removed ones are refused. Rejected: a subdirectory of a worktree, a
|
|
159
|
+
relative or nonexistent path, and a directory that is not in a git worktree. Git runs without
|
|
160
|
+
`GIT_*` variables, so `GIT_DIR` cannot redirect the check, and with `LC_ALL=C` and no `LANGUAGE`, so
|
|
161
|
+
its "not a git repository" diagnostic is never translated.
|
|
162
|
+
|
|
163
|
+
`worktree` only chooses where the outbox lives. It is not a trust boundary for loading code: the
|
|
164
|
+
server never imports modules from it, and a tool that needs to must first get a repository allowlist
|
|
165
|
+
from the machine registration.
|
|
166
|
+
|
|
167
|
+
When `worktree` is omitted, the launch directory's worktree top level is used if the launch
|
|
168
|
+
directory is inside one, and it is validated like any explicit path. If the server was launched
|
|
169
|
+
outside a git worktree, the call fails with a tool error asking for an explicit `worktree`.
|
|
122
170
|
|
|
123
171
|
## Registering the server
|
|
124
172
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
173
|
+
Register the machine-wide installation from [Install](#install) at user level, with absolute paths.
|
|
174
|
+
Never check a registration into a repository: it names a path and a Node that exist only on your
|
|
175
|
+
machine, and every worktree inherits it from your user config. Replace `/abs/node` with the absolute
|
|
176
|
+
path of a Node >= 24 and `/home/you` with your home directory (neither file expands `~`). Set the
|
|
177
|
+
blackboard credentials in the environment you start the harness from.
|
|
128
178
|
|
|
129
|
-
Claude Code,
|
|
179
|
+
Claude Code, `mcpServers` in `~/.claude.json`:
|
|
130
180
|
|
|
131
181
|
```json
|
|
132
182
|
{
|
|
133
183
|
"mcpServers": {
|
|
134
184
|
"vouchington-tooling": {
|
|
135
|
-
"command": "
|
|
136
|
-
"args": [
|
|
185
|
+
"command": "/abs/node",
|
|
186
|
+
"args": [
|
|
187
|
+
"/home/you/.local/share/vouchington-mcp/node_modules/vouchington-tooling/bin/vouchington-mcp.mjs"
|
|
188
|
+
]
|
|
137
189
|
}
|
|
138
190
|
}
|
|
139
191
|
}
|
|
140
192
|
```
|
|
141
193
|
|
|
142
|
-
Codex,
|
|
194
|
+
Codex, `~/.codex/config.toml`:
|
|
143
195
|
|
|
144
196
|
```toml
|
|
145
197
|
[mcp_servers.vouchington-tooling]
|
|
146
|
-
command = "
|
|
147
|
-
args = [
|
|
198
|
+
command = "/abs/node"
|
|
199
|
+
args = [
|
|
200
|
+
"/home/you/.local/share/vouchington-mcp/node_modules/vouchington-tooling/bin/vouchington-mcp.mjs",
|
|
201
|
+
]
|
|
202
|
+
env_vars = ["AGENT_BLACKBOARD_URL", "AGENT_BLACKBOARD_TOKEN"]
|
|
148
203
|
default_tools_approval_mode = "approve"
|
|
149
204
|
required = false
|
|
150
205
|
```
|
|
151
206
|
|
|
207
|
+
`env_vars` forwards those variables from Codex's own environment. Codex does not expand `${VAR}`
|
|
208
|
+
inside `env`, so do not write the credentials there.
|
|
209
|
+
|
|
152
210
|
Approving every tool of the server at once:
|
|
153
211
|
|
|
154
212
|
- Claude Code permission rule: `mcp__vouchington-tooling__*`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Vouchington CLI and extractable tooling libraries.",
|
|
5
5
|
"homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
"directory": "packages/vouchington-tooling"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
|
-
"vouchington": "
|
|
17
|
+
"vouchington": "bin/vouchington.mjs",
|
|
18
|
+
"vouchington-mcp": "bin/vouchington-mcp.mjs"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"ast-grep",
|
|
22
|
+
"bin",
|
|
21
23
|
"dist",
|
|
22
24
|
"docs",
|
|
23
25
|
"skills",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"import": "./dist/agent-blackboard/index.mjs",
|
|
69
71
|
"default": "./dist/agent-blackboard/index.mjs"
|
|
70
72
|
},
|
|
73
|
+
"./mcp-server/bin": "./bin/vouchington-mcp.mjs",
|
|
71
74
|
"./session-friction": {
|
|
72
75
|
"types": "./dist/session-friction/index.d.mts",
|
|
73
76
|
"import": "./dist/session-friction/index.mjs",
|
|
@@ -113,11 +116,6 @@
|
|
|
113
116
|
"import": "./dist/ast-grep-pack/index.mjs",
|
|
114
117
|
"default": "./dist/ast-grep-pack/index.mjs"
|
|
115
118
|
},
|
|
116
|
-
"./agent-harness-config": {
|
|
117
|
-
"types": "./dist/agent-harness-config/index.d.mts",
|
|
118
|
-
"import": "./dist/agent-harness-config/index.mjs",
|
|
119
|
-
"default": "./dist/agent-harness-config/index.mjs"
|
|
120
|
-
},
|
|
121
119
|
"./vitest-blob-manifest": {
|
|
122
120
|
"types": "./dist/vitest-blob-manifest/index.d.mts",
|
|
123
121
|
"import": "./dist/vitest-blob-manifest/index.mjs",
|
|
@@ -352,7 +350,6 @@
|
|
|
352
350
|
"picomatch": "4.0.7",
|
|
353
351
|
"remark": "15.0.1",
|
|
354
352
|
"remark-gfm": "4.0.1",
|
|
355
|
-
"smol-toml": "1.9.0",
|
|
356
353
|
"spdx-expression-parse": "5.0.0",
|
|
357
354
|
"yaml": "2.9.1"
|
|
358
355
|
},
|
|
@@ -361,14 +358,19 @@
|
|
|
361
358
|
"@modelcontextprotocol/sdk": "1.31.0",
|
|
362
359
|
"@types/picomatch": "^4.0.3",
|
|
363
360
|
"@types/spdx-expression-parse": "4.0.0",
|
|
364
|
-
"agent-blackboard": "^0.6.0"
|
|
361
|
+
"agent-blackboard": "^0.6.0",
|
|
362
|
+
"smol-toml": "1.9.0"
|
|
365
363
|
},
|
|
366
364
|
"peerDependencies": {
|
|
367
365
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
366
|
+
"agent-blackboard": "^0.6.0",
|
|
368
367
|
"typescript": ">=5",
|
|
369
368
|
"vitest": ">=5"
|
|
370
369
|
},
|
|
371
370
|
"peerDependenciesMeta": {
|
|
371
|
+
"agent-blackboard": {
|
|
372
|
+
"optional": true
|
|
373
|
+
},
|
|
372
374
|
"@modelcontextprotocol/sdk": {
|
|
373
375
|
"optional": true
|
|
374
376
|
},
|
package/scripts/build.mjs
CHANGED
|
@@ -34,3 +34,5 @@ await copyFile(
|
|
|
34
34
|
new URL('../skills/manifest.json', import.meta.url),
|
|
35
35
|
)
|
|
36
36
|
await chmod(new URL('../dist/cli/index.mjs', import.meta.url), 0o755)
|
|
37
|
+
await chmod(new URL('../bin/vouchington.mjs', import.meta.url), 0o755)
|
|
38
|
+
await chmod(new URL('../bin/vouchington-mcp.mjs', import.meta.url), 0o755)
|
|
@@ -35,9 +35,71 @@ launch worktree by default). Writing or retrying an entry needs no provider skil
|
|
|
35
35
|
`--file` flag, or replay command.
|
|
36
36
|
Search for `journal_append` (by bare name, so every prefix matches) before concluding the server is
|
|
37
37
|
unavailable; a deferred harness lists the tool only on request. If the search finds no such tool
|
|
38
|
-
because the server is not registered
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
because the server is not registered or not connected, use the supported CLI fallback below.
|
|
39
|
+
Tell the human that MCP is unavailable and which CLI is being used. Record the fallback reason,
|
|
40
|
+
entrypoint, affected operations, and observed delivery status in the caller's journal before
|
|
41
|
+
continuing primary work. Do not treat a permission denial as tool unavailability or bypass a host
|
|
42
|
+
approval requirement through the CLI. If neither path persists the note, stop and report the blocker.
|
|
43
|
+
|
|
44
|
+
## CLI fallback
|
|
45
|
+
|
|
46
|
+
Use a trusted machine-installed `vouchington` executable at a verified absolute path outside the
|
|
47
|
+
consumer worktree. Resolve its symlinks and verify the installation's source and version before
|
|
48
|
+
using credentials or requesting an unsandboxed run; its runtime dependencies must also belong to
|
|
49
|
+
that trusted installation. The CLI resolves its client relative to its own installation; installing
|
|
50
|
+
`agent-blackboard` only in the consumer worktree is insufficient for CLI fallback. Do not resolve the executable through the consumer's `node_modules/.bin`,
|
|
51
|
+
`pnpm exec`, or repository-controlled `PATH`. Set `BLACKBOARD_CLI` below to that verified absolute
|
|
52
|
+
script path, and bind any approval request to the same interpreter, script, and arguments.
|
|
53
|
+
Never execute the script directly: its `#!/usr/bin/env node` line would pick whichever `node` comes
|
|
54
|
+
first on `PATH`, which a consumer worktree can control. Set `BLACKBOARD_NODE` to a verified
|
|
55
|
+
absolute Node 24+ binary outside the consumer worktree, and run it with `env -i` so only `HOME`, a
|
|
56
|
+
fixed system `PATH`, and the two blackboard variables reach it. That also drops `NODE_OPTIONS` and
|
|
57
|
+
other loader variables. If no trusted CLI is
|
|
58
|
+
available, report the blocker; do not escalate a consumer-supplied executable. For local development,
|
|
59
|
+
the reviewed source CLI may run inside the sandbox, subject to the harness's credential policy.
|
|
60
|
+
`journal append` invokes the same validated writer as MCP, ensures the exact session identity,
|
|
61
|
+
patches its repository union, and verifies delivery. Use a temporary `0600` markdown input file,
|
|
62
|
+
remove it after the call, and inspect the JSON result: exit zero can mean retained pending feedback.
|
|
63
|
+
The input file is transport to the supported writer, never a replacement journal. Append returns
|
|
64
|
+
`pendingCount` for the caller and `worktreePendingCount` for the whole outbox. Use `journal status`
|
|
65
|
+
or pass `--session-id` to `journal flush` for the same two counts; an unfiltered legacy flush reports
|
|
66
|
+
only the whole-outbox count.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
blackboard() {
|
|
70
|
+
env -i HOME="$HOME" PATH=/usr/bin:/bin \
|
|
71
|
+
AGENT_BLACKBOARD_URL="$AGENT_BLACKBOARD_URL" \
|
|
72
|
+
AGENT_BLACKBOARD_TOKEN="$AGENT_BLACKBOARD_TOKEN" \
|
|
73
|
+
"$BLACKBOARD_NODE" "$BLACKBOARD_CLI" agent-blackboard "$@"
|
|
74
|
+
}
|
|
75
|
+
blackboard journal append \
|
|
76
|
+
--session-id SESSION --agent AGENT --version VERSION \
|
|
77
|
+
--mode interactive --source-event-id EVENT --work-outcome unknown \
|
|
78
|
+
--repository owner/name --coverage-status not-assessed --dropped-count 0 \
|
|
79
|
+
--outbox-directory "$PWD/.local/blackboard-outbox" --file NOTE
|
|
80
|
+
blackboard journal entries --session-id SESSION
|
|
81
|
+
blackboard journal status \
|
|
82
|
+
--session-id SESSION --outbox-directory "$PWD/.local/blackboard-outbox"
|
|
83
|
+
blackboard journal flush \
|
|
84
|
+
--session-id SESSION --outbox-directory "$PWD/.local/blackboard-outbox"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Pass `--parent-session-id` for a child, repeat `--repository` for additional repositories, and use
|
|
88
|
+
explicit coverage sources when assessed. Omit `--timestamp` so the writer owns time. Autonomous
|
|
89
|
+
mode omits `--outbox-directory` and still requires fresh online admission plus readback.
|
|
90
|
+
For session-only or snapshot operations not exposed by this CLI, use the upstream
|
|
91
|
+
`agent-blackboard` CLI under its skill's operation contract; preserve exact identities and tags.
|
|
92
|
+
A sandboxed fallback may only write the outbox: the machine's harness sandbox can withhold
|
|
93
|
+
`AGENT_BLACKBOARD_TOKEN`, so it cannot deliver or read back. If delivery or readback needs the
|
|
94
|
+
credentials and the sandbox withholds them, run that one command unsandboxed through the harness's
|
|
95
|
+
normal approval path for running one command outside the sandbox, with each such run approved
|
|
96
|
+
individually. Never read, print, copy, or export the token
|
|
97
|
+
to work around the sandbox. If approval is denied, leave the record pending in the outbox, report
|
|
98
|
+
`pendingCount`, and tell the human.
|
|
99
|
+
Return to MCP when available and journal recovery. Never silently downgrade envelope validation,
|
|
100
|
+
retention, or delivery checks because the transport changed.
|
|
101
|
+
|
|
102
|
+
## Journaling policy
|
|
41
103
|
|
|
42
104
|
1. Capture consequential observations before filing an issue: failed or recovered checks, denied or
|
|
43
105
|
approved permissions, repeated fixes, scope changes, first-party tool behavior, and architectural
|
|
@@ -65,7 +127,7 @@ it (the consumer wrapper documents it). Do not fall back to a CLI command.
|
|
|
65
127
|
`journal_append` reports it as pending. Report `pendingCount` (this session's unsent records),
|
|
66
128
|
and `worktreePendingCount` when other sessions' records are also unsent, from `journal_append`
|
|
67
129
|
or `outbox_status`. Deliver with `outbox_flush` once the provider is reachable, and continue
|
|
68
|
-
primary work only after persistence succeeds. A full,
|
|
130
|
+
primary work only after persistence succeeds. A full, corrupt, or unwritable outbox blocks
|
|
69
131
|
capture; never evict or silently discard an unsent record.
|
|
70
132
|
5. Autonomous runners require online `session_ensure`, a fresh admission `journal_append`, and
|
|
71
133
|
readback before launching an attempt. Give each attempt's admission entry a `sourceEventId`
|