@aiwg/cli 2026.10.9 → 2026.10.11

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 (116) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +275 -17
  2. package/bin/aiwg.mjs +8 -0
  3. package/dist/src/cli/handlers/mc-admission.js +39 -0
  4. package/dist/src/cli/handlers/mc.js +2 -31
  5. package/dist/src/cli/handlers/models-help.js +52 -0
  6. package/dist/src/cli/handlers/models.js +11 -81
  7. package/dist/src/cli/handlers/sessions.js +198 -15
  8. package/dist/src/cli/handlers/use-addon-profile.js +72 -0
  9. package/dist/src/cli/handlers/use.js +203 -234
  10. package/dist/src/cli/ui.js +1 -0
  11. package/dist/src/config/aiwg-config.js +38 -184
  12. package/dist/src/config/cli-help.js +54 -0
  13. package/dist/src/config/cli.js +5 -69
  14. package/dist/src/config/config-normalization.js +10 -0
  15. package/dist/src/config/worker-activation-gate.js +26 -0
  16. package/dist/src/config/worker-adoption-cli.js +102 -0
  17. package/dist/src/config/worker-adoption.js +368 -0
  18. package/dist/src/config/worker-config-digest.js +14 -0
  19. package/dist/src/config/worker-context.js +337 -0
  20. package/dist/src/config/worker-group-templates.js +150 -0
  21. package/dist/src/config/worker-guidance-warning.js +28 -0
  22. package/dist/src/config/worker-owner-lock.js +152 -0
  23. package/dist/src/config/worker-parallelism.js +154 -0
  24. package/dist/src/config/worker-write.js +75 -0
  25. package/dist/src/decision/telemetry/exporter.js +2 -1
  26. package/dist/src/decision/telemetry/index.js +1 -0
  27. package/dist/src/decision/telemetry/mapping.js +43 -0
  28. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  29. package/dist/src/decision/telemetry/redaction.js +6 -1
  30. package/dist/src/decision/telemetry/types.js +3 -0
  31. package/dist/src/decision/telemetry/worker.js +69 -0
  32. package/dist/src/lint/rule-activation.js +15 -7
  33. package/dist/src/lint/runner.js +2 -2
  34. package/dist/src/mcp/server.mjs +2 -11
  35. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  36. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  37. package/dist/src/mcp/tools/worker-invoke.mjs +114 -16
  38. package/dist/src/mission-protocol/codecs.js +8 -38
  39. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  40. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  41. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  42. package/dist/src/models/conductor-data-policy.mjs +190 -0
  43. package/dist/src/models/config-loader.js +19 -3
  44. package/dist/src/models/config-writer.js +27 -0
  45. package/dist/src/models/model-capabilities.v1.json +275 -17
  46. package/dist/src/models/provider-policy.js +14 -155
  47. package/dist/src/models/resolver.js +2 -293
  48. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  49. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  50. package/dist/src/models/worker-direct-readiness.js +9 -218
  51. package/dist/src/models/worker-health-readiness.js +105 -26
  52. package/dist/src/models/worker-installation.mjs +12 -14
  53. package/dist/src/models/worker-member-references.js +64 -0
  54. package/dist/src/models/worker-plan.js +317 -0
  55. package/dist/src/models/worker-selection-policy.js +217 -0
  56. package/dist/src/models/wrapper-deployment.js +1 -1
  57. package/dist/src/policy/authorization.js +29 -1
  58. package/dist/src/process/aiwg-probe-command.d.mts +15 -0
  59. package/dist/src/process/aiwg-probe-command.mjs +37 -0
  60. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  61. package/dist/src/serve/shared-host-scheduler.js +25 -107
  62. package/dist/src/serve/shared-host-starvation.js +73 -0
  63. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  64. package/dist/src/serve/worker-artifacts.mjs +56 -0
  65. package/dist/src/serve/worker-command-request.js +68 -0
  66. package/dist/src/serve/worker-invocation-paths.js +144 -0
  67. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  68. package/dist/src/serve/worker-invocation.js +24 -291
  69. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  70. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  71. package/dist/src/sessions/adapters/grokbot.js +7 -0
  72. package/dist/src/sessions/batch-import.js +34 -6
  73. package/dist/src/sessions/contracts.js +19 -0
  74. package/dist/src/sessions/coverage-preflight.js +7 -1
  75. package/dist/src/sessions/fortemi-export-mapping.js +33 -3
  76. package/dist/src/sessions/host-catalog.js +157 -0
  77. package/dist/src/sessions/image-blobs.js +141 -0
  78. package/dist/src/sessions/importer.js +23 -3
  79. package/dist/src/sessions/index.js +2 -0
  80. package/dist/src/sessions/refresh.js +25 -2
  81. package/dist/src/sessions/repository.js +164 -22
  82. package/dist/src/sessions/workspace-discovery.js +5 -2
  83. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  84. package/package.json +15 -1
  85. package/schemas/mission-v1.schema.json +20 -19
  86. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  87. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  88. package/tools/agents/providers/claude.mjs +3 -30
  89. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  90. package/tools/agents/providers/codex.mjs +2 -8
  91. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  92. package/tools/agents/providers/rule-delivery.mjs +3 -16
  93. package/tools/agents/providers/rule-presentation.mjs +38 -0
  94. package/tools/plugin/package-plugins.mjs +1 -1
  95. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  96. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  97. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  98. package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
  99. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  100. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  101. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  102. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  103. package/tools/ralph-external/lib/worker-context.mjs +237 -0
  104. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  105. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  106. package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
  107. package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
  108. package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
  109. package/tools/ralph-external/lib/worker-isolation.mjs +19 -73
  110. package/tools/ralph-external/lib/worker-launch-cli.mjs +49 -0
  111. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  112. package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
  113. package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
  114. package/tools/ralph-external/lib/worker-termination.mjs +82 -0
  115. package/tools/ralph-external/session-launcher.mjs +124 -313
  116. package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
@@ -1,3 +1,8 @@
1
+ import { MODELS_HELP } from './models-help.js';
2
+ import { updateJson } from '../../models/config-writer.js';
3
+ import { atomicConfigWrite as atomicWrite } from '../../config/worker-write.js';
4
+ import { handleWorkerAdoptionArgs } from '../../config/worker-adoption-cli.js';
5
+ import { resolveParallelism } from '../../config/aiwg-config.js';
1
6
  /**
2
7
  * Safe model-policy audit, resolution, and mutation commands.
3
8
  * @implements #1804
@@ -8,7 +13,7 @@ import { isDeepStrictEqual } from 'node:util';
8
13
  import path from 'node:path';
9
14
  import { glob } from 'glob';
10
15
  import { load as loadYaml } from 'js-yaml';
11
- import { compileModelPolicy, validateCanonicalModelPolicy, validateUserProjectModelConfig, loadProviderModelCapabilities, WorkerPolicyError, assertWorkerRoleEffort, } from '../../models/provider-policy.js';
16
+ import { compileModelPolicy, validateCanonicalModelPolicy, loadProviderModelCapabilities, WorkerPolicyError, assertWorkerRoleEffort, } from '../../models/provider-policy.js';
12
17
  import { resolveGrokBuildRoleModel } from '../../models/grok-build-models.js';
13
18
  import { readAiwgConfig, emptyConfig } from '../../config/aiwg-config.js';
14
19
  import { ConfigLoader } from '../../models/config-loader.js';
@@ -34,22 +39,21 @@ export async function loadWorkerInputs(ctx) {
34
39
  const models = await loader.load(config.parallelism);
35
40
  const catalog = await effectiveCatalog(ctx, ctx.cwd, false, parseArgs(['resolve']));
36
41
  const registry = loadProviderModelCapabilities();
37
- // Let the resolver explain the dispatch gate before requiring host qualification (#3070).
38
42
  if (!config.parallelism?.worker_dispatch) {
39
43
  return { snapshot: { config, models, modelDigests: loader.getInputDigests(), catalog,
40
44
  registry, installations: {} }, drivers: {} };
41
45
  }
42
46
  // Explain missing policy before a recorded approval's digest check can
43
47
  // mask it. The host owner first preserves its missing-installation errors.
44
- const parallelism = config.parallelism;
48
+ const parallelism = resolveParallelism(config.parallelism, config.providers[0]);
45
49
  const precheckPolicy = () => {
46
50
  if (!ctx.args.some(arg => arg === '--role' || arg.startsWith('--role=')))
47
51
  return;
48
52
  const request = parseWorkerCommandArgs(ctx.args.slice(1));
49
53
  const members = Object.entries(parallelism.groups ?? {})
50
54
  .filter(([id]) => !request.group || request.group === id)
51
- .flatMap(([, group]) => group.members).filter(member => member.enabled !== false
52
- && parallelism.clis?.[member.cli]?.enabled !== false && member.roles.includes(request.role));
55
+ .flatMap(([, group]) => group.members ?? []).filter(member => member.enabled !== false
56
+ && parallelism.clis?.[member.cli]?.enabled !== false && member.roles.includes((models.defaults.workerSelection?.roleGuidance?.[request.role]?.baseRole ?? request.role)));
53
57
  const requiresExplicit = members.length > 0 && members.every(member => {
54
58
  // Health metadata can identify the provider before an operator has
55
59
  // qualified its dispatch adapter. It grants no dispatch authority.
@@ -269,35 +273,6 @@ function print(value, json) {
269
273
  else
270
274
  console.log(JSON.stringify(value, null, 2));
271
275
  }
272
- async function atomicWrite(file, content) {
273
- await fs.mkdir(path.dirname(file), { recursive: true });
274
- const temporary = `${file}.tmp-${process.pid}`;
275
- let mode = 0o600;
276
- try {
277
- mode = (await fs.stat(file)).mode;
278
- }
279
- catch { /* new file */ }
280
- await fs.writeFile(temporary, content, { encoding: 'utf8', mode });
281
- await fs.rename(temporary, file);
282
- }
283
- async function updateJson(file, mutate, dryRun) {
284
- let value = {};
285
- try {
286
- value = JSON.parse(await fs.readFile(file, 'utf8'));
287
- }
288
- catch (error) {
289
- if (error?.code !== 'ENOENT')
290
- throw error;
291
- }
292
- mutate(value);
293
- const validation = validateUserProjectModelConfig(value);
294
- if (!validation.valid)
295
- throw new Error(validation.diagnostics.map(item => item.message).join('; '));
296
- const content = `${JSON.stringify(value, null, 2)}\n`;
297
- if (!dryRun)
298
- await atomicWrite(file, content);
299
- return { file, beforeWrite: value, dryRun };
300
- }
301
276
  async function effectiveCatalog(ctx, target, allowNetwork, parsed, provider) {
302
277
  const { collectProviderInventory } = await import('../../providers/provider-inventory.js');
303
278
  const { resolveDynamicModelCatalog } = await import('../../models/model-discovery.js');
@@ -350,53 +325,6 @@ function replaceFrontmatter(raw, item, tier) {
350
325
  }
351
326
  return `---\n${frontmatter}\n---\n${match[2]}`;
352
327
  }
353
- const MODELS_HELP = `Usage: aiwg models <subcommand> [options]
354
-
355
- Subcommands:
356
- list List discovered agent and skill model policies
357
- audit Compile selected policies and report diagnostics
358
- resolve Resolve artifact policies, or a direct worker invocation
359
- validate Validate selected artifact policies (not worker configuration)
360
- sources Show the effective model catalog without network refresh
361
- refresh Refresh model discovery (may access the network)
362
- set-default <TIER> Write defaults.tier to models.json
363
- set --tier <TIER> Update selected agent/skill frontmatter; requires a selector or --all
364
- migrate Add canonical role/tier hints to legacy skills
365
-
366
- Artifact selectors and options:
367
- --agent <name> --skill <name> --glob <pattern> --role <role>
368
- --tier <tier> --framework <name> --all
369
- --provider <provider> Provider for artifact compilation (default: claude)
370
- --model <id> Explicit Grok Build model for artifact resolve
371
- --target <directory> Artifact/project root (default: current directory)
372
- --scope project|user set-default destination (default: project)
373
- --dry-run Preview mutations without writing
374
- --json Emit JSON
375
- --drift Include catalog drift for sources/refresh
376
- --url <url> Remote catalog URL for refresh
377
- --help, -h Show this reference
378
- TIER: economy | standard | premium | max-quality
379
-
380
- Direct worker resolution (plans an invocation; does not launch it):
381
- aiwg models resolve --role <role> --task <class> --group <group>
382
- --brief <file> --workdir <tree> --final-output <file>
383
- [--transport shell|mcp] [--format json|shell] [--authorize-premium]
384
- --role efficiency | coding | reasoning
385
- --task Task class from defaults.workerSelection.taskClasses
386
- Built-ins: inventory, implementation, architecture, review
387
- --group Group from parallelism.groups and the role's guidance
388
- --brief Absolute path to the task brief
389
- --workdir Absolute path to the worker tree
390
- --final-output Absolute path for the final result
391
- --transport Must match group execution transport (default: shell)
392
- --format JSON plan or POSIX shell command (default: json)
393
- --json Alias for --format json
394
- --authorize-premium Authorize premium for this resolution only
395
- Agents require explicit human confirmation of escalation
396
-
397
- Worker groups live in .aiwg/aiwg.config; role guidance lives in project-root models.json.
398
- Configure both, then run aiwg doctor --probe-workers --qualify.
399
- See docs/getting-started/worker-dispatch.md for a complete example.`;
400
328
  async function execute(ctx, services = { load: loadWorkerInputs }) {
401
329
  if (ctx.args.length === 0 || ctx.args[0] === 'help' || ctx.args.includes('--help') || ctx.args.includes('-h')) {
402
330
  print(MODELS_HELP, false);
@@ -406,6 +334,8 @@ async function execute(ctx, services = { load: loadWorkerInputs }) {
406
334
  if (ctx.args[0] === 'resolve' && ctx.args.slice(1).some(arg => /^--(?:task|group|brief|workdir|final-output|transport|format|authorize-premium)(?:=|$)/.test(arg))) {
407
335
  return resolveDirect(ctx, services);
408
336
  }
337
+ if (await handleWorkerAdoptionArgs(ctx.args, ctx.cwd, 'models', ctx.signal, ctx.dryRun))
338
+ return { exitCode: 0 };
409
339
  const parsed = parseArgs(ctx.args);
410
340
  if (parsed.subcommand === 'resolve') {
411
341
  const supported = new Set(['agent', 'skill', 'glob', 'role', 'tier', 'framework', 'all', 'target', 'provider', 'model', 'json', 'help', 'dry-run']);
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
3
3
  import { basename, dirname, isAbsolute, join, resolve, } from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
5
  import { providerScopeWarnings } from '../../sessions/coverage-preflight.js';
6
- 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, GROKBOT_ADAPTER_VERSION, GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS, GrokbotSessionAdapter, GROK_BUILD_ADAPTER_VERSION, GROK_BUILD_CLI_EXPORT_LOCATOR_CLASS, GrokBuildSessionAdapter, grokBuildList, grokBuildSearch, grokBuildExport, grokHeadlessSessionIdFromFile, MUSE_ADAPTER_VERSION, MuseSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, DEEPSEEK_HARNESS_ADAPTER_VERSION, DeepSeekHarnessSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, createSessionAdapterRegistry, coveragePreflight, expectedSessionProviders, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, buildSessionExportPlan, reverifySessionExportPlan, buildSessionAiwgFortemiIndexExport, recoverAiwgFortemiIndexFromFullV1Shard, embedAttachmentsInFullV1Shard, recoverEmbeddedAttachments, SESSION_EXPORT_PLAN_SCHEMA_VERSION, } from '../../sessions/index.js';
6
+ 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, GROKBOT_ADAPTER_VERSION, GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS, GrokbotSessionAdapter, GROK_BUILD_ADAPTER_VERSION, GROK_BUILD_CLI_EXPORT_LOCATOR_CLASS, GrokBuildSessionAdapter, grokBuildList, grokBuildSearch, grokBuildExport, grokHeadlessSessionIdFromFile, MUSE_ADAPTER_VERSION, MuseSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, DEEPSEEK_HARNESS_ADAPTER_VERSION, DeepSeekHarnessSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, countUniqueSessionsAcrossCatalogs, defaultDiscoveryManifestPath, defaultHostCatalogPath, HOST_CATALOG_WORKSPACE_ID, indexIdentitiesBySessionId, manifestNeedsHostCatalog, mergeCrossCatalogRows, mergeSessionLists, resolveHostReferences, summarizeSessionAnalyticsFacts, discoverWorkspaceHistories, createSessionAdapterRegistry, coveragePreflight, expectedSessionProviders, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, buildSessionExportPlan, reverifySessionExportPlan, buildSessionAiwgFortemiIndexExport, recoverAiwgFortemiIndexFromFullV1Shard, embedAttachmentsInFullV1Shard, recoverEmbeddedAttachments, SESSION_EXPORT_PLAN_SCHEMA_VERSION, } from '../../sessions/index.js';
7
7
  import { resolveImportExclusions } from '../../sessions/import-exclusions.js';
8
8
  import { resolveImportLimits } from '../../sessions/import-limits.js';
9
9
  import { classifyGrokbotBlobName } from '../../sessions/adapters/grokbot-locator.js';
@@ -227,18 +227,35 @@ async function executeCommand(ctx, args) {
227
227
  limit,
228
228
  cursor,
229
229
  });
230
+ // Account-scoped sessions live once in the host catalog; the
231
+ // workspace holds references (#3232). Read through them on the
232
+ // opening page so the workspace lists its referenced sessions.
233
+ // Cursor pages continue the local snapshot. Totals dedupe legacy
234
+ // copied rows against references, counting each session once.
235
+ const hostView = cursor
236
+ ? null
237
+ : readHostReferencedSessions(repository, { provider, tag: args.values.get('--tag') });
238
+ const items = hostView && hostView.sessions.length > 0
239
+ ? mergeSessionLists(result.items, hostView.sessions).slice(0, limit)
240
+ : result.items;
241
+ const total = hostView && hostView.references.length > 0
242
+ ? countUniqueSessionsAcrossCatalogs([{
243
+ identities: repository.listSessionIdentities(workspaceId),
244
+ references: hostView.resolvedReferences,
245
+ }])
246
+ : result.total;
230
247
  const coverage = {
231
248
  ...repository.getCoverage(workspaceId),
232
249
  catalogScope: catalogScope(ctx, args, repository, workspaceId),
233
250
  };
234
251
  const response = {
235
- items: result.items,
252
+ items,
236
253
  page: {
237
254
  limit,
238
255
  cursor: cursor ?? null,
239
256
  nextCursor: result.nextCursor,
240
257
  snapshotRowid: result.snapshotRowid,
241
- total: result.total,
258
+ total,
242
259
  },
243
260
  coverage,
244
261
  };
@@ -314,10 +331,15 @@ async function executeCommand(ctx, args) {
314
331
  const session = matches[0];
315
332
  if (!session)
316
333
  throw new CliError('SESSION_NOT_FOUND', `session not found: ${id}${scope.hint ? `; ${scope.hint}` : ''}`, EXIT.unavailable, { catalogScope: scope });
334
+ // Image references resolve against the blob store: available blobs
335
+ // report their reference, missing ones an explicit unavailable
336
+ // state. Bytes stay out of the response; fetch by digest (#3265).
337
+ const events = repository.listEvents(session.sessionId, session.workspaceId)
338
+ .map((event) => ({ ...event, images: repository.resolveEventImages(event) }));
317
339
  return ok(command, {
318
340
  session,
319
341
  tags: repository.listTags(session.sessionId, session.workspaceId),
320
- events: repository.listEvents(session.sessionId, session.workspaceId),
342
+ events,
321
343
  catalogScope: catalogScope(ctx, args, repository, session.workspaceId),
322
344
  });
323
345
  }
@@ -326,7 +348,8 @@ async function executeCommand(ctx, args) {
326
348
  const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
327
349
  const providerInput = args.values.get('--provider');
328
350
  const provider = providerInput ? assertSessionProviderId(providerInput) : undefined;
329
- const result = repository.search({
351
+ const limit = boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit');
352
+ const searchOptions = {
330
353
  query,
331
354
  workspaceId,
332
355
  providers: provider ? [provider] : undefined,
@@ -341,13 +364,24 @@ async function executeCommand(ctx, args) {
341
364
  sensitivity: args.values.get('--sensitivity'),
342
365
  extractionState: args.values.get('--extraction-state'),
343
366
  controlEvents: controlEventMode(args.values.get('--control-events')),
344
- limit: boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit'),
367
+ limit,
345
368
  cursor: args.values.get('--cursor'),
346
- });
369
+ };
370
+ const result = repository.search(searchOptions);
371
+ // Read through host references on the opening page so workspace
372
+ // search finds referenced account-scoped sessions. Each session
373
+ // counts once: host hits for sessions already present locally
374
+ // (legacy copied rows) are dropped by provider + native session.
375
+ const hostRead = searchOptions.cursor
376
+ ? null
377
+ : searchHostReferencedSessions(repository, searchOptions, provider);
378
+ const items = hostRead && hostRead.hits.length > 0
379
+ ? mergeCrossCatalogRows(result.items, hostRead.hits, indexIdentitiesBySessionId(repository.listSessionIdentities(workspaceId)), indexIdentitiesBySessionId(hostRead.hostIdentities), (hit) => [hit.provider, hit.eventId].join(' ')).slice(0, limit)
380
+ : result.items;
347
381
  return ok(command, {
348
- items: result.items,
382
+ items,
349
383
  catalogScope: catalogScope(ctx, args, repository, workspaceId),
350
- page: { limit: boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit'),
384
+ page: { limit,
351
385
  nextCursor: result.nextCursor },
352
386
  });
353
387
  }
@@ -355,14 +389,22 @@ async function executeCommand(ctx, args) {
355
389
  const view = requiredPositional(args, 0, 'analytics view');
356
390
  const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
357
391
  const query = analyticsQuery(args, workspaceId);
392
+ // Host references resolve to facts stored under the host catalog;
393
+ // each session counts once by provider + native session id. Without
394
+ // references the single-catalog path below is unchanged.
395
+ const mergedFacts = readWorkspaceAnalyticsFacts(repository, query);
358
396
  if (view === 'summary') {
359
397
  return ok(command, {
360
398
  view,
361
- summary: repository.analyticsSummary(query),
399
+ summary: mergedFacts
400
+ ? summarizeSessionAnalyticsFacts(mergedFacts, workspaceId)
401
+ : repository.analyticsSummary(query),
362
402
  });
363
403
  }
364
404
  const categories = analyticsCategories(view);
365
- const items = repository.listAnalyticsFacts({ ...query, categories });
405
+ const items = mergedFacts
406
+ ? mergedFacts.filter((fact) => categories.includes(fact.category))
407
+ : repository.listAnalyticsFacts({ ...query, categories });
366
408
  return ok(command, {
367
409
  analyticsVersion: '1.0.0',
368
410
  view,
@@ -1075,7 +1117,10 @@ async function exportBuild(ctx, args, repository, command) {
1075
1117
  ...output,
1076
1118
  bytesEmbedded: includeBytes && outputBytesByRegistrationId.has(output.registrationId),
1077
1119
  }));
1078
- const index = buildSessionAiwgFortemiIndexExport(plan.workspaceId, sessions, eventsBySessionId, outputs, catalogTagsBySessionId);
1120
+ const index = buildSessionAiwgFortemiIndexExport(plan.workspaceId, sessions, eventsBySessionId, outputs, catalogTagsBySessionId,
1121
+ // Resolve image references against the live catalog: available blobs
1122
+ // export as references, missing ones as explicit unavailable (#3265).
1123
+ (event) => repository.resolveEventImages(event));
1079
1124
  // aiwgFortemiIndexToKnowledgeShardWithReport targets the declared
1080
1125
  // full-v1/2.0.0 archive contract and validates losslessness -- unlike the
1081
1126
  // plain aiwgFortemiIndexToKnowledgeShard (core-v1), which silently embeds
@@ -1355,18 +1400,33 @@ async function importDiscovered(ctx, args) {
1355
1400
  ...(carryPath ? { carryForward: redactSourceLocator(carryPath) } : {}),
1356
1401
  });
1357
1402
  }
1358
- const lease = await acquireImportLease(databasePath(ctx, args), sha256(['discovered-import', manifest.manifestId, manifest.workspaceId].join('\0')), {
1359
- waitMs: boundedInteger(args.values.get('--lock-wait-ms'), 5_000, 0, 300_000, '--lock-wait-ms'),
1360
- });
1403
+ const waitMs = boundedInteger(args.values.get('--lock-wait-ms'), 5_000, 0, 300_000, '--lock-wait-ms');
1404
+ const lease = await acquireImportLease(databasePath(ctx, args), sha256(['discovered-import', manifest.manifestId, manifest.workspaceId].join('\0')), { waitMs });
1405
+ let hostLease;
1361
1406
  try {
1362
1407
  const previous = carryPath ? new SessionRepository(carryPath, { readonly: true }) : undefined;
1363
1408
  let repository;
1409
+ let hostRepository;
1364
1410
  try {
1365
1411
  repository = openRepository(ctx, args);
1412
+ // Account-scoped providers (Grok Bot) import once into the host
1413
+ // catalog; the workspace catalog records a reference (#3232). The
1414
+ // host catalog is only opened when the manifest actually holds an
1415
+ // account-scoped source.
1416
+ const hostCatalogPath = defaultHostCatalogPath();
1417
+ if (manifestNeedsHostCatalog(manifest.sources)) {
1418
+ mkdirSync(dirname(hostCatalogPath), { recursive: true, mode: 0o700 });
1419
+ // The workspace lease does not cover the shared host catalog; serialize
1420
+ // host writers the same way the hourly refresh does.
1421
+ hostLease = await acquireImportLease(hostCatalogPath, `host-import-${process.pid}`, { waitMs });
1422
+ hostRepository = new SessionRepository(hostCatalogPath);
1423
+ }
1366
1424
  const interruptedRuns = repository.interruptRunningBatchImports();
1367
1425
  const receipt = await importDiscoveryManifest({
1368
1426
  manifest,
1369
1427
  repository,
1428
+ hostRepository,
1429
+ hostCatalogPath,
1370
1430
  carryForward: previous,
1371
1431
  limits,
1372
1432
  exclude,
@@ -1388,10 +1448,12 @@ async function importDiscovered(ctx, args) {
1388
1448
  }
1389
1449
  finally {
1390
1450
  repository?.close();
1451
+ hostRepository?.close();
1391
1452
  previous?.close();
1392
1453
  }
1393
1454
  }
1394
1455
  finally {
1456
+ await hostLease?.release();
1395
1457
  await lease.release();
1396
1458
  }
1397
1459
  }
@@ -1718,6 +1780,127 @@ function catalogScope(ctx, args, repository, workspaceId) {
1718
1780
  : null,
1719
1781
  };
1720
1782
  }
1783
+ /**
1784
+ * Host-catalog read-through for account-scoped sessions (#3232). The
1785
+ * workspace catalog holds references instead of copies; each reference
1786
+ * records its host catalog path. Every referenced catalog opens read-only
1787
+ * and closes before returning. Unreadable catalogs contribute nothing, so
1788
+ * local rows still list when the host catalog is missing.
1789
+ */
1790
+ function eachReferencedHostCatalog(repository, provider, read) {
1791
+ const references = repository.listHostSessionReferences()
1792
+ .filter((reference) => !provider || reference.provider === provider);
1793
+ if (references.length === 0)
1794
+ return [];
1795
+ const byCatalog = new Map();
1796
+ for (const reference of references) {
1797
+ const group = byCatalog.get(reference.hostCatalog);
1798
+ if (group)
1799
+ group.push(reference);
1800
+ else
1801
+ byCatalog.set(reference.hostCatalog, [reference]);
1802
+ }
1803
+ const reads = [];
1804
+ for (const [catalogPath, group] of byCatalog) {
1805
+ let host;
1806
+ try {
1807
+ host = new SessionRepository(catalogPath, { readonly: true });
1808
+ }
1809
+ catch {
1810
+ continue;
1811
+ }
1812
+ try {
1813
+ reads.push({ references: group, value: read(host, group) });
1814
+ }
1815
+ finally {
1816
+ host.close();
1817
+ }
1818
+ }
1819
+ return reads;
1820
+ }
1821
+ function readHostReferencedSessions(repository, filters) {
1822
+ const reads = eachReferencedHostCatalog(repository, filters.provider, (host, group) => {
1823
+ const page = host.listSessions({
1824
+ provider: filters.provider,
1825
+ workspaceId: HOST_CATALOG_WORKSPACE_ID,
1826
+ limit: group.length,
1827
+ });
1828
+ const sessions = resolveHostReferences(group, page.items)
1829
+ .filter((session) => !filters.tag || host.listTags(session.sessionId).includes(filters.tag));
1830
+ const resolvedSourceIds = new Set(sessions.map((session) => session.sourceId));
1831
+ return {
1832
+ sessions,
1833
+ resolvedReferences: group.filter((reference) => resolvedSourceIds.has(reference.sourceId)),
1834
+ };
1835
+ });
1836
+ return {
1837
+ references: reads.flatMap((read) => read.references),
1838
+ resolvedReferences: reads.flatMap((read) => read.value.resolvedReferences),
1839
+ sessions: reads.flatMap((read) => read.value.sessions),
1840
+ };
1841
+ }
1842
+ function searchHostReferencedSessions(repository, searchOptions, provider) {
1843
+ const reads = eachReferencedHostCatalog(repository, provider, (host) => {
1844
+ const result = host.search({
1845
+ ...searchOptions,
1846
+ workspaceId: HOST_CATALOG_WORKSPACE_ID,
1847
+ cursor: undefined,
1848
+ });
1849
+ return {
1850
+ hits: result.items,
1851
+ hostIdentities: host.listSessionIdentities(HOST_CATALOG_WORKSPACE_ID),
1852
+ };
1853
+ });
1854
+ return {
1855
+ hits: reads.flatMap((read) => read.value.hits),
1856
+ hostIdentities: reads.flatMap((read) => read.value.hostIdentities),
1857
+ };
1858
+ }
1859
+ function readHostReferencedAnalyticsFacts(repository, query) {
1860
+ // query.sessionId is a workspace-local internal id; host rows carry host
1861
+ // stable ids, so resolve it to a provider + native session key first.
1862
+ let sessionKey = null;
1863
+ if (query.sessionId) {
1864
+ const session = repository.getSession(query.sessionId, query.workspaceId);
1865
+ if (!session)
1866
+ return { facts: [], hostIdentities: [] };
1867
+ sessionKey = `${session.provider} ${session.nativeSessionId}`;
1868
+ }
1869
+ const reads = eachReferencedHostCatalog(repository, query.provider, (host, group) => {
1870
+ const wanted = new Set(group.map((reference) => `${reference.provider} ${reference.nativeSessionId}`));
1871
+ const identities = host.listSessionIdentities(HOST_CATALOG_WORKSPACE_ID);
1872
+ const hostSessionIds = new Set(identities
1873
+ .filter((identity) => wanted.has(`${identity.provider} ${identity.nativeSessionId}`))
1874
+ .map((identity) => identity.sessionId));
1875
+ const { sessionId: _localSessionId, ...hostQuery } = query;
1876
+ const facts = host.listAnalyticsFacts({ ...hostQuery, workspaceId: HOST_CATALOG_WORKSPACE_ID })
1877
+ .filter((fact) => hostSessionIds.has(fact.sessionId)
1878
+ && (!sessionKey || `${fact.provider} ${hostSessionKey(fact.sessionId, identities)}` === sessionKey));
1879
+ return { facts, identities };
1880
+ });
1881
+ return {
1882
+ facts: reads.flatMap((read) => read.value.facts),
1883
+ hostIdentities: reads.flatMap((read) => read.value.identities),
1884
+ };
1885
+ }
1886
+ function hostSessionKey(sessionId, identities) {
1887
+ return identities.find((identity) => identity.sessionId === sessionId)?.nativeSessionId ?? null;
1888
+ }
1889
+ /**
1890
+ * Workspace analytics facts with host references resolved. Returns null
1891
+ * when the workspace holds no references so callers keep the exact
1892
+ * single-catalog query path (SQL limit/filter semantics unchanged).
1893
+ */
1894
+ function readWorkspaceAnalyticsFacts(repository, query) {
1895
+ const references = repository.listHostSessionReferences()
1896
+ .filter((reference) => !query.provider || reference.provider === query.provider);
1897
+ if (references.length === 0)
1898
+ return null;
1899
+ const hostRead = readHostReferencedAnalyticsFacts(repository, query);
1900
+ if (hostRead.facts.length === 0)
1901
+ return null;
1902
+ return mergeCrossCatalogRows(repository.listAnalyticsFacts(query), hostRead.facts, indexIdentitiesBySessionId(repository.listSessionIdentities(query.workspaceId)), indexIdentitiesBySessionId(hostRead.hostIdentities), (fact) => `${fact.provider} ${fact.factId}`);
1903
+ }
1721
1904
  async function configuredImportLimits(ctx, args, workspace) {
1722
1905
  const selectedWorkspace = workspace ?? (args.values.has('--workspace')
1723
1906
  ? resolve(ctx.cwd, args.values.get('--workspace')) : ctx.cwd);
@@ -0,0 +1,72 @@
1
+ // Select and persist an optional bundled addon memory profile.
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import * as ui from '../ui.js';
5
+ export async function selectAddonProfile(options) {
6
+ const { addonSource, framework, target, args, dryRunAddon, bulkIntent, signal } = options;
7
+ try {
8
+ const profileManifestPath = path.join(addonSource, 'manifest.json');
9
+ const profileManifestContent = await fs.readFile(profileManifestPath, 'utf-8');
10
+ const profileManifest = JSON.parse(profileManifestContent);
11
+ const topology = profileManifest.memory?.topology;
12
+ const templates = profileManifest.templates;
13
+ if (topology && templates && templates.length > 1) {
14
+ const profileIdx = args.findIndex(a => a === '--profile');
15
+ let selectedProfile;
16
+ if (profileIdx >= 0 && args[profileIdx + 1]) {
17
+ // Explicit --profile flag
18
+ selectedProfile = args[profileIdx + 1];
19
+ const templateFile = templates.find((t) => t.replace('.md', '') === selectedProfile || t === selectedProfile);
20
+ if (!templateFile) {
21
+ ui.warn(`Unknown profile "${selectedProfile}". Available: ${templates.map((t) => t.replace('.md', '')).join(', ')}`);
22
+ selectedProfile = undefined;
23
+ }
24
+ }
25
+ else if (bulkIntent || !process.stdin.isTTY) {
26
+ // Bulk/automation or non-TTY: silently pick 'generic' default.
27
+ // The profile picker is annoying during `aiwg use all`.
28
+ selectedProfile = 'generic';
29
+ }
30
+ else if (process.stdin.isTTY) {
31
+ // Interactive profile selection via the shared `listSelect` helper
32
+ // (POC for spike #926). One call renders the option list, handles
33
+ // number-or-name matching, threads `signal` for Ctrl-C
34
+ // cancellation, and resolves to the fallback on timeout or empty
35
+ // input. The hand-rolled parse-and-branch that used to live here
36
+ // is now a one-liner.
37
+ const { createPromptInterface, listSelect } = await import('../prompt-utils.js');
38
+ ui.blank();
39
+ ui.header(' Select a topology profile:');
40
+ const templateNames = templates.map((t) => t.replace('.md', ''));
41
+ const options = templateNames.map((name) => ({
42
+ label: name === 'generic' ? `${name} (default)` : name,
43
+ value: name,
44
+ }));
45
+ const rl = createPromptInterface();
46
+ try {
47
+ selectedProfile = await listSelect(rl, ' Enter number or name [generic]: ', options, 'generic', signal);
48
+ }
49
+ finally {
50
+ rl.close();
51
+ }
52
+ }
53
+ // Write profile config to project namespace
54
+ if (selectedProfile && !dryRunAddon) {
55
+ const namespace = topology.namespace || `.aiwg/${framework}`;
56
+ const configDir = path.join(target, namespace);
57
+ await fs.mkdir(configDir, { recursive: true });
58
+ const profileConfig = {
59
+ profile: selectedProfile,
60
+ pageTemplate: `templates/${selectedProfile}.md`,
61
+ selectedAt: new Date().toISOString(),
62
+ };
63
+ await fs.writeFile(path.join(configDir, 'config.json'), JSON.stringify(profileConfig, null, 2) + '\n');
64
+ ui.success(`Profile "${selectedProfile}" selected → ${namespace}/config.json`);
65
+ }
66
+ }
67
+ }
68
+ catch {
69
+ // Profile selection is optional — don't fail deployment
70
+ }
71
+ }
72
+ //# sourceMappingURL=use-addon-profile.js.map