@aiwg/cli 2026.7.21 → 2026.7.24

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 (32) hide show
  1. package/README.md +14 -3
  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 +2 -0
  5. package/dist/src/artifacts/index-builder.js +44 -8
  6. package/dist/src/artifacts/query-engine.js +1 -1
  7. package/dist/src/artifacts/types.js +1 -0
  8. package/dist/src/cli/handlers/index.js +5 -1
  9. package/dist/src/cli/handlers/sessions.js +339 -40
  10. package/dist/src/cli/handlers/setup-manifest.js +800 -0
  11. package/dist/src/cli/handlers/use.js +127 -17
  12. package/dist/src/config/aiwg-config.js +18 -2
  13. package/dist/src/config/cli.js +16 -3
  14. package/dist/src/extensions/commands/definitions.js +99 -0
  15. package/dist/src/security/threat-assessment-config.js +296 -0
  16. package/dist/src/serve/sandbox-registry.js +34 -0
  17. package/dist/src/sessions/adapters/claude.js +37 -9
  18. package/dist/src/sessions/adapters/codex.js +38 -11
  19. package/dist/src/sessions/adapters/cursor.js +166 -10
  20. package/dist/src/sessions/adapters/factory.js +50 -9
  21. package/dist/src/sessions/batch-contracts.js +121 -0
  22. package/dist/src/sessions/batch-import.js +265 -0
  23. package/dist/src/sessions/contracts.js +32 -5
  24. package/dist/src/sessions/import-lease.js +152 -0
  25. package/dist/src/sessions/importer.js +163 -14
  26. package/dist/src/sessions/index.js +6 -0
  27. package/dist/src/sessions/origin.js +117 -0
  28. package/dist/src/sessions/readers.js +1 -1
  29. package/dist/src/sessions/repository.js +354 -13
  30. package/dist/src/sessions/timeline.js +148 -0
  31. package/dist/src/sessions/workspace-discovery.js +319 -0
  32. package/package.json +1 -1
@@ -1,9 +1,10 @@
1
1
  import { SESSION_CONTRACT_VERSION, SessionEventSchema, SessionSchema, assertSupportedSchemaMajor, sha256, stableEventId, stableSessionId, SessionContractError, } from './contracts.js';
2
2
  import { redactSessionText, sanitizeNativeExtensions } from './policy.js';
3
3
  import { fingerprintSourcePrefix } from './readers.js';
4
+ import { classifySessionEventOrigin, deriveSessionIntent, } from './origin.js';
4
5
  const DEFAULT_LIMITS = {
5
6
  maxRecords: 1_000_000,
6
- maxRecordBytes: 4 * 1024 * 1024,
7
+ maxRecordBytes: 8 * 1024 * 1024,
7
8
  maxTotalBytes: 1024 * 1024 * 1024,
8
9
  batchSize: 1_000,
9
10
  };
@@ -22,12 +23,21 @@ export class IncrementalSessionImporter {
22
23
  }
23
24
  async import(request) {
24
25
  assertSupportedSchemaMajor(request.source.sourceSchemaVersion);
26
+ if (request.inactivityThresholdMs !== undefined
27
+ && (!Number.isFinite(request.inactivityThresholdMs) || request.inactivityThresholdMs < 0)) {
28
+ throw new SessionContractError('INVALID_ARGUMENT', 'session inactivity threshold must be a non-negative duration');
29
+ }
25
30
  if (request.workspaceId !== request.selectedSource.authorizedScope.workspaceId) {
26
31
  throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'session import workspace is outside the authorized source scope');
27
32
  }
28
33
  const limits = { ...DEFAULT_LIMITS, ...request.limits };
29
- const previous = this.repository.getCheckpoint(request.source.sourceId, request.adapter.adapterVersion);
34
+ const previous = request.batchRunId
35
+ ? this.repository.getBatchCheckpoint(request.source.sourceId, request.adapter.adapterVersion, request.batchRunId) ?? this.repository.getCheckpoint(request.source.sourceId, request.adapter.adapterVersion)
36
+ : this.repository.getCheckpoint(request.source.sourceId, request.adapter.adapterVersion);
30
37
  const continuity = await sourceContinuity(request, previous);
38
+ const sourceObservedAt = continuity
39
+ ? new Date(continuity.mtimeMs).toISOString()
40
+ : undefined;
31
41
  const sourceGeneration = continuity?.sourceGeneration ?? sourceGenerationDigest(request);
32
42
  if (previous?.sourceGeneration && previous.sourceGeneration !== sourceGeneration) {
33
43
  throw new SessionContractError('SCHEMA_DRIFT', 'session source generation changed; explicit restart or migration is required');
@@ -73,12 +83,15 @@ export class IncrementalSessionImporter {
73
83
  const flush = () => {
74
84
  if (batch.length === 0)
75
85
  return;
86
+ if (request.signal?.aborted) {
87
+ throw new SessionContractError('IMPORT_INTERRUPTED', 'session import was cancelled at a durable batch boundary');
88
+ }
76
89
  const runId = sha256([
77
90
  request.source.sourceId, request.adapter.adapterVersion,
78
91
  batchStart, checkpoint.recordsRead,
79
92
  ...batch.map((record) => sha256(JSON.stringify(record))),
80
93
  ].join('\0'));
81
- const normalized = normalizeBatch(request, batch, runId);
94
+ const normalized = normalizeBatch(request, batch, runId, sourceObservedAt);
82
95
  const run = {
83
96
  contractVersion: SESSION_CONTRACT_VERSION,
84
97
  importRunId: runId,
@@ -101,7 +114,8 @@ export class IncrementalSessionImporter {
101
114
  const receipt = this.repository.applyImport({
102
115
  source: sanitizedSource, run,
103
116
  sessions: normalized.sessions, events: normalized.events,
104
- }, { ...checkpoint }, request.source.consistency !== 'complete');
117
+ batchRunId: request.batchRunId,
118
+ }, { ...checkpoint }, request.publish ?? request.source.consistency !== 'complete');
105
119
  const memory = process.memoryUsage();
106
120
  peakHeap = Math.max(peakHeap, memory.heapUsed);
107
121
  peakRss = Math.max(peakRss, memory.rss);
@@ -122,6 +136,9 @@ export class IncrementalSessionImporter {
122
136
  };
123
137
  try {
124
138
  for await (const record of request.adapter.stream(request.selectedSource, previous ? { value: previous.cursor } : undefined)) {
139
+ if (request.signal?.aborted) {
140
+ throw new SessionContractError('IMPORT_INTERRUPTED', 'session import was cancelled and can resume from its durable checkpoint');
141
+ }
125
142
  const bytes = Buffer.byteLength(JSON.stringify(record));
126
143
  if (bytes > limits.maxRecordBytes
127
144
  || checkpoint.bytesRead + bytes > limits.maxTotalBytes
@@ -145,7 +162,8 @@ export class IncrementalSessionImporter {
145
162
  flush();
146
163
  }
147
164
  catch (error) {
148
- const committed = request.source.consistency === 'complete' ? [] : receipts;
165
+ const published = request.publish ?? request.source.consistency !== 'complete';
166
+ const committed = published ? receipts : [];
149
167
  throw new SessionImportFailure({
150
168
  contractVersion: '1.0.0',
151
169
  outcome: 'terminal-failure',
@@ -157,7 +175,7 @@ export class IncrementalSessionImporter {
157
175
  records: committed.reduce((total, receipt) => total + (receipt.metrics?.records ?? 0), 0),
158
176
  events: committed.reduce((total, receipt) => total + receipt.eventsInserted, 0),
159
177
  },
160
- resumableCheckpoint: request.source.consistency === 'complete'
178
+ resumableCheckpoint: !published
161
179
  ? {
162
180
  ...durableCheckpoint,
163
181
  cursor: '',
@@ -170,7 +188,7 @@ export class IncrementalSessionImporter {
170
188
  ? error.code : 'IMPORT_INTERRUPTED',
171
189
  }, error);
172
190
  }
173
- if (request.source.consistency === 'complete') {
191
+ if (request.publish !== false && request.source.consistency === 'complete') {
174
192
  this.repository.commitStagedImports(request.source.sourceId, request.adapter.adapterVersion);
175
193
  for (const receipt of receipts) {
176
194
  if (receipt.outcome === 'staged')
@@ -180,22 +198,31 @@ export class IncrementalSessionImporter {
180
198
  return receipts;
181
199
  }
182
200
  }
183
- function normalizeBatch(request, records, importRunId) {
201
+ function normalizeBatch(request, records, importRunId, sourceObservedAt) {
184
202
  const sessions = new Map();
185
203
  const events = records.map((record) => {
186
204
  const sessionId = stableSessionId(request.source.provider, request.source.sourceId, record.nativeSessionId);
187
205
  const redacted = redactSessionText(record.text);
188
206
  const native = sanitizeNativeExtensions(record.extensions ?? {});
189
207
  const digest = sha256(JSON.stringify(record));
208
+ const origin = classifySessionEventOrigin(request.source.provider, record);
209
+ const lifecycle = sessionLifecycle(request, record, sourceObservedAt);
210
+ const lifecycleEvidence = sessionLifecycleEvidence(request, record, sourceObservedAt);
190
211
  const extensions = {
191
212
  [`native.${request.source.provider}`]: native.value,
192
213
  };
193
214
  const event = SessionEventSchema.parse({
194
215
  contractVersion: SESSION_CONTRACT_VERSION,
195
- eventId: stableEventId(request.source.sourceId, record, digest),
216
+ eventId: stableEventId(request.source.provider, request.source.sourceId, record, digest),
196
217
  sessionId, sourceId: request.source.sourceId, importRunId,
197
218
  nativeId: record.nativeEventId ?? null, sequence: record.sequence,
198
219
  kind: record.kind, role: record.role ?? null, occurredAt: record.occurredAt ?? null,
220
+ activityBoundary: record.activityBoundary ?? null,
221
+ activityBoundaryBasis: record.activityBoundaryBasis ?? null,
222
+ activityBoundaryConfidence: record.activityBoundaryConfidence ?? null,
223
+ origin: origin.origin,
224
+ originRule: origin.rule,
225
+ originClassifierVersion: origin.classifierVersion,
199
226
  participant: record.participant ?? null,
200
227
  toolName: record.toolName ?? null,
201
228
  toolCallId: record.toolCallId ?? null,
@@ -220,9 +247,13 @@ function normalizeBatch(request, records, importRunId) {
220
247
  nativeSessionId: record.nativeSessionId, workspaceId: request.workspaceId,
221
248
  startedAt: record.occurredAt ?? null, updatedAt: record.occurredAt ?? null,
222
249
  consistency: request.source.consistency,
223
- lifecycle: request.source.consistency === 'complete' ? 'complete' : 'active',
250
+ lifecycle,
224
251
  sourceDigest: sha256(`${sourceGenerationDigest(request)}\0${record.nativeSessionId}`),
225
- extensions: { [`native.${request.source.provider}`]: {} },
252
+ extensions: {
253
+ [`native.${request.source.provider}`]: {
254
+ lifecycleEvidence,
255
+ },
256
+ },
226
257
  }));
227
258
  }
228
259
  else {
@@ -230,12 +261,23 @@ function normalizeBatch(request, records, importRunId) {
230
261
  session.startedAt = earlierTimestamp(session.startedAt, record.occurredAt ?? null);
231
262
  session.updatedAt = laterTimestamp(session.updatedAt, record.occurredAt ?? null);
232
263
  session.consistency = strongerConsistency(session.consistency, request.source.consistency);
233
- if (session.lifecycle !== 'tombstoned' && request.source.consistency === 'complete') {
234
- session.lifecycle = 'complete';
235
- }
264
+ const nativeKey = `native.${request.source.provider}`;
265
+ const nativeEnvelope = session.extensions[nativeKey];
266
+ const nativeRecord = nativeEnvelope && typeof nativeEnvelope === 'object' && !Array.isArray(nativeEnvelope)
267
+ ? nativeEnvelope
268
+ : {};
269
+ const selected = selectLifecycleTransition(session.lifecycle, nativeRecord.lifecycleEvidence, lifecycle, lifecycleEvidence);
270
+ session.lifecycle = selected.lifecycle;
271
+ session.extensions[nativeKey] = {
272
+ ...nativeRecord,
273
+ lifecycleEvidence: selected.evidence,
274
+ };
236
275
  }
237
276
  return event;
238
277
  });
278
+ for (const session of sessions.values()) {
279
+ session.intent = deriveSessionIntent(events.filter((event) => event.sessionId === session.sessionId));
280
+ }
239
281
  return { sessions: [...sessions.values()], events };
240
282
  }
241
283
  const KNOWN_EVENT_KINDS = new Set(['message', 'tool-call', 'tool-result', 'artifact', 'attachment', 'summary']);
@@ -261,6 +303,113 @@ function strongerConsistency(left, right) {
261
303
  };
262
304
  return rank[left] >= rank[right] ? left : right;
263
305
  }
306
+ function sessionLifecycle(request, record, sourceObservedAt) {
307
+ const explicit = nativeLifecycle(record.extensions);
308
+ if (explicit)
309
+ return explicit;
310
+ if (request.source.consistency === 'complete')
311
+ return 'complete';
312
+ const observedAt = record.occurredAt ?? sourceObservedAt;
313
+ if (observedAt && isStaleHistoricalTimestamp(observedAt, inactivityThresholdMs(request)))
314
+ return 'inactive';
315
+ return 'active';
316
+ }
317
+ function sessionLifecycleEvidence(request, record, sourceObservedAt) {
318
+ const observedAt = record.occurredAt ?? sourceObservedAt;
319
+ const explicit = nativeLifecycle(record.extensions);
320
+ if (explicit) {
321
+ return {
322
+ basis: 'provider-explicit-event',
323
+ state: explicit,
324
+ observedAt: observedAt ?? request.source.authorizedAt,
325
+ confidence: 'high',
326
+ };
327
+ }
328
+ if (request.source.consistency === 'complete') {
329
+ return {
330
+ basis: 'complete-source',
331
+ state: 'complete',
332
+ observedAt: observedAt ?? request.source.authorizedAt,
333
+ confidence: 'high',
334
+ };
335
+ }
336
+ const thresholdMs = inactivityThresholdMs(request);
337
+ if (observedAt && isStaleHistoricalTimestamp(observedAt, thresholdMs)) {
338
+ return {
339
+ basis: 'inactivity-threshold',
340
+ state: 'inactive',
341
+ observedAt,
342
+ confidence: 'medium',
343
+ thresholdMs,
344
+ };
345
+ }
346
+ return {
347
+ basis: 'open-provisional-source',
348
+ state: 'active',
349
+ observedAt: observedAt ?? request.source.authorizedAt,
350
+ confidence: 'provisional',
351
+ };
352
+ }
353
+ function nativeLifecycle(extensions) {
354
+ const raw = typeof extensions?.lifecycle === 'string' ? extensions.lifecycle : undefined;
355
+ if (!raw)
356
+ return null;
357
+ const normalized = {
358
+ active: 'active',
359
+ inactive: 'inactive',
360
+ paused: 'paused',
361
+ complete: 'complete',
362
+ completed: 'complete',
363
+ archived: 'archived',
364
+ deleted: 'tombstoned',
365
+ cancelled: 'interrupted',
366
+ canceled: 'interrupted',
367
+ failed: 'interrupted',
368
+ interrupted: 'interrupted',
369
+ unknown: 'unknown',
370
+ 'unknown-at-import': 'unknown',
371
+ 'completed-at-import': 'complete',
372
+ };
373
+ return normalized[raw] ?? null;
374
+ }
375
+ function selectLifecycleTransition(leftLifecycle, left, rightLifecycle, right) {
376
+ const leftEvidence = asLifecycleEvidence(left);
377
+ if (leftLifecycle === 'tombstoned') {
378
+ return { lifecycle: leftLifecycle, evidence: leftEvidence ?? right };
379
+ }
380
+ if (!leftEvidence)
381
+ return { lifecycle: rightLifecycle, evidence: right };
382
+ const rank = {
383
+ 'open-provisional-source': 1,
384
+ 'inactivity-threshold': 1,
385
+ 'complete-source': 2,
386
+ 'provider-explicit-event': 3,
387
+ };
388
+ const leftRank = rank[String(leftEvidence.basis)] ?? 0;
389
+ const rightRank = rank[String(right.basis)] ?? 0;
390
+ const rightIsNewer = evidenceTimestamp(right) > evidenceTimestamp(leftEvidence);
391
+ return rightRank > leftRank || (rightRank === leftRank && rightIsNewer)
392
+ ? { lifecycle: rightLifecycle, evidence: right }
393
+ : { lifecycle: leftLifecycle, evidence: leftEvidence };
394
+ }
395
+ function asLifecycleEvidence(value) {
396
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
397
+ ? value
398
+ : null;
399
+ }
400
+ function evidenceTimestamp(value) {
401
+ const parsed = Date.parse(String(value.observedAt ?? ''));
402
+ return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
403
+ }
404
+ const DEFAULT_INACTIVITY_THRESHOLD_MS = 24 * 60 * 60 * 1000;
405
+ function inactivityThresholdMs(request) {
406
+ return request.inactivityThresholdMs ?? DEFAULT_INACTIVITY_THRESHOLD_MS;
407
+ }
408
+ function isStaleHistoricalTimestamp(value, thresholdMs) {
409
+ const timestamp = Date.parse(value);
410
+ return Number.isFinite(timestamp)
411
+ && Date.now() - timestamp > thresholdMs;
412
+ }
264
413
  function sourceGenerationDigest(request) {
265
414
  return sha256([
266
415
  request.source.sourceId,
@@ -10,6 +10,12 @@ export * from './knowledge-shard.js';
10
10
  export * from './candidates.js';
11
11
  export * from './promotion.js';
12
12
  export * from './importer.js';
13
+ export * from './import-lease.js';
14
+ export * from './batch-contracts.js';
15
+ export * from './batch-import.js';
16
+ export * from './workspace-discovery.js';
17
+ export * from './timeline.js';
18
+ export * from './origin.js';
13
19
  export * from './adapters/generic.js';
14
20
  export * from './adapters/claude.js';
15
21
  export * from './adapters/codex.js';
@@ -0,0 +1,117 @@
1
+ export const ORIGIN_CLASSIFIER_VERSION = '1.0.0';
2
+ export function classifySessionEventOrigin(provider, record) {
3
+ const envelope = classifyWholeEnvelope(record.text);
4
+ if (envelope)
5
+ return envelope;
6
+ const kind = record.kind.toLowerCase();
7
+ if (isProviderBootstrap(provider, kind)) {
8
+ return classified('provider-bootstrap', `${provider}:structured-bootstrap`);
9
+ }
10
+ if (isToolControl(kind, record.role)) {
11
+ return classified('tool-control', `${provider}:structured-control`);
12
+ }
13
+ if (record.role === 'assistant') {
14
+ return classified('assistant-generated', `${provider}:assistant-role`);
15
+ }
16
+ if (record.role === 'user' && kind === 'message' && record.text.trim().length > 0) {
17
+ return classified('user-authored', `${provider}:user-message`);
18
+ }
19
+ return classified('unknown', `${provider}:insufficient-authorship-evidence`);
20
+ }
21
+ export function isControlOrigin(origin) {
22
+ return origin === 'provider-bootstrap'
23
+ || origin === 'workspace-instruction'
24
+ || origin === 'tool-control';
25
+ }
26
+ export function deriveSessionIntent(events) {
27
+ const eligible = events
28
+ .filter((event) => event.origin === 'user-authored'
29
+ && event.kind === 'message'
30
+ && event.searchableText.trim().length > 0)
31
+ .sort((left, right) => left.sequence - right.sequence || left.eventId.localeCompare(right.eventId));
32
+ const selected = eligible[0];
33
+ if (selected) {
34
+ const summary = normalizeExcerpt(selected.searchableText, 240);
35
+ return {
36
+ status: 'selected',
37
+ eventId: selected.eventId,
38
+ sequence: selected.sequence,
39
+ title: normalizeExcerpt(summary.split('\n')[0], 80),
40
+ summary,
41
+ };
42
+ }
43
+ const uncertain = events.some((event) => event.origin === 'unknown' && event.searchableText.trim().length > 0);
44
+ return {
45
+ status: uncertain ? 'unknown' : 'absent',
46
+ eventId: null,
47
+ sequence: null,
48
+ title: null,
49
+ summary: null,
50
+ };
51
+ }
52
+ function classifyWholeEnvelope(text) {
53
+ const trimmed = text.trim();
54
+ if (!trimmed)
55
+ return null;
56
+ if (/^# AGENTS\.md instructions for [^\n]+\n+<INSTRUCTIONS>\n[\s\S]*\n<\/INSTRUCTIONS>$/.test(trimmed)) {
57
+ return classified('workspace-instruction', 'envelope:agents-instructions');
58
+ }
59
+ for (const tag of [
60
+ 'recommended_plugins',
61
+ 'codex_internal_context',
62
+ 'environment_context',
63
+ ]) {
64
+ if (wholeTag(trimmed, tag)) {
65
+ return classified('provider-bootstrap', `envelope:${tag}`);
66
+ }
67
+ }
68
+ if (wholeTag(trimmed, 'local-command-caveat')) {
69
+ return classified('tool-control', 'envelope:local-command-caveat');
70
+ }
71
+ if (/^<[A-Za-z_][\w.-]*(?:\s[^>]*)?>[\s\S]*<\/[A-Za-z_][\w.-]*>$/.test(trimmed)) {
72
+ return classified('unknown', 'envelope:unrecognized');
73
+ }
74
+ return null;
75
+ }
76
+ function wholeTag(value, tag) {
77
+ const escaped = tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
78
+ return new RegExp(`^<${escaped}(?:\\s[^>]*)?>[\\s\\S]*<\\/${escaped}>$`).test(value);
79
+ }
80
+ function isProviderBootstrap(provider, kind) {
81
+ if (provider === 'codex') {
82
+ return kind === 'codex.session_meta'
83
+ || kind === 'codex.turn_context'
84
+ || kind === 'codex.thread-state';
85
+ }
86
+ if (provider === 'factory') {
87
+ return kind === 'factory.session_start' || kind === 'factory.settings';
88
+ }
89
+ if (provider === 'cursor')
90
+ return kind === 'system';
91
+ return false;
92
+ }
93
+ function isToolControl(kind, role) {
94
+ return role === 'tool'
95
+ || role === 'system'
96
+ || kind === 'lifecycle-hook'
97
+ || kind === 'tool-call'
98
+ || kind === 'tool-result'
99
+ || kind === 'summary'
100
+ || kind.includes('lifecycle')
101
+ || kind.startsWith('tool.')
102
+ || kind.startsWith('cursor.cloud.')
103
+ || kind === 'cursor.agent.turn_ended'
104
+ || kind === 'factory.session_end';
105
+ }
106
+ function classified(origin, rule) {
107
+ return {
108
+ origin,
109
+ rule,
110
+ classifierVersion: ORIGIN_CLASSIFIER_VERSION,
111
+ };
112
+ }
113
+ function normalizeExcerpt(value, max) {
114
+ const normalized = value.replace(/\s+/g, ' ').trim();
115
+ return normalized.length <= max ? normalized : `${normalized.slice(0, max - 1)}…`;
116
+ }
117
+ //# sourceMappingURL=origin.js.map
@@ -6,7 +6,7 @@ import { SessionContractError } from './contracts.js';
6
6
  import { authorizeSourceFile } from './policy.js';
7
7
  export const DEFAULT_READER_LIMITS = Object.freeze({
8
8
  maxRecords: 1_000_000,
9
- maxRecordBytes: 4 * 1024 * 1024,
9
+ maxRecordBytes: 8 * 1024 * 1024,
10
10
  maxTotalBytes: 1024 * 1024 * 1024,
11
11
  maxNestingDepth: 64,
12
12
  });