@aiwg/cli 2026.7.25 → 2026.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +33 -0
  2. package/agentic/code/providers/capability-matrix.yaml +511 -0
  3. package/agentic/code/providers/model-capabilities.v1.json +120 -0
  4. package/agentic/code/providers/model-catalog.v1.json +96 -0
  5. package/agentic/code/providers/model-policy-evaluations.v1.json +50 -0
  6. package/agentic/code/providers/premium-model-allowlist.v1.json +36 -0
  7. package/bin/aiwg.mjs +14 -10
  8. package/dist/src/api/index.d.ts +1 -0
  9. package/dist/src/api/index.js +1 -0
  10. package/dist/src/artifacts/cli.js +55 -10
  11. package/dist/src/artifacts/fortemi-shard-export.js +107 -18
  12. package/dist/src/artifacts/types.js +4 -0
  13. package/dist/src/auth/client.js +209 -0
  14. package/dist/src/auth/config.js +38 -0
  15. package/dist/src/auth/credential-store.js +141 -0
  16. package/dist/src/auth/resource-credentials.js +25 -0
  17. package/dist/src/auth/types.js +2 -0
  18. package/dist/src/channel/manager.mjs +5 -5
  19. package/dist/src/cli/handlers/auth.js +125 -0
  20. package/dist/src/cli/handlers/help.js +1 -0
  21. package/dist/src/cli/handlers/index.js +6 -2
  22. package/dist/src/cli/handlers/job.js +97 -0
  23. package/dist/src/cli/handlers/resource-versions.js +2 -0
  24. package/dist/src/cli/handlers/runtime-info.js +2 -2
  25. package/dist/src/cli/handlers/serve.js +2 -2
  26. package/dist/src/cli/handlers/sessions.js +211 -5
  27. package/dist/src/cli/handlers/steward.js +16 -3
  28. package/dist/src/cli/handlers/subcommands.js +10 -1
  29. package/dist/src/cli/handlers/use.js +342 -43
  30. package/dist/src/config/gitignore.js +1 -0
  31. package/dist/src/extensions/commands/definitions.js +49 -5
  32. package/dist/src/extensions/manifest.js +1 -0
  33. package/dist/src/features/catalog.js +3 -3
  34. package/dist/src/jobs/executor.js +83 -0
  35. package/dist/src/jobs/flow.js +106 -0
  36. package/dist/src/jobs/gitea.js +91 -0
  37. package/dist/src/jobs/render.js +53 -0
  38. package/dist/src/jobs/runner.js +315 -0
  39. package/dist/src/jobs/types.js +3 -0
  40. package/dist/src/memory/canonical-context.js +342 -0
  41. package/dist/src/memory/context-pack.js +282 -0
  42. package/dist/src/memory/index.js +4 -0
  43. package/dist/src/memory/intake.js +118 -0
  44. package/dist/src/providers/capability-matrix.js +11 -4
  45. package/dist/src/providers/capability-matrix.yaml +39 -42
  46. package/dist/src/resources/resolver.js +1 -0
  47. package/dist/src/resources/web-release.d.ts +3 -1
  48. package/dist/src/resources/web-release.js +14 -6
  49. package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
  50. package/dist/src/serve/fleet-mission-conductor.js +293 -0
  51. package/dist/src/sessions/analytics.js +303 -0
  52. package/dist/src/sessions/importer.js +7 -1
  53. package/dist/src/sessions/index.js +2 -0
  54. package/dist/src/sessions/output-registration.js +338 -0
  55. package/dist/src/sessions/policy.js +1 -1
  56. package/dist/src/sessions/promotion.js +73 -2
  57. package/dist/src/sessions/repository.js +215 -1
  58. package/dist/src/update/notifier.mjs +13 -2
  59. package/package.json +17 -10
  60. package/tools/_resolve-impl.mjs +74 -0
  61. package/tools/agents/deploy-agents.mjs +962 -0
  62. package/tools/agents/providers/base.mjs +2954 -0
  63. package/tools/agents/providers/claude.mjs +711 -0
  64. package/tools/agents/providers/codex.mjs +699 -0
  65. package/tools/agents/providers/copilot.mjs +659 -0
  66. package/tools/agents/providers/cursor.mjs +714 -0
  67. package/tools/agents/providers/factory.mjs +1130 -0
  68. package/tools/agents/providers/hermes.mjs +663 -0
  69. package/tools/agents/providers/hook-capabilities.mjs +85 -0
  70. package/tools/agents/providers/model-role.mjs +56 -0
  71. package/tools/agents/providers/openclaw-translator.mjs +348 -0
  72. package/tools/agents/providers/openclaw.mjs +680 -0
  73. package/tools/agents/providers/opencode.mjs +675 -0
  74. package/tools/agents/providers/openhuman.mjs +292 -0
  75. package/tools/agents/providers/warp.mjs +413 -0
  76. package/tools/agents/providers/windsurf.mjs +748 -0
  77. package/tools/commands/deploy-prompts-codex.mjs +336 -0
  78. package/tools/plugin/package-plugins.mjs +1013 -0
  79. package/tools/skills/deploy-skills-codex.mjs +571 -0
@@ -12,7 +12,7 @@ export class MemoryPromotionGateway {
12
12
  throw new SessionContractError('MALFORMED_SOURCE', 'candidate version does not exist');
13
13
  }
14
14
  const existing = this.store.getPromotionReceipt(input.candidateId, input.version, input.destination.consumer);
15
- if (candidate.reviewState !== 'accepted' && !(candidate.reviewState === 'promoted' && existing)) {
15
+ if (candidate.reviewState !== 'accepted' && candidate.reviewState !== 'promoted') {
16
16
  throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'promotion requires an accepted exact candidate version');
17
17
  }
18
18
  const security = candidateSecurity(candidate);
@@ -165,7 +165,7 @@ export class FilesystemPromotionDispositionCoordinator {
165
165
  if (!decision) {
166
166
  throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'every promoted artifact requires an explicit disposition');
167
167
  }
168
- this.authorizedPath(dependent.destinationRef);
168
+ this.authorizedDispositionRef(dependent.destinationRef);
169
169
  return {
170
170
  dependentId: dependent.dependentId,
171
171
  destinationRef: dependent.destinationRef,
@@ -194,6 +194,12 @@ export class FilesystemPromotionDispositionCoordinator {
194
194
  for (const effect of journal.effects) {
195
195
  if (effect.outcome !== 'pending')
196
196
  continue;
197
+ const lineMemory = this.lineMemoryRef(effect.destinationRef);
198
+ if (lineMemory) {
199
+ effect.outcome = this.applyLineMemoryDisposition(lineMemory.metadataPath, lineMemory.handle, purge.operationId, effect);
200
+ this.writeJournal(journalPath, journal);
201
+ continue;
202
+ }
197
203
  const target = this.authorizedPath(effect.destinationRef);
198
204
  const marker = dispositionMarker(purge.operationId, effect);
199
205
  if (effect.action === 'delete') {
@@ -252,6 +258,65 @@ export class FilesystemPromotionDispositionCoordinator {
252
258
  }
253
259
  return target;
254
260
  }
261
+ authorizedDispositionRef(destinationRef) {
262
+ if (this.lineMemoryRef(destinationRef))
263
+ return;
264
+ this.authorizedPath(destinationRef);
265
+ }
266
+ lineMemoryRef(destinationRef) {
267
+ const separator = destinationRef.lastIndexOf('#');
268
+ if (separator < 1)
269
+ return null;
270
+ const metadataRef = destinationRef.slice(0, separator);
271
+ const handle = destinationRef.slice(separator + 1);
272
+ if (!/^lm_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(handle)) {
273
+ return null;
274
+ }
275
+ return { metadataPath: this.authorizedPath(metadataRef), handle };
276
+ }
277
+ applyLineMemoryDisposition(metadataPath, handle, operationId, effect) {
278
+ if (!existsSync(metadataPath))
279
+ return 'already-applied';
280
+ const metadata = JSON.parse(readFileSync(metadataPath, 'utf8'));
281
+ if (metadata.schemaVersion !== 'aiwg.line-memory.v1' || !metadata.entries) {
282
+ throw new SessionContractError('IMPORT_CONFLICT', 'line-memory metadata schema is invalid');
283
+ }
284
+ const entry = metadata.entries[handle];
285
+ if (!entry)
286
+ return 'already-applied';
287
+ const priorOperation = entry.disposition?.operationId;
288
+ if (priorOperation === operationId)
289
+ return 'already-applied';
290
+ const memoryRef = metadata.store?.memoryPath ?? '.aiwg/memory/line-memory.txt';
291
+ const memoryPath = this.authorizedPath(memoryRef);
292
+ const lines = existsSync(memoryPath)
293
+ ? readFileSync(memoryPath, 'utf8').split(/\r?\n/).filter(Boolean)
294
+ : [];
295
+ const removesActiveFact = ['delete', 'revoke', 'supersede'].includes(effect.action);
296
+ const nextLines = removesActiveFact
297
+ ? removeFirstExact(lines, entry.value)
298
+ : lines;
299
+ if (effect.action === 'delete')
300
+ delete metadata.entries[handle];
301
+ else {
302
+ entry.status = effect.action === 'revoke' ? 'revoked'
303
+ : effect.action === 'supersede' ? 'superseded'
304
+ : effect.action === 'origin_unavailable' ? 'origin-unavailable'
305
+ : 'active';
306
+ entry.updatedAt = new Date().toISOString();
307
+ entry.disposition = {
308
+ operationId,
309
+ action: effect.action,
310
+ effect: effect.effect,
311
+ originAvailable: effect.action !== 'origin_unavailable',
312
+ };
313
+ }
314
+ if (removesActiveFact) {
315
+ this.atomicWrite(memoryPath, nextLines.length ? `${nextLines.join('\n')}\n` : '');
316
+ }
317
+ this.atomicWrite(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`);
318
+ return 'applied';
319
+ }
255
320
  journalPath(operationId) {
256
321
  return resolve(this.journalRoot, `${operationId.replace(':', '-')}.json`);
257
322
  }
@@ -290,6 +355,12 @@ function requireJournalFiles(root) {
290
355
  .sort()
291
356
  .map((name) => resolve(root, name));
292
357
  }
358
+ function removeFirstExact(lines, value) {
359
+ const index = lines.indexOf(value);
360
+ return index < 0
361
+ ? [...lines]
362
+ : lines.filter((_, candidate) => candidate !== index);
363
+ }
293
364
  export function resolveMemoryConsumerManifest(projectRoot, consumer) {
294
365
  const safeConsumer = assertConsumerId(consumer);
295
366
  const candidates = [
@@ -2,6 +2,7 @@ import { createRequire } from 'node:module';
2
2
  import { CandidateReviewReceiptSchema, DeletionReceiptSchema, IntelligenceCandidateSchema, PromotionDependencyDecisionSchema, PromotionReceiptSchema, SessionContractError, sha256, } from './contracts.js';
3
3
  import { coverageFromBatchRun, } from './batch-contracts.js';
4
4
  import { redactSessionText, sanitizeNativeExtensions } from './policy.js';
5
+ import { deriveSessionAnalytics, } from './analytics.js';
5
6
  const require = createRequire(import.meta.url);
6
7
  const POLICY_PROVIDER_MIGRATION = 'policy-provider-identity:v2';
7
8
  const EVENT_ORIGIN_INTENT_MIGRATION = 'event-origin-intent:v1';
@@ -103,10 +104,23 @@ export class SessionRepository {
103
104
  CREATE TABLE IF NOT EXISTS session_catalog_meta (
104
105
  key TEXT PRIMARY KEY, value TEXT NOT NULL
105
106
  );
107
+ CREATE TABLE IF NOT EXISTS session_analytics_facts (
108
+ fact_id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL,
109
+ session_id TEXT NOT NULL, event_id TEXT NOT NULL,
110
+ category TEXT NOT NULL, status TEXT NOT NULL,
111
+ provider TEXT NOT NULL, tool_name TEXT, occurred_at TEXT,
112
+ data TEXT NOT NULL,
113
+ FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE,
114
+ FOREIGN KEY(event_id) REFERENCES session_events(event_id) ON DELETE CASCADE
115
+ );
106
116
  CREATE INDEX IF NOT EXISTS idx_session_workspace_provider
107
117
  ON sessions(workspace_id, source_id, lifecycle);
108
118
  CREATE INDEX IF NOT EXISTS idx_event_session_sequence
109
119
  ON session_events(session_id, sequence_no);
120
+ CREATE INDEX IF NOT EXISTS idx_session_analytics_scope
121
+ ON session_analytics_facts(
122
+ workspace_id, category, provider, status, occurred_at, fact_id
123
+ );
110
124
  CREATE VIRTUAL TABLE IF NOT EXISTS session_event_fts USING fts5(
111
125
  event_id UNINDEXED, searchable_text, tokenize='unicode61'
112
126
  );
@@ -139,6 +153,23 @@ export class SessionRepository {
139
153
  this.alignSessionFtsRowids();
140
154
  this.migrateSessionPolicyAndProviderIdentity();
141
155
  this.migrateEventOriginAndIntent();
156
+ this.alignSessionAnalytics();
157
+ }
158
+ alignSessionAnalytics() {
159
+ const eventCount = Number(this.db.prepare(`SELECT COUNT(*) AS count
160
+ FROM session_events e
161
+ JOIN import_runs r ON r.import_run_id=e.import_run_id
162
+ JOIN sessions s ON s.session_id=e.session_id
163
+ WHERE r.status='committed' AND s.lifecycle!='tombstoned'`).get()?.count ?? 0);
164
+ const factSessions = Number(this.db.prepare('SELECT COUNT(DISTINCT session_id) AS count FROM session_analytics_facts').get()?.count ?? 0);
165
+ const sourceSessions = Number(this.db.prepare(`SELECT COUNT(DISTINCT e.session_id) AS count
166
+ FROM session_events e
167
+ JOIN import_runs r ON r.import_run_id=e.import_run_id
168
+ JOIN sessions s ON s.session_id=e.session_id
169
+ WHERE r.status='committed' AND s.lifecycle!='tombstoned'`).get()?.count ?? 0);
170
+ if (eventCount === 0 || factSessions === sourceSessions)
171
+ return;
172
+ this.rebuildAnalytics();
142
173
  }
143
174
  alignSessionFtsRowids() {
144
175
  const eventCount = Number(this.db.prepare('SELECT COUNT(*) AS count FROM session_events').get()?.count ?? 0);
@@ -470,6 +501,11 @@ export class SessionRepository {
470
501
  }
471
502
  const outcome = publish ? 'committed' : 'staged';
472
503
  this.db.prepare(`UPDATE import_runs SET status=?, checkpoint=? WHERE import_run_id=?`).run(outcome, JSON.stringify(checkpoint), batch.run.importRunId);
504
+ if (publish) {
505
+ for (const session of batch.sessions) {
506
+ this.rebuildAnalytics(session.workspaceId, session.sessionId);
507
+ }
508
+ }
473
509
  const counts = { sessions: sessionsInserted, events: eventsInserted };
474
510
  this.db.prepare(`INSERT INTO import_receipts(operation_id, import_run_id, outcome, counts, checkpoint)
475
511
  VALUES (?, ?, ?, ?, ?)`).run(batch.run.importRunId, batch.run.importRunId, outcome, JSON.stringify(counts), JSON.stringify(checkpoint));
@@ -525,6 +561,8 @@ export class SessionRepository {
525
561
  this.db.prepare(`UPDATE mutation_audit SET outcome='committed', occurred_at=?, data=?
526
562
  WHERE operation_id=?`).run(updated.observedAt, JSON.stringify(updated), operationId);
527
563
  }
564
+ if (result.changes > 0)
565
+ this.rebuildAnalytics();
528
566
  return result.changes;
529
567
  });
530
568
  return commit();
@@ -561,6 +599,8 @@ export class SessionRepository {
561
599
  this.db.prepare(`UPDATE mutation_audit SET outcome='committed', occurred_at=?, data=?
562
600
  WHERE operation_id=?`).run(updated.observedAt, JSON.stringify(updated), operationId);
563
601
  }
602
+ if (result.changes > 0)
603
+ this.rebuildAnalytics();
564
604
  return result.changes;
565
605
  });
566
606
  return commit();
@@ -876,6 +916,163 @@ export class SessionRepository {
876
916
  snapshotRowid,
877
917
  };
878
918
  }
919
+ rebuildAnalytics(workspaceId, sessionId) {
920
+ const where = [
921
+ `r.status='committed'`,
922
+ `s.lifecycle!='tombstoned'`,
923
+ ];
924
+ const params = [];
925
+ if (workspaceId) {
926
+ where.push('s.workspace_id=?');
927
+ params.push(workspaceId);
928
+ }
929
+ if (sessionId) {
930
+ where.push('s.session_id=?');
931
+ params.push(sessionId);
932
+ }
933
+ const sessions = this.db.prepare(`SELECT s.session_id, s.data
934
+ FROM sessions s
935
+ WHERE s.lifecycle!='tombstoned'
936
+ ${workspaceId ? 'AND s.workspace_id=?' : ''}
937
+ ${sessionId ? 'AND s.session_id=?' : ''}
938
+ AND EXISTS (
939
+ SELECT 1 FROM session_events e
940
+ JOIN import_runs r ON r.import_run_id=e.import_run_id
941
+ WHERE e.session_id=s.session_id AND r.status='committed'
942
+ )
943
+ ORDER BY s.session_id`).all(...[
944
+ ...(workspaceId ? [workspaceId] : []),
945
+ ...(sessionId ? [sessionId] : []),
946
+ ]);
947
+ if (!workspaceId && !sessionId) {
948
+ this.db.exec('DELETE FROM session_analytics_facts');
949
+ }
950
+ else if (sessionId) {
951
+ this.db.prepare('DELETE FROM session_analytics_facts WHERE session_id=?').run(sessionId);
952
+ }
953
+ else {
954
+ this.db.prepare('DELETE FROM session_analytics_facts WHERE workspace_id=?').run(workspaceId);
955
+ }
956
+ const eventQuery = this.db.prepare(`SELECT e.data
957
+ FROM session_events e
958
+ JOIN import_runs r ON r.import_run_id=e.import_run_id
959
+ JOIN sessions s ON s.session_id=e.session_id
960
+ WHERE e.session_id=? AND ${where.join(' AND ')}
961
+ ORDER BY e.sequence_no, e.event_id`);
962
+ const insert = this.db.prepare(`INSERT OR REPLACE INTO session_analytics_facts(
963
+ fact_id, workspace_id, session_id, event_id, category, status,
964
+ provider, tool_name, occurred_at, data
965
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
966
+ let inserted = 0;
967
+ for (const row of sessions) {
968
+ const session = JSON.parse(String(row.data));
969
+ const events = eventQuery.all(session.sessionId, ...params)
970
+ .map((eventRow) => JSON.parse(String(eventRow.data)));
971
+ for (const fact of deriveSessionAnalytics(session, events)) {
972
+ inserted += insert.run(fact.factId, fact.workspaceId, fact.sessionId, fact.eventId, fact.category, fact.status, fact.provider, fact.toolName, fact.occurredAt, JSON.stringify(fact)).changes;
973
+ }
974
+ }
975
+ return inserted;
976
+ }
977
+ listAnalyticsFacts(options) {
978
+ const where = [
979
+ 'f.workspace_id=?',
980
+ `s.lifecycle!='tombstoned'`,
981
+ `r.status='committed'`,
982
+ ];
983
+ const params = [options.workspaceId];
984
+ if (options.categories?.length) {
985
+ where.push(`f.category IN (${options.categories.map(() => '?').join(', ')})`);
986
+ params.push(...options.categories);
987
+ }
988
+ const scalar = [
989
+ ['f.provider', options.provider],
990
+ ['f.session_id', options.sessionId],
991
+ ['f.tool_name', options.tool],
992
+ ['f.status', options.status],
993
+ [`json_extract(f.data, '$.actor')`, options.actor],
994
+ [`json_extract(f.data, '$.sensitivity')`, options.sensitivity],
995
+ [`json_extract(f.data, '$.extractionState')`, options.extractionState],
996
+ ];
997
+ for (const [column, value] of scalar) {
998
+ if (value === undefined)
999
+ continue;
1000
+ where.push(`${column}=?`);
1001
+ params.push(value);
1002
+ }
1003
+ if (options.dateFrom) {
1004
+ where.push('f.occurred_at>=?');
1005
+ params.push(options.dateFrom);
1006
+ }
1007
+ if (options.dateTo) {
1008
+ where.push('f.occurred_at<=?');
1009
+ params.push(options.dateTo);
1010
+ }
1011
+ if (options.tag) {
1012
+ where.push(`EXISTS (
1013
+ SELECT 1 FROM session_tags t
1014
+ WHERE t.session_id=f.session_id AND t.tag=?
1015
+ )`);
1016
+ params.push(options.tag);
1017
+ }
1018
+ const limit = options.limit ?? 500;
1019
+ if (limit < 1 || limit > 5_000) {
1020
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'analytics limit must be between 1 and 5000');
1021
+ }
1022
+ return this.db.prepare(`SELECT f.data
1023
+ FROM session_analytics_facts f
1024
+ JOIN sessions s ON s.session_id=f.session_id
1025
+ JOIN session_events e ON e.event_id=f.event_id
1026
+ JOIN import_runs r ON r.import_run_id=e.import_run_id
1027
+ WHERE ${where.join(' AND ')}
1028
+ ORDER BY
1029
+ CASE WHEN f.occurred_at IS NULL THEN 1 ELSE 0 END,
1030
+ f.occurred_at, f.session_id,
1031
+ json_extract(f.data, '$.sequence'), f.fact_id
1032
+ LIMIT ?`).all(...params, limit)
1033
+ .map((row) => JSON.parse(String(row.data)));
1034
+ }
1035
+ analyticsSummary(options) {
1036
+ const facts = this.listAnalyticsFacts({ ...options, limit: options.limit ?? 5_000 });
1037
+ const byCategory = countBy(facts, (fact) => fact.category);
1038
+ const byStatus = countBy(facts, (fact) => fact.status);
1039
+ const byProvider = countBy(facts, (fact) => fact.provider);
1040
+ const byTool = countBy(facts.filter((fact) => fact.toolName), (fact) => fact.toolName);
1041
+ return {
1042
+ analyticsVersion: '1.0.0',
1043
+ workspaceId: options.workspaceId,
1044
+ totals: {
1045
+ facts: facts.length,
1046
+ sessions: new Set(facts.map((fact) => fact.sessionId)).size,
1047
+ toolCalls: facts.filter((fact) => fact.category === 'tool-call').length,
1048
+ toolFailures: facts.filter((fact) => fact.category === 'tool-result' && fact.status === 'failed').length,
1049
+ escalations: facts.filter((fact) => fact.category === 'escalation').length,
1050
+ hitlDecisions: facts.filter((fact) => fact.category === 'hitl').length,
1051
+ indicators: facts.filter((fact) => fact.category === 'indicator').length,
1052
+ retryGroups: new Set(facts.map((fact) => fact.retryGroupId).filter(Boolean)).size,
1053
+ },
1054
+ byCategory,
1055
+ byStatus,
1056
+ byProvider,
1057
+ byTool,
1058
+ };
1059
+ }
1060
+ getAnalyticsEvidence(id, workspaceId) {
1061
+ const row = this.db.prepare(`SELECT f.data AS fact_data, e.data AS event_data
1062
+ FROM session_analytics_facts f
1063
+ JOIN sessions s ON s.session_id=f.session_id
1064
+ JOIN session_events e ON e.event_id=f.event_id
1065
+ JOIN import_runs r ON r.import_run_id=e.import_run_id
1066
+ WHERE (f.fact_id=? OR f.event_id=?)
1067
+ AND f.workspace_id=? AND s.lifecycle!='tombstoned' AND r.status='committed'
1068
+ ORDER BY f.fact_id LIMIT 1`).get(id, id, workspaceId);
1069
+ return row
1070
+ ? {
1071
+ fact: JSON.parse(String(row.fact_data)),
1072
+ event: JSON.parse(String(row.event_data)),
1073
+ }
1074
+ : { fact: null, event: null };
1075
+ }
879
1076
  saveCandidates(candidates) {
880
1077
  const save = this.db.transaction(() => {
881
1078
  let inserted = 0;
@@ -1022,7 +1219,8 @@ export class SessionRepository {
1022
1219
  if (existing)
1023
1220
  return { ...existing, duplicate: true };
1024
1221
  const candidate = this.getCandidate(receipt.candidateId, receipt.candidateVersion);
1025
- if (!candidate || candidate.reviewState !== 'accepted') {
1222
+ if (!candidate
1223
+ || (candidate.reviewState !== 'accepted' && candidate.reviewState !== 'promoted')) {
1026
1224
  throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'promotion requires an accepted exact candidate version');
1027
1225
  }
1028
1226
  const evidenceIds = [...new Set(candidate.evidence.map((item) => item.eventId))].sort();
@@ -1161,6 +1359,9 @@ export class SessionRepository {
1161
1359
  counts: { sessions: 1 },
1162
1360
  });
1163
1361
  }
1362
+ if (changed) {
1363
+ this.db.prepare('DELETE FROM session_analytics_facts WHERE session_id=?').run(sessionId);
1364
+ }
1164
1365
  return changed;
1165
1366
  });
1166
1367
  return tombstone();
@@ -1187,6 +1388,8 @@ export class SessionRepository {
1187
1388
  counts: { sessions: 1 },
1188
1389
  });
1189
1390
  }
1391
+ if (changed)
1392
+ this.rebuildAnalytics(workspaceId, sessionId);
1190
1393
  return changed;
1191
1394
  });
1192
1395
  return restore();
@@ -1385,6 +1588,7 @@ export class SessionRepository {
1385
1588
  dependencyDispositions: validatedDecisions.length,
1386
1589
  },
1387
1590
  });
1591
+ this.db.prepare('DELETE FROM session_analytics_facts WHERE session_id=?').run(input.preview.sessionId);
1388
1592
  return receipt;
1389
1593
  });
1390
1594
  return apply();
@@ -1400,6 +1604,7 @@ export class SessionRepository {
1400
1604
  SELECT rowid, event_id, json_extract(data, '$.searchableText') FROM session_events
1401
1605
  `);
1402
1606
  indexed = Number(this.db.prepare('SELECT COUNT(*) AS count FROM session_events').get()?.count ?? 0);
1607
+ this.rebuildAnalytics();
1403
1608
  }
1404
1609
  else {
1405
1610
  this.db.prepare(`DELETE FROM session_event_fts WHERE rowid IN (
@@ -1412,6 +1617,7 @@ export class SessionRepository {
1412
1617
  FROM session_events e
1413
1618
  JOIN sessions s ON s.session_id=e.session_id
1414
1619
  WHERE s.workspace_id=?`).run(workspaceId).changes;
1620
+ this.rebuildAnalytics(workspaceId);
1415
1621
  }
1416
1622
  this.emitRepositoryMutation({
1417
1623
  operationId: sha256([
@@ -1506,6 +1712,14 @@ function canonicalDevinLocator(value) {
1506
1712
  ? `devin-desktop-${value.slice('windsurf-'.length)}`
1507
1713
  : value;
1508
1714
  }
1715
+ function countBy(items, key) {
1716
+ const counts = {};
1717
+ for (const item of items) {
1718
+ const value = key(item);
1719
+ counts[value] = (counts[value] ?? 0) + 1;
1720
+ }
1721
+ return Object.fromEntries(Object.entries(counts).sort(([left], [right]) => left.localeCompare(right)));
1722
+ }
1509
1723
  function encodeMutationCursor(rowid, workspaceId) {
1510
1724
  const unsigned = { rowid, workspaceId };
1511
1725
  return Buffer.from(JSON.stringify({
@@ -157,6 +157,16 @@ function readCurrentVersion(packageRoot) {
157
157
  }
158
158
  }
159
159
 
160
+ /**
161
+ * Return true when a cached check belongs to the package currently handling
162
+ * the command. A global launcher can dispatch into a newer development
163
+ * checkout, so cache age alone is not sufficient.
164
+ */
165
+ export function cacheMatchesPackage(cache, packageRoot) {
166
+ const currentVersion = readCurrentVersion(packageRoot);
167
+ return !!(currentVersion && cache?.current === currentVersion);
168
+ }
169
+
160
170
  /**
161
171
  * Run the actual update check and write the cache file. Invoked by the
162
172
  * spawned background child via `node src/update/notifier.mjs --check`.
@@ -183,7 +193,7 @@ export function scheduleBackgroundCheck(packageRoot) {
183
193
  if (isDisabled()) return;
184
194
 
185
195
  const cache = readCache();
186
- if (cache?.lastCheckAt) {
196
+ if (cacheMatchesPackage(cache, packageRoot) && cache?.lastCheckAt) {
187
197
  const age = Date.now() - new Date(cache.lastCheckAt).getTime();
188
198
  if (age < CHECK_INTERVAL_MS) return; // recent enough — skip
189
199
  }
@@ -213,9 +223,10 @@ export function scheduleBackgroundCheck(packageRoot) {
213
223
  * is interactive, print a single-line notice to stderr. Never prompts.
214
224
  * Safe to call unconditionally from `bin/aiwg.mjs` — gated by isDisabled().
215
225
  */
216
- export function maybePrintNotice() {
226
+ export function maybePrintNotice(packageRoot) {
217
227
  if (isDisabled()) return;
218
228
  const cache = readCache();
229
+ if (!cacheMatchesPackage(cache, packageRoot)) return;
219
230
  if (!cache?.hasUpdate || !cache.current || !cache.latest) return;
220
231
  // One-line, non-intrusive. Users who want to update run `aiwg update`.
221
232
  const msg = `aiwg: update available ${cache.current} → ${cache.latest} (run: aiwg update)`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cli",
3
- "version": "2026.7.25",
3
+ "version": "2026.8.1",
4
4
  "description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,12 +25,19 @@
25
25
  "files": [
26
26
  "LICENSE",
27
27
  "bin/",
28
+ "agentic/code/providers/",
28
29
  "dist/src/",
29
30
  "!dist/**/*.d.ts",
30
31
  "!dist/**/*.map",
31
32
  "dist/src/api/index.d.ts",
32
33
  "dist/src/resources/index.d.ts",
33
34
  "dist/src/resources/web-release.d.ts",
35
+ "tools/_resolve-impl.mjs",
36
+ "tools/agents/deploy-agents.mjs",
37
+ "tools/agents/providers/",
38
+ "tools/commands/deploy-prompts-codex.mjs",
39
+ "tools/plugin/package-plugins.mjs",
40
+ "tools/skills/deploy-skills-codex.mjs",
34
41
  "README.md"
35
42
  ],
36
43
  "repository": {
@@ -56,23 +63,23 @@
56
63
  "node": ">=20.0.0"
57
64
  },
58
65
  "dependencies": {
59
- "@fortemi/core": "2026.7.14",
60
- "@modelcontextprotocol/sdk": "^1.24.0",
66
+ "@fortemi/core": "2026.7.15",
67
+ "@modelcontextprotocol/sdk": "^1.29.0",
61
68
  "chalk": "^4.1.2",
62
- "chokidar": "^3.6.0",
69
+ "chokidar": "^4.0.3",
63
70
  "commander": "^12.1.0",
64
- "glob": "^13.0.1",
71
+ "glob": "^13.0.6",
65
72
  "graceful-fs": "^4.2.11",
66
- "js-yaml": "^4.1.0",
73
+ "js-yaml": "^4.3.0",
67
74
  "listr2": "^8.2.5",
68
75
  "ora": "^5.4.1",
69
76
  "semver": "^7.8.5",
70
- "yaml": "^2.8.1",
77
+ "yaml": "^2.9.0",
71
78
  "zod": "^3.25.0"
72
79
  },
73
80
  "optionalDependencies": {
74
- "@hono/node-server": "^1.19.14",
75
- "hono": "^4.12.18",
76
- "ws": "^8.20.0"
81
+ "@hono/node-server": "2.0.11",
82
+ "hono": "^4.12.31",
83
+ "ws": "^8.21.1"
77
84
  }
78
85
  }
@@ -0,0 +1,74 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, extname, join, resolve } from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+
5
+ function findPackageRoot(callerUrl) {
6
+ let dir = dirname(fileURLToPath(callerUrl));
7
+ for (let i = 0; i < 12; i += 1) {
8
+ const pkgPath = join(dir, 'package.json');
9
+ if (existsSync(pkgPath)) {
10
+ try {
11
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
12
+ if (pkg.name === 'aiwg' || pkg.name === '@aiwg/cli') return dir;
13
+ } catch {
14
+ // Keep walking.
15
+ }
16
+ }
17
+
18
+ const parent = dirname(dir);
19
+ if (parent === dir) break;
20
+ dir = parent;
21
+ }
22
+
23
+ throw new Error(`Could not locate AIWG package root from ${callerUrl}`);
24
+ }
25
+
26
+ function candidatesFor(root, relativeFromSrc, base) {
27
+ const primary = resolve(root, base, relativeFromSrc);
28
+ const candidates = [primary];
29
+
30
+ if (base === 'src' && extname(primary) === '.js') {
31
+ candidates.push(primary.slice(0, -3) + '.ts');
32
+ }
33
+
34
+ return candidates;
35
+ }
36
+
37
+ function resolveCandidate(root, relativeFromSrc, base) {
38
+ for (const candidate of candidatesFor(root, relativeFromSrc, base)) {
39
+ if (existsSync(candidate)) return candidate;
40
+ }
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * Resolve a module that may live under compiled dist/src/ or source src/.
46
+ *
47
+ * Prefers dist/src/ when present. Set AIWG_RESOLVE_IMPL_FROM=dist or src to
48
+ * force a side during CI/package-layout checks.
49
+ */
50
+ export function resolveImpl(callerUrl, relativeFromSrc) {
51
+ const root = findPackageRoot(callerUrl);
52
+ const forced = process.env.AIWG_RESOLVE_IMPL_FROM;
53
+
54
+ const order = forced === 'src'
55
+ ? ['src']
56
+ : forced === 'dist'
57
+ ? ['dist/src']
58
+ : ['dist/src', 'src'];
59
+
60
+ for (const base of order) {
61
+ const candidate = resolveCandidate(root, relativeFromSrc, base);
62
+ if (candidate) return candidate;
63
+ }
64
+
65
+ const attempted = order
66
+ .flatMap((base) => candidatesFor(root, relativeFromSrc, base))
67
+ .join(', ');
68
+ throw new Error(`Could not resolve ${relativeFromSrc}; attempted: ${attempted}`);
69
+ }
70
+
71
+ export async function importImpl(callerUrl, relativeFromSrc) {
72
+ const modulePath = resolveImpl(callerUrl, relativeFromSrc);
73
+ return import(pathToFileURL(modulePath).href);
74
+ }