@aiwg/cli 2026.10.10 → 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 (34) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +78 -14
  2. package/dist/src/cli/handlers/sessions.js +198 -15
  3. package/dist/src/cli/handlers/use.js +12 -4
  4. package/dist/src/config/worker-context.js +337 -0
  5. package/dist/src/mcp/tools/worker-invoke.mjs +108 -10
  6. package/dist/src/models/model-capabilities.v1.json +78 -14
  7. package/dist/src/models/provider-policy.js +7 -0
  8. package/dist/src/policy/authorization.js +29 -1
  9. package/dist/src/process/aiwg-probe-command.d.mts +15 -0
  10. package/dist/src/process/aiwg-probe-command.mjs +37 -0
  11. package/dist/src/sessions/adapters/grokbot.js +7 -0
  12. package/dist/src/sessions/batch-import.js +34 -6
  13. package/dist/src/sessions/contracts.js +19 -0
  14. package/dist/src/sessions/coverage-preflight.js +7 -1
  15. package/dist/src/sessions/fortemi-export-mapping.js +33 -3
  16. package/dist/src/sessions/host-catalog.js +157 -0
  17. package/dist/src/sessions/image-blobs.js +141 -0
  18. package/dist/src/sessions/importer.js +23 -3
  19. package/dist/src/sessions/index.js +2 -0
  20. package/dist/src/sessions/refresh.js +25 -2
  21. package/dist/src/sessions/repository.js +164 -22
  22. package/dist/src/sessions/workspace-discovery.js +5 -2
  23. package/package.json +7 -1
  24. package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
  25. package/tools/ralph-external/lib/worker-context.mjs +237 -0
  26. package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
  27. package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
  28. package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
  29. package/tools/ralph-external/lib/worker-isolation.mjs +17 -29
  30. package/tools/ralph-external/lib/worker-launch-cli.mjs +3 -1
  31. package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
  32. package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
  33. package/tools/ralph-external/lib/worker-termination.mjs +82 -0
  34. package/tools/ralph-external/session-launcher.mjs +51 -96
@@ -4,6 +4,9 @@
4
4
  "verifiedAt": "2026-07-20",
5
5
  "providers": {
6
6
  "antigravity": {
7
+ "sessionEnvAllowlist": [
8
+ "GEMINI_API_KEY"
9
+ ],
7
10
  "agent": "native",
8
11
  "skill": "native",
9
12
  "globalChild": "inherited",
@@ -19,7 +22,7 @@
19
22
  "invalidPinFallback": "Provider validation/error; never assume fallback",
20
23
  "configTarget": "agy launch arguments; custom-agent model tiers are not projected from generic AIWG model ids",
21
24
  "artifactFormat": "YAML frontmatter plus Markdown; runtime --model and --effort flags",
22
- "verification": "Pinned offline contract (--help/--version only); authenticated model selection was not executed",
25
+ "verification": "Pinned offline contract (--help/--version only); authenticated model selection was not executed. Session credential evidence: $AIWG_ROOT/docs/cli/sensitive-values.md#provider-recipient-exceptions (#3624).",
23
26
  "sourceUrl": "https://antigravity.google/docs/cli/subagents/",
24
27
  "verifiedAt": "2026-09-04",
25
28
  "workerHealthAdapter": {
@@ -154,7 +157,7 @@
154
157
  "Native MCP/client, MSP/ACP, gateway and conversation capabilities do not establish worker authority.",
155
158
  "--dangerously-skip-permissions and --remote-control are never part of the direct profile; neither was ever shipped as a new argv-prompt path.",
156
159
  "A soft-denied tool permission request (exit 0, stderr notice naming the allow-rule needed) is NOT the same as success; its exact stderr wording is not evidenced from help or changelog, so this is an open, honestly-marked detection gap rather than an invented check (tools/ralph-external/lib/antigravity-adapter.mjs ANTIGRAVITY_SOFT_DENY_RISK_NOTE).",
157
- "GEMINI_API_KEY in envAllowlist is a strings-sourced GUESS, not confirmed to authenticate this CLI. Live fix-round-1 probes (D/E, no login present) show a bare run stops at \"Error: authentication required. Run 'agy' to log in, then retry.\" — this is an interactive Google-account login, not a documented env-key or OpenRouter-style flag. The manual smoke run is BLOCKED until an operator completes an interactive `agy` login on the target host.",
160
+ "GEMINI_API_KEY is documented at https://antigravity.google/docs/install/ (reviewed 2026-10-26): settings.json must also set modelProvider to gemini; the key alone has no effect. Earlier 1.3.3 probes only exercised missing account login. This documentation confirms the credential name, not authenticated execution or qualification of this version-bound worker profile.",
158
161
  "The 1.3.3 --effort enum (low|medium|high|xhigh|max) supersedes the stale 1.1.26-pinned three-value set in tools/providers/antigravity-transport.mjs (a separate, unedited legacy transport used by the MCP/doctor bridge, not this direct-shell profile).",
159
162
  "Flag validation happens BEFORE auth: live probe F, `agy --print=x --input-format bogus --output-format stream-json`, returned \"Error: unknown --input-format \\\"bogus\\\" (want text or stream-json)\" without reaching the auth stop, confirming --input-format is validated against exactly {text, stream-json} independent of login state.",
160
163
  "The brief reaches agy through adapter-frame: the installed buildWorkerStdinFrame hook supplies exactly one NDJSON user message and EOF. The stdin control stays unqualified pending one authenticated smoke run."
@@ -865,6 +868,9 @@
865
868
  }
866
869
  },
867
870
  "cursor": {
871
+ "sessionEnvAllowlist": [
872
+ "CURSOR_API_KEY"
873
+ ],
868
874
  "agent": "native",
869
875
  "skill": "unsupported",
870
876
  "globalChild": "inherited",
@@ -873,7 +879,7 @@
873
879
  "invalidPinFallback": "Plan/admin fallback may apply; record observed result",
874
880
  "configTarget": ".cursor/agents/*.md",
875
881
  "artifactFormat": "YAML frontmatter plus Markdown",
876
- "verification": "Schema fixture and entitled Cursor invocation",
882
+ "verification": "Schema fixture and entitled Cursor invocation. Session credential evidence: $AIWG_ROOT/docs/cli/sensitive-values.md#provider-recipient-exceptions (#3624).",
877
883
  "sourceUrl": "https://cursor.com/docs/subagents",
878
884
  "verifiedAt": "2026-07-20",
879
885
  "workerHealthAdapter": {
@@ -1465,6 +1471,9 @@
1465
1471
  }
1466
1472
  },
1467
1473
  "warp": {
1474
+ "sessionEnvAllowlist": [
1475
+ "WARP_API_KEY"
1476
+ ],
1468
1477
  "agent": "global-only",
1469
1478
  "skill": "unsupported",
1470
1479
  "globalChild": "native",
@@ -1473,7 +1482,7 @@
1473
1482
  "invalidPinFallback": "Profile availability dependent",
1474
1483
  "configTarget": "Warp agent profile or run configuration",
1475
1484
  "artifactFormat": "Saved profile / run control plane",
1476
- "verification": "Inspect active profile and run metadata",
1485
+ "verification": "Inspect active profile and run metadata. Session credential evidence: $AIWG_ROOT/docs/cli/sensitive-values.md#provider-recipient-exceptions (#3624).",
1477
1486
  "sourceUrl": "https://docs.warp.dev/agent-platform/capabilities/agent-profiles-permissions",
1478
1487
  "verifiedAt": "2026-07-20",
1479
1488
  "workerHealthAdapter": {
@@ -2284,6 +2293,7 @@
2284
2293
  }
2285
2294
  },
2286
2295
  "windsurf": {
2296
+ "sessionEnvAllowlist": [],
2287
2297
  "agent": "unsupported",
2288
2298
  "skill": "unsupported",
2289
2299
  "globalChild": "inherited",
@@ -2292,7 +2302,7 @@
2292
2302
  "invalidPinFallback": "No portable custom-child selector",
2293
2303
  "configTarget": "No supported portable artifact field",
2294
2304
  "artifactFormat": "Informational policy only",
2295
- "verification": "Assert unsupported fields are omitted",
2305
+ "verification": "Assert unsupported fields are omitted. Session environment is explicitly empty: desktop/cloud login owns authentication; no CLI credential variable is evidenced. Coverage-only: no worker execution or qualified worker adapter. See $AIWG_ROOT/docs/cli/sensitive-values.md#provider-recipient-exceptions (#3624).",
2296
2306
  "sourceUrl": "https://docs.devin.ai/desktop/cascade/skills",
2297
2307
  "verifiedAt": "2026-07-20",
2298
2308
  "workerHealthCoverage": {
@@ -2312,6 +2322,22 @@
2312
2322
  }
2313
2323
  },
2314
2324
  "openclaw": {
2325
+ "sessionEnvAllowlist": [
2326
+ "OPENAI_API_KEY",
2327
+ "ANTHROPIC_API_KEY",
2328
+ "ANTHROPIC_OAUTH_TOKEN",
2329
+ "GEMINI_API_KEY",
2330
+ "GOOGLE_API_KEY",
2331
+ "OPENROUTER_API_KEY",
2332
+ "MISTRAL_API_KEY",
2333
+ "XAI_API_KEY",
2334
+ "OPENCLAW_HOME",
2335
+ "OPENCLAW_STATE_DIR",
2336
+ "OPENCLAW_CONFIG_PATH",
2337
+ "OPENCLAW_GATEWAY_URL",
2338
+ "OPENCLAW_GATEWAY_TOKEN",
2339
+ "OPENCLAW_GATEWAY_PASSWORD"
2340
+ ],
2315
2341
  "agent": "native",
2316
2342
  "skill": "unsupported",
2317
2343
  "globalChild": "native",
@@ -2320,7 +2346,7 @@
2320
2346
  "invalidPinFallback": "Provider/account fallback must be observed",
2321
2347
  "configTarget": "OpenClaw subagent configuration",
2322
2348
  "artifactFormat": "JSON/JSONC runtime configuration",
2323
- "verification": "Inspect resolved subagent metadata",
2349
+ "verification": "Inspect resolved subagent metadata. Session credential evidence: $AIWG_ROOT/docs/cli/sensitive-values.md#provider-recipient-exceptions (#3624).",
2324
2350
  "sourceUrl": "https://docs.openclaw.ai/tools/subagents",
2325
2351
  "verifiedAt": "2026-07-20",
2326
2352
  "workerHealthAdapter": {
@@ -2508,6 +2534,7 @@
2508
2534
  }
2509
2535
  },
2510
2536
  "grokbot": {
2537
+ "sessionEnvAllowlist": [],
2511
2538
  "agent": "unsupported",
2512
2539
  "skill": "unsupported",
2513
2540
  "globalChild": "informational",
@@ -2516,7 +2543,7 @@
2516
2543
  "invalidPinFallback": "Report unsupported; do not fabricate xAI/Grok model pins as native",
2517
2544
  "configTarget": "Grok Bot has no verified AIWG-owned model config path",
2518
2545
  "artifactFormat": "Informational only",
2519
- "verification": "Confirm steward models --route --provider grokbot emits unsupported/informational",
2546
+ "verification": "Confirm steward models --route --provider grokbot emits unsupported/informational. Session environment is explicitly empty: desktop/cloud login owns authentication; no CLI credential variable is evidenced. Coverage-only: no worker execution or qualified worker adapter. See $AIWG_ROOT/docs/cli/sensitive-values.md#provider-recipient-exceptions (#3624).",
2520
2547
  "sourceUrl": "https://github.com/jmagly/aiwg/blob/main/docs/architecture/adr-grokbot-provider-target.md",
2521
2548
  "verifiedAt": "2026-09-15",
2522
2549
  "workerHealthCoverage": {
@@ -2536,6 +2563,10 @@
2536
2563
  }
2537
2564
  },
2538
2565
  "grok-build": {
2566
+ "sessionEnvAllowlist": [
2567
+ "XAI_API_KEY",
2568
+ "GROK_HOME"
2569
+ ],
2539
2570
  "agent": "inherited",
2540
2571
  "skill": "inherited",
2541
2572
  "globalChild": "native",
@@ -2544,7 +2575,7 @@
2544
2575
  "invalidPinFallback": "Report the unavailable alias; use the sole discovered model only when exactly one is available",
2545
2576
  "configTarget": "$GROK_HOME/config.toml [models] and [model.<alias>] (read-only discovery)",
2546
2577
  "artifactFormat": "Grok Build model alias",
2547
- "verification": "grok inspect --json plus bounded parsing of model fields from reported config layers",
2578
+ "verification": "grok inspect --json plus bounded parsing of model fields from reported config layers. Session credential evidence: $AIWG_ROOT/docs/cli/sensitive-values.md#provider-recipient-exceptions (#3624).",
2548
2579
  "sourceUrl": "https://docs.x.ai/build/settings",
2549
2580
  "verifiedAt": "2026-09-21",
2550
2581
  "workerHealthAdapter": {
@@ -2736,6 +2767,25 @@
2736
2767
  }
2737
2768
  },
2738
2769
  "hermes": {
2770
+ "sessionEnvAllowlist": [
2771
+ "OPENAI_API_KEY",
2772
+ "ANTHROPIC_API_KEY",
2773
+ "ANTHROPIC_TOKEN",
2774
+ "CLAUDE_CODE_OAUTH_TOKEN",
2775
+ "GEMINI_API_KEY",
2776
+ "GOOGLE_API_KEY",
2777
+ "OPENROUTER_API_KEY",
2778
+ "GROQ_API_KEY",
2779
+ "MISTRAL_API_KEY",
2780
+ "XAI_API_KEY",
2781
+ "DEEPSEEK_API_KEY",
2782
+ "OPENAI_BASE_URL",
2783
+ "ANTHROPIC_BASE_URL",
2784
+ "OPENROUTER_BASE_URL",
2785
+ "CUSTOM_BASE_URL",
2786
+ "HERMES_HOME",
2787
+ "HERMES_INFERENCE_PROVIDER"
2788
+ ],
2739
2789
  "agent": "global-only",
2740
2790
  "skill": "unsupported",
2741
2791
  "globalChild": "native",
@@ -2744,7 +2794,7 @@
2744
2794
  "invalidPinFallback": "Provider resolution is global; report actual model",
2745
2795
  "configTarget": "Hermes delegation configuration",
2746
2796
  "artifactFormat": "Global CLI/config setting",
2747
- "verification": "Inspect Hermes delegation configuration",
2797
+ "verification": "Inspect Hermes delegation configuration. Session credential evidence: $AIWG_ROOT/docs/cli/sensitive-values.md#provider-recipient-exceptions (#3624).",
2748
2798
  "sourceUrl": "https://hermes-agent.nousresearch.com/docs/user-guide/features/delegation",
2749
2799
  "verifiedAt": "2026-07-20",
2750
2800
  "workerHealthAdapter": {
@@ -2927,6 +2977,19 @@
2927
2977
  }
2928
2978
  },
2929
2979
  "openhuman": {
2980
+ "sessionEnvAllowlist": [
2981
+ "OPENHUMAN_CORE_TOKEN",
2982
+ "OPENHUMAN_CORE_RPC_URL",
2983
+ "OPENHUMAN_CORE_HOST",
2984
+ "OPENHUMAN_CORE_PORT",
2985
+ "OPENHUMAN_WORKSPACE",
2986
+ "OPENHUMAN_DOTENV_PATH",
2987
+ "OPENHUMAN_KEYRING_MASTER_KEY",
2988
+ "OPENHUMAN_KEYRING_MASTER_KEY_FILE",
2989
+ "OPENHUMAN_APP_ENV",
2990
+ "BACKEND_URL",
2991
+ "OPENHUMAN_MODEL"
2992
+ ],
2930
2993
  "agent": "compiled",
2931
2994
  "skill": "unsupported",
2932
2995
  "globalChild": "inherited",
@@ -2935,7 +2998,7 @@
2935
2998
  "invalidPinFallback": "Exact pins validate; semantic hints route dynamically",
2936
2999
  "configTarget": "OpenHuman agent TOML",
2937
3000
  "artifactFormat": "Standalone TOML agent definition",
2938
- "verification": "Parse against OpenHuman definition schema",
3001
+ "verification": "Parse against OpenHuman definition schema. Session credential evidence: $AIWG_ROOT/docs/cli/sensitive-values.md#provider-recipient-exceptions (#3624).",
2939
3002
  "sourceUrl": "https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/harness/definition.rs",
2940
3003
  "verifiedAt": "2026-07-20",
2941
3004
  "workerHealthAdapter": {
@@ -3154,6 +3217,7 @@
3154
3217
  "${effort}",
3155
3218
  "--workspace",
3156
3219
  "${workspace}",
3220
+ "--trust-workspace",
3157
3221
  "--approval-mode",
3158
3222
  "never",
3159
3223
  "--approval-judge",
@@ -3246,8 +3310,8 @@
3246
3310
  },
3247
3311
  "trust": {
3248
3312
  "support": "unqualified",
3249
- "mapping": "[]",
3250
- "evidence": "Installed muse exec --help (Muse Code 1.4.12, 2026-10-23); bounded renderer checked on operator qualification; no backend attestation."
3313
+ "mapping": "[\"--trust-workspace\"]",
3314
+ "evidence": "Installed muse exec --help (Muse Code 1.4.12, 2026-10-23); bounded renderer checked on operator qualification; no backend attestation. Tier 0 workspaces are conductor-prepared worker trees the conductor owns, so the profile binds --trust-workspace by default (#3685: untrusted Muse loads project_sources=0 with user skills only and empty hook admission; trusted loads project_sources=1, project skills and hooks)."
3251
3315
  },
3252
3316
  "children": {
3253
3317
  "support": "unqualified",
@@ -3268,13 +3332,13 @@
3268
3332
  "Live qualification is an open S1 blocker; installed help and synthetic/echo mechanics are not paid inference qualification.",
3269
3333
  "Configured-model events provide runtime-configured identity only, never backend attestation.",
3270
3334
  "Contributor data policy is explicit; shared policy denies contributor models and never silently selects the CLI default.",
3271
- "Workspace trust defaults off; only a host-qualified trust mapping may load rules/skills. Approval-mode never and approval-judge off retain the sandbox. Muse requires a qualified external boundary.",
3335
+ "Workspace trust is bound on for conductor-prepared worker trees (the conductor owns the worktree), so project rules/skills/hooks load; only a host-qualified trust mapping may change it. Approval-mode never and approval-judge off retain the sandbox. Muse requires a qualified external boundary.",
3272
3336
  "Host qualification must bind an absolute output-schema mapping and include that schema in runtimeFiles.",
3273
3337
  "Muse JSONL strict normalization needs the shared strict-parser hook; the MCP bridge must consume the selected provider parser. MSP is not MCP.",
3274
3338
  "Semantic step/tool completion grammar and native session-log layout remain unavailable; unrelated logs, deltas and CPU cannot establish progress.",
3275
3339
  "Shared version gate: installation requires the raw trimmed banner Muse Code 1.4.4 (1.4.4-R5419.1), but semver selection requires 1.4.4-R5419.1. No normalization exists; keep unqualified until the shared owner binds raw and normalized versions.",
3276
3340
  "Operator qualification approves the installed direct renderer, not authenticated inference or backend model identity.",
3277
- "Workspace trust defaults off. Only never approval is supported. Explicit unsupported controls block dispatch.",
3341
+ "Workspace trust is on for conductor-prepared worker trees. Only never approval is supported. Explicit unsupported controls block dispatch.",
3278
3342
  "Self-delegation must be explicitly allowed by policy; no native child-denial flag is assumed."
3279
3343
  ],
3280
3344
  "health": {
@@ -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);
@@ -29,7 +29,7 @@ import { loadCliCommandsContribution, registerCliCommands, registerHooks, } from
29
29
  import { translateSkillsToCommands, providerNeedsCommands } from '../../plugin/skill-command-translator.js';
30
30
  import * as ui from '../ui.js';
31
31
  import { readAiwgConfig, writeAiwgConfig, updateInstalled, hashManifest, emptyConfig, getProjectDir, getConfigPath } from '../../config/aiwg-config.js';
32
- import { appendGitignore } from '../../config/gitignore.js';
32
+ import { AIWG_RUNTIME_PATTERNS, appendGitignore } from '../../config/gitignore.js';
33
33
  import { getLogger } from '../log.js';
34
34
  import { installCockpit } from './cockpit.js';
35
35
  import { initHandler } from './init.js';
@@ -501,11 +501,19 @@ function getProviderKernelSkillsPath(provider) {
501
501
  const PROVIDER_GENERATED_GITIGNORE_PATTERNS = {
502
502
  codex: ['.codex/', '.agents/'],
503
503
  };
504
- async function ensureProviderGeneratedDirsIgnored(projectRoot, provider, opts) {
504
+ /**
505
+ * Patterns `aiwg use` ensures in the project's .gitignore on every deploy:
506
+ * the AIWG runtime subdirs doctor expects, plus any provider-generated dirs.
507
+ * Append-only via appendGitignore — operator content is never rewritten (#3635).
508
+ */
509
+ export function gitignorePatternsForDeploy(provider) {
510
+ return [...AIWG_RUNTIME_PATTERNS, ...(PROVIDER_GENERATED_GITIGNORE_PATTERNS[provider] ?? [])];
511
+ }
512
+ export async function ensureProviderGeneratedDirsIgnored(projectRoot, provider, opts) {
505
513
  if (opts.dryRun)
506
514
  return;
507
- const patterns = PROVIDER_GENERATED_GITIGNORE_PATTERNS[provider];
508
- if (!patterns || patterns.length === 0)
515
+ const patterns = gitignorePatternsForDeploy(provider);
516
+ if (patterns.length === 0)
509
517
  return;
510
518
  try {
511
519
  const result = await appendGitignore(projectRoot, patterns);