@staix/agent-hub 0.12.43 → 0.12.45

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 (180) hide show
  1. package/CHANGELOG.md +38 -3
  2. package/README.md +3 -3
  3. package/THIRD_PARTY_NOTICES.md +29 -0
  4. package/docs/adding-an-ai-tool.md +228 -0
  5. package/docs/agent-notes/README.md +14 -0
  6. package/docs/agent-notes/adapters.md +61 -42
  7. package/docs/agent-notes/budget.md +2 -2
  8. package/docs/agent-notes/daemon.md +16 -10
  9. package/docs/agent-notes/local-worker.md +4 -3
  10. package/docs/agent-notes/tasks.md +13 -6
  11. package/docs/agent-notes/tests.md +9 -7
  12. package/docs/operations.md +163 -12
  13. package/docs/quickstart.md +10 -2
  14. package/docs/sdlc.md +26 -8
  15. package/docs/security.md +11 -6
  16. package/docs/specs/2026-09-19-agent-hub-design.md +273 -8
  17. package/docs/verification/2026-10-21-0.12.44.md +54 -0
  18. package/docs/verification/2026-10-21-469-r1-dashboard-tokens.md +43 -0
  19. package/docs/verification/2026-10-21-469-r2-dashboard-shell.md +54 -0
  20. package/docs/verification/2026-10-21-469-r3-dashboard-board.md +51 -0
  21. package/docs/verification/2026-10-21-469-r4-dashboard-views.md +77 -0
  22. package/docs/verification/2026-10-21-469-r6-dashboard-motion-states.md +82 -0
  23. package/docs/verification/2026-10-21-grok-config-acp-t0.md +30 -0
  24. package/docs/verification/2026-10-22-0.12.45.md +60 -0
  25. package/docs/verification/assets/469-r1-board-1440-dark.png +0 -0
  26. package/docs/verification/assets/469-r1-board-1440-light.png +0 -0
  27. package/docs/verification/assets/469-r1-board-390-dark.png +0 -0
  28. package/docs/verification/assets/469-r1-board-390-light.png +0 -0
  29. package/docs/verification/assets/469-r2-agents-1440-dark.png +0 -0
  30. package/docs/verification/assets/469-r2-agents-768-dark.png +0 -0
  31. package/docs/verification/assets/469-r2-board-1024-light.png +0 -0
  32. package/docs/verification/assets/469-r2-board-1440-dark.png +0 -0
  33. package/docs/verification/assets/469-r2-board-1440-light.png +0 -0
  34. package/docs/verification/assets/469-r2-board-390-dark.png +0 -0
  35. package/docs/verification/assets/469-r2-board-390-light.png +0 -0
  36. package/docs/verification/assets/469-r2-board-768-light.png +0 -0
  37. package/docs/verification/assets/469-r3-board-1440-dark.png +0 -0
  38. package/docs/verification/assets/469-r3-board-1440-light.png +0 -0
  39. package/docs/verification/assets/469-r3-board-390-light.png +0 -0
  40. package/docs/verification/assets/469-r3-detail-1440-dark.png +0 -0
  41. package/docs/verification/assets/469-r3-detail-1440-light.png +0 -0
  42. package/docs/verification/assets/469-r3-detail-390-dark.png +0 -0
  43. package/docs/verification/assets/469-r3-plan-1440-light.png +0 -0
  44. package/docs/verification/assets/469-r3-plan-390-light.png +0 -0
  45. package/docs/verification/assets/469-r4-agents-1440-dark.png +0 -0
  46. package/docs/verification/assets/469-r4-agents-1440-light.png +0 -0
  47. package/docs/verification/assets/469-r4-agents-390-dark.png +0 -0
  48. package/docs/verification/assets/469-r4-memory-1440-dark.png +0 -0
  49. package/docs/verification/assets/469-r4-memory-1440-light.png +0 -0
  50. package/docs/verification/assets/469-r4-memory-390-dark.png +0 -0
  51. package/docs/verification/assets/469-r4-message-1440-light.png +0 -0
  52. package/docs/verification/assets/469-r4-office-1440-dark.png +0 -0
  53. package/docs/verification/assets/469-r4-office-1440-light.png +0 -0
  54. package/docs/verification/assets/469-r4-settings-1440-dark.png +0 -0
  55. package/docs/verification/assets/469-r4-settings-1440-light.png +0 -0
  56. package/docs/verification/assets/469-r4-stream-1440-light.png +0 -0
  57. package/docs/verification/assets/469-r4-stream-390-light.png +0 -0
  58. package/docs/verification/assets/469-r6-agents-empty-390-dark.png +0 -0
  59. package/docs/verification/assets/469-r6-agents-empty-390-light.png +0 -0
  60. package/docs/verification/assets/469-r6-board-1440-light.png +0 -0
  61. package/docs/verification/assets/469-r6-board-390-dark.png +0 -0
  62. package/docs/verification/assets/469-r6-board-empty-1440-dark.png +0 -0
  63. package/docs/verification/assets/469-r6-board-empty-1440-light.png +0 -0
  64. package/docs/verification/assets/469-r6-disconnected-1440-dark.png +0 -0
  65. package/docs/verification/assets/469-r6-disconnected-1440-light.png +0 -0
  66. package/docs/verification/assets/469-r6-disconnected-390-dark.png +0 -0
  67. package/docs/verification/assets/469-r6-skeleton-1440-light.png +0 -0
  68. package/docs/verification/assets/469-r6-skeleton-390-dark.png +0 -0
  69. package/docs/verification/assets/469-r6-toasts-1440-dark.png +0 -0
  70. package/docs/verification/assets/469-r6-toasts-1440-light.png +0 -0
  71. package/docs/verification/assets/469-r6-toasts-390-light.png +0 -0
  72. package/docs/verified.json +38 -20
  73. package/package.json +5 -1
  74. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  75. package/plugins/agent-hub/server.js +10287 -9560
  76. package/src/adapters/acp-owned-group.ts +124 -0
  77. package/src/adapters/acp.ts +92 -25
  78. package/src/adapters/adapter-api.ts +2 -0
  79. package/src/adapters/agy/definition.ts +4 -4
  80. package/src/adapters/agy/entry.ts +4 -1
  81. package/src/adapters/channel-peer.ts +143 -0
  82. package/src/adapters/claude-channel.ts +21 -12
  83. package/src/adapters/codex-appserver.ts +17 -2
  84. package/src/adapters/configured-acp.ts +124 -0
  85. package/src/adapters/conformance/acp.ts +114 -0
  86. package/src/adapters/conformance/codex.ts +191 -0
  87. package/src/adapters/conformance/enrollment.ts +43 -0
  88. package/src/adapters/conformance/factories.ts +17 -0
  89. package/src/adapters/conformance/fake-paths.ts +12 -0
  90. package/src/adapters/conformance/fakes/acp-server.ts +267 -0
  91. package/src/adapters/conformance/fakes/acp-watchdog-agent.ts +10 -0
  92. package/src/adapters/conformance/fakes/app-server.ts +119 -0
  93. package/src/adapters/conformance/fakes/codex-bin.ts +15 -0
  94. package/src/adapters/conformance/fakes/jcode-agent.ts +91 -0
  95. package/src/adapters/conformance/fakes/model-server.ts +39 -0
  96. package/src/adapters/conformance/fakes/pi-rpc.ts +32 -0
  97. package/src/adapters/conformance/index.ts +11 -0
  98. package/src/adapters/conformance/kit.ts +50 -0
  99. package/src/adapters/conformance/local-pi.ts +100 -0
  100. package/src/adapters/conformance/owned-acp.ts +44 -0
  101. package/src/adapters/definitions/builtin-metadata.ts +60 -39
  102. package/src/adapters/definitions/native-channel-state.ts +41 -0
  103. package/src/adapters/definitions/native-cli-entry-kimi.ts +9 -1
  104. package/src/adapters/definitions/native-cli-entry.ts +7 -19
  105. package/src/adapters/definitions/native-cli-preview-qwen.ts +1 -1
  106. package/src/adapters/definitions/native-cli-preview.ts +2 -11
  107. package/src/adapters/definitions/native-identity-defaults.ts +14 -0
  108. package/src/adapters/definitions/qwen.ts +7 -9
  109. package/src/adapters/definitions/reviewer-variants.ts +3 -7
  110. package/src/adapters/external-metadata.ts +48 -0
  111. package/src/adapters/external-module-scan.ts +126 -0
  112. package/src/adapters/external-modules.ts +240 -0
  113. package/src/adapters/jcode.ts +4 -0
  114. package/src/adapters/local-worker.ts +4 -0
  115. package/src/adapters/pi.ts +6 -0
  116. package/src/adapters/public.ts +7 -0
  117. package/src/adapters/registry-types.ts +28 -16
  118. package/src/adapters/registry.ts +115 -17
  119. package/src/adapters/reviewer-print.ts +4 -0
  120. package/src/cli/agy-mcp.ts +3 -2
  121. package/src/cli/configured-acp-entry.ts +58 -0
  122. package/src/cli/console-state.ts +9 -4
  123. package/src/cli/facts-hook.ts +2 -1
  124. package/src/cli/help.ts +27 -16
  125. package/src/cli/identity.ts +8 -7
  126. package/src/cli/launch.ts +5 -4
  127. package/src/cli/main.ts +101 -32
  128. package/src/cli/memory.ts +13 -10
  129. package/src/cli/output.ts +1 -0
  130. package/src/cli/preview.ts +9 -0
  131. package/src/cli/recovery-guidance.ts +1 -1
  132. package/src/cli/terminal-recovery.ts +16 -13
  133. package/src/cli/upgrade-labels.ts +1 -1
  134. package/src/cli/upgrade-presentation.ts +1 -1
  135. package/src/cli/upgrade-restore-state.ts +6 -4
  136. package/src/cli/upgrade-runtime.ts +25 -7
  137. package/src/cli/upgrade.ts +3 -2
  138. package/src/hub/board.ts +37 -8
  139. package/src/hub/budget.ts +9 -2
  140. package/src/hub/bus.ts +6 -2
  141. package/src/hub/claude-state.ts +2 -37
  142. package/src/hub/conductor.ts +8 -6
  143. package/src/hub/config-apply.ts +37 -17
  144. package/src/hub/config-reload.ts +42 -11
  145. package/src/hub/config-trust.ts +1 -1
  146. package/src/hub/context-window.ts +6 -0
  147. package/src/hub/control-client.ts +4 -1
  148. package/src/hub/crash.ts +8 -6
  149. package/src/hub/daemon.ts +412 -328
  150. package/src/hub/facts.ts +76 -13
  151. package/src/hub/hub-tools.ts +57 -5
  152. package/src/hub/instance-config.ts +33 -12
  153. package/src/hub/instance-execution.ts +23 -11
  154. package/src/hub/instances.ts +47 -22
  155. package/src/hub/lifecycle-schema.ts +5 -3
  156. package/src/hub/memory-profile.ts +16 -3
  157. package/src/hub/no-writes.ts +50 -0
  158. package/src/hub/peers.ts +9 -0
  159. package/src/hub/permission-mode.ts +9 -7
  160. package/src/hub/plan-conformance.ts +83 -0
  161. package/src/hub/review-findings.ts +104 -0
  162. package/src/hub/roster.ts +35 -6
  163. package/src/hub/routing.ts +21 -20
  164. package/src/hub/sdlc-queue.ts +19 -4
  165. package/src/hub/sdlc-release.ts +16 -1
  166. package/src/hub/sdlc.ts +27 -12
  167. package/src/hub/settings.ts +54 -16
  168. package/src/hub/snapshots.ts +32 -6
  169. package/src/hub/start-mode.ts +10 -8
  170. package/src/hub/task-sweep.ts +8 -2
  171. package/src/hub/tasks.ts +358 -60
  172. package/src/hub/ui.ts +2 -2
  173. package/src/memory/capture.ts +2 -1
  174. package/src/ui/index.html +563 -77
  175. package/src/ui/office-model.ts +3 -1
  176. package/src/ui/peer-palette.ts +44 -0
  177. package/templates/AGENTS.block.md +1 -1
  178. package/templates/profiles/general.json +1 -1
  179. package/templates/profiles/research.json +1 -1
  180. package/templates/profiles/writing.json +1 -1
@@ -1,10 +1,12 @@
1
- import { ADAPTERS } from "../adapters/registry.ts";
1
+ import { ADAPTERS, createProjectAdapterCatalog } from "../adapters/registry.ts";
2
+ import { externalMetadataDefinitions, type ExternalAdapterMetadata } from "../adapters/external-metadata.ts";
2
3
  import { createHash } from "node:crypto";
3
4
  import { readFileSync } from "node:fs";
4
5
  import { join } from "node:path";
5
- import { validateRawConfig, type ConfigLayer } from "./config-apply.ts";
6
+ import { validateRawConfig, type ConfigLayer, type ConfigIdentitySnapshot } from "./config-apply.ts";
6
7
  import { parseOverlay, parseRouting, type Routing } from "./routing.ts";
7
8
  import { CLASSES } from "./board.ts";
9
+ import { readInstanceConfig } from "./instance-config.ts";
8
10
  import { validateInstances } from "./instances.ts";
9
11
 
10
12
  export const CONFIG_SOURCE_FILES = ["config.json", "config.local.json", "routing.toml", "routing.local.toml"] as const;
@@ -16,6 +18,8 @@ export interface ConfigSources {
16
18
  trustLayers: { name: ConfigSourceName; raw: Record<string, unknown> }[];
17
19
  routing: Routing;
18
20
  fingerprint: string;
21
+ /** Captured trusted declarations, never a peer-name prefix or a request value. */
22
+ identity: ConfigIdentitySnapshot;
19
23
  }
20
24
  const MAX_CONFIG_BYTES = 1024 * 1024;
21
25
 
@@ -83,16 +87,38 @@ function validateRoutingRaw(value: unknown): void {
83
87
  }
84
88
 
85
89
  /** Strict reloads never use routing's last-good fallback. Startup can retain its existing fallback explicitly. */
86
- export function parseConfigSources(texts: ConfigSourceTexts, fallbackRouting?: Routing): ConfigSources {
90
+ export function parseConfigSources(texts: ConfigSourceTexts, fallbackRouting?: Routing, cwd?: string, externalAdapters?: readonly ExternalAdapterMetadata[]): ConfigSources {
87
91
  const layers: ConfigLayer[] = [], trustLayers: ConfigSources["trustLayers"] = [];
88
- let localRaw: Record<string, unknown> = {};
89
- try { localRaw = JSON.parse(texts["config.local.json"] ?? "{}"); }
90
- catch { if (!fallbackRouting) throw new Error(".agenthub/config.local.json: invalid JSON object"); }
92
+ const jsonLayers: ConfigLayer[] = [];
93
+ for (const name of ["config.json", "config.local.json"] as const) {
94
+ const text = texts[name];
95
+ if (text === null) continue;
96
+ try {
97
+ const value: unknown = JSON.parse(text);
98
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error();
99
+ jsonLayers.push({ name, raw: value as Record<string, unknown> });
100
+ } catch {
101
+ if (!fallbackRouting) throw new Error(`.agenthub/${name}: invalid JSON object`);
102
+ }
103
+ }
104
+ const localRaw = jsonLayers.find(layer => layer.name === "config.local.json")?.raw ?? {};
91
105
  let declared;
92
- try { declared = validateInstances(localRaw?.instances); }
93
- catch (error) {
106
+ let identityAdapters: ConfigIdentitySnapshot["adapters"];
107
+ let inertModuleRoster = false;
108
+ let catalog = createProjectAdapterCatalog();
109
+ try {
110
+ if (cwd) {
111
+ const accepted = readInstanceConfig(cwd, jsonLayers, externalAdapters === undefined ? undefined : externalMetadataDefinitions(externalAdapters));
112
+ catalog = accepted.catalog; declared = accepted.instances; identityAdapters = accepted.adapters;
113
+ inertModuleRoster = !!accepted.adapter_modules?.length;
114
+ } else {
115
+ catalog = createProjectAdapterCatalog(localRaw?.adapters, externalMetadataDefinitions(externalAdapters));
116
+ identityAdapters = localRaw?.adapters as ConfigIdentitySnapshot["adapters"];
117
+ declared = validateInstances(localRaw?.instances, catalog);
118
+ }
119
+ } catch (error) {
94
120
  if (!fallbackRouting) throw new Error(`.agenthub/config.local.json: ${(error as Error).message}`);
95
- declared = validateInstances(undefined);
121
+ declared = validateInstances(undefined, catalog);
96
122
  }
97
123
  for (const name of CONFIG_SOURCE_FILES) {
98
124
  const text = texts[name];
@@ -108,7 +134,12 @@ export function parseConfigSources(texts: ConfigSourceTexts, fallbackRouting?: R
108
134
  }
109
135
  if (name === "config.json" || name === "config.local.json") {
110
136
  if (!fallbackRouting) {
111
- try { validateRawConfig(raw, declared); }
137
+ try {
138
+ const structural = { ...raw };
139
+ // Declaration provenance was checked before compilation; ignored source data is never re-admitted.
140
+ delete structural.adapters; delete structural.instances;
141
+ validateRawConfig(structural, declared, catalog, inertModuleRoster);
142
+ }
112
143
  catch (error) { throw new Error(`.agenthub/${name}: ${(error as Error).message}`); }
113
144
  }
114
145
  layers.push({ name, raw });
@@ -125,5 +156,5 @@ export function parseConfigSources(texts: ConfigSourceTexts, fallbackRouting?: R
125
156
  try { routing = parseRouting(base, overlay); }
126
157
  catch { throw new Error(".agenthub/routing.toml / routing.local.toml: invalid effective routing"); }
127
158
  }
128
- return { texts, layers, trustLayers, routing, fingerprint: configFingerprint(texts) };
159
+ return { texts, layers, trustLayers, routing, fingerprint: configFingerprint(texts), identity: { ...(identityAdapters ? { adapters: structuredClone(identityAdapters) } : {}), instances: declared ?? validateInstances(undefined, catalog), ...(catalog.externalDefinitions.length ? { externalAdapters: catalog.externalDefinitions.map(({ kind, displayName }) => ({ kind, displayName })) } : {}) } };
129
160
  }
@@ -29,7 +29,7 @@ export const MACHINE_LOCAL = [
29
29
  "terminal.open", // the command a hub-made TUI start runs to open a terminal (#269)
30
30
  ] as const;
31
31
  /** Legacy exported field list stays compatible; new policies use the complete list below. */
32
- export const ALL_MACHINE_LOCAL = [...MACHINE_LOCAL, "protect_instruction_files", "qwen_cmd", "jcode_cmd", "jcode_home", "jcode_provider_profile", "text_projection", "instances"] as const;
32
+ export const ALL_MACHINE_LOCAL = [...MACHINE_LOCAL, "protect_instruction_files", "qwen_cmd", "jcode_cmd", "jcode_home", "jcode_provider_profile", "adapters", "adapter_modules", "text_projection", "instances"] as const;
33
33
 
34
34
  /** A known repository, including an unreadable marker, must never be mistaken for a plain directory. */
35
35
  export function configRepositoryPresent(cwd: string): boolean {
@@ -1,6 +1,12 @@
1
1
  import { nonnegative, positive } from "../adapters/definitions/native-context.ts";
2
+ import type { AdapterDefinition } from "../adapters/registry-types.ts";
2
3
  export { claudeContext, codexContext, acpContext } from "../adapters/definitions/native-context.ts";
3
4
 
5
+ /** The host retains session/time/privacy fences; the declared reader owns normalization. */
6
+ export function readNativeContext(definition: AdapterDefinition | undefined, value: unknown, sessionId: string, measuredAt: number): ContextReading | undefined {
7
+ return definition?.budget?.context?.read(value, sessionId, measuredAt);
8
+ }
9
+
4
10
  /** Native active-context occupancy, separate from accumulated billable usage (#185). */
5
11
  export interface ContextReading {
6
12
  source: "claude_statusline" | "codex_token_usage" | "acp_usage_update";
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { projectContext } from "./project.ts";
4
+ import { externalAdapterMetadata, type ExternalAdapterMetadata } from "../adapters/external-metadata.ts";
4
5
 
5
6
  export function stateDirFor(cwd: string): string {
6
7
  return projectContext(cwd).stateDir;
@@ -8,7 +9,7 @@ export function stateDirFor(cwd: string): string {
8
9
 
9
10
  /** Control WS wire version. 2 = `deliver` carries `envs` (digests); 3 = `tools` role and task messages; 4 = budget messages and `hub_checkpoint`; 5 = `ask`; 6 = console-only `ui` session bootstrap; 8 = controlled recovery; 9 = Pi bridge metadata; 10 = durable delivery receipts; 11 = queue hold diagnostics; 12 = generation-bound channel settlement and execution budgets; 13 = turn-free facts (`facts` with tool, session and transcript binding, `silenced`) and per-recipient send results (issue #108). 14 = native context-window status and session-bound context checkpoint request ids (issue #185). 15 = conductor tools and permission deadlines/closed events (issues #190, #194). 16 = a Claude session without channel evidence: hello `channel: false` and `inbox` (issue #205). The plugin is installed apart from the daemon, so they can drift. */
10
11
  export const PROTOCOL = 16;
11
- export const DAEMON_FEATURES = ["native_hook_launch", "task_cancel", "recovery_deferred_hub", "peer_instances", "release_trains", "reviewer_pool", "native_tui_launch", "terminal_owner", "cli_audit_delegated"] as const;
12
+ export const DAEMON_FEATURES = ["external_adapter_modules", "configured_acp", "native_hook_launch", "task_cancel", "recovery_deferred_hub", "peer_instances", "release_trains", "reviewer_pool", "native_tui_launch", "terminal_owner", "cli_audit_delegated"] as const;
12
13
  /** Protocols a current coordinator may authenticate while upgrading a running source. */
13
14
  export const RECOVERY_SOURCE_PROTOCOLS = [9, 10, 11, 12, 13, 14, 15, PROTOCOL] as const;
14
15
 
@@ -38,6 +39,7 @@ export interface ControlDescriptor {
38
39
  features?: readonly string[];
39
40
  /** The daemon's accepted native start modes, used by launchers when the file value is retained. */
40
41
  startModes?: Readonly<Record<string, "tui" | "headless">>;
42
+ externalAdapters?: readonly ExternalAdapterMetadata[];
41
43
  /** #226: the daemon's processSignature, written into status.json only (not the status reply); absent before 0.12.21. */
42
44
  pidSignature?: string;
43
45
  }
@@ -54,6 +56,7 @@ export function readControl(stateDir: string): ControlDescriptor | undefined {
54
56
  return { url: `ws://127.0.0.1:${status.controlPort}`, token, projectId: status.projectId,
55
57
  instanceId: status.instanceId, cwd: status.cwd, protocol: status.protocol, pid: status.pid, features: Array.isArray(status.features) ? status.features.filter((feature: unknown): feature is string => typeof feature === "string") : [],
56
58
  ...(startModes ? { startModes } : {}),
59
+ ...(status.externalAdapters !== undefined ? { externalAdapters: externalAdapterMetadata(status.externalAdapters) } : {}),
57
60
  ...(typeof status.pidSignature === "string" && status.pidSignature ? { pidSignature: status.pidSignature } : {}) };
58
61
  } catch {
59
62
  return undefined;
package/src/hub/crash.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { definitionOf, type KindMap } from "../adapters/registry.ts";
1
+ import { definitionOf, type ProjectAdapterCatalog, type KindMap } from "../adapters/registry.ts";
2
2
  import { chmodSync, existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import type { JournalDelivery } from "./delivery-journal.ts";
@@ -56,17 +56,19 @@ const str = (v: unknown) => (typeof v === "string" && v ? v : undefined);
56
56
  * What can come back after a crash. `resume` holds the start arguments for peers the hub launches itself (Kimi through
57
57
  * ACP `session/load`, Pi through its session file, the local worker afresh); the others say what the user has to do.
58
58
  */
59
- export function crashPlan(records: SessionRecord[], instances?: KindMap): { peer: string; resume?: Record<string, string>; how: string; fresh?: Record<string, string>; tui?: true }[] {
59
+ export function crashPlan(records: SessionRecord[], instances?: KindMap, catalog?: ProjectAdapterCatalog): { peer: string; resume?: Record<string, string>; how: string; fresh?: Record<string, string>; tui?: true }[] {
60
60
  return records.map(({ peer, meta }) => {
61
61
  const launch = (meta.launch && typeof meta.launch === "object" ? meta.launch : {}) as Record<string, unknown>;
62
62
  const model = str(launch.model);
63
- const adapter = definitionOf(peer, instances);
64
- const extra = adapter !== undefined && definitionOf(peer) === undefined;
63
+ const adapter = (catalog?.definitionOf ?? definitionOf)(peer, instances);
64
+ const extra = adapter !== undefined && adapter.kind !== peer;
65
65
  const command = extra ? `ahub ${adapter.cli.command} --as ${peer}` : `ahub ${peer}`;
66
66
  if (adapter?.session.resumeById) {
67
- const sessionId = adapter.session.crashIdentity?.(meta).sessionId;
67
+ const rawId = meta[adapter.session.idField];
68
+ const sessionId = adapter.session.crashIdentity ? adapter.session.crashIdentity(meta).sessionId
69
+ : adapter.transport === "acp" && typeof rawId === "string" && rawId.length > 0 && Buffer.byteLength(rawId) <= 4096 && !/[\p{Cc}\p{Cf}]/u.test(rawId) ? rawId : undefined;
68
70
  if (!sessionId) return { peer, how: `${peer}: saved ACP session identity unavailable; start it deliberately with ${command}` };
69
- return { peer, resume: { sessionId, ...(model ? { model } : {}) }, how: `${peer}: the recorded conversation can be loaded again (ACP session/load)` };
71
+ return { peer, resume: { sessionId, ...(model && definitionOf(adapter.kind) ? { model } : {}) }, how: `${peer}: the recorded conversation can be loaded again (ACP session/load)` };
70
72
  }
71
73
  const native = adapter?.session.crashPlan?.(meta);
72
74
  if (!native) return { peer, how: `${peer}: reconnects by itself if its client is still running` };