@aiwg/cli 2026.7.20 → 2026.7.23

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 (62) hide show
  1. package/README.md +18 -7
  2. package/dist/src/api/index.d.ts +2 -0
  3. package/dist/src/api/index.js +2 -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 +1265 -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/config/aiwg-config.js +12 -0
  17. package/dist/src/config/cli.js +16 -3
  18. package/dist/src/extensions/commands/definitions.js +29 -0
  19. package/dist/src/extensions/manifest.js +29 -0
  20. package/dist/src/security/threat-assessment-config.js +296 -0
  21. package/dist/src/sessions/adapters/claude.js +385 -0
  22. package/dist/src/sessions/adapters/codex.js +548 -0
  23. package/dist/src/sessions/adapters/copilot.js +226 -0
  24. package/dist/src/sessions/adapters/cursor.js +528 -0
  25. package/dist/src/sessions/adapters/factory.js +386 -0
  26. package/dist/src/sessions/adapters/generic.js +225 -0
  27. package/dist/src/sessions/adapters/hermes.js +341 -0
  28. package/dist/src/sessions/adapters/openclaw.js +381 -0
  29. package/dist/src/sessions/adapters/opencode.js +454 -0
  30. package/dist/src/sessions/adapters/openhuman.js +315 -0
  31. package/dist/src/sessions/adapters/warp.js +160 -0
  32. package/dist/src/sessions/adapters/windsurf.js +212 -0
  33. package/dist/src/sessions/batch-contracts.js +121 -0
  34. package/dist/src/sessions/batch-import.js +265 -0
  35. package/dist/src/sessions/candidates.js +210 -0
  36. package/dist/src/sessions/contracts.js +337 -0
  37. package/dist/src/sessions/discovery.js +51 -0
  38. package/dist/src/sessions/fixtures.js +12 -0
  39. package/dist/src/sessions/import-lease.js +152 -0
  40. package/dist/src/sessions/importer.js +464 -0
  41. package/dist/src/sessions/index.js +31 -0
  42. package/dist/src/sessions/knowledge-shard.js +61 -0
  43. package/dist/src/sessions/optional-backends.js +238 -0
  44. package/dist/src/sessions/origin.js +117 -0
  45. package/dist/src/sessions/policy.js +192 -0
  46. package/dist/src/sessions/ports.js +2 -0
  47. package/dist/src/sessions/promotion.js +367 -0
  48. package/dist/src/sessions/readers.js +176 -0
  49. package/dist/src/sessions/repository.js +1892 -0
  50. package/dist/src/sessions/timeline.js +148 -0
  51. package/dist/src/sessions/workspace-discovery.js +319 -0
  52. package/dist/src/skills/adapters/agent-skills.js +59 -0
  53. package/dist/src/skills/adapters/local.js +19 -1
  54. package/dist/src/skills/agent-skills.js +249 -0
  55. package/dist/src/skills/cli.js +463 -7
  56. package/dist/src/skills/deployer.js +554 -0
  57. package/dist/src/skills/doctor.js +105 -0
  58. package/dist/src/skills/exporter.js +382 -0
  59. package/dist/src/skills/importer.js +921 -0
  60. package/dist/src/skills/registry.js +19 -0
  61. package/dist/src/skills/validator.js +323 -0
  62. package/package.json +2 -2
@@ -0,0 +1,1265 @@
1
+ import { existsSync, mkdirSync, realpathSync, statSync, } from 'node:fs';
2
+ import { dirname, isAbsolute, 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, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, } 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
+ locked: 7, coverage: 8,
8
+ };
9
+ const HELP = `Usage: aiwg sessions <command> [options]
10
+
11
+ Commands:
12
+ sources Show all canonical provider dispositions
13
+ discover --workspace <path> Inventory authorized workspace histories
14
+ import-discovered --workspace <path> Import the exact saved manifest
15
+ import <file> --source-id <id> Import a supported provider JSONL source
16
+ list [--limit N] [--cursor N] List normalized sessions
17
+ timeline [--gap 30m] Report chronological activity segments
18
+ show <session-id> Show a session with events and tags
19
+ search <query> --workspace <id> Search authorized normalized content
20
+ extract [session-id] --workspace <id> Extract structural candidates
21
+ candidates [--state <state>] List the candidate review queue
22
+ review <id> <version> <state> Record an explicit review transition
23
+ promote <id> <version> Preview promotion; use --confirm to write
24
+ tag <session-id> <tag> Add a catalog tag
25
+ relocate <source-id> <file> Update AIWG source-location metadata
26
+ reindex Rebuild catalog indexes
27
+ delete <session-id> Preview deletion; use --confirm to tombstone
28
+ restore <session-id> Restore a reversible catalog tombstone
29
+ purge <session-id> Preview terminal AIWG-copy purge
30
+ audit --workspace <id> Read content-free mutation events
31
+ doctor Check catalog availability and integrity
32
+
33
+ Options:
34
+ --json Emit the versioned JSON contract
35
+ --dry-run Preview a mutation without changing state
36
+ --db <path> Override .aiwg/sessions/catalog.sqlite
37
+ --manifest <path> Override the discovery manifest path
38
+ --provider-home <path> Override the provider home root (testing/portable homes)
39
+ --codex-root <path> Explicitly authorize a shared Codex sessions/export root
40
+ --confirm, --yes Confirm a persistent discovered batch import
41
+ --lock-wait-ms <n> Maximum import-lease wait (default 5000)
42
+ --inactivity-threshold <duration> Historical inactivity threshold (default 24h)
43
+ --min-coverage <0..1> Fail list/report commands below a coverage ratio
44
+ --provider <id> Filter list or select an import adapter
45
+ --consumer <id> Select a named memory consumer for promotion
46
+ --workspace <id>, --tag <tag>, --limit <n>, --cursor <n>
47
+ --page-size <n> Extraction scan page size (default 250, maximum 500)
48
+ --max-documents <n> Explicit extraction safety limit; returns a partial receipt`;
49
+ export const sessionsHandler = {
50
+ id: 'sessions',
51
+ name: 'Sessions',
52
+ description: 'Manage the normalized session catalog (the singular `session` command remains the launcher)',
53
+ category: 'project',
54
+ aliases: [],
55
+ async execute(ctx) {
56
+ const json = ctx.args.includes('--json');
57
+ let parsed;
58
+ try {
59
+ parsed = parseArgs(ctx.args);
60
+ }
61
+ catch (error) {
62
+ const normalized = normalizeError(error);
63
+ const output = envelope('sessions', 'error', null, normalized.error);
64
+ if (json)
65
+ emit(output);
66
+ else
67
+ console.error(`${normalized.error.code}: ${normalized.error.message}`);
68
+ return { exitCode: normalized.exitCode, message: normalized.error.message };
69
+ }
70
+ if (!parsed.command || parsed.flags.has('--help') || parsed.flags.has('-h')) {
71
+ if (json)
72
+ emit(envelope('sessions.help', 'ok', { usage: HELP }, null));
73
+ else
74
+ console.log(HELP);
75
+ return { exitCode: parsed.command ? EXIT.ok : EXIT.usage };
76
+ }
77
+ try {
78
+ const result = await executeCommand(ctx, parsed);
79
+ if (json)
80
+ emit(result.envelope);
81
+ else
82
+ printHuman(result.envelope);
83
+ return { exitCode: result.exitCode };
84
+ }
85
+ catch (error) {
86
+ const normalized = normalizeError(error);
87
+ const output = envelope(`sessions.${parsed.command}`, 'error', null, normalized.error);
88
+ if (json)
89
+ emit(output);
90
+ else
91
+ console.error(`${normalized.error.code}: ${normalized.error.message}`);
92
+ return { exitCode: normalized.exitCode, message: normalized.error.message };
93
+ }
94
+ },
95
+ };
96
+ async function executeCommand(ctx, args) {
97
+ const command = args.command;
98
+ if (command === 'sources') {
99
+ const reports = [...SESSION_PROVIDER_IDS].sort().map(providerDisposition);
100
+ return ok(command, { providers: reports, count: reports.length });
101
+ }
102
+ if (command === 'import')
103
+ return importSource(ctx, args);
104
+ if (command === 'discover')
105
+ return discoverWorkspace(ctx, args);
106
+ if (command === 'import-discovered')
107
+ return importDiscovered(ctx, args);
108
+ const repository = openRepository(ctx, args);
109
+ try {
110
+ switch (command) {
111
+ case 'list': {
112
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
113
+ const limit = boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit');
114
+ const cursor = args.values.get('--cursor');
115
+ const providerInput = args.values.get('--provider');
116
+ const provider = providerInput ? assertSessionProviderId(providerInput) : undefined;
117
+ const result = repository.listSessions({
118
+ provider,
119
+ workspaceId,
120
+ tag: args.values.get('--tag'),
121
+ limit,
122
+ cursor,
123
+ });
124
+ const coverage = repository.getCoverage(workspaceId);
125
+ const response = {
126
+ items: result.items,
127
+ page: {
128
+ limit,
129
+ cursor: cursor ?? null,
130
+ nextCursor: result.nextCursor,
131
+ snapshotRowid: result.snapshotRowid,
132
+ total: result.total,
133
+ },
134
+ coverage,
135
+ };
136
+ const minimum = args.values.has('--min-coverage')
137
+ ? boundedNumber(args.values.get('--min-coverage'), 0, 0, 1, '--min-coverage')
138
+ : null;
139
+ if (minimum !== null
140
+ && (coverage.coverageRatio === null || coverage.coverageRatio < minimum)) {
141
+ return {
142
+ envelope: envelope(`sessions.${command}`, 'error', response, {
143
+ code: 'COVERAGE_BELOW_THRESHOLD',
144
+ message: `session coverage is below the requested threshold ${minimum}`,
145
+ details: coverage,
146
+ }),
147
+ exitCode: EXIT.coverage,
148
+ };
149
+ }
150
+ return ok(command, response);
151
+ }
152
+ case 'timeline': {
153
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
154
+ const gapMs = timelineGap(args.values.get('--gap'));
155
+ const items = deriveSessionTimeline(repository.listTimelineInputs(workspaceId), gapMs);
156
+ const coverage = repository.getCoverage(workspaceId);
157
+ const response = {
158
+ schemaVersion: '1.0.0',
159
+ workspaceId,
160
+ gapMs,
161
+ items,
162
+ count: items.length,
163
+ coverage,
164
+ };
165
+ const minimum = args.values.has('--min-coverage')
166
+ ? boundedNumber(args.values.get('--min-coverage'), 0, 0, 1, '--min-coverage')
167
+ : null;
168
+ if (minimum !== null
169
+ && (coverage.coverageRatio === null || coverage.coverageRatio < minimum)) {
170
+ return {
171
+ envelope: envelope(`sessions.${command}`, 'error', response, {
172
+ code: 'COVERAGE_BELOW_THRESHOLD',
173
+ message: `session coverage is below the requested threshold ${minimum}`,
174
+ details: coverage,
175
+ }),
176
+ exitCode: EXIT.coverage,
177
+ };
178
+ }
179
+ return ok(command, response);
180
+ }
181
+ case 'show': {
182
+ const id = requiredPositional(args, 0, 'session-id');
183
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
184
+ const session = repository.getSession(id, workspaceId);
185
+ if (!session)
186
+ throw new CliError('SESSION_NOT_FOUND', `session not found: ${id}`, EXIT.unavailable);
187
+ return ok(command, {
188
+ session,
189
+ tags: repository.listTags(id, workspaceId),
190
+ events: repository.listEvents(id, workspaceId),
191
+ });
192
+ }
193
+ case 'search': {
194
+ const query = requiredPositional(args, 0, 'query');
195
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
196
+ const providerInput = args.values.get('--provider');
197
+ const provider = providerInput ? assertSessionProviderId(providerInput) : undefined;
198
+ const result = repository.search({
199
+ query,
200
+ workspaceId,
201
+ providers: provider ? [provider] : undefined,
202
+ dateFrom: args.values.get('--date-from'),
203
+ dateTo: args.values.get('--date-to'),
204
+ participant: args.values.get('--participant'),
205
+ model: args.values.get('--model'),
206
+ role: args.values.get('--role'),
207
+ tool: args.values.get('--tool'),
208
+ tag: args.values.get('--tag'),
209
+ entity: args.values.get('--entity'),
210
+ sensitivity: args.values.get('--sensitivity'),
211
+ extractionState: args.values.get('--extraction-state'),
212
+ controlEvents: controlEventMode(args.values.get('--control-events')),
213
+ limit: boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit'),
214
+ cursor: args.values.get('--cursor'),
215
+ });
216
+ return ok(command, {
217
+ items: result.items,
218
+ page: { limit: boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit'),
219
+ nextCursor: result.nextCursor },
220
+ });
221
+ }
222
+ case 'extract': {
223
+ const { workspaceId } = authorizationContext(ctx, args, command);
224
+ const sessionId = args.positionals[0];
225
+ const pageSize = boundedInteger(args.values.get('--page-size'), 250, 1, 500, '--page-size');
226
+ const maxDocuments = args.values.has('--max-documents')
227
+ ? boundedInteger(args.values.get('--max-documents'), pageSize, 1, 1_000_000, '--max-documents')
228
+ : null;
229
+ const documents = [];
230
+ let cursor;
231
+ let truncated = false;
232
+ do {
233
+ const remaining = maxDocuments === null
234
+ ? pageSize
235
+ : Math.min(pageSize, maxDocuments - documents.length);
236
+ if (remaining <= 0) {
237
+ truncated = true;
238
+ break;
239
+ }
240
+ const page = repository.authorizedSearchDocumentPage({
241
+ workspaceId,
242
+ sessionIds: sessionId ? [sessionId] : undefined,
243
+ limit: remaining,
244
+ cursor,
245
+ });
246
+ documents.push(...page.items);
247
+ cursor = page.nextCursor ?? undefined;
248
+ if (maxDocuments !== null && documents.length >= maxDocuments && cursor) {
249
+ truncated = true;
250
+ break;
251
+ }
252
+ } while (cursor);
253
+ if (sessionId && documents.length === 0) {
254
+ throw new CliError('SESSION_NOT_FOUND', `no authorized evidence found for session: ${sessionId}`, EXIT.unavailable);
255
+ }
256
+ const dryRun = isDryRun(ctx, args);
257
+ const service = new CandidateExtractionService(dryRun
258
+ ? { saveCandidates: (candidates) => [...candidates] }
259
+ : repository);
260
+ const items = await service.extract({
261
+ documents,
262
+ extractor: new StructuralCandidateExtractor(),
263
+ policy: {
264
+ version: args.values.get('--policy-version') ?? '1.0.0',
265
+ projectScope: workspaceId,
266
+ temporalScope: 'source-event',
267
+ minimumConfidence: boundedNumber(args.values.get('--min-confidence'), 0.5, 0, 1, '--min-confidence'),
268
+ },
269
+ });
270
+ const scan = {
271
+ complete: !truncated,
272
+ documentsScanned: documents.length,
273
+ pageSize,
274
+ nextCursor: truncated ? cursor ?? null : null,
275
+ limit: maxDocuments,
276
+ };
277
+ return dryRun
278
+ ? preview(command, { items, count: items.length, durableMemoryWrites: 0, scan })
279
+ : ok(command, { items, count: items.length, durableMemoryWrites: 0, scan });
280
+ }
281
+ case 'candidates': {
282
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
283
+ const state = candidateState(args.values.get('--state'));
284
+ return ok(command, {
285
+ items: repository.listCandidates(state, workspaceId),
286
+ });
287
+ }
288
+ case 'review': {
289
+ const { workspaceId } = authorizationContext(ctx, args, command);
290
+ const candidateId = requiredPositional(args, 0, 'candidate-id');
291
+ const version = boundedInteger(requiredPositional(args, 1, 'version'), 1, 1, Number.MAX_SAFE_INTEGER, 'version');
292
+ const toState = candidateState(requiredPositional(args, 2, 'state'));
293
+ if (!toState)
294
+ throw new CliError('INVALID_ARGUMENT', 'review state is required', EXIT.usage);
295
+ if (isDryRun(ctx, args)) {
296
+ return preview(command, {
297
+ candidateId, version, toState,
298
+ reviewer: requiredValue(args, '--reviewer'),
299
+ reason: requiredValue(args, '--reason'),
300
+ securityAcknowledged: args.flags.has('--acknowledge-security-risk'),
301
+ });
302
+ }
303
+ return ok(command, repository.reviewCandidate({
304
+ candidateId,
305
+ version,
306
+ toState,
307
+ reviewer: requiredValue(args, '--reviewer'),
308
+ reason: requiredValue(args, '--reason'),
309
+ securityAcknowledged: args.flags.has('--acknowledge-security-risk'),
310
+ workspaceId,
311
+ }));
312
+ }
313
+ case 'promote': {
314
+ const { workspaceId } = authorizationContext(ctx, args, command);
315
+ const candidateId = requiredPositional(args, 0, 'candidate-id');
316
+ const version = boundedInteger(requiredPositional(args, 1, 'version'), 1, 1, Number.MAX_SAFE_INTEGER, 'version');
317
+ const consumer = requiredValue(args, '--consumer');
318
+ const destination = new FilesystemMemoryDestination({
319
+ projectRoot: ctx.cwd,
320
+ consumer,
321
+ manifestPath: resolveMemoryConsumerManifest(ctx.cwd, consumer),
322
+ });
323
+ const scopedPromotionStore = {
324
+ getCandidate: (id, candidateVersion) => repository.getCandidate(id, candidateVersion, workspaceId),
325
+ getPromotionReceipt: (id, candidateVersion, namedConsumer) => repository.getCandidate(id, candidateVersion, workspaceId)
326
+ ? repository.getPromotionReceipt(id, candidateVersion, namedConsumer)
327
+ : null,
328
+ recordPromotion: (receipt) => {
329
+ if (!repository.getCandidate(receipt.candidateId, receipt.candidateVersion, workspaceId)) {
330
+ throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'candidate version is not available in the authorized workspace');
331
+ }
332
+ return repository.recordPromotion(receipt);
333
+ },
334
+ };
335
+ const gateway = new MemoryPromotionGateway(scopedPromotionStore);
336
+ const promotionPreview = gateway.preview({ candidateId, version, destination });
337
+ if (!args.flags.has('--confirm') || isDryRun(ctx, args)) {
338
+ return preview(command, promotionPreview);
339
+ }
340
+ const receipt = await gateway.promote({
341
+ candidateId,
342
+ version,
343
+ destination,
344
+ reviewer: requiredValue(args, '--reviewer'),
345
+ operationId: promotionPreview.operationId,
346
+ });
347
+ return ok(command, { preview: promotionPreview, receipt });
348
+ }
349
+ case 'tag': {
350
+ const id = requiredPositional(args, 0, 'session-id');
351
+ const { workspaceId } = authorizationContext(ctx, args, command);
352
+ const tag = requiredPositional(args, 1, 'tag');
353
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(tag)) {
354
+ throw new CliError('INVALID_TAG', 'tag must be 1-64 safe identifier characters', EXIT.usage);
355
+ }
356
+ if (isDryRun(ctx, args))
357
+ return preview(command, { sessionId: id, tag, wouldAdd: true });
358
+ if (!repository.tagSession(id, tag, workspaceId)) {
359
+ if (!repository.getSession(id, workspaceId)) {
360
+ throw new CliError('SESSION_NOT_FOUND', `session not found: ${id}`, EXIT.unavailable);
361
+ }
362
+ }
363
+ return ok(command, { sessionId: id, tag, tags: repository.listTags(id, workspaceId) });
364
+ }
365
+ case 'relocate': {
366
+ const sourceId = requiredPositional(args, 0, 'source-id');
367
+ const { workspaceId } = authorizationContext(ctx, args, command);
368
+ const locator = requiredPositional(args, 1, 'file');
369
+ if (!repository.getSource(sourceId, workspaceId)) {
370
+ throw new CliError('SOURCE_NOT_FOUND', `source not found: ${sourceId}`, EXIT.unavailable);
371
+ }
372
+ const redactedLocator = redactSourceLocator(locator);
373
+ if (isDryRun(ctx, args))
374
+ return preview(command, { sourceId, redactedLocator });
375
+ repository.relocateSource(sourceId, redactedLocator, workspaceId);
376
+ return ok(command, { sourceId, redactedLocator });
377
+ }
378
+ case 'reindex': {
379
+ const { workspaceId } = authorizationContext(ctx, args, command);
380
+ if (isDryRun(ctx, args))
381
+ return preview(command, { operation: 'reindex' });
382
+ repository.reindex(workspaceId);
383
+ return ok(command, { operation: 'reindex' });
384
+ }
385
+ case 'audit': {
386
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
387
+ const limit = boundedInteger(args.values.get('--limit'), 100, 1, 500, '--limit');
388
+ const cursor = args.values.get('--cursor');
389
+ if (args.flags.has('--otel')) {
390
+ return ok(command, repository.exportMutationEventsOtel({
391
+ workspaceId,
392
+ limit,
393
+ cursor,
394
+ }));
395
+ }
396
+ const page = repository.listMutationEvents({ workspaceId, limit, cursor });
397
+ return ok(command, {
398
+ items: page.items,
399
+ page: { limit, nextCursor: page.nextCursor },
400
+ });
401
+ }
402
+ case 'delete': {
403
+ const id = requiredPositional(args, 0, 'session-id');
404
+ const { workspaceId } = authorizationContext(ctx, args, command);
405
+ const counts = repository.deletionPreview(id, workspaceId);
406
+ if (counts.sessions === 0) {
407
+ throw new CliError('SESSION_NOT_FOUND', `session not found: ${id}`, EXIT.unavailable);
408
+ }
409
+ if (!args.flags.has('--confirm') || isDryRun(ctx, args)) {
410
+ return preview(command, {
411
+ sessionId: id, counts, providerLogsModified: false,
412
+ confirmationRequired: true,
413
+ });
414
+ }
415
+ repository.tombstoneSession(id, workspaceId);
416
+ return ok(command, {
417
+ sessionId: id, counts, providerLogsModified: false, outcome: 'tombstoned',
418
+ });
419
+ }
420
+ case 'restore': {
421
+ const id = requiredPositional(args, 0, 'session-id');
422
+ const { workspaceId } = authorizationContext(ctx, args, command);
423
+ if (isDryRun(ctx, args))
424
+ return preview(command, { sessionId: id, wouldRestore: true });
425
+ if (!repository.restoreSession(id, workspaceId)) {
426
+ throw new CliError('SESSION_NOT_FOUND', `tombstoned session not found: ${id}`, EXIT.unavailable);
427
+ }
428
+ return ok(command, { sessionId: id, outcome: 'restored', providerLogsModified: false });
429
+ }
430
+ case 'purge': {
431
+ const id = requiredPositional(args, 0, 'session-id');
432
+ const { workspaceId } = authorizationContext(ctx, args, command);
433
+ const completed = repository.getCompletedPurge(id, workspaceId);
434
+ if (completed) {
435
+ return ok(command, {
436
+ receipt: completed,
437
+ dependentDecisions: repository.listPromotionDependencyDecisions(completed.operationId),
438
+ duplicate: true,
439
+ providerLogsModified: false,
440
+ });
441
+ }
442
+ const purgePreview = repository.previewPurge(id, workspaceId);
443
+ const requestedAction = args.values.get('--dependent-action');
444
+ const action = purgePreview.promotedDependents.length > 0 && requestedAction
445
+ ? dependentAction(requestedAction)
446
+ : 'origin_unavailable';
447
+ const basis = purgePreview.promotedDependents.length > 0
448
+ ? (args.values.get('--basis') ?? 'not-yet-confirmed')
449
+ : (args.values.get('--basis') ?? 'no-promoted-dependents');
450
+ const decisions = purgePreview.promotedDependents.map((item) => ({
451
+ dependentId: item.dependentId,
452
+ action,
453
+ basis,
454
+ }));
455
+ const dispositionCoordinator = new FilesystemPromotionDispositionCoordinator({
456
+ projectRoot: ctx.cwd,
457
+ allowedRoots: ['.aiwg'],
458
+ });
459
+ if (!args.flags.has('--confirm') || isDryRun(ctx, args)) {
460
+ return preview(command, {
461
+ ...purgePreview,
462
+ artifactEffects: requestedAction
463
+ ? dispositionCoordinator.preview(purgePreview, decisions)
464
+ : purgePreview.promotedDependents.map((item) => ({
465
+ dependentId: item.dependentId,
466
+ destinationRef: item.destinationRef,
467
+ allowedActions: [
468
+ 'origin_unavailable', 'retain', 'revoke',
469
+ 'supersede', 'delete', 'abort',
470
+ ],
471
+ confirmationRequired: true,
472
+ })),
473
+ providerLogsModified: false,
474
+ });
475
+ }
476
+ if (purgePreview.promotedDependents.length > 0) {
477
+ dependentAction(requiredValue(args, '--dependent-action'));
478
+ requiredValue(args, '--basis');
479
+ }
480
+ const disposition = dispositionCoordinator.apply(purgePreview, decisions);
481
+ const receipt = repository.purgeSession({
482
+ preview: purgePreview,
483
+ actorClass: requiredValue(args, '--actor-class'),
484
+ reasonCode: requiredValue(args, '--reason-code'),
485
+ decisions,
486
+ });
487
+ dispositionCoordinator.catalogCommitted(receipt.operationId);
488
+ return ok(command, {
489
+ receipt,
490
+ artifactDisposition: disposition,
491
+ dependentDecisions: repository.listPromotionDependencyDecisions(receipt.operationId),
492
+ providerLogsModified: false,
493
+ });
494
+ }
495
+ case 'doctor':
496
+ return ok(command, {
497
+ database: redactSourceLocator(databasePath(ctx, args)),
498
+ health: repository.doctor(),
499
+ jsonContractVersion: JSON_CONTRACT_VERSION,
500
+ });
501
+ default:
502
+ throw new CliError('UNKNOWN_COMMAND', `unknown sessions command: ${command}`, EXIT.usage);
503
+ }
504
+ }
505
+ finally {
506
+ repository.close();
507
+ }
508
+ }
509
+ async function importSource(ctx, args) {
510
+ const input = resolve(ctx.cwd, requiredPositional(args, 0, 'file'));
511
+ const providerInput = args.values.get('--provider') ?? 'generic';
512
+ const provider = assertSessionProviderId(providerInput);
513
+ if (provider !== 'generic' && provider !== 'claude' && provider !== 'codex'
514
+ && provider !== 'copilot' && provider !== 'cursor' && provider !== 'factory'
515
+ && provider !== 'hermes' && provider !== 'opencode' && provider !== 'openclaw'
516
+ && provider !== 'openhuman' && provider !== 'warp' && provider !== 'devin-desktop') {
517
+ throw new CliError('UNSUPPORTED_OPERATION', `session import is not implemented for ${provider}`, EXIT.unsupported);
518
+ }
519
+ const sourceId = requiredValue(args, '--source-id');
520
+ const workspaceId = args.values.has('--workspace')
521
+ ? normalizeWorkspaceId(ctx.cwd, args.values.get('--workspace'))
522
+ : 'default';
523
+ const isClaude = provider === 'claude';
524
+ const isCodex = provider === 'codex';
525
+ const isCopilot = provider === 'copilot';
526
+ const isCursor = provider === 'cursor';
527
+ const isFactory = provider === 'factory';
528
+ const isHermes = provider === 'hermes';
529
+ const isOpenCode = provider === 'opencode';
530
+ const isOpenClaw = provider === 'openclaw';
531
+ const isOpenHuman = provider === 'openhuman';
532
+ const isWarp = provider === 'warp';
533
+ const isDevinDesktop = provider === 'devin-desktop';
534
+ const adapter = isClaude
535
+ ? new ClaudeSessionAdapter()
536
+ : isCodex
537
+ ? new CodexSessionAdapter()
538
+ : isCopilot
539
+ ? new CopilotSessionAdapter()
540
+ : isCursor
541
+ ? new CursorSessionAdapter()
542
+ : isFactory
543
+ ? new FactorySessionAdapter()
544
+ : isHermes
545
+ ? new HermesSessionAdapter()
546
+ : isOpenCode
547
+ ? new OpenCodeSessionAdapter()
548
+ : isOpenClaw
549
+ ? new OpenClawSessionAdapter()
550
+ : isOpenHuman
551
+ ? new OpenHumanSessionAdapter()
552
+ : isWarp
553
+ ? new WarpSessionAdapter()
554
+ : isDevinDesktop
555
+ ? new DevinDesktopSessionAdapter()
556
+ : new GenericSessionInterchangeAdapter();
557
+ const locatorClass = isClaude
558
+ ? (input.endsWith('.hooks.jsonl') ? 'claude-hook-jsonl' : 'claude-transcript-jsonl')
559
+ : isCodex
560
+ ? (input.endsWith('.app-server.jsonl') ? 'codex-app-server-jsonl' : 'codex-rollout-jsonl')
561
+ : isCopilot
562
+ ? 'copilot-chat-json-export'
563
+ : isCursor
564
+ ? cursorLocatorClass(input)
565
+ : isFactory
566
+ ? 'factory-droid-jsonl'
567
+ : isHermes
568
+ ? 'hermes-export-jsonl'
569
+ : isOpenCode
570
+ ? 'opencode-export-json'
571
+ : isOpenClaw
572
+ ? 'openclaw-consistent-snapshot-jsonl'
573
+ : isOpenHuman
574
+ ? 'openhuman-enriched-jsonl'
575
+ : isWarp
576
+ ? 'warp-markdown-export'
577
+ : isDevinDesktop
578
+ ? 'devin-desktop-cascade-hook-jsonl'
579
+ : 'manual-export';
580
+ const selectedSource = {
581
+ provider, locator: input, locatorClass, sourceId,
582
+ authorizedScope: { workspaceId, allowedRoots: [dirname(input)] },
583
+ };
584
+ const probe = await adapter.inspect(selectedSource);
585
+ const source = SessionSourceSchema.parse({
586
+ contractVersion: SESSION_CONTRACT_VERSION, sourceId, provider,
587
+ providerProfile: isClaude
588
+ ? 'documented-local-jsonl'
589
+ : isCodex
590
+ ? 'app-server-v2-rollout-fallback'
591
+ : isCopilot
592
+ ? 'vscode-chat-json-export'
593
+ : isCursor
594
+ ? cursorProviderProfile(locatorClass)
595
+ : isFactory
596
+ ? 'documented-project-jsonl'
597
+ : isHermes
598
+ ? 'native-schema-23-export'
599
+ : isOpenCode
600
+ ? 'sanitized-json-export'
601
+ : isOpenClaw
602
+ ? 'schema-16-event-v3-consistent-snapshot'
603
+ : isOpenHuman
604
+ ? 'schema-1-session-raw-enriched'
605
+ : isWarp
606
+ ? 'manual-lossy-markdown-export'
607
+ : isDevinDesktop
608
+ ? 'opt-in-cascade-transcript-hook'
609
+ : 'manual-interchange',
610
+ locatorClass, redactedLocator: redactSourceLocator(input),
611
+ adapterVersion: isClaude
612
+ ? CLAUDE_ADAPTER_VERSION
613
+ : isCodex
614
+ ? CODEX_ADAPTER_VERSION
615
+ : isCopilot
616
+ ? COPILOT_ADAPTER_VERSION
617
+ : isCursor
618
+ ? CURSOR_ADAPTER_VERSION
619
+ : isFactory
620
+ ? FACTORY_ADAPTER_VERSION
621
+ : isHermes
622
+ ? HERMES_ADAPTER_VERSION
623
+ : isOpenCode
624
+ ? OPENCODE_ADAPTER_VERSION
625
+ : isOpenClaw
626
+ ? OPENCLAW_ADAPTER_VERSION
627
+ : isOpenHuman
628
+ ? OPENHUMAN_ADAPTER_VERSION
629
+ : isWarp
630
+ ? WARP_ADAPTER_VERSION
631
+ : isDevinDesktop
632
+ ? DEVIN_DESKTOP_ADAPTER_VERSION
633
+ : GENERIC_ADAPTER_VERSION,
634
+ sourceSchemaVersion: probe.sourceSchemaVersion,
635
+ disposition: isWarp
636
+ ? 'manual-only'
637
+ : isClaude || isCodex || isCopilot || isCursor || isFactory || isHermes
638
+ || isOpenCode || isOpenClaw || isOpenHuman || isDevinDesktop
639
+ ? 'implemented' : 'manual-only',
640
+ operationalState: probe.operationalState,
641
+ consistency: probe.consistency, authorizedAt: new Date().toISOString(),
642
+ extensions: isClaude
643
+ ? { 'native.claude': {} }
644
+ : isCodex
645
+ ? { 'native.codex': {} }
646
+ : isCopilot
647
+ ? { 'native.copilot': {} }
648
+ : isCursor
649
+ ? { 'native.cursor': {} }
650
+ : isFactory
651
+ ? { 'native.factory': {} }
652
+ : isHermes
653
+ ? { 'native.hermes': {} }
654
+ : isOpenCode
655
+ ? { 'native.opencode': {} }
656
+ : isOpenClaw
657
+ ? { 'native.openclaw': {} }
658
+ : isOpenHuman
659
+ ? { 'native.openhuman': {} }
660
+ : isWarp
661
+ ? { 'native.warp': {} }
662
+ : isDevinDesktop
663
+ ? { 'native.devin-desktop': {
664
+ product: 'Devin Desktop',
665
+ compatibilityProviderId: 'windsurf',
666
+ } }
667
+ : { 'native.generic': {} },
668
+ });
669
+ if (isDryRun(ctx, args)) {
670
+ return preview('import', { source, wouldInspect: true, wouldPersist: false });
671
+ }
672
+ const lease = await acquireImportLease(databasePath(ctx, args), sha256(['single-source-import', sourceId, workspaceId].join('\0')), {
673
+ waitMs: boundedInteger(args.values.get('--lock-wait-ms'), 5_000, 0, 300_000, '--lock-wait-ms'),
674
+ });
675
+ try {
676
+ const repository = openRepository(ctx, args);
677
+ try {
678
+ const receipts = await new IncrementalSessionImporter(repository).import({
679
+ source, selectedSource, adapter, workspaceId, policyVersion: '1.0.0',
680
+ inactivityThresholdMs: inactivityThreshold(args.values.get('--inactivity-threshold')),
681
+ });
682
+ return ok('import', {
683
+ sourceId,
684
+ receipts,
685
+ totals: {
686
+ sessionsInserted: receipts.reduce((sum, item) => sum + item.sessionsInserted, 0),
687
+ eventsInserted: receipts.reduce((sum, item) => sum + item.eventsInserted, 0),
688
+ },
689
+ });
690
+ }
691
+ finally {
692
+ repository.close();
693
+ }
694
+ }
695
+ finally {
696
+ await lease.release();
697
+ }
698
+ }
699
+ async function discoverWorkspace(ctx, args) {
700
+ const workspace = resolve(ctx.cwd, requiredValue(args, '--workspace'));
701
+ const manifest = await discoverWorkspaceHistories({
702
+ workspace,
703
+ providerHome: args.values.has('--provider-home')
704
+ ? resolve(ctx.cwd, args.values.get('--provider-home'))
705
+ : undefined,
706
+ codexRoot: args.values.has('--codex-root')
707
+ ? resolve(ctx.cwd, args.values.get('--codex-root'))
708
+ : undefined,
709
+ });
710
+ const manifestPath = resolve(ctx.cwd, args.values.get('--manifest') ?? defaultDiscoveryManifestPath(manifest.workspacePath));
711
+ const data = {
712
+ manifest: publicDiscoveryManifest(manifest),
713
+ manifestPath: redactSourceLocator(manifestPath),
714
+ exactManifestRequiredForImport: true,
715
+ };
716
+ if (isDryRun(ctx, args)) {
717
+ return preview('discover', { ...data, wouldPersistManifest: false });
718
+ }
719
+ await writeDiscoveryManifest(manifestPath, manifest);
720
+ return ok('discover', { ...data, manifestPersisted: true });
721
+ }
722
+ async function importDiscovered(ctx, args) {
723
+ const workspace = resolve(ctx.cwd, requiredValue(args, '--workspace'));
724
+ const canonicalWorkspace = realpathSync(workspace);
725
+ const manifestPath = resolve(ctx.cwd, args.values.get('--manifest') ?? defaultDiscoveryManifestPath(canonicalWorkspace));
726
+ const manifest = await readDiscoveryManifest(manifestPath);
727
+ if (manifest.workspaceId !== canonicalWorkspace) {
728
+ throw new CliError('MANIFEST_WORKSPACE_MISMATCH', 'discovery manifest does not belong to the explicitly authorized workspace', EXIT.contract);
729
+ }
730
+ const confirmed = args.flags.has('--confirm') || args.flags.has('--yes');
731
+ if (isDryRun(ctx, args) || !confirmed) {
732
+ let existing = null;
733
+ try {
734
+ const repository = openRepository(ctx, args);
735
+ existing = repository.getBatchImportRunForManifest(manifest.manifestId, manifest.workspaceId);
736
+ repository.close();
737
+ }
738
+ catch {
739
+ // A preview can still describe the exact manifest before a catalog exists.
740
+ }
741
+ return preview('import-discovered', {
742
+ manifest: publicDiscoveryManifest(manifest),
743
+ receipt: previewDiscoveryImport(manifest, existing),
744
+ confirmationRequired: !isDryRun(ctx, args),
745
+ wouldPersist: false,
746
+ });
747
+ }
748
+ const lease = await acquireImportLease(databasePath(ctx, args), sha256(['discovered-import', manifest.manifestId, manifest.workspaceId].join('\0')), {
749
+ waitMs: boundedInteger(args.values.get('--lock-wait-ms'), 5_000, 0, 300_000, '--lock-wait-ms'),
750
+ });
751
+ try {
752
+ const repository = openRepository(ctx, args);
753
+ try {
754
+ const receipt = await importDiscoveryManifest({
755
+ manifest,
756
+ repository,
757
+ signal: ctx.signal,
758
+ inactivityThresholdMs: inactivityThreshold(args.values.get('--inactivity-threshold')),
759
+ });
760
+ return ok('import-discovered', {
761
+ manifestId: manifest.manifestId,
762
+ receipt,
763
+ resumable: receipt.run.status !== 'complete',
764
+ });
765
+ }
766
+ finally {
767
+ repository.close();
768
+ }
769
+ }
770
+ finally {
771
+ await lease.release();
772
+ }
773
+ }
774
+ function providerDisposition(provider) {
775
+ if (provider === 'codex') {
776
+ return {
777
+ provider, disposition: 'implemented', operationalState: 'available',
778
+ supportedOperations: ['discover', 'inspect', 'stream'],
779
+ acquisitionModes: ['api', 'jsonl'],
780
+ reasonCode: null,
781
+ remediation: 'Authorize an App Server export or Codex sessions root, then import an explicit JSONL source.',
782
+ evidence: {
783
+ adapterVersion: CODEX_ADAPTER_VERSION,
784
+ verifiedAt: '2026-07-27',
785
+ documentation: 'https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md',
786
+ },
787
+ };
788
+ }
789
+ if (provider === 'claude') {
790
+ return {
791
+ provider, disposition: 'implemented', operationalState: 'available',
792
+ supportedOperations: ['discover', 'inspect', 'stream'],
793
+ acquisitionModes: ['jsonl', 'hook'],
794
+ reasonCode: null,
795
+ remediation: 'Authorize a Claude projects or hook root, then import an explicit JSONL file.',
796
+ evidence: {
797
+ adapterVersion: CLAUDE_ADAPTER_VERSION,
798
+ verifiedAt: '2026-07-27',
799
+ documentation: 'https://code.claude.com/docs/en/sessions',
800
+ },
801
+ };
802
+ }
803
+ if (provider === 'copilot') {
804
+ return {
805
+ provider, disposition: 'implemented', operationalState: 'available',
806
+ supportedOperations: ['inspect', 'stream'],
807
+ acquisitionModes: ['manual-export'],
808
+ reasonCode: null,
809
+ remediation: 'Use Chat: Export Chat in VS Code, then import the explicitly selected JSON file.',
810
+ evidence: {
811
+ adapterVersion: COPILOT_ADAPTER_VERSION,
812
+ verifiedAt: '2026-07-27',
813
+ documentation: 'https://code.visualstudio.com/docs/chat/chat-sessions#_export-a-chat-session-as-a-json-file',
814
+ },
815
+ };
816
+ }
817
+ if (provider === 'cursor') {
818
+ return {
819
+ provider, disposition: 'implemented', operationalState: 'available',
820
+ supportedOperations: ['discover', 'inspect', 'stream'],
821
+ acquisitionModes: ['api', 'jsonl', 'manual-export'],
822
+ reasonCode: null,
823
+ remediation: 'Authorize a workspace agent-transcripts root, or import Cursor CLI/Cloud Agent exports explicitly.',
824
+ evidence: {
825
+ adapterVersion: CURSOR_ADAPTER_VERSION,
826
+ verifiedAt: '2026-07-27',
827
+ documentation: 'https://docs.cursor.com/en/cli/reference/output-format',
828
+ },
829
+ };
830
+ }
831
+ if (provider === 'factory') {
832
+ return {
833
+ provider, disposition: 'implemented', operationalState: 'available',
834
+ supportedOperations: ['discover', 'inspect', 'stream'],
835
+ acquisitionModes: ['jsonl', 'api'],
836
+ reasonCode: null,
837
+ remediation: 'Authorize a Factory projects root and import a Droid JSONL transcript; API and Exec require explicit negotiated transports.',
838
+ evidence: {
839
+ adapterVersion: FACTORY_ADAPTER_VERSION,
840
+ verifiedAt: '2026-07-27',
841
+ documentation: 'https://docs.factory.ai/reference/hooks-reference',
842
+ },
843
+ };
844
+ }
845
+ if (provider === 'hermes') {
846
+ return {
847
+ provider, disposition: 'implemented', operationalState: 'available',
848
+ supportedOperations: ['inspect', 'stream'],
849
+ acquisitionModes: ['jsonl', 'api', 'sqlite-snapshot'],
850
+ reasonCode: null,
851
+ remediation: 'Use `hermes sessions export` or an explicitly verified sqlite3.backup() snapshot export.',
852
+ evidence: {
853
+ adapterVersion: HERMES_ADAPTER_VERSION,
854
+ verifiedAt: '2026-07-27',
855
+ documentation: 'https://hermes-agent.nousresearch.com/docs/user-guide/sessions/',
856
+ },
857
+ };
858
+ }
859
+ if (provider === 'opencode') {
860
+ return {
861
+ provider, disposition: 'implemented', operationalState: 'available',
862
+ supportedOperations: ['inspect', 'stream'],
863
+ acquisitionModes: ['manual-export', 'api', 'jsonl'],
864
+ reasonCode: null,
865
+ remediation: 'Use `opencode export <sessionID>` or an explicitly authorized negotiated local API/SSE transport.',
866
+ evidence: {
867
+ adapterVersion: OPENCODE_ADAPTER_VERSION,
868
+ verifiedAt: '2026-07-27',
869
+ documentation: 'https://opencode.ai/docs/cli/',
870
+ },
871
+ };
872
+ }
873
+ if (provider === 'openclaw') {
874
+ return {
875
+ provider, disposition: 'implemented', operationalState: 'available',
876
+ supportedOperations: ['inspect', 'stream'],
877
+ acquisitionModes: ['api', 'sqlite-snapshot', 'jsonl'],
878
+ reasonCode: null,
879
+ remediation: 'Use an authorized read-only Gateway transport or a schema-16/event-v3 sqlite3.backup() projection.',
880
+ evidence: {
881
+ adapterVersion: OPENCLAW_ADAPTER_VERSION,
882
+ verifiedAt: '2026-07-27',
883
+ documentation: 'https://docs.openclaw.ai/reference/session-management-compaction',
884
+ },
885
+ };
886
+ }
887
+ if (provider === 'openhuman') {
888
+ return {
889
+ provider, disposition: 'implemented', operationalState: 'available',
890
+ supportedOperations: ['inspect', 'stream'],
891
+ acquisitionModes: ['jsonl'],
892
+ reasonCode: null,
893
+ remediation: 'Import an explicitly selected schema-1 session_raw JSONL file, optionally bundled with thread/turn enrichment.',
894
+ evidence: {
895
+ adapterVersion: OPENHUMAN_ADAPTER_VERSION,
896
+ verifiedAt: '2026-07-27',
897
+ documentation: 'https://github.com/tinyhumansai/openhuman/releases',
898
+ },
899
+ };
900
+ }
901
+ if (provider === 'warp') {
902
+ return {
903
+ provider, disposition: 'manual-only', operationalState: 'available',
904
+ supportedOperations: ['inspect', 'stream'],
905
+ acquisitionModes: ['manual-export'],
906
+ reasonCode: 'LOSSY_MARKDOWN_ONLY',
907
+ remediation: 'Run `/export-to-file` in Warp and import the explicitly selected Markdown file.',
908
+ evidence: {
909
+ adapterVersion: WARP_ADAPTER_VERSION,
910
+ verifiedAt: '2026-07-27',
911
+ documentation: 'https://docs.warp.dev/agent-platform/capabilities/slash-commands',
912
+ },
913
+ };
914
+ }
915
+ if (provider === 'devin-desktop') {
916
+ return {
917
+ provider,
918
+ product: 'Devin Desktop',
919
+ compatibilityAliases: ['windsurf'],
920
+ aliasDeprecatedAfter: '2027-07-27',
921
+ disposition: 'implemented', operationalState: 'available',
922
+ supportedOperations: ['inspect', 'stream'],
923
+ acquisitionModes: ['hook', 'jsonl'],
924
+ reasonCode: null,
925
+ remediation: 'Enable Devin Desktop post_cascade_response_with_transcript, then explicitly select its JSONL output.',
926
+ evidence: {
927
+ adapterVersion: DEVIN_DESKTOP_ADAPTER_VERSION,
928
+ verifiedAt: '2026-07-27',
929
+ documentation: 'https://docs.devin.ai/desktop/cascade/hooks',
930
+ },
931
+ };
932
+ }
933
+ if (provider === 'generic') {
934
+ return {
935
+ provider, disposition: 'manual-only', operationalState: 'available',
936
+ supportedOperations: ['inspect', 'stream'], acquisitionModes: ['manual-export'],
937
+ reasonCode: 'MANUAL_SOURCE_SELECTION_REQUIRED',
938
+ remediation: 'Pass a declared interchange file to `aiwg sessions import`.',
939
+ evidence: { adapterVersion: GENERIC_ADAPTER_VERSION, verifiedAt: '2026-07-26' },
940
+ };
941
+ }
942
+ return {
943
+ provider, disposition: 'unsupported', operationalState: 'unavailable',
944
+ supportedOperations: [], acquisitionModes: [],
945
+ reasonCode: 'ADAPTER_NOT_IMPLEMENTED',
946
+ remediation: 'Use the generic interchange until the provider adapter milestone is delivered.',
947
+ evidence: { adapterVersion: null, verifiedAt: '2026-07-26' },
948
+ };
949
+ }
950
+ function cursorLocatorClass(input) {
951
+ if (input.endsWith('.md') || input.endsWith('.markdown'))
952
+ return 'cursor-editor-markdown';
953
+ if (input.endsWith('.cloud.jsonl'))
954
+ return 'cursor-cloud-events-jsonl';
955
+ if (input.split(/[\\/]+/).includes('agent-transcripts'))
956
+ return 'cursor-agent-transcript-jsonl';
957
+ return 'cursor-cli-stream-json';
958
+ }
959
+ function cursorProviderProfile(locatorClass) {
960
+ if (locatorClass === 'cursor-editor-markdown')
961
+ return 'editor-markdown-lossy';
962
+ if (locatorClass === 'cursor-cloud-events-jsonl')
963
+ return 'cloud-agents-api-v1';
964
+ if (locatorClass === 'cursor-agent-transcript-jsonl')
965
+ return 'agent-transcript-jsonl';
966
+ return 'cli-stream-json';
967
+ }
968
+ function openRepository(ctx, args) {
969
+ const path = databasePath(ctx, args);
970
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
971
+ try {
972
+ return new SessionRepository(path);
973
+ }
974
+ catch (error) {
975
+ const message = error instanceof Error ? error.message : String(error);
976
+ if (/SQLITE_BUSY|database is locked/i.test(message)) {
977
+ throw new CliError('IMPORT_LOCKED', 'the session catalog is busy; retry after the active writer finishes', EXIT.locked, { owner: null, waitedMs: 0 });
978
+ }
979
+ throw new CliError('CATALOG_UNAVAILABLE', message, EXIT.storage);
980
+ }
981
+ }
982
+ function databasePath(ctx, args) {
983
+ if (args.values.has('--db'))
984
+ return resolve(ctx.cwd, args.values.get('--db'));
985
+ return resolve(projectRootCandidate(ctx.cwd) ?? ctx.cwd, '.aiwg/sessions/catalog.sqlite');
986
+ }
987
+ function parseArgs(argv) {
988
+ const flags = new Set();
989
+ const values = new Map();
990
+ const positionals = [];
991
+ const valueFlags = new Set([
992
+ '--db', '--provider', '--workspace', '--tag', '--limit', '--cursor', '--source-id',
993
+ '--date-from', '--date-to', '--participant', '--model', '--role', '--tool',
994
+ '--entity', '--sensitivity', '--extraction-state', '--page-size', '--max-documents',
995
+ '--state', '--reviewer', '--reason', '--policy-version', '--min-confidence',
996
+ '--consumer', '--actor-class', '--reason-code', '--dependent-action', '--basis',
997
+ '--manifest', '--provider-home', '--codex-root', '--lock-wait-ms', '--min-coverage', '--gap',
998
+ '--inactivity-threshold',
999
+ '--control-events',
1000
+ ]);
1001
+ let command;
1002
+ for (let index = 0; index < argv.length; index += 1) {
1003
+ const arg = argv[index];
1004
+ if (valueFlags.has(arg)) {
1005
+ const value = argv[index + 1];
1006
+ if (!value || value.startsWith('--')) {
1007
+ throw new CliError('MISSING_OPTION_VALUE', `missing value for ${arg}`, EXIT.usage);
1008
+ }
1009
+ values.set(arg, value);
1010
+ index += 1;
1011
+ }
1012
+ else if (arg.startsWith('-')) {
1013
+ flags.add(arg);
1014
+ }
1015
+ else if (!command) {
1016
+ command = arg;
1017
+ }
1018
+ else {
1019
+ positionals.push(arg);
1020
+ }
1021
+ }
1022
+ return { command, positionals, flags, values };
1023
+ }
1024
+ function requiredPositional(args, index, name) {
1025
+ const value = args.positionals[index];
1026
+ if (!value)
1027
+ throw new CliError('MISSING_ARGUMENT', `missing required ${name}`, EXIT.usage);
1028
+ return value;
1029
+ }
1030
+ function requiredValue(args, flag) {
1031
+ const value = args.values.get(flag);
1032
+ if (!value)
1033
+ throw new CliError('MISSING_ARGUMENT', `missing required ${flag}`, EXIT.usage);
1034
+ return value;
1035
+ }
1036
+ function authorizationContext(ctx, args, operation) {
1037
+ return {
1038
+ actorId: 'local-catalog-owner',
1039
+ workspaceId: normalizeWorkspaceId(ctx.cwd, requiredValue(args, '--workspace')),
1040
+ operation,
1041
+ catalogScope: 'workspace',
1042
+ mode: 'local-owner',
1043
+ };
1044
+ }
1045
+ function readAuthorizationContext(ctx, args, operation, repository) {
1046
+ const explicit = args.values.get('--workspace');
1047
+ if (explicit) {
1048
+ return {
1049
+ actorId: 'local-catalog-owner',
1050
+ workspaceId: normalizeWorkspaceId(ctx.cwd, explicit),
1051
+ operation,
1052
+ catalogScope: 'workspace',
1053
+ mode: 'local-owner',
1054
+ };
1055
+ }
1056
+ const project = projectRootCandidate(ctx.cwd);
1057
+ if (project) {
1058
+ return {
1059
+ actorId: 'local-catalog-owner',
1060
+ workspaceId: project,
1061
+ operation,
1062
+ catalogScope: 'workspace',
1063
+ mode: 'local-owner',
1064
+ };
1065
+ }
1066
+ const candidates = repository.listWorkspaceIds();
1067
+ if (candidates.length === 1) {
1068
+ return {
1069
+ actorId: 'local-catalog-owner',
1070
+ workspaceId: candidates[0],
1071
+ operation,
1072
+ catalogScope: 'workspace',
1073
+ mode: 'local-owner',
1074
+ };
1075
+ }
1076
+ if (candidates.length > 1) {
1077
+ throw new CliError('WORKSPACE_AMBIGUOUS', 'current workspace cannot be inferred safely; pass --workspace explicitly', EXIT.usage, {
1078
+ candidates,
1079
+ example: `aiwg sessions ${operation} --workspace ${JSON.stringify(candidates[0])}`,
1080
+ });
1081
+ }
1082
+ throw new CliError('WORKSPACE_REQUIRED', 'current workspace cannot be inferred; pass --workspace explicitly', EXIT.usage, {
1083
+ candidates: [],
1084
+ example: `aiwg sessions ${operation} --workspace <path-or-id>`,
1085
+ });
1086
+ }
1087
+ function projectRootCandidate(start) {
1088
+ let current;
1089
+ try {
1090
+ const resolved = realpathSync(resolve(start));
1091
+ current = statSync(resolved).isDirectory() ? resolved : dirname(resolved);
1092
+ }
1093
+ catch {
1094
+ current = resolve(start);
1095
+ }
1096
+ let gitRoot = null;
1097
+ while (true) {
1098
+ if (existsSync(resolve(current, '.aiwg', 'aiwg.config')))
1099
+ return current;
1100
+ if (!gitRoot && existsSync(resolve(current, '.git')))
1101
+ gitRoot = current;
1102
+ const parent = dirname(current);
1103
+ if (parent === current)
1104
+ return gitRoot;
1105
+ current = parent;
1106
+ }
1107
+ }
1108
+ function normalizeWorkspaceId(cwd, value) {
1109
+ const pathLike = isAbsolute(value)
1110
+ || value === '.'
1111
+ || value === '..'
1112
+ || value.startsWith('./')
1113
+ || value.startsWith('../');
1114
+ if (!pathLike)
1115
+ return value;
1116
+ const candidate = resolve(cwd, value);
1117
+ try {
1118
+ return realpathSync(candidate);
1119
+ }
1120
+ catch {
1121
+ return candidate;
1122
+ }
1123
+ }
1124
+ function boundedInteger(value, fallback, min, max, name) {
1125
+ if (value === undefined)
1126
+ return fallback;
1127
+ if (!/^\d+$/.test(value))
1128
+ throw new CliError('INVALID_ARGUMENT', `${name} must be an integer`, EXIT.usage);
1129
+ const parsed = Number(value);
1130
+ if (parsed < min || parsed > max) {
1131
+ throw new CliError('INVALID_ARGUMENT', `${name} must be between ${min} and ${max}`, EXIT.usage);
1132
+ }
1133
+ return parsed;
1134
+ }
1135
+ function boundedNumber(value, fallback, min, max, name) {
1136
+ if (value === undefined)
1137
+ return fallback;
1138
+ const parsed = Number(value);
1139
+ if (!Number.isFinite(parsed) || parsed < min || parsed > max) {
1140
+ throw new CliError('INVALID_ARGUMENT', `${name} must be between ${min} and ${max}`, EXIT.usage);
1141
+ }
1142
+ return parsed;
1143
+ }
1144
+ function timelineGap(value) {
1145
+ try {
1146
+ return parseTimelineGap(value);
1147
+ }
1148
+ catch (error) {
1149
+ throw new CliError('INVALID_ARGUMENT', error instanceof Error ? error.message : 'invalid timeline gap', EXIT.usage);
1150
+ }
1151
+ }
1152
+ function inactivityThreshold(value) {
1153
+ try {
1154
+ return parseTimelineGap(value ?? '24h');
1155
+ }
1156
+ catch (error) {
1157
+ throw new CliError('INVALID_ARGUMENT', error instanceof Error ? error.message : 'invalid inactivity threshold', EXIT.usage);
1158
+ }
1159
+ }
1160
+ function controlEventMode(value) {
1161
+ if (value === undefined)
1162
+ return 'exclude';
1163
+ if (value === 'exclude' || value === 'include' || value === 'only')
1164
+ return value;
1165
+ throw new CliError('INVALID_ARGUMENT', '--control-events must be exclude, include, or only', EXIT.usage);
1166
+ }
1167
+ function candidateState(value) {
1168
+ if (value === undefined)
1169
+ return undefined;
1170
+ const states = new Set([
1171
+ 'pending', 'accepted', 'rejected', 'deferred', 'promoted', 'superseded',
1172
+ ]);
1173
+ if (!states.has(value)) {
1174
+ throw new CliError('INVALID_ARGUMENT', `invalid candidate state: ${value}`, EXIT.usage);
1175
+ }
1176
+ return value;
1177
+ }
1178
+ function dependentAction(value) {
1179
+ const actions = new Set([
1180
+ 'revoke', 'supersede', 'retain', 'origin_unavailable', 'delete', 'abort',
1181
+ ]);
1182
+ if (!actions.has(value)) {
1183
+ throw new CliError('INVALID_ARGUMENT', `invalid dependent action: ${value}`, EXIT.usage);
1184
+ }
1185
+ return value;
1186
+ }
1187
+ function isDryRun(ctx, args) {
1188
+ return Boolean(ctx.dryRun || args.flags.has('--dry-run'));
1189
+ }
1190
+ function ok(command, data) {
1191
+ return { envelope: envelope(`sessions.${command}`, 'ok', data, null), exitCode: EXIT.ok };
1192
+ }
1193
+ function preview(command, data) {
1194
+ return { envelope: envelope(`sessions.${command}`, 'preview', data, null), exitCode: EXIT.ok };
1195
+ }
1196
+ function envelope(command, status, data, error) {
1197
+ return { contractVersion: JSON_CONTRACT_VERSION, command, status, data, error };
1198
+ }
1199
+ function emit(value) {
1200
+ console.log(JSON.stringify(value, null, 2));
1201
+ }
1202
+ function printHuman(value) {
1203
+ if (value.status === 'preview')
1204
+ console.log('Preview (no changes applied)');
1205
+ if (value.command === 'sessions.timeline' && value.data
1206
+ && typeof value.data === 'object' && 'items' in value.data) {
1207
+ const data = value.data;
1208
+ for (const item of data.items) {
1209
+ console.log([
1210
+ item.startAt ?? '<unknown-time>',
1211
+ item.endAt ?? '<unknown-time>',
1212
+ item.provider,
1213
+ item.sessionId,
1214
+ `segment=${item.segmentIndex}`,
1215
+ `events=${item.eventCount}`,
1216
+ `boundary=${item.boundaryBasis}`,
1217
+ `confidence=${item.confidence}`,
1218
+ ].join(' '));
1219
+ }
1220
+ console.log(`Coverage: ${data.coverage?.status ?? 'unknown'}`);
1221
+ return;
1222
+ }
1223
+ console.log(JSON.stringify(value.data, null, 2));
1224
+ }
1225
+ class CliError extends Error {
1226
+ code;
1227
+ exitCode;
1228
+ details;
1229
+ constructor(code, message, exitCode, details) {
1230
+ super(message);
1231
+ this.code = code;
1232
+ this.exitCode = exitCode;
1233
+ this.details = details;
1234
+ }
1235
+ }
1236
+ function normalizeError(error) {
1237
+ if (error instanceof CliError) {
1238
+ return {
1239
+ error: { code: error.code, message: error.message, details: error.details },
1240
+ exitCode: error.exitCode,
1241
+ };
1242
+ }
1243
+ if (error instanceof SessionContractError) {
1244
+ const exitCode = error.code === 'UNSUPPORTED_OPERATION' ? EXIT.unsupported : EXIT.contract;
1245
+ return { error: { code: error.code, message: error.message }, exitCode };
1246
+ }
1247
+ if (error instanceof ImportLeaseContentionError) {
1248
+ return {
1249
+ error: {
1250
+ code: error.code,
1251
+ message: error.message,
1252
+ details: {
1253
+ owner: error.owner,
1254
+ waitedMs: error.waitMs,
1255
+ },
1256
+ },
1257
+ exitCode: EXIT.locked,
1258
+ };
1259
+ }
1260
+ return {
1261
+ error: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : String(error) },
1262
+ exitCode: EXIT.storage,
1263
+ };
1264
+ }
1265
+ //# sourceMappingURL=sessions.js.map