@aiwg/cli 2026.7.20 → 2026.7.21

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 (53) hide show
  1. package/README.md +4 -4
  2. package/dist/src/api/index.d.ts +1 -0
  3. package/dist/src/api/index.js +1 -0
  4. package/dist/src/artifacts/browser-export.js +7 -0
  5. package/dist/src/artifacts/citation-parser.js +96 -35
  6. package/dist/src/artifacts/index-builder.js +54 -17
  7. package/dist/src/artifacts/state-transfer.js +27 -0
  8. package/dist/src/artifacts/stats.js +8 -0
  9. package/dist/src/cli/cli-extension-loader.js +73 -0
  10. package/dist/src/cli/handlers/index.js +3 -1
  11. package/dist/src/cli/handlers/sessions.js +966 -0
  12. package/dist/src/cli/handlers/skill-lint.js +49 -45
  13. package/dist/src/cli/handlers/use.js +143 -60
  14. package/dist/src/cli/handlers/utilities.js +22 -8
  15. package/dist/src/cli/skill-usage.js +146 -24
  16. package/dist/src/extensions/commands/definitions.js +29 -0
  17. package/dist/src/extensions/manifest.js +29 -0
  18. package/dist/src/sessions/adapters/claude.js +357 -0
  19. package/dist/src/sessions/adapters/codex.js +521 -0
  20. package/dist/src/sessions/adapters/copilot.js +226 -0
  21. package/dist/src/sessions/adapters/cursor.js +372 -0
  22. package/dist/src/sessions/adapters/factory.js +345 -0
  23. package/dist/src/sessions/adapters/generic.js +225 -0
  24. package/dist/src/sessions/adapters/hermes.js +341 -0
  25. package/dist/src/sessions/adapters/openclaw.js +381 -0
  26. package/dist/src/sessions/adapters/opencode.js +454 -0
  27. package/dist/src/sessions/adapters/openhuman.js +315 -0
  28. package/dist/src/sessions/adapters/warp.js +160 -0
  29. package/dist/src/sessions/adapters/windsurf.js +212 -0
  30. package/dist/src/sessions/candidates.js +210 -0
  31. package/dist/src/sessions/contracts.js +310 -0
  32. package/dist/src/sessions/discovery.js +51 -0
  33. package/dist/src/sessions/fixtures.js +12 -0
  34. package/dist/src/sessions/importer.js +315 -0
  35. package/dist/src/sessions/index.js +25 -0
  36. package/dist/src/sessions/knowledge-shard.js +61 -0
  37. package/dist/src/sessions/optional-backends.js +238 -0
  38. package/dist/src/sessions/policy.js +192 -0
  39. package/dist/src/sessions/ports.js +2 -0
  40. package/dist/src/sessions/promotion.js +367 -0
  41. package/dist/src/sessions/readers.js +176 -0
  42. package/dist/src/sessions/repository.js +1551 -0
  43. package/dist/src/skills/adapters/agent-skills.js +59 -0
  44. package/dist/src/skills/adapters/local.js +19 -1
  45. package/dist/src/skills/agent-skills.js +249 -0
  46. package/dist/src/skills/cli.js +463 -7
  47. package/dist/src/skills/deployer.js +554 -0
  48. package/dist/src/skills/doctor.js +105 -0
  49. package/dist/src/skills/exporter.js +382 -0
  50. package/dist/src/skills/importer.js +921 -0
  51. package/dist/src/skills/registry.js +19 -0
  52. package/dist/src/skills/validator.js +323 -0
  53. package/package.json +2 -2
@@ -0,0 +1,966 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, redactSourceLocator, } from '../../sessions/index.js';
4
+ const JSON_CONTRACT_VERSION = '1.0.0';
5
+ const EXIT = {
6
+ ok: 0, usage: 2, unsupported: 3, unavailable: 4, contract: 5, storage: 6,
7
+ };
8
+ const HELP = `Usage: aiwg sessions <command> [options]
9
+
10
+ Commands:
11
+ sources Show all canonical provider dispositions
12
+ import <file> --source-id <id> Import a supported provider JSONL source
13
+ list [--limit N] [--cursor N] List normalized sessions
14
+ show <session-id> Show a session with events and tags
15
+ search <query> --workspace <id> Search authorized normalized content
16
+ extract [session-id] --workspace <id> Extract structural candidates
17
+ candidates [--state <state>] List the candidate review queue
18
+ review <id> <version> <state> Record an explicit review transition
19
+ promote <id> <version> Preview promotion; use --confirm to write
20
+ tag <session-id> <tag> Add a catalog tag
21
+ relocate <source-id> <file> Update AIWG source-location metadata
22
+ reindex Rebuild catalog indexes
23
+ delete <session-id> Preview deletion; use --confirm to tombstone
24
+ restore <session-id> Restore a reversible catalog tombstone
25
+ purge <session-id> Preview terminal AIWG-copy purge
26
+ audit --workspace <id> Read content-free mutation events
27
+ doctor Check catalog availability and integrity
28
+
29
+ Options:
30
+ --json Emit the versioned JSON contract
31
+ --dry-run Preview a mutation without changing state
32
+ --db <path> Override .aiwg/sessions/catalog.sqlite
33
+ --provider <id> Filter list or select an import adapter
34
+ --consumer <id> Select a named memory consumer for promotion
35
+ --workspace <id>, --tag <tag>, --limit <n>, --cursor <n>
36
+ --page-size <n> Extraction scan page size (default 250, maximum 500)
37
+ --max-documents <n> Explicit extraction safety limit; returns a partial receipt`;
38
+ export const sessionsHandler = {
39
+ id: 'sessions',
40
+ name: 'Sessions',
41
+ description: 'Manage the normalized session catalog (the singular `session` command remains the launcher)',
42
+ category: 'project',
43
+ aliases: [],
44
+ async execute(ctx) {
45
+ const json = ctx.args.includes('--json');
46
+ let parsed;
47
+ try {
48
+ parsed = parseArgs(ctx.args);
49
+ }
50
+ catch (error) {
51
+ const normalized = normalizeError(error);
52
+ const output = envelope('sessions', 'error', null, normalized.error);
53
+ if (json)
54
+ emit(output);
55
+ else
56
+ console.error(`${normalized.error.code}: ${normalized.error.message}`);
57
+ return { exitCode: normalized.exitCode, message: normalized.error.message };
58
+ }
59
+ if (!parsed.command || parsed.flags.has('--help') || parsed.flags.has('-h')) {
60
+ if (json)
61
+ emit(envelope('sessions.help', 'ok', { usage: HELP }, null));
62
+ else
63
+ console.log(HELP);
64
+ return { exitCode: parsed.command ? EXIT.ok : EXIT.usage };
65
+ }
66
+ try {
67
+ const result = await executeCommand(ctx, parsed);
68
+ if (json)
69
+ emit(result.envelope);
70
+ else
71
+ printHuman(result.envelope);
72
+ return { exitCode: result.exitCode };
73
+ }
74
+ catch (error) {
75
+ const normalized = normalizeError(error);
76
+ const output = envelope(`sessions.${parsed.command}`, 'error', null, normalized.error);
77
+ if (json)
78
+ emit(output);
79
+ else
80
+ console.error(`${normalized.error.code}: ${normalized.error.message}`);
81
+ return { exitCode: normalized.exitCode, message: normalized.error.message };
82
+ }
83
+ },
84
+ };
85
+ async function executeCommand(ctx, args) {
86
+ const command = args.command;
87
+ if (command === 'sources') {
88
+ const reports = [...SESSION_PROVIDER_IDS].sort().map(providerDisposition);
89
+ return ok(command, { providers: reports, count: reports.length });
90
+ }
91
+ if (command === 'import')
92
+ return importSource(ctx, args);
93
+ const repository = openRepository(ctx, args);
94
+ try {
95
+ switch (command) {
96
+ case 'list': {
97
+ const { workspaceId } = authorizationContext(args, command);
98
+ const limit = boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit');
99
+ const cursor = args.values.get('--cursor');
100
+ const providerInput = args.values.get('--provider');
101
+ const provider = providerInput ? assertSessionProviderId(providerInput) : undefined;
102
+ const result = repository.listSessions({
103
+ provider,
104
+ workspaceId,
105
+ tag: args.values.get('--tag'),
106
+ limit,
107
+ cursor,
108
+ });
109
+ return ok(command, {
110
+ items: result.items,
111
+ page: {
112
+ limit,
113
+ cursor: cursor ?? null,
114
+ nextCursor: result.nextCursor,
115
+ snapshotRowid: result.snapshotRowid,
116
+ total: result.total,
117
+ },
118
+ });
119
+ }
120
+ case 'show': {
121
+ const id = requiredPositional(args, 0, 'session-id');
122
+ const { workspaceId } = authorizationContext(args, command);
123
+ const session = repository.getSession(id, workspaceId);
124
+ if (!session)
125
+ throw new CliError('SESSION_NOT_FOUND', `session not found: ${id}`, EXIT.unavailable);
126
+ return ok(command, {
127
+ session,
128
+ tags: repository.listTags(id, workspaceId),
129
+ events: repository.listEvents(id, workspaceId),
130
+ });
131
+ }
132
+ case 'search': {
133
+ const query = requiredPositional(args, 0, 'query');
134
+ const { workspaceId } = authorizationContext(args, command);
135
+ const providerInput = args.values.get('--provider');
136
+ const provider = providerInput ? assertSessionProviderId(providerInput) : undefined;
137
+ const result = repository.search({
138
+ query,
139
+ workspaceId,
140
+ providers: provider ? [provider] : undefined,
141
+ dateFrom: args.values.get('--date-from'),
142
+ dateTo: args.values.get('--date-to'),
143
+ participant: args.values.get('--participant'),
144
+ model: args.values.get('--model'),
145
+ role: args.values.get('--role'),
146
+ tool: args.values.get('--tool'),
147
+ tag: args.values.get('--tag'),
148
+ entity: args.values.get('--entity'),
149
+ sensitivity: args.values.get('--sensitivity'),
150
+ extractionState: args.values.get('--extraction-state'),
151
+ limit: boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit'),
152
+ cursor: args.values.get('--cursor'),
153
+ });
154
+ return ok(command, {
155
+ items: result.items,
156
+ page: { limit: boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit'),
157
+ nextCursor: result.nextCursor },
158
+ });
159
+ }
160
+ case 'extract': {
161
+ const { workspaceId } = authorizationContext(args, command);
162
+ const sessionId = args.positionals[0];
163
+ const pageSize = boundedInteger(args.values.get('--page-size'), 250, 1, 500, '--page-size');
164
+ const maxDocuments = args.values.has('--max-documents')
165
+ ? boundedInteger(args.values.get('--max-documents'), pageSize, 1, 1_000_000, '--max-documents')
166
+ : null;
167
+ const documents = [];
168
+ let cursor;
169
+ let truncated = false;
170
+ do {
171
+ const remaining = maxDocuments === null
172
+ ? pageSize
173
+ : Math.min(pageSize, maxDocuments - documents.length);
174
+ if (remaining <= 0) {
175
+ truncated = true;
176
+ break;
177
+ }
178
+ const page = repository.authorizedSearchDocumentPage({
179
+ workspaceId,
180
+ sessionIds: sessionId ? [sessionId] : undefined,
181
+ limit: remaining,
182
+ cursor,
183
+ });
184
+ documents.push(...page.items);
185
+ cursor = page.nextCursor ?? undefined;
186
+ if (maxDocuments !== null && documents.length >= maxDocuments && cursor) {
187
+ truncated = true;
188
+ break;
189
+ }
190
+ } while (cursor);
191
+ if (sessionId && documents.length === 0) {
192
+ throw new CliError('SESSION_NOT_FOUND', `no authorized evidence found for session: ${sessionId}`, EXIT.unavailable);
193
+ }
194
+ const dryRun = isDryRun(ctx, args);
195
+ const service = new CandidateExtractionService(dryRun
196
+ ? { saveCandidates: (candidates) => [...candidates] }
197
+ : repository);
198
+ const items = await service.extract({
199
+ documents,
200
+ extractor: new StructuralCandidateExtractor(),
201
+ policy: {
202
+ version: args.values.get('--policy-version') ?? '1.0.0',
203
+ projectScope: workspaceId,
204
+ temporalScope: 'source-event',
205
+ minimumConfidence: boundedNumber(args.values.get('--min-confidence'), 0.5, 0, 1, '--min-confidence'),
206
+ },
207
+ });
208
+ const scan = {
209
+ complete: !truncated,
210
+ documentsScanned: documents.length,
211
+ pageSize,
212
+ nextCursor: truncated ? cursor ?? null : null,
213
+ limit: maxDocuments,
214
+ };
215
+ return dryRun
216
+ ? preview(command, { items, count: items.length, durableMemoryWrites: 0, scan })
217
+ : ok(command, { items, count: items.length, durableMemoryWrites: 0, scan });
218
+ }
219
+ case 'candidates': {
220
+ const { workspaceId } = authorizationContext(args, command);
221
+ const state = candidateState(args.values.get('--state'));
222
+ return ok(command, {
223
+ items: repository.listCandidates(state, workspaceId),
224
+ });
225
+ }
226
+ case 'review': {
227
+ const { workspaceId } = authorizationContext(args, command);
228
+ const candidateId = requiredPositional(args, 0, 'candidate-id');
229
+ const version = boundedInteger(requiredPositional(args, 1, 'version'), 1, 1, Number.MAX_SAFE_INTEGER, 'version');
230
+ const toState = candidateState(requiredPositional(args, 2, 'state'));
231
+ if (!toState)
232
+ throw new CliError('INVALID_ARGUMENT', 'review state is required', EXIT.usage);
233
+ if (isDryRun(ctx, args)) {
234
+ return preview(command, {
235
+ candidateId, version, toState,
236
+ reviewer: requiredValue(args, '--reviewer'),
237
+ reason: requiredValue(args, '--reason'),
238
+ securityAcknowledged: args.flags.has('--acknowledge-security-risk'),
239
+ });
240
+ }
241
+ return ok(command, repository.reviewCandidate({
242
+ candidateId,
243
+ version,
244
+ toState,
245
+ reviewer: requiredValue(args, '--reviewer'),
246
+ reason: requiredValue(args, '--reason'),
247
+ securityAcknowledged: args.flags.has('--acknowledge-security-risk'),
248
+ workspaceId,
249
+ }));
250
+ }
251
+ case 'promote': {
252
+ const { workspaceId } = authorizationContext(args, command);
253
+ const candidateId = requiredPositional(args, 0, 'candidate-id');
254
+ const version = boundedInteger(requiredPositional(args, 1, 'version'), 1, 1, Number.MAX_SAFE_INTEGER, 'version');
255
+ const consumer = requiredValue(args, '--consumer');
256
+ const destination = new FilesystemMemoryDestination({
257
+ projectRoot: ctx.cwd,
258
+ consumer,
259
+ manifestPath: resolveMemoryConsumerManifest(ctx.cwd, consumer),
260
+ });
261
+ const scopedPromotionStore = {
262
+ getCandidate: (id, candidateVersion) => repository.getCandidate(id, candidateVersion, workspaceId),
263
+ getPromotionReceipt: (id, candidateVersion, namedConsumer) => repository.getCandidate(id, candidateVersion, workspaceId)
264
+ ? repository.getPromotionReceipt(id, candidateVersion, namedConsumer)
265
+ : null,
266
+ recordPromotion: (receipt) => {
267
+ if (!repository.getCandidate(receipt.candidateId, receipt.candidateVersion, workspaceId)) {
268
+ throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'candidate version is not available in the authorized workspace');
269
+ }
270
+ return repository.recordPromotion(receipt);
271
+ },
272
+ };
273
+ const gateway = new MemoryPromotionGateway(scopedPromotionStore);
274
+ const promotionPreview = gateway.preview({ candidateId, version, destination });
275
+ if (!args.flags.has('--confirm') || isDryRun(ctx, args)) {
276
+ return preview(command, promotionPreview);
277
+ }
278
+ const receipt = await gateway.promote({
279
+ candidateId,
280
+ version,
281
+ destination,
282
+ reviewer: requiredValue(args, '--reviewer'),
283
+ operationId: promotionPreview.operationId,
284
+ });
285
+ return ok(command, { preview: promotionPreview, receipt });
286
+ }
287
+ case 'tag': {
288
+ const id = requiredPositional(args, 0, 'session-id');
289
+ const { workspaceId } = authorizationContext(args, command);
290
+ const tag = requiredPositional(args, 1, 'tag');
291
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(tag)) {
292
+ throw new CliError('INVALID_TAG', 'tag must be 1-64 safe identifier characters', EXIT.usage);
293
+ }
294
+ if (isDryRun(ctx, args))
295
+ return preview(command, { sessionId: id, tag, wouldAdd: true });
296
+ if (!repository.tagSession(id, tag, workspaceId)) {
297
+ if (!repository.getSession(id, workspaceId)) {
298
+ throw new CliError('SESSION_NOT_FOUND', `session not found: ${id}`, EXIT.unavailable);
299
+ }
300
+ }
301
+ return ok(command, { sessionId: id, tag, tags: repository.listTags(id, workspaceId) });
302
+ }
303
+ case 'relocate': {
304
+ const sourceId = requiredPositional(args, 0, 'source-id');
305
+ const { workspaceId } = authorizationContext(args, command);
306
+ const locator = requiredPositional(args, 1, 'file');
307
+ if (!repository.getSource(sourceId, workspaceId)) {
308
+ throw new CliError('SOURCE_NOT_FOUND', `source not found: ${sourceId}`, EXIT.unavailable);
309
+ }
310
+ const redactedLocator = redactSourceLocator(locator);
311
+ if (isDryRun(ctx, args))
312
+ return preview(command, { sourceId, redactedLocator });
313
+ repository.relocateSource(sourceId, redactedLocator, workspaceId);
314
+ return ok(command, { sourceId, redactedLocator });
315
+ }
316
+ case 'reindex': {
317
+ const { workspaceId } = authorizationContext(args, command);
318
+ if (isDryRun(ctx, args))
319
+ return preview(command, { operation: 'reindex' });
320
+ repository.reindex(workspaceId);
321
+ return ok(command, { operation: 'reindex' });
322
+ }
323
+ case 'audit': {
324
+ const { workspaceId } = authorizationContext(args, command);
325
+ const limit = boundedInteger(args.values.get('--limit'), 100, 1, 500, '--limit');
326
+ const cursor = args.values.get('--cursor');
327
+ if (args.flags.has('--otel')) {
328
+ return ok(command, repository.exportMutationEventsOtel({
329
+ workspaceId,
330
+ limit,
331
+ cursor,
332
+ }));
333
+ }
334
+ const page = repository.listMutationEvents({ workspaceId, limit, cursor });
335
+ return ok(command, {
336
+ items: page.items,
337
+ page: { limit, nextCursor: page.nextCursor },
338
+ });
339
+ }
340
+ case 'delete': {
341
+ const id = requiredPositional(args, 0, 'session-id');
342
+ const { workspaceId } = authorizationContext(args, command);
343
+ const counts = repository.deletionPreview(id, workspaceId);
344
+ if (counts.sessions === 0) {
345
+ throw new CliError('SESSION_NOT_FOUND', `session not found: ${id}`, EXIT.unavailable);
346
+ }
347
+ if (!args.flags.has('--confirm') || isDryRun(ctx, args)) {
348
+ return preview(command, {
349
+ sessionId: id, counts, providerLogsModified: false,
350
+ confirmationRequired: true,
351
+ });
352
+ }
353
+ repository.tombstoneSession(id, workspaceId);
354
+ return ok(command, {
355
+ sessionId: id, counts, providerLogsModified: false, outcome: 'tombstoned',
356
+ });
357
+ }
358
+ case 'restore': {
359
+ const id = requiredPositional(args, 0, 'session-id');
360
+ const { workspaceId } = authorizationContext(args, command);
361
+ if (isDryRun(ctx, args))
362
+ return preview(command, { sessionId: id, wouldRestore: true });
363
+ if (!repository.restoreSession(id, workspaceId)) {
364
+ throw new CliError('SESSION_NOT_FOUND', `tombstoned session not found: ${id}`, EXIT.unavailable);
365
+ }
366
+ return ok(command, { sessionId: id, outcome: 'restored', providerLogsModified: false });
367
+ }
368
+ case 'purge': {
369
+ const id = requiredPositional(args, 0, 'session-id');
370
+ const { workspaceId } = authorizationContext(args, command);
371
+ const completed = repository.getCompletedPurge(id, workspaceId);
372
+ if (completed) {
373
+ return ok(command, {
374
+ receipt: completed,
375
+ dependentDecisions: repository.listPromotionDependencyDecisions(completed.operationId),
376
+ duplicate: true,
377
+ providerLogsModified: false,
378
+ });
379
+ }
380
+ const purgePreview = repository.previewPurge(id, workspaceId);
381
+ const requestedAction = args.values.get('--dependent-action');
382
+ const action = purgePreview.promotedDependents.length > 0 && requestedAction
383
+ ? dependentAction(requestedAction)
384
+ : 'origin_unavailable';
385
+ const basis = purgePreview.promotedDependents.length > 0
386
+ ? (args.values.get('--basis') ?? 'not-yet-confirmed')
387
+ : (args.values.get('--basis') ?? 'no-promoted-dependents');
388
+ const decisions = purgePreview.promotedDependents.map((item) => ({
389
+ dependentId: item.dependentId,
390
+ action,
391
+ basis,
392
+ }));
393
+ const dispositionCoordinator = new FilesystemPromotionDispositionCoordinator({
394
+ projectRoot: ctx.cwd,
395
+ allowedRoots: ['.aiwg'],
396
+ });
397
+ if (!args.flags.has('--confirm') || isDryRun(ctx, args)) {
398
+ return preview(command, {
399
+ ...purgePreview,
400
+ artifactEffects: requestedAction
401
+ ? dispositionCoordinator.preview(purgePreview, decisions)
402
+ : purgePreview.promotedDependents.map((item) => ({
403
+ dependentId: item.dependentId,
404
+ destinationRef: item.destinationRef,
405
+ allowedActions: [
406
+ 'origin_unavailable', 'retain', 'revoke',
407
+ 'supersede', 'delete', 'abort',
408
+ ],
409
+ confirmationRequired: true,
410
+ })),
411
+ providerLogsModified: false,
412
+ });
413
+ }
414
+ if (purgePreview.promotedDependents.length > 0) {
415
+ dependentAction(requiredValue(args, '--dependent-action'));
416
+ requiredValue(args, '--basis');
417
+ }
418
+ const disposition = dispositionCoordinator.apply(purgePreview, decisions);
419
+ const receipt = repository.purgeSession({
420
+ preview: purgePreview,
421
+ actorClass: requiredValue(args, '--actor-class'),
422
+ reasonCode: requiredValue(args, '--reason-code'),
423
+ decisions,
424
+ });
425
+ dispositionCoordinator.catalogCommitted(receipt.operationId);
426
+ return ok(command, {
427
+ receipt,
428
+ artifactDisposition: disposition,
429
+ dependentDecisions: repository.listPromotionDependencyDecisions(receipt.operationId),
430
+ providerLogsModified: false,
431
+ });
432
+ }
433
+ case 'doctor':
434
+ return ok(command, {
435
+ database: redactSourceLocator(databasePath(ctx, args)),
436
+ health: repository.doctor(),
437
+ jsonContractVersion: JSON_CONTRACT_VERSION,
438
+ });
439
+ default:
440
+ throw new CliError('UNKNOWN_COMMAND', `unknown sessions command: ${command}`, EXIT.usage);
441
+ }
442
+ }
443
+ finally {
444
+ repository.close();
445
+ }
446
+ }
447
+ async function importSource(ctx, args) {
448
+ const input = resolve(ctx.cwd, requiredPositional(args, 0, 'file'));
449
+ const providerInput = args.values.get('--provider') ?? 'generic';
450
+ const provider = assertSessionProviderId(providerInput);
451
+ if (provider !== 'generic' && provider !== 'claude' && provider !== 'codex'
452
+ && provider !== 'copilot' && provider !== 'cursor' && provider !== 'factory'
453
+ && provider !== 'hermes' && provider !== 'opencode' && provider !== 'openclaw'
454
+ && provider !== 'openhuman' && provider !== 'warp' && provider !== 'devin-desktop') {
455
+ throw new CliError('UNSUPPORTED_OPERATION', `session import is not implemented for ${provider}`, EXIT.unsupported);
456
+ }
457
+ const sourceId = requiredValue(args, '--source-id');
458
+ const workspaceId = args.values.get('--workspace') ?? 'default';
459
+ const isClaude = provider === 'claude';
460
+ const isCodex = provider === 'codex';
461
+ const isCopilot = provider === 'copilot';
462
+ const isCursor = provider === 'cursor';
463
+ const isFactory = provider === 'factory';
464
+ const isHermes = provider === 'hermes';
465
+ const isOpenCode = provider === 'opencode';
466
+ const isOpenClaw = provider === 'openclaw';
467
+ const isOpenHuman = provider === 'openhuman';
468
+ const isWarp = provider === 'warp';
469
+ const isDevinDesktop = provider === 'devin-desktop';
470
+ const adapter = isClaude
471
+ ? new ClaudeSessionAdapter()
472
+ : isCodex
473
+ ? new CodexSessionAdapter()
474
+ : isCopilot
475
+ ? new CopilotSessionAdapter()
476
+ : isCursor
477
+ ? new CursorSessionAdapter()
478
+ : isFactory
479
+ ? new FactorySessionAdapter()
480
+ : isHermes
481
+ ? new HermesSessionAdapter()
482
+ : isOpenCode
483
+ ? new OpenCodeSessionAdapter()
484
+ : isOpenClaw
485
+ ? new OpenClawSessionAdapter()
486
+ : isOpenHuman
487
+ ? new OpenHumanSessionAdapter()
488
+ : isWarp
489
+ ? new WarpSessionAdapter()
490
+ : isDevinDesktop
491
+ ? new DevinDesktopSessionAdapter()
492
+ : new GenericSessionInterchangeAdapter();
493
+ const locatorClass = isClaude
494
+ ? (input.endsWith('.hooks.jsonl') ? 'claude-hook-jsonl' : 'claude-transcript-jsonl')
495
+ : isCodex
496
+ ? (input.endsWith('.app-server.jsonl') ? 'codex-app-server-jsonl' : 'codex-rollout-jsonl')
497
+ : isCopilot
498
+ ? 'copilot-chat-json-export'
499
+ : isCursor
500
+ ? cursorLocatorClass(input)
501
+ : isFactory
502
+ ? 'factory-droid-jsonl'
503
+ : isHermes
504
+ ? 'hermes-export-jsonl'
505
+ : isOpenCode
506
+ ? 'opencode-export-json'
507
+ : isOpenClaw
508
+ ? 'openclaw-consistent-snapshot-jsonl'
509
+ : isOpenHuman
510
+ ? 'openhuman-enriched-jsonl'
511
+ : isWarp
512
+ ? 'warp-markdown-export'
513
+ : isDevinDesktop
514
+ ? 'devin-desktop-cascade-hook-jsonl'
515
+ : 'manual-export';
516
+ const selectedSource = {
517
+ provider, locator: input, locatorClass, sourceId,
518
+ authorizedScope: { workspaceId, allowedRoots: [dirname(input)] },
519
+ };
520
+ const probe = await adapter.inspect(selectedSource);
521
+ const source = SessionSourceSchema.parse({
522
+ contractVersion: SESSION_CONTRACT_VERSION, sourceId, provider,
523
+ providerProfile: isClaude
524
+ ? 'documented-local-jsonl'
525
+ : isCodex
526
+ ? 'app-server-v2-rollout-fallback'
527
+ : isCopilot
528
+ ? 'vscode-chat-json-export'
529
+ : isCursor
530
+ ? cursorProviderProfile(locatorClass)
531
+ : isFactory
532
+ ? 'documented-project-jsonl'
533
+ : isHermes
534
+ ? 'native-schema-23-export'
535
+ : isOpenCode
536
+ ? 'sanitized-json-export'
537
+ : isOpenClaw
538
+ ? 'schema-16-event-v3-consistent-snapshot'
539
+ : isOpenHuman
540
+ ? 'schema-1-session-raw-enriched'
541
+ : isWarp
542
+ ? 'manual-lossy-markdown-export'
543
+ : isDevinDesktop
544
+ ? 'opt-in-cascade-transcript-hook'
545
+ : 'manual-interchange',
546
+ locatorClass, redactedLocator: redactSourceLocator(input),
547
+ adapterVersion: isClaude
548
+ ? CLAUDE_ADAPTER_VERSION
549
+ : isCodex
550
+ ? CODEX_ADAPTER_VERSION
551
+ : isCopilot
552
+ ? COPILOT_ADAPTER_VERSION
553
+ : isCursor
554
+ ? CURSOR_ADAPTER_VERSION
555
+ : isFactory
556
+ ? FACTORY_ADAPTER_VERSION
557
+ : isHermes
558
+ ? HERMES_ADAPTER_VERSION
559
+ : isOpenCode
560
+ ? OPENCODE_ADAPTER_VERSION
561
+ : isOpenClaw
562
+ ? OPENCLAW_ADAPTER_VERSION
563
+ : isOpenHuman
564
+ ? OPENHUMAN_ADAPTER_VERSION
565
+ : isWarp
566
+ ? WARP_ADAPTER_VERSION
567
+ : isDevinDesktop
568
+ ? DEVIN_DESKTOP_ADAPTER_VERSION
569
+ : GENERIC_ADAPTER_VERSION,
570
+ sourceSchemaVersion: probe.sourceSchemaVersion,
571
+ disposition: isWarp
572
+ ? 'manual-only'
573
+ : isClaude || isCodex || isCopilot || isCursor || isFactory || isHermes
574
+ || isOpenCode || isOpenClaw || isOpenHuman || isDevinDesktop
575
+ ? 'implemented' : 'manual-only',
576
+ operationalState: probe.operationalState,
577
+ consistency: probe.consistency, authorizedAt: new Date().toISOString(),
578
+ extensions: isClaude
579
+ ? { 'native.claude': {} }
580
+ : isCodex
581
+ ? { 'native.codex': {} }
582
+ : isCopilot
583
+ ? { 'native.copilot': {} }
584
+ : isCursor
585
+ ? { 'native.cursor': {} }
586
+ : isFactory
587
+ ? { 'native.factory': {} }
588
+ : isHermes
589
+ ? { 'native.hermes': {} }
590
+ : isOpenCode
591
+ ? { 'native.opencode': {} }
592
+ : isOpenClaw
593
+ ? { 'native.openclaw': {} }
594
+ : isOpenHuman
595
+ ? { 'native.openhuman': {} }
596
+ : isWarp
597
+ ? { 'native.warp': {} }
598
+ : isDevinDesktop
599
+ ? { 'native.devin-desktop': {
600
+ product: 'Devin Desktop',
601
+ compatibilityProviderId: 'windsurf',
602
+ } }
603
+ : { 'native.generic': {} },
604
+ });
605
+ if (isDryRun(ctx, args)) {
606
+ return preview('import', { source, wouldInspect: true, wouldPersist: false });
607
+ }
608
+ const repository = openRepository(ctx, args);
609
+ try {
610
+ const receipts = await new IncrementalSessionImporter(repository).import({
611
+ source, selectedSource, adapter, workspaceId, policyVersion: '1.0.0',
612
+ });
613
+ return ok('import', {
614
+ sourceId,
615
+ receipts,
616
+ totals: {
617
+ sessionsInserted: receipts.reduce((sum, item) => sum + item.sessionsInserted, 0),
618
+ eventsInserted: receipts.reduce((sum, item) => sum + item.eventsInserted, 0),
619
+ },
620
+ });
621
+ }
622
+ finally {
623
+ repository.close();
624
+ }
625
+ }
626
+ function providerDisposition(provider) {
627
+ if (provider === 'codex') {
628
+ return {
629
+ provider, disposition: 'implemented', operationalState: 'available',
630
+ supportedOperations: ['discover', 'inspect', 'stream'],
631
+ acquisitionModes: ['api', 'jsonl'],
632
+ reasonCode: null,
633
+ remediation: 'Authorize an App Server export or Codex sessions root, then import an explicit JSONL source.',
634
+ evidence: {
635
+ adapterVersion: CODEX_ADAPTER_VERSION,
636
+ verifiedAt: '2026-07-27',
637
+ documentation: 'https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md',
638
+ },
639
+ };
640
+ }
641
+ if (provider === 'claude') {
642
+ return {
643
+ provider, disposition: 'implemented', operationalState: 'available',
644
+ supportedOperations: ['discover', 'inspect', 'stream'],
645
+ acquisitionModes: ['jsonl', 'hook'],
646
+ reasonCode: null,
647
+ remediation: 'Authorize a Claude projects or hook root, then import an explicit JSONL file.',
648
+ evidence: {
649
+ adapterVersion: CLAUDE_ADAPTER_VERSION,
650
+ verifiedAt: '2026-07-27',
651
+ documentation: 'https://code.claude.com/docs/en/sessions',
652
+ },
653
+ };
654
+ }
655
+ if (provider === 'copilot') {
656
+ return {
657
+ provider, disposition: 'implemented', operationalState: 'available',
658
+ supportedOperations: ['inspect', 'stream'],
659
+ acquisitionModes: ['manual-export'],
660
+ reasonCode: null,
661
+ remediation: 'Use Chat: Export Chat in VS Code, then import the explicitly selected JSON file.',
662
+ evidence: {
663
+ adapterVersion: COPILOT_ADAPTER_VERSION,
664
+ verifiedAt: '2026-07-27',
665
+ documentation: 'https://code.visualstudio.com/docs/chat/chat-sessions#_export-a-chat-session-as-a-json-file',
666
+ },
667
+ };
668
+ }
669
+ if (provider === 'cursor') {
670
+ return {
671
+ provider, disposition: 'implemented', operationalState: 'available',
672
+ supportedOperations: ['inspect', 'stream'],
673
+ acquisitionModes: ['api', 'jsonl', 'manual-export'],
674
+ reasonCode: null,
675
+ remediation: 'Import Cursor CLI stream-json, captured Cloud Agent v1 events, or an editor Markdown export.',
676
+ evidence: {
677
+ adapterVersion: CURSOR_ADAPTER_VERSION,
678
+ verifiedAt: '2026-07-27',
679
+ documentation: 'https://docs.cursor.com/en/cli/reference/output-format',
680
+ },
681
+ };
682
+ }
683
+ if (provider === 'factory') {
684
+ return {
685
+ provider, disposition: 'implemented', operationalState: 'available',
686
+ supportedOperations: ['discover', 'inspect', 'stream'],
687
+ acquisitionModes: ['jsonl', 'api'],
688
+ reasonCode: null,
689
+ remediation: 'Authorize a Factory projects root and import a Droid JSONL transcript; API and Exec require explicit negotiated transports.',
690
+ evidence: {
691
+ adapterVersion: FACTORY_ADAPTER_VERSION,
692
+ verifiedAt: '2026-07-27',
693
+ documentation: 'https://docs.factory.ai/reference/hooks-reference',
694
+ },
695
+ };
696
+ }
697
+ if (provider === 'hermes') {
698
+ return {
699
+ provider, disposition: 'implemented', operationalState: 'available',
700
+ supportedOperations: ['inspect', 'stream'],
701
+ acquisitionModes: ['jsonl', 'api', 'sqlite-snapshot'],
702
+ reasonCode: null,
703
+ remediation: 'Use `hermes sessions export` or an explicitly verified sqlite3.backup() snapshot export.',
704
+ evidence: {
705
+ adapterVersion: HERMES_ADAPTER_VERSION,
706
+ verifiedAt: '2026-07-27',
707
+ documentation: 'https://hermes-agent.nousresearch.com/docs/user-guide/sessions/',
708
+ },
709
+ };
710
+ }
711
+ if (provider === 'opencode') {
712
+ return {
713
+ provider, disposition: 'implemented', operationalState: 'available',
714
+ supportedOperations: ['inspect', 'stream'],
715
+ acquisitionModes: ['manual-export', 'api', 'jsonl'],
716
+ reasonCode: null,
717
+ remediation: 'Use `opencode export <sessionID>` or an explicitly authorized negotiated local API/SSE transport.',
718
+ evidence: {
719
+ adapterVersion: OPENCODE_ADAPTER_VERSION,
720
+ verifiedAt: '2026-07-27',
721
+ documentation: 'https://opencode.ai/docs/cli/',
722
+ },
723
+ };
724
+ }
725
+ if (provider === 'openclaw') {
726
+ return {
727
+ provider, disposition: 'implemented', operationalState: 'available',
728
+ supportedOperations: ['inspect', 'stream'],
729
+ acquisitionModes: ['api', 'sqlite-snapshot', 'jsonl'],
730
+ reasonCode: null,
731
+ remediation: 'Use an authorized read-only Gateway transport or a schema-16/event-v3 sqlite3.backup() projection.',
732
+ evidence: {
733
+ adapterVersion: OPENCLAW_ADAPTER_VERSION,
734
+ verifiedAt: '2026-07-27',
735
+ documentation: 'https://docs.openclaw.ai/reference/session-management-compaction',
736
+ },
737
+ };
738
+ }
739
+ if (provider === 'openhuman') {
740
+ return {
741
+ provider, disposition: 'implemented', operationalState: 'available',
742
+ supportedOperations: ['inspect', 'stream'],
743
+ acquisitionModes: ['jsonl'],
744
+ reasonCode: null,
745
+ remediation: 'Import an explicitly selected schema-1 session_raw JSONL file, optionally bundled with thread/turn enrichment.',
746
+ evidence: {
747
+ adapterVersion: OPENHUMAN_ADAPTER_VERSION,
748
+ verifiedAt: '2026-07-27',
749
+ documentation: 'https://github.com/tinyhumansai/openhuman/releases',
750
+ },
751
+ };
752
+ }
753
+ if (provider === 'warp') {
754
+ return {
755
+ provider, disposition: 'manual-only', operationalState: 'available',
756
+ supportedOperations: ['inspect', 'stream'],
757
+ acquisitionModes: ['manual-export'],
758
+ reasonCode: 'LOSSY_MARKDOWN_ONLY',
759
+ remediation: 'Run `/export-to-file` in Warp and import the explicitly selected Markdown file.',
760
+ evidence: {
761
+ adapterVersion: WARP_ADAPTER_VERSION,
762
+ verifiedAt: '2026-07-27',
763
+ documentation: 'https://docs.warp.dev/agent-platform/capabilities/slash-commands',
764
+ },
765
+ };
766
+ }
767
+ if (provider === 'devin-desktop') {
768
+ return {
769
+ provider,
770
+ product: 'Devin Desktop',
771
+ compatibilityAliases: ['windsurf'],
772
+ aliasDeprecatedAfter: '2027-07-27',
773
+ disposition: 'implemented', operationalState: 'available',
774
+ supportedOperations: ['inspect', 'stream'],
775
+ acquisitionModes: ['hook', 'jsonl'],
776
+ reasonCode: null,
777
+ remediation: 'Enable Devin Desktop post_cascade_response_with_transcript, then explicitly select its JSONL output.',
778
+ evidence: {
779
+ adapterVersion: DEVIN_DESKTOP_ADAPTER_VERSION,
780
+ verifiedAt: '2026-07-27',
781
+ documentation: 'https://docs.devin.ai/desktop/cascade/hooks',
782
+ },
783
+ };
784
+ }
785
+ if (provider === 'generic') {
786
+ return {
787
+ provider, disposition: 'manual-only', operationalState: 'available',
788
+ supportedOperations: ['inspect', 'stream'], acquisitionModes: ['manual-export'],
789
+ reasonCode: 'MANUAL_SOURCE_SELECTION_REQUIRED',
790
+ remediation: 'Pass a declared interchange file to `aiwg sessions import`.',
791
+ evidence: { adapterVersion: GENERIC_ADAPTER_VERSION, verifiedAt: '2026-07-26' },
792
+ };
793
+ }
794
+ return {
795
+ provider, disposition: 'unsupported', operationalState: 'unavailable',
796
+ supportedOperations: [], acquisitionModes: [],
797
+ reasonCode: 'ADAPTER_NOT_IMPLEMENTED',
798
+ remediation: 'Use the generic interchange until the provider adapter milestone is delivered.',
799
+ evidence: { adapterVersion: null, verifiedAt: '2026-07-26' },
800
+ };
801
+ }
802
+ function cursorLocatorClass(input) {
803
+ if (input.endsWith('.md') || input.endsWith('.markdown'))
804
+ return 'cursor-editor-markdown';
805
+ if (input.endsWith('.cloud.jsonl'))
806
+ return 'cursor-cloud-events-jsonl';
807
+ return 'cursor-cli-stream-json';
808
+ }
809
+ function cursorProviderProfile(locatorClass) {
810
+ if (locatorClass === 'cursor-editor-markdown')
811
+ return 'editor-markdown-lossy';
812
+ if (locatorClass === 'cursor-cloud-events-jsonl')
813
+ return 'cloud-agents-api-v1';
814
+ return 'cli-stream-json';
815
+ }
816
+ function openRepository(ctx, args) {
817
+ const path = databasePath(ctx, args);
818
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
819
+ try {
820
+ return new SessionRepository(path);
821
+ }
822
+ catch (error) {
823
+ throw new CliError('CATALOG_UNAVAILABLE', error instanceof Error ? error.message : String(error), EXIT.storage);
824
+ }
825
+ }
826
+ function databasePath(ctx, args) {
827
+ return resolve(ctx.cwd, args.values.get('--db') ?? '.aiwg/sessions/catalog.sqlite');
828
+ }
829
+ function parseArgs(argv) {
830
+ const flags = new Set();
831
+ const values = new Map();
832
+ const positionals = [];
833
+ const valueFlags = new Set([
834
+ '--db', '--provider', '--workspace', '--tag', '--limit', '--cursor', '--source-id',
835
+ '--date-from', '--date-to', '--participant', '--model', '--role', '--tool',
836
+ '--entity', '--sensitivity', '--extraction-state', '--page-size', '--max-documents',
837
+ '--state', '--reviewer', '--reason', '--policy-version', '--min-confidence',
838
+ '--consumer', '--actor-class', '--reason-code', '--dependent-action', '--basis',
839
+ ]);
840
+ let command;
841
+ for (let index = 0; index < argv.length; index += 1) {
842
+ const arg = argv[index];
843
+ if (valueFlags.has(arg)) {
844
+ const value = argv[index + 1];
845
+ if (!value || value.startsWith('--')) {
846
+ throw new CliError('MISSING_OPTION_VALUE', `missing value for ${arg}`, EXIT.usage);
847
+ }
848
+ values.set(arg, value);
849
+ index += 1;
850
+ }
851
+ else if (arg.startsWith('-')) {
852
+ flags.add(arg);
853
+ }
854
+ else if (!command) {
855
+ command = arg;
856
+ }
857
+ else {
858
+ positionals.push(arg);
859
+ }
860
+ }
861
+ return { command, positionals, flags, values };
862
+ }
863
+ function requiredPositional(args, index, name) {
864
+ const value = args.positionals[index];
865
+ if (!value)
866
+ throw new CliError('MISSING_ARGUMENT', `missing required ${name}`, EXIT.usage);
867
+ return value;
868
+ }
869
+ function requiredValue(args, flag) {
870
+ const value = args.values.get(flag);
871
+ if (!value)
872
+ throw new CliError('MISSING_ARGUMENT', `missing required ${flag}`, EXIT.usage);
873
+ return value;
874
+ }
875
+ function authorizationContext(args, operation) {
876
+ return {
877
+ actorId: 'local-catalog-owner',
878
+ workspaceId: requiredValue(args, '--workspace'),
879
+ operation,
880
+ catalogScope: 'workspace',
881
+ mode: 'local-owner',
882
+ };
883
+ }
884
+ function boundedInteger(value, fallback, min, max, name) {
885
+ if (value === undefined)
886
+ return fallback;
887
+ if (!/^\d+$/.test(value))
888
+ throw new CliError('INVALID_ARGUMENT', `${name} must be an integer`, EXIT.usage);
889
+ const parsed = Number(value);
890
+ if (parsed < min || parsed > max) {
891
+ throw new CliError('INVALID_ARGUMENT', `${name} must be between ${min} and ${max}`, EXIT.usage);
892
+ }
893
+ return parsed;
894
+ }
895
+ function boundedNumber(value, fallback, min, max, name) {
896
+ if (value === undefined)
897
+ return fallback;
898
+ const parsed = Number(value);
899
+ if (!Number.isFinite(parsed) || parsed < min || parsed > max) {
900
+ throw new CliError('INVALID_ARGUMENT', `${name} must be between ${min} and ${max}`, EXIT.usage);
901
+ }
902
+ return parsed;
903
+ }
904
+ function candidateState(value) {
905
+ if (value === undefined)
906
+ return undefined;
907
+ const states = new Set([
908
+ 'pending', 'accepted', 'rejected', 'deferred', 'promoted', 'superseded',
909
+ ]);
910
+ if (!states.has(value)) {
911
+ throw new CliError('INVALID_ARGUMENT', `invalid candidate state: ${value}`, EXIT.usage);
912
+ }
913
+ return value;
914
+ }
915
+ function dependentAction(value) {
916
+ const actions = new Set([
917
+ 'revoke', 'supersede', 'retain', 'origin_unavailable', 'delete', 'abort',
918
+ ]);
919
+ if (!actions.has(value)) {
920
+ throw new CliError('INVALID_ARGUMENT', `invalid dependent action: ${value}`, EXIT.usage);
921
+ }
922
+ return value;
923
+ }
924
+ function isDryRun(ctx, args) {
925
+ return Boolean(ctx.dryRun || args.flags.has('--dry-run'));
926
+ }
927
+ function ok(command, data) {
928
+ return { envelope: envelope(`sessions.${command}`, 'ok', data, null), exitCode: EXIT.ok };
929
+ }
930
+ function preview(command, data) {
931
+ return { envelope: envelope(`sessions.${command}`, 'preview', data, null), exitCode: EXIT.ok };
932
+ }
933
+ function envelope(command, status, data, error) {
934
+ return { contractVersion: JSON_CONTRACT_VERSION, command, status, data, error };
935
+ }
936
+ function emit(value) {
937
+ console.log(JSON.stringify(value, null, 2));
938
+ }
939
+ function printHuman(value) {
940
+ if (value.status === 'preview')
941
+ console.log('Preview (no changes applied)');
942
+ console.log(JSON.stringify(value.data, null, 2));
943
+ }
944
+ class CliError extends Error {
945
+ code;
946
+ exitCode;
947
+ constructor(code, message, exitCode) {
948
+ super(message);
949
+ this.code = code;
950
+ this.exitCode = exitCode;
951
+ }
952
+ }
953
+ function normalizeError(error) {
954
+ if (error instanceof CliError) {
955
+ return { error: { code: error.code, message: error.message }, exitCode: error.exitCode };
956
+ }
957
+ if (error instanceof SessionContractError) {
958
+ const exitCode = error.code === 'UNSUPPORTED_OPERATION' ? EXIT.unsupported : EXIT.contract;
959
+ return { error: { code: error.code, message: error.message }, exitCode };
960
+ }
961
+ return {
962
+ error: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : String(error) },
963
+ exitCode: EXIT.storage,
964
+ };
965
+ }
966
+ //# sourceMappingURL=sessions.js.map