@aiwg/cli 2026.7.25 → 2026.8.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 (79) hide show
  1. package/README.md +33 -0
  2. package/agentic/code/providers/capability-matrix.yaml +511 -0
  3. package/agentic/code/providers/model-capabilities.v1.json +120 -0
  4. package/agentic/code/providers/model-catalog.v1.json +96 -0
  5. package/agentic/code/providers/model-policy-evaluations.v1.json +50 -0
  6. package/agentic/code/providers/premium-model-allowlist.v1.json +36 -0
  7. package/bin/aiwg.mjs +14 -10
  8. package/dist/src/api/index.d.ts +1 -0
  9. package/dist/src/api/index.js +1 -0
  10. package/dist/src/artifacts/cli.js +55 -10
  11. package/dist/src/artifacts/fortemi-shard-export.js +107 -18
  12. package/dist/src/artifacts/types.js +4 -0
  13. package/dist/src/auth/client.js +209 -0
  14. package/dist/src/auth/config.js +38 -0
  15. package/dist/src/auth/credential-store.js +141 -0
  16. package/dist/src/auth/resource-credentials.js +25 -0
  17. package/dist/src/auth/types.js +2 -0
  18. package/dist/src/channel/manager.mjs +5 -5
  19. package/dist/src/cli/handlers/auth.js +125 -0
  20. package/dist/src/cli/handlers/help.js +1 -0
  21. package/dist/src/cli/handlers/index.js +6 -2
  22. package/dist/src/cli/handlers/job.js +97 -0
  23. package/dist/src/cli/handlers/resource-versions.js +2 -0
  24. package/dist/src/cli/handlers/runtime-info.js +2 -2
  25. package/dist/src/cli/handlers/serve.js +2 -2
  26. package/dist/src/cli/handlers/sessions.js +211 -5
  27. package/dist/src/cli/handlers/steward.js +16 -3
  28. package/dist/src/cli/handlers/subcommands.js +10 -1
  29. package/dist/src/cli/handlers/use.js +342 -43
  30. package/dist/src/config/gitignore.js +1 -0
  31. package/dist/src/extensions/commands/definitions.js +49 -5
  32. package/dist/src/extensions/manifest.js +1 -0
  33. package/dist/src/features/catalog.js +3 -3
  34. package/dist/src/jobs/executor.js +83 -0
  35. package/dist/src/jobs/flow.js +106 -0
  36. package/dist/src/jobs/gitea.js +91 -0
  37. package/dist/src/jobs/render.js +53 -0
  38. package/dist/src/jobs/runner.js +315 -0
  39. package/dist/src/jobs/types.js +3 -0
  40. package/dist/src/memory/canonical-context.js +342 -0
  41. package/dist/src/memory/context-pack.js +282 -0
  42. package/dist/src/memory/index.js +4 -0
  43. package/dist/src/memory/intake.js +118 -0
  44. package/dist/src/providers/capability-matrix.js +11 -4
  45. package/dist/src/providers/capability-matrix.yaml +39 -42
  46. package/dist/src/resources/resolver.js +1 -0
  47. package/dist/src/resources/web-release.d.ts +3 -1
  48. package/dist/src/resources/web-release.js +14 -6
  49. package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
  50. package/dist/src/serve/fleet-mission-conductor.js +293 -0
  51. package/dist/src/sessions/analytics.js +303 -0
  52. package/dist/src/sessions/importer.js +7 -1
  53. package/dist/src/sessions/index.js +2 -0
  54. package/dist/src/sessions/output-registration.js +338 -0
  55. package/dist/src/sessions/policy.js +1 -1
  56. package/dist/src/sessions/promotion.js +73 -2
  57. package/dist/src/sessions/repository.js +215 -1
  58. package/dist/src/update/notifier.mjs +13 -2
  59. package/package.json +17 -10
  60. package/tools/_resolve-impl.mjs +74 -0
  61. package/tools/agents/deploy-agents.mjs +962 -0
  62. package/tools/agents/providers/base.mjs +2954 -0
  63. package/tools/agents/providers/claude.mjs +711 -0
  64. package/tools/agents/providers/codex.mjs +699 -0
  65. package/tools/agents/providers/copilot.mjs +659 -0
  66. package/tools/agents/providers/cursor.mjs +714 -0
  67. package/tools/agents/providers/factory.mjs +1130 -0
  68. package/tools/agents/providers/hermes.mjs +663 -0
  69. package/tools/agents/providers/hook-capabilities.mjs +85 -0
  70. package/tools/agents/providers/model-role.mjs +56 -0
  71. package/tools/agents/providers/openclaw-translator.mjs +348 -0
  72. package/tools/agents/providers/openclaw.mjs +680 -0
  73. package/tools/agents/providers/opencode.mjs +675 -0
  74. package/tools/agents/providers/openhuman.mjs +292 -0
  75. package/tools/agents/providers/warp.mjs +413 -0
  76. package/tools/agents/providers/windsurf.mjs +748 -0
  77. package/tools/commands/deploy-prompts-codex.mjs +336 -0
  78. package/tools/plugin/package-plugins.mjs +1013 -0
  79. package/tools/skills/deploy-skills-codex.mjs +571 -0
@@ -0,0 +1,303 @@
1
+ import { z } from 'zod';
2
+ import { sha256 } from './contracts.js';
3
+ export const SESSION_ANALYTICS_VERSION = '1.0.0';
4
+ export const SessionAnalyticsCategorySchema = z.enum([
5
+ 'tool-call',
6
+ 'tool-result',
7
+ 'escalation',
8
+ 'hitl',
9
+ 'boundary',
10
+ 'indicator',
11
+ ]);
12
+ export const SessionAnalyticsStatusSchema = z.enum([
13
+ 'requested',
14
+ 'running',
15
+ 'succeeded',
16
+ 'failed',
17
+ 'granted',
18
+ 'denied',
19
+ 'timed-out',
20
+ 'unsupported',
21
+ 'provider-unknown',
22
+ 'observed',
23
+ ]);
24
+ export const SessionAnalyticsFactSchema = z.object({
25
+ analyticsVersion: z.literal(SESSION_ANALYTICS_VERSION),
26
+ factId: z.string().min(1),
27
+ category: SessionAnalyticsCategorySchema,
28
+ status: SessionAnalyticsStatusSchema,
29
+ provider: z.string().min(1),
30
+ workspaceId: z.string().min(1),
31
+ sessionId: z.string().min(1),
32
+ eventId: z.string().min(1),
33
+ sourceId: z.string().min(1),
34
+ importRunId: z.string().min(1),
35
+ occurredAt: z.string().datetime({ offset: true }).nullable(),
36
+ sequence: z.number().int().nonnegative(),
37
+ actor: z.string().min(1).nullable(),
38
+ participant: z.string().min(1).nullable(),
39
+ toolName: z.string().min(1).nullable(),
40
+ toolCallId: z.string().min(1).nullable(),
41
+ retryGroupId: z.string().min(1).nullable(),
42
+ retryOrdinal: z.number().int().positive().nullable(),
43
+ errorClass: z.string().min(1).nullable(),
44
+ capability: z.string().min(1).nullable(),
45
+ decision: z.string().min(1).nullable(),
46
+ promptType: z.string().min(1).nullable(),
47
+ latencyMs: z.number().int().nonnegative().nullable(),
48
+ transition: z.string().min(1).nullable(),
49
+ indicator: z.string().min(1).nullable(),
50
+ sensitivity: z.enum(['none', 'sensitive']),
51
+ extractionState: z.string().min(1).nullable(),
52
+ sourceCitation: z.object({
53
+ provider: z.string().min(1),
54
+ sessionId: z.string().min(1),
55
+ eventId: z.string().min(1),
56
+ importRunId: z.string().min(1),
57
+ sourceId: z.string().min(1),
58
+ locatorClass: z.string().min(1),
59
+ sequence: z.number().int().nonnegative(),
60
+ }).strict(),
61
+ }).strict();
62
+ /**
63
+ * Derive content-free analytics facts from normalized session events.
64
+ *
65
+ * Historical text is deliberately excluded. Classification uses the canonical
66
+ * event shape and already-sanitized native extension metadata only; commands,
67
+ * URLs, and provider payloads are never executed or copied into the index.
68
+ */
69
+ export function deriveSessionAnalytics(session, events) {
70
+ const ordered = [...events].sort((left, right) => left.sequence - right.sequence || left.eventId.localeCompare(right.eventId));
71
+ const facts = [];
72
+ const retries = new Map();
73
+ const calls = new Map();
74
+ for (const event of ordered) {
75
+ const metadata = flattenMetadata(event.extensions);
76
+ const normalizedKind = event.kind.toLowerCase();
77
+ const status = classifyStatus(normalizedKind, metadata);
78
+ const common = baseFact(session, event);
79
+ if (isToolCall(event, normalizedKind)) {
80
+ const retryKey = `${event.toolName ?? 'unknown'}\0${metadata.input_hash ?? metadata.arguments_digest ?? ''}`;
81
+ const prior = retries.get(retryKey);
82
+ const sameGroup = prior && event.sequence - prior.lastSequence <= 4;
83
+ const retry = sameGroup
84
+ ? { ...prior, count: prior.count + 1, lastSequence: event.sequence }
85
+ : {
86
+ groupId: sha256(['session-tool-retry', session.sessionId, retryKey, event.sequence].join('\0')),
87
+ count: 1,
88
+ lastSequence: event.sequence,
89
+ };
90
+ retries.set(retryKey, retry);
91
+ if (event.toolCallId) {
92
+ calls.set(event.toolCallId, { occurredAt: event.occurredAt, sequence: event.sequence });
93
+ }
94
+ facts.push({
95
+ ...common,
96
+ category: 'tool-call',
97
+ status: status === 'failed' ? 'failed' : 'requested',
98
+ retryGroupId: retry.groupId,
99
+ retryOrdinal: retry.count,
100
+ });
101
+ }
102
+ if (normalizedKind.includes('tool-result')) {
103
+ const call = event.toolCallId ? calls.get(event.toolCallId) : undefined;
104
+ facts.push({
105
+ ...common,
106
+ category: 'tool-result',
107
+ status: status === 'failed' ? 'failed' : 'succeeded',
108
+ latencyMs: elapsedMs(call?.occurredAt, event.occurredAt),
109
+ errorClass: status === 'failed'
110
+ ? (metadata.error_class ?? metadata.error_code ?? 'provider-error')
111
+ : null,
112
+ });
113
+ }
114
+ if (isEscalation(normalizedKind, metadata)) {
115
+ const decision = escalationDecision(status, metadata);
116
+ facts.push({
117
+ ...common,
118
+ category: 'escalation',
119
+ status: decision,
120
+ capability: metadata.capability ?? metadata.permission ?? metadata.scope ?? 'provider-unknown',
121
+ decision,
122
+ });
123
+ }
124
+ if (isHitl(normalizedKind, metadata)) {
125
+ facts.push({
126
+ ...common,
127
+ category: 'hitl',
128
+ status,
129
+ promptType: metadata.prompt_type ?? metadata.input_type ?? 'provider-unknown',
130
+ transition: metadata.transition ?? metadata.task_state ?? metadata.session_state ?? null,
131
+ latencyMs: integerMetadata(metadata.latency_ms),
132
+ });
133
+ }
134
+ if (event.activityBoundary) {
135
+ facts.push({
136
+ ...common,
137
+ category: 'boundary',
138
+ status: 'observed',
139
+ transition: event.activityBoundary,
140
+ });
141
+ }
142
+ const indicators = eventIndicators(event, normalizedKind, status, metadata);
143
+ for (const indicator of indicators) {
144
+ facts.push({
145
+ ...common,
146
+ category: 'indicator',
147
+ status: 'observed',
148
+ indicator,
149
+ errorClass: status === 'failed'
150
+ ? (metadata.error_class ?? metadata.error_code ?? 'provider-error')
151
+ : null,
152
+ });
153
+ }
154
+ }
155
+ for (const retry of retries.values()) {
156
+ if (retry.count < 3)
157
+ continue;
158
+ const anchor = facts.find((fact) => fact.retryGroupId === retry.groupId);
159
+ if (!anchor)
160
+ continue;
161
+ facts.push({
162
+ ...anchor,
163
+ category: 'indicator',
164
+ status: 'observed',
165
+ indicator: 'tool-quota-pressure',
166
+ factId: undefined,
167
+ });
168
+ }
169
+ return facts.map((fact) => SessionAnalyticsFactSchema.parse({
170
+ ...fact,
171
+ factId: sha256([
172
+ 'session-analytics',
173
+ SESSION_ANALYTICS_VERSION,
174
+ fact.category,
175
+ fact.eventId,
176
+ fact.indicator ?? '',
177
+ ].join('\0')),
178
+ }));
179
+ }
180
+ function baseFact(session, event) {
181
+ return {
182
+ analyticsVersion: SESSION_ANALYTICS_VERSION,
183
+ category: 'indicator',
184
+ status: 'observed',
185
+ provider: session.provider,
186
+ workspaceId: session.workspaceId,
187
+ sessionId: session.sessionId,
188
+ eventId: event.eventId,
189
+ sourceId: event.sourceId,
190
+ importRunId: event.importRunId,
191
+ occurredAt: event.occurredAt,
192
+ sequence: event.sequence,
193
+ actor: event.role,
194
+ participant: event.participant,
195
+ toolName: event.toolName,
196
+ toolCallId: event.toolCallId,
197
+ retryGroupId: null,
198
+ retryOrdinal: null,
199
+ errorClass: null,
200
+ capability: null,
201
+ decision: null,
202
+ promptType: null,
203
+ latencyMs: null,
204
+ transition: null,
205
+ indicator: null,
206
+ sensitivity: event.sensitivity.classification,
207
+ extractionState: event.extractionState,
208
+ sourceCitation: {
209
+ provider: session.provider,
210
+ sessionId: session.sessionId,
211
+ eventId: event.eventId,
212
+ importRunId: event.importRunId,
213
+ sourceId: event.sourceId,
214
+ locatorClass: event.rawReference.locatorClass,
215
+ sequence: event.sequence,
216
+ },
217
+ };
218
+ }
219
+ function flattenMetadata(value) {
220
+ const output = {};
221
+ const visit = (input, depth) => {
222
+ if (depth > 3 || !input || typeof input !== 'object' || Array.isArray(input))
223
+ return;
224
+ for (const [key, child] of Object.entries(input)) {
225
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]+/g, '_');
226
+ if (typeof child === 'string' || typeof child === 'number' || typeof child === 'boolean') {
227
+ if (!(normalized in output))
228
+ output[normalized] = String(child).slice(0, 160);
229
+ }
230
+ else {
231
+ visit(child, depth + 1);
232
+ }
233
+ }
234
+ };
235
+ visit(value, 0);
236
+ return output;
237
+ }
238
+ function classifyStatus(kind, metadata) {
239
+ const value = `${kind} ${metadata.status ?? ''} ${metadata.decision ?? ''}`.toLowerCase();
240
+ if (/(timeout|timed.out)/.test(value))
241
+ return 'timed-out';
242
+ if (/(unsupported|unavailable)/.test(value))
243
+ return 'unsupported';
244
+ if (/(deny|denied|reject|rejected)/.test(value))
245
+ return 'denied';
246
+ if (/(grant|granted|approve|approved|allow|allowed)/.test(value))
247
+ return 'granted';
248
+ if (/(fail|failed|error|errored)/.test(value))
249
+ return 'failed';
250
+ if (/(success|succeeded|complete|completed)/.test(value))
251
+ return 'succeeded';
252
+ if (/(request|requested|input.required|prompt)/.test(value))
253
+ return 'requested';
254
+ if (/(running|started|pending)/.test(value))
255
+ return 'running';
256
+ return 'provider-unknown';
257
+ }
258
+ function isToolCall(event, kind) {
259
+ return kind.includes('tool-call') || kind.includes('tool_use')
260
+ || Boolean(event.toolName && !kind.includes('tool-result'));
261
+ }
262
+ function isEscalation(kind, metadata) {
263
+ return /(escalat|permission|approval|sandbox|capability.required)/.test(kind)
264
+ || ['permission', 'capability', 'approval', 'scope'].some((key) => key in metadata);
265
+ }
266
+ function isHitl(kind, metadata) {
267
+ return /(hitl|human|input.required|input_required|operator.prompt)/.test(kind)
268
+ || ['prompt_type', 'input_type', 'reviewer'].some((key) => key in metadata);
269
+ }
270
+ function escalationDecision(status, metadata) {
271
+ if (['granted', 'denied', 'timed-out', 'unsupported'].includes(status))
272
+ return status;
273
+ if (metadata.decision)
274
+ return classifyStatus('', { decision: metadata.decision });
275
+ return status === 'requested' ? 'requested' : 'provider-unknown';
276
+ }
277
+ function eventIndicators(event, kind, status, metadata) {
278
+ const indicators = new Set();
279
+ if (event.opaque || /(unknown|malformed|unsupported)/.test(kind)) {
280
+ indicators.add('provider-schema-drift');
281
+ }
282
+ if (status === 'failed')
283
+ indicators.add('failed-operation');
284
+ if (event.sensitivity.classification === 'sensitive')
285
+ indicators.add('sensitive-field-redaction');
286
+ if (metadata.redaction_hit === 'true' || metadata.redacted === 'true') {
287
+ indicators.add('sensitive-field-redaction');
288
+ }
289
+ return [...indicators].sort();
290
+ }
291
+ function elapsedMs(start, end) {
292
+ if (!start || !end)
293
+ return null;
294
+ const value = Date.parse(end) - Date.parse(start);
295
+ return Number.isFinite(value) && value >= 0 ? value : null;
296
+ }
297
+ function integerMetadata(value) {
298
+ if (!value || !/^\d+$/.test(value))
299
+ return null;
300
+ const parsed = Number(value);
301
+ return Number.isSafeInteger(parsed) ? parsed : null;
302
+ }
303
+ //# sourceMappingURL=analytics.js.map
@@ -237,7 +237,7 @@ function normalizeBatch(request, records, importRunId, sourceObservedAt) {
237
237
  ? 'sensitive' : 'none',
238
238
  classes: [...new Set([...redacted.classes, ...native.classes])].sort(),
239
239
  },
240
- opaque: !KNOWN_EVENT_KINDS.has(record.kind),
240
+ opaque: !isKnownEventKind(record.kind),
241
241
  extensions,
242
242
  });
243
243
  if (!sessions.has(sessionId)) {
@@ -281,6 +281,12 @@ function normalizeBatch(request, records, importRunId, sourceObservedAt) {
281
281
  return { sessions: [...sessions.values()], events };
282
282
  }
283
283
  const KNOWN_EVENT_KINDS = new Set(['message', 'tool-call', 'tool-result', 'artifact', 'attachment', 'summary']);
284
+ function isKnownEventKind(kind) {
285
+ const normalized = kind.toLowerCase();
286
+ return KNOWN_EVENT_KINDS.has(normalized)
287
+ || /^(?:tool-call|tool-result)(?:[._-]|$)/.test(normalized)
288
+ || /^(?:sandbox|permission|approval|hitl|human|lifecycle)(?:[._-]|$)/.test(normalized);
289
+ }
284
290
  function earlierTimestamp(left, right) {
285
291
  if (!left)
286
292
  return right;
@@ -9,6 +9,7 @@ export * from './optional-backends.js';
9
9
  export * from './knowledge-shard.js';
10
10
  export * from './candidates.js';
11
11
  export * from './promotion.js';
12
+ export * from './output-registration.js';
12
13
  export * from './importer.js';
13
14
  export * from './import-lease.js';
14
15
  export * from './batch-contracts.js';
@@ -16,6 +17,7 @@ export * from './batch-import.js';
16
17
  export * from './workspace-discovery.js';
17
18
  export * from './timeline.js';
18
19
  export * from './origin.js';
20
+ export * from './analytics.js';
19
21
  export * from './adapters/generic.js';
20
22
  export * from './adapters/claude.js';
21
23
  export * from './adapters/codex.js';
@@ -0,0 +1,338 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { dirname, relative, resolve, sep } from 'node:path';
4
+ import { z } from 'zod';
5
+ import { SessionContractError, sha256 } from './contracts.js';
6
+ const DigestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/);
7
+ const ReferenceSchema = z.string().min(1).max(512).refine(value => !/[\r\n\0]/.test(value), 'references must be single-line inert locators');
8
+ export const OutputSourceReferenceSchema = z.object({
9
+ kind: z.enum(['file', 'url', 'note', 'session', 'artifact', 'context-pack']),
10
+ ref: ReferenceSchema,
11
+ digest: DigestSchema.nullable().default(null),
12
+ span: z.object({
13
+ start: z.number().int().nonnegative(),
14
+ end: z.number().int().positive(),
15
+ quoteDigest: DigestSchema,
16
+ }).strict().nullable().default(null),
17
+ }).strict().superRefine((value, context) => {
18
+ if (value.span && value.span.end <= value.span.start) {
19
+ context.addIssue({ code: z.ZodIssueCode.custom, message: 'source span end must exceed start' });
20
+ }
21
+ });
22
+ export const OutputRegistrationRequestSchema = z.object({
23
+ outputPath: z.string().min(1),
24
+ mediaType: z.string().min(1).max(128),
25
+ contextPack: z.object({
26
+ id: ReferenceSchema,
27
+ digest: DigestSchema,
28
+ sources: z.array(OutputSourceReferenceSchema).min(1).max(256),
29
+ }).strict(),
30
+ supersedes: z.array(ReferenceSchema).max(128).default([]),
31
+ conflictsWith: z.array(ReferenceSchema).max(128).default([]),
32
+ }).strict();
33
+ /**
34
+ * Minimal incremental index sink. Each registration is independently atomic,
35
+ * discoverable, and replay-safe; corpus-wide index builders can consume these
36
+ * bounded records without rescanning output bodies.
37
+ */
38
+ export class FilesystemDerivedOutputIndex {
39
+ root;
40
+ constructor(projectRoot) {
41
+ this.root = resolve(projectRoot, '.aiwg/memory/output-registration/index');
42
+ assertStorageRootInsideProject(projectRoot, this.root);
43
+ }
44
+ register(registration) {
45
+ if (!/^sha256:[0-9a-f]{64}$/.test(registration.registrationId)) {
46
+ throw new Error('invalid output registration identity');
47
+ }
48
+ const filePath = resolve(this.root, `${registration.registrationId.replace(':', '_')}.json`);
49
+ const existing = readJsonIfPresent(filePath);
50
+ if (existing) {
51
+ if (JSON.stringify(existing) !== JSON.stringify(registration)) {
52
+ throw new SessionContractError('IMPORT_CONFLICT', 'derived output index identity already has different content');
53
+ }
54
+ return;
55
+ }
56
+ writeJsonAtomic(filePath, registration);
57
+ }
58
+ registrations() {
59
+ if (!existsSync(this.root))
60
+ return [];
61
+ return readdirSync(this.root)
62
+ .filter(name => /^sha256_[0-9a-f]{64}\.json$/.test(name))
63
+ .sort()
64
+ .map(name => JSON.parse(readFileSync(resolve(this.root, name), 'utf8')));
65
+ }
66
+ }
67
+ function canonicalReference(value) {
68
+ try {
69
+ const parsed = new URL(value);
70
+ if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
71
+ parsed.username = '';
72
+ parsed.password = '';
73
+ parsed.search = '';
74
+ parsed.hash = '';
75
+ return parsed.toString();
76
+ }
77
+ }
78
+ catch {
79
+ // Non-URL references are opaque inert identifiers.
80
+ }
81
+ return value;
82
+ }
83
+ function assertStorageRootInsideProject(projectRoot, storageRoot) {
84
+ const root = realpathSync(projectRoot);
85
+ const candidate = resolve(projectRoot, storageRoot);
86
+ if (candidate !== resolve(projectRoot) && !candidate.startsWith(`${resolve(projectRoot)}${sep}`)) {
87
+ throw new SessionContractError('SOURCE_OUTSIDE_ALLOWED_ROOT', 'memory storage must be inside the project');
88
+ }
89
+ let ancestor = candidate;
90
+ while (!existsSync(ancestor)) {
91
+ const parent = dirname(ancestor);
92
+ if (parent === ancestor)
93
+ break;
94
+ ancestor = parent;
95
+ }
96
+ const actualAncestor = realpathSync(ancestor);
97
+ if (actualAncestor !== root && !actualAncestor.startsWith(`${root}${sep}`)) {
98
+ throw new SessionContractError('SOURCE_OUTSIDE_ALLOWED_ROOT', 'memory storage cannot traverse a link outside the project');
99
+ }
100
+ }
101
+ function assertNoSecretMaterial(value, field) {
102
+ const secretAssignment = /(?:^|[?&;:\s])(?:api[_-]?key|access[_-]?token|token|secret|password|passwd|authorization)\s*[:=]\s*[^\s&;]+/i;
103
+ const privateKey = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/;
104
+ const providerToken = /(?:^|[^a-z0-9])(?:ghp|github_pat|sk|xox[baprs])_[a-z0-9_-]{16,}/i;
105
+ if (secretAssignment.test(value) || privateKey.test(value) || providerToken.test(value)) {
106
+ throw new SessionContractError('SOURCE_NOT_AUTHORIZED', `${field} appears to contain secret material; store only a non-secret locator`);
107
+ }
108
+ }
109
+ function canonicalRequest(request) {
110
+ const canonical = {
111
+ ...request,
112
+ contextPack: {
113
+ ...request.contextPack,
114
+ id: canonicalReference(request.contextPack.id),
115
+ sources: request.contextPack.sources.map(source => ({
116
+ ...source,
117
+ ref: canonicalReference(source.ref),
118
+ })),
119
+ },
120
+ supersedes: [...new Set(request.supersedes.map(canonicalReference))].sort(),
121
+ conflictsWith: [...new Set(request.conflictsWith.map(canonicalReference))].sort(),
122
+ };
123
+ assertNoSecretMaterial(canonical.contextPack.id, 'context-pack identity');
124
+ for (const source of canonical.contextPack.sources) {
125
+ assertNoSecretMaterial(source.ref, 'source reference');
126
+ }
127
+ for (const value of [...canonical.supersedes, ...canonical.conflictsWith]) {
128
+ assertNoSecretMaterial(value, 'lifecycle reference');
129
+ }
130
+ return canonical;
131
+ }
132
+ function resolveImmutableOutput(projectRoot, requestedPath) {
133
+ if (requestedPath.includes('\0')) {
134
+ throw new SessionContractError('MALFORMED_SOURCE', 'output path contains a null byte');
135
+ }
136
+ const pathSegments = requestedPath.toLocaleLowerCase().split(/[\\/]+/);
137
+ if (pathSegments.some(segment => segment === '.env'
138
+ || segment === '.ssh'
139
+ || /^(?:credentials?|secrets?|tokens?)(?:\.|$)/.test(segment))) {
140
+ throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'sensitive credential/secret paths cannot be registered as ordinary outputs');
141
+ }
142
+ const root = realpathSync(projectRoot);
143
+ const candidate = realpathSync(resolve(root, requestedPath));
144
+ if (candidate !== root && !candidate.startsWith(`${root}${sep}`)) {
145
+ throw new SessionContractError('SOURCE_OUTSIDE_ALLOWED_ROOT', 'registered output must resolve inside the project');
146
+ }
147
+ return { absolute: candidate, locator: relative(root, candidate).split(sep).join('/') };
148
+ }
149
+ function registrationFor(projectRoot, raw) {
150
+ const request = canonicalRequest(OutputRegistrationRequestSchema.parse(raw));
151
+ const output = resolveImmutableOutput(projectRoot, request.outputPath);
152
+ const content = readFileSync(output.absolute);
153
+ const digest = `sha256:${createHash('sha256').update(content).digest('hex')}`;
154
+ const identity = {
155
+ output: { locator: output.locator, mediaType: request.mediaType, digest, byteLength: content.length },
156
+ contextPack: request.contextPack,
157
+ supersedes: request.supersedes,
158
+ conflictsWith: request.conflictsWith,
159
+ };
160
+ return {
161
+ schemaVersion: 'aiwg.output-registration.v1',
162
+ registrationId: sha256(JSON.stringify(identity)),
163
+ ...identity,
164
+ };
165
+ }
166
+ export class FilesystemOutputRegistrationStore {
167
+ root;
168
+ outboxRoot;
169
+ receiptRoot;
170
+ constructor(projectRoot) {
171
+ this.root = resolve(projectRoot, '.aiwg/memory/output-registration');
172
+ assertStorageRootInsideProject(projectRoot, this.root);
173
+ this.outboxRoot = resolve(this.root, 'outbox');
174
+ this.receiptRoot = resolve(this.root, 'receipts');
175
+ }
176
+ getReceipt(registrationId) {
177
+ return readJsonIfPresent(this.receiptPath(registrationId));
178
+ }
179
+ begin(operationId, registration) {
180
+ const existing = readJsonIfPresent(this.outboxPath(registration.registrationId));
181
+ if (existing)
182
+ return existing;
183
+ const record = {
184
+ schemaVersion: 'aiwg.output-registration-outbox.v1',
185
+ operationId,
186
+ registration,
187
+ state: 'pending',
188
+ attempts: 0,
189
+ lastError: null,
190
+ updatedAt: new Date().toISOString(),
191
+ };
192
+ writeJsonAtomic(this.outboxPath(registration.registrationId), record);
193
+ return record;
194
+ }
195
+ fail(registrationId, message) {
196
+ const path = this.outboxPath(registrationId);
197
+ const record = readJsonIfPresent(path);
198
+ if (!record)
199
+ throw new Error(`missing output-registration outbox record: ${registrationId}`);
200
+ const failed = {
201
+ ...record,
202
+ attempts: record.attempts + 1,
203
+ lastError: message.slice(0, 512),
204
+ updatedAt: new Date().toISOString(),
205
+ };
206
+ writeJsonAtomic(path, failed);
207
+ return failed;
208
+ }
209
+ complete(operationId, registration) {
210
+ const existing = this.getReceipt(registration.registrationId);
211
+ if (existing) {
212
+ unlinkIfPresent(this.outboxPath(registration.registrationId));
213
+ return { ...existing, duplicate: true };
214
+ }
215
+ const receipt = {
216
+ schemaVersion: 'aiwg.output-registration-receipt.v1',
217
+ receiptId: sha256(`${operationId}\0${registration.registrationId}`),
218
+ registrationId: registration.registrationId,
219
+ operationId,
220
+ outputLocator: registration.output.locator,
221
+ outputDigest: registration.output.digest,
222
+ contextPackId: registration.contextPack.id,
223
+ contextPackDigest: registration.contextPack.digest,
224
+ sourceRefs: registration.contextPack.sources.map(source => source.ref),
225
+ registeredAt: new Date().toISOString(),
226
+ duplicate: false,
227
+ };
228
+ writeJsonAtomic(this.receiptPath(registration.registrationId), receipt);
229
+ unlinkIfPresent(this.outboxPath(registration.registrationId));
230
+ return receipt;
231
+ }
232
+ pending() {
233
+ if (!existsSync(this.outboxRoot))
234
+ return [];
235
+ return readdirSync(this.outboxRoot)
236
+ .filter(name => /^sha256_[0-9a-f]{64}\.json$/.test(name))
237
+ .sort()
238
+ .map(name => JSON.parse(readFileSync(resolve(this.outboxRoot, name), 'utf8')));
239
+ }
240
+ safeName(registrationId) {
241
+ if (!/^sha256:[0-9a-f]{64}$/.test(registrationId)) {
242
+ throw new Error('invalid output registration identity');
243
+ }
244
+ return `${registrationId.replace(':', '_')}.json`;
245
+ }
246
+ outboxPath(registrationId) {
247
+ return resolve(this.outboxRoot, this.safeName(registrationId));
248
+ }
249
+ receiptPath(registrationId) {
250
+ return resolve(this.receiptRoot, this.safeName(registrationId));
251
+ }
252
+ }
253
+ export class OutputRegistrationCoordinator {
254
+ store;
255
+ index;
256
+ projectRoot;
257
+ constructor(projectRoot, store, index) {
258
+ this.store = store;
259
+ this.index = index;
260
+ this.projectRoot = resolve(projectRoot);
261
+ }
262
+ preview(request) {
263
+ const registration = registrationFor(this.projectRoot, request);
264
+ const operationId = sha256(JSON.stringify({
265
+ registrationId: registration.registrationId,
266
+ outputDigest: registration.output.digest,
267
+ contextPackDigest: registration.contextPack.digest,
268
+ }));
269
+ return {
270
+ ...registration,
271
+ operationId,
272
+ duplicate: Boolean(this.store.getReceipt(registration.registrationId)),
273
+ confirmationRequired: true,
274
+ };
275
+ }
276
+ async register(input) {
277
+ const preview = this.preview(input.request);
278
+ if (preview.operationId !== input.operationId) {
279
+ throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'output registration requires confirmation of the exact current preview');
280
+ }
281
+ const existing = this.store.getReceipt(preview.registrationId);
282
+ if (existing)
283
+ return { ...existing, duplicate: true };
284
+ const registration = registrationFor(this.projectRoot, input.request);
285
+ if (registration.registrationId !== preview.registrationId
286
+ || registration.output.digest !== preview.output.digest) {
287
+ throw new SessionContractError('IMPORT_CONFLICT', 'output changed after registration preview');
288
+ }
289
+ this.store.begin(input.operationId, registration);
290
+ try {
291
+ await this.index.register(registration);
292
+ }
293
+ catch (error) {
294
+ this.store.fail(registration.registrationId, error instanceof Error ? error.message : String(error));
295
+ throw error;
296
+ }
297
+ return this.store.complete(input.operationId, registration);
298
+ }
299
+ async replayPending() {
300
+ const receipts = [];
301
+ for (const record of this.store.pending()) {
302
+ try {
303
+ await this.index.register(record.registration);
304
+ receipts.push(this.store.complete(record.operationId, record.registration));
305
+ }
306
+ catch (error) {
307
+ this.store.fail(record.registration.registrationId, error instanceof Error ? error.message : String(error));
308
+ }
309
+ }
310
+ return receipts;
311
+ }
312
+ }
313
+ function readJsonIfPresent(filePath) {
314
+ try {
315
+ return JSON.parse(readFileSync(filePath, 'utf8'));
316
+ }
317
+ catch (error) {
318
+ if (error.code === 'ENOENT')
319
+ return null;
320
+ throw error;
321
+ }
322
+ }
323
+ function writeJsonAtomic(filePath, value) {
324
+ mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
325
+ const temporary = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
326
+ writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
327
+ renameSync(temporary, filePath);
328
+ }
329
+ function unlinkIfPresent(filePath) {
330
+ try {
331
+ unlinkSync(filePath);
332
+ }
333
+ catch (error) {
334
+ if (error.code !== 'ENOENT')
335
+ throw error;
336
+ }
337
+ }
338
+ //# sourceMappingURL=output-registration.js.map
@@ -70,7 +70,7 @@ const SECRET_PATTERNS = [
70
70
  const SENSITIVE_KEY = /(?:authorization|auth[_-]?header|cookie|credential|password|passwd|secret|token|api[_-]?key|private[_-]?key|connection[_-]?string)/i;
71
71
  const CONTENT_KEY = /(?:^|[_-])(?:text|content|prompt|command|arguments?|args|result|output|body|request|response|source|code|query)(?:$|[_-])/i;
72
72
  const PATH_KEY = /(?:^|[_-])(?:path|cwd|directory|filename|file)(?:$|[_-])/i;
73
- const SAFE_STRING_KEY = /(?:^|[_-])(?:id|kind|type|role|status|state|lifecycle|reason|name|product|provider|version|schema|model|format|class|mode|event|operation|tool|method|language|scope|consistency|disposition|phase|visibility|protocol|category|classification)(?:$|[_-])/i;
73
+ const SAFE_STRING_KEY = /(?:^|[_-])(?:id|kind|type|role|status|state|lifecycle|reason|name|product|provider|version|schema|model|format|class|mode|event|operation|tool|method|language|scope|consistency|disposition|phase|visibility|protocol|category|classification|decision|capability|permission|transition|hash|digest)(?:$|[_-])/i;
74
74
  const CONTROL_CHARACTERS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g;
75
75
  const DEFAULT_NATIVE_LIMITS = {
76
76
  maxDepth: 8,