@mirrowel/opencode-subagent-explorer 0.2.0-dev.4 → 0.2.0-dev.5

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.
@@ -40,13 +40,31 @@ export declare function fetchSessions(client: unknown, directory: string): Promi
40
40
  */
41
41
  export declare function fetchActiveSessionIDs(client: unknown): Promise<Set<string> | undefined>;
42
42
  /** Minimal agent-variants sidecar view: which parents exist and which have
43
- * the base disabled. Absent file / parse errors -> empty (fail-soft). */
43
+ * the base disabled, plus the full alias set (variant name -> parent) used
44
+ * to validate parsed annotations. Absent file / parse errors -> empty
45
+ * (fail-soft). */
44
46
  export type AvSidecar = {
45
47
  parents: Map<string, {
46
48
  disableBase: boolean;
47
49
  }>;
50
+ aliases: Map<string, string>;
48
51
  };
49
52
  export declare function loadAvSidecar(globalConfigDir: string | undefined): AvSidecar;
53
+ /** Reads the last VALID variant annotation from a text. Native suffixes are
54
+ * never evidence. */
55
+ export declare function parseVariantAnnotation(text: string | undefined): {
56
+ alias: string;
57
+ switchedFrom?: string;
58
+ } | undefined;
59
+ /** Title-annotation fallback for children beyond the attribution walk's
60
+ * depth: AV writes the annotation into the child's session title at call
61
+ * time and it survives forever, so it attributes what the windowed walk
62
+ * cannot reach. Validated against the sidecar's alias set - an unknown
63
+ * token never attributes. */
64
+ export declare function variantFromTitle(title: string | undefined, sidecar: AvSidecar): {
65
+ alias: string;
66
+ parent: string;
67
+ } | undefined;
50
68
  /** child session id -> the AV variant that created it (from the parent's
51
69
  * task-part metadata). Paged backward through the parent's history via the
52
70
  * `before` message cursor (up to ATTRIBUTION_PAGES pages), so large trees
package/dist/sessions.js CHANGED
@@ -121,24 +121,68 @@ export async function fetchActiveSessionIDs(client) {
121
121
  }
122
122
  export function loadAvSidecar(globalConfigDir) {
123
123
  const parents = new Map();
124
+ const aliases = new Map();
124
125
  if (!globalConfigDir)
125
- return { parents };
126
+ return { parents, aliases };
126
127
  try {
127
128
  const path = `${globalConfigDir.replace(/[\\/]+$/, "")}/agent-variants.jsonc`;
128
129
  if (!existsSync(path))
129
- return { parents };
130
+ return { parents, aliases };
130
131
  const raw = readFileSync(path, "utf8");
131
132
  const json = JSON.parse(stripJsoncComments(raw));
132
133
  for (const [parent, entry] of Object.entries(json.agents ?? {})) {
133
134
  if (!entry || typeof entry !== "object")
134
135
  continue;
135
136
  parents.set(parent, { disableBase: entry.disable_base === true && entry.disable !== true });
137
+ // Alias = the variant's custom name when set, else parent-key (mirrors
138
+ // AV's variantName) - the validation set for annotation parsing.
139
+ for (const [key, variant] of Object.entries(entry.variants ?? {})) {
140
+ if (!variant || typeof variant !== "object" || variant.disable === true)
141
+ continue;
142
+ const alias = typeof variant.name === "string" && variant.name !== "" ? variant.name : `${parent}-${key}`;
143
+ aliases.set(alias, parent);
144
+ }
136
145
  }
137
146
  }
138
147
  catch {
139
148
  // Not our file to complain about - AV may simply not be installed.
140
149
  }
141
- return { parents };
150
+ return { parents, aliases };
151
+ }
152
+ /** One annotation token in any historical form: legacy "(@x variant)",
153
+ * terse "(@x)", switch "(@x ← y)". Mirrors AV's parseVariantAnnotation. */
154
+ const ANNOTATION_TOKEN = /\(@\s*([^()@←]*?)\s*(?:\s+variant)?\s*(?:\s*←\s*([^()@←]*?))?\s*\)/g;
155
+ /** OpenCode's own task-title suffix " (@agent subagent)" (task.ts appends it
156
+ * on agent-switch resumes, always AFTER our note) matches the terse form
157
+ * grammatically - excluded explicitly or it would override the true alias
158
+ * under last-wins. */
159
+ const NATIVE_TITLE_SUFFIX = /^[^\s]+ subagent$/;
160
+ /** Reads the last VALID variant annotation from a text. Native suffixes are
161
+ * never evidence. */
162
+ export function parseVariantAnnotation(text) {
163
+ if (typeof text !== "string")
164
+ return undefined;
165
+ let last;
166
+ for (const match of text.matchAll(ANNOTATION_TOKEN)) {
167
+ const alias = (match[1] ?? "").trim();
168
+ if (alias === "" || NATIVE_TITLE_SUFFIX.test(alias))
169
+ continue;
170
+ const switchedFrom = (match[2] ?? "").trim();
171
+ last = switchedFrom === "" ? { alias } : { alias, switchedFrom };
172
+ }
173
+ return last;
174
+ }
175
+ /** Title-annotation fallback for children beyond the attribution walk's
176
+ * depth: AV writes the annotation into the child's session title at call
177
+ * time and it survives forever, so it attributes what the windowed walk
178
+ * cannot reach. Validated against the sidecar's alias set - an unknown
179
+ * token never attributes. */
180
+ export function variantFromTitle(title, sidecar) {
181
+ const alias = parseVariantAnnotation(title)?.alias;
182
+ if (alias === undefined || sidecar.aliases.size === 0)
183
+ return undefined;
184
+ const parent = sidecar.aliases.get(alias);
185
+ return parent !== undefined ? { alias, parent } : undefined;
142
186
  }
143
187
  function stripJsoncComments(text) {
144
188
  // Block comments, line comments, and trailing commas before } or ].
@@ -191,11 +235,10 @@ export async function fetchVariantAttribution(client, parentSessionID, directory
191
235
  // Two alias sources: the after-hook metadata stamp (terminal
192
236
  // completed parts) and the before-hook description annotation -
193
237
  // aborted/error parts never receive the metadata stamp, but the
194
- // annotation persists. Reads every historical annotation form:
195
- // legacy "(@x variant)", terse "(@x)", switch "(@x ← y)".
238
+ // annotation persists. The shared parser excludes OpenCode's native
239
+ // "(@agent subagent)" suffix, which would otherwise win last-wins.
196
240
  const description = part.state?.input?.description;
197
- const descriptionMatch = typeof description === "string" ? [...description.matchAll(/\(@\s*([^()@←]*?)\s*(?:\s+variant)?\s*(?:\s*←\s*[^()@←]*?)?\s*\)/g)].at(-1) : undefined;
198
- const descriptionAlias = (descriptionMatch?.[1] ?? "").trim();
241
+ const descriptionAlias = parseVariantAnnotation(typeof description === "string" ? description : undefined)?.alias;
199
242
  const alias = (typeof variantMeta?.alias === "string" ? variantMeta.alias : undefined) ?? (descriptionAlias !== "" ? descriptionAlias : undefined);
200
243
  const routedAgent = variantMeta?.routedAgent;
201
244
  if (typeof child === "string" && typeof alias === "string" && !map.has(child)) {
package/dist/tui.js CHANGED
@@ -152,21 +152,50 @@ async function fetchActiveSessionIDs(client) {
152
152
  }
153
153
  function loadAvSidecar(globalConfigDir) {
154
154
  const parents = new Map;
155
+ const aliases = new Map;
155
156
  if (!globalConfigDir)
156
- return { parents };
157
+ return { parents, aliases };
157
158
  try {
158
159
  const path = `${globalConfigDir.replace(/[\\/]+$/, "")}/agent-variants.jsonc`;
159
160
  if (!existsSync(path))
160
- return { parents };
161
+ return { parents, aliases };
161
162
  const raw = readFileSync(path, "utf8");
162
163
  const json = JSON.parse(stripJsoncComments(raw));
163
164
  for (const [parent, entry] of Object.entries(json.agents ?? {})) {
164
165
  if (!entry || typeof entry !== "object")
165
166
  continue;
166
167
  parents.set(parent, { disableBase: entry.disable_base === true && entry.disable !== true });
168
+ for (const [key, variant] of Object.entries(entry.variants ?? {})) {
169
+ if (!variant || typeof variant !== "object" || variant.disable === true)
170
+ continue;
171
+ const alias = typeof variant.name === "string" && variant.name !== "" ? variant.name : `${parent}-${key}`;
172
+ aliases.set(alias, parent);
173
+ }
167
174
  }
168
175
  } catch {}
169
- return { parents };
176
+ return { parents, aliases };
177
+ }
178
+ var ANNOTATION_TOKEN = /\(@\s*([^()@\u2190]*?)\s*(?:\s+variant)?\s*(?:\s*\u2190\s*([^()@\u2190]*?))?\s*\)/g;
179
+ var NATIVE_TITLE_SUFFIX = /^[^\s]+ subagent$/;
180
+ function parseVariantAnnotation(text) {
181
+ if (typeof text !== "string")
182
+ return;
183
+ let last;
184
+ for (const match of text.matchAll(ANNOTATION_TOKEN)) {
185
+ const alias = (match[1] ?? "").trim();
186
+ if (alias === "" || NATIVE_TITLE_SUFFIX.test(alias))
187
+ continue;
188
+ const switchedFrom = (match[2] ?? "").trim();
189
+ last = switchedFrom === "" ? { alias } : { alias, switchedFrom };
190
+ }
191
+ return last;
192
+ }
193
+ function variantFromTitle(title, sidecar) {
194
+ const alias = parseVariantAnnotation(title)?.alias;
195
+ if (alias === undefined || sidecar.aliases.size === 0)
196
+ return;
197
+ const parent = sidecar.aliases.get(alias);
198
+ return parent !== undefined ? { alias, parent } : undefined;
170
199
  }
171
200
  function stripJsoncComments(text) {
172
201
  return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'\\])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1");
@@ -212,8 +241,7 @@ async function fetchVariantAttribution(client, parentSessionID, directory, avPar
212
241
  const child = metadata?.sessionId;
213
242
  const variantMeta = metadata?.agentVariants;
214
243
  const description = part.state?.input?.description;
215
- const descriptionMatch = typeof description === "string" ? [...description.matchAll(/\(@\s*([^()@\u2190]*?)\s*(?:\s+variant)?\s*(?:\s*\u2190\s*[^()@\u2190]*?)?\s*\)/g)].at(-1) : undefined;
216
- const descriptionAlias = (descriptionMatch?.[1] ?? "").trim();
244
+ const descriptionAlias = parseVariantAnnotation(typeof description === "string" ? description : undefined)?.alias;
217
245
  const alias = (typeof variantMeta?.alias === "string" ? variantMeta.alias : undefined) ?? (descriptionAlias !== "" ? descriptionAlias : undefined);
218
246
  const routedAgent = variantMeta?.routedAgent;
219
247
  if (typeof child === "string" && typeof alias === "string" && !map.has(child)) {
@@ -429,14 +457,15 @@ async function loadExplorerData(api, directory, rootOverride) {
429
457
  hidden: hiddenAgentIDs(api.state.config),
430
458
  configAgents: configAgentIDs(api.state.config),
431
459
  variantOf,
432
- avParents: avSidecar.parents
460
+ avParents: avSidecar.parents,
461
+ avSidecar
433
462
  };
434
463
  }
435
464
  function explorerRows(tree, data) {
436
465
  const rows = [];
437
466
  const walk = (nodes, depth) => {
438
467
  for (const node of nodes) {
439
- const variant = data.variantOf.get(node.session.id);
468
+ const variant = data.variantOf.get(node.session.id) ?? variantFromTitle(node.session.title, data.avSidecar);
440
469
  const agent = node.session.agent;
441
470
  rows.push({
442
471
  session: node.session,
@@ -1267,7 +1296,11 @@ async function remainingIdleCount(api, directory, rootID) {
1267
1296
  hidden: new Set,
1268
1297
  configAgents: new Set,
1269
1298
  variantOf: new Map,
1270
- avParents: new Map
1299
+ avParents: new Map,
1300
+ avSidecar: {
1301
+ parents: new Map,
1302
+ aliases: new Map
1303
+ }
1271
1304
  });
1272
1305
  return rows.filter((row) => !row.running).length;
1273
1306
  }
package/dist/wizard.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * import mainMenu from here and drive it with their own TuiHostApi instance.
11
11
  */
12
12
  import type { TuiHostApi as TuiPluginApi } from "./tui-host.js";
13
- import { type SessionLite, type SessionTreeNode, type VariantAttribution } from "./sessions.js";
13
+ import { type AvSidecar, type SessionLite, type SessionTreeNode, type VariantAttribution } from "./sessions.js";
14
14
  export type ExplorerRow = {
15
15
  session: SessionLite;
16
16
  depth: number;
@@ -33,6 +33,8 @@ export type ExplorerData = {
33
33
  avParents: Map<string, {
34
34
  disableBase: boolean;
35
35
  }>;
36
+ /** The full sidecar view - the alias set validates title annotations. */
37
+ avSidecar: AvSidecar;
36
38
  };
37
39
  export type TreeAction = {
38
40
  type: "delete";
package/dist/wizard.js CHANGED
@@ -99,21 +99,50 @@ async function fetchActiveSessionIDs(client) {
99
99
  }
100
100
  function loadAvSidecar(globalConfigDir) {
101
101
  const parents = new Map;
102
+ const aliases = new Map;
102
103
  if (!globalConfigDir)
103
- return { parents };
104
+ return { parents, aliases };
104
105
  try {
105
106
  const path = `${globalConfigDir.replace(/[\\/]+$/, "")}/agent-variants.jsonc`;
106
107
  if (!existsSync(path))
107
- return { parents };
108
+ return { parents, aliases };
108
109
  const raw = readFileSync(path, "utf8");
109
110
  const json = JSON.parse(stripJsoncComments(raw));
110
111
  for (const [parent, entry] of Object.entries(json.agents ?? {})) {
111
112
  if (!entry || typeof entry !== "object")
112
113
  continue;
113
114
  parents.set(parent, { disableBase: entry.disable_base === true && entry.disable !== true });
115
+ for (const [key, variant] of Object.entries(entry.variants ?? {})) {
116
+ if (!variant || typeof variant !== "object" || variant.disable === true)
117
+ continue;
118
+ const alias = typeof variant.name === "string" && variant.name !== "" ? variant.name : `${parent}-${key}`;
119
+ aliases.set(alias, parent);
120
+ }
114
121
  }
115
122
  } catch {}
116
- return { parents };
123
+ return { parents, aliases };
124
+ }
125
+ var ANNOTATION_TOKEN = /\(@\s*([^()@\u2190]*?)\s*(?:\s+variant)?\s*(?:\s*\u2190\s*([^()@\u2190]*?))?\s*\)/g;
126
+ var NATIVE_TITLE_SUFFIX = /^[^\s]+ subagent$/;
127
+ function parseVariantAnnotation(text) {
128
+ if (typeof text !== "string")
129
+ return;
130
+ let last;
131
+ for (const match of text.matchAll(ANNOTATION_TOKEN)) {
132
+ const alias = (match[1] ?? "").trim();
133
+ if (alias === "" || NATIVE_TITLE_SUFFIX.test(alias))
134
+ continue;
135
+ const switchedFrom = (match[2] ?? "").trim();
136
+ last = switchedFrom === "" ? { alias } : { alias, switchedFrom };
137
+ }
138
+ return last;
139
+ }
140
+ function variantFromTitle(title, sidecar) {
141
+ const alias = parseVariantAnnotation(title)?.alias;
142
+ if (alias === undefined || sidecar.aliases.size === 0)
143
+ return;
144
+ const parent = sidecar.aliases.get(alias);
145
+ return parent !== undefined ? { alias, parent } : undefined;
117
146
  }
118
147
  function stripJsoncComments(text) {
119
148
  return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'\\])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1");
@@ -159,8 +188,7 @@ async function fetchVariantAttribution(client, parentSessionID, directory, avPar
159
188
  const child = metadata?.sessionId;
160
189
  const variantMeta = metadata?.agentVariants;
161
190
  const description = part.state?.input?.description;
162
- const descriptionMatch = typeof description === "string" ? [...description.matchAll(/\(@\s*([^()@\u2190]*?)\s*(?:\s+variant)?\s*(?:\s*\u2190\s*[^()@\u2190]*?)?\s*\)/g)].at(-1) : undefined;
163
- const descriptionAlias = (descriptionMatch?.[1] ?? "").trim();
191
+ const descriptionAlias = parseVariantAnnotation(typeof description === "string" ? description : undefined)?.alias;
164
192
  const alias = (typeof variantMeta?.alias === "string" ? variantMeta.alias : undefined) ?? (descriptionAlias !== "" ? descriptionAlias : undefined);
165
193
  const routedAgent = variantMeta?.routedAgent;
166
194
  if (typeof child === "string" && typeof alias === "string" && !map.has(child)) {
@@ -376,14 +404,15 @@ async function loadExplorerData(api, directory, rootOverride) {
376
404
  hidden: hiddenAgentIDs(api.state.config),
377
405
  configAgents: configAgentIDs(api.state.config),
378
406
  variantOf,
379
- avParents: avSidecar.parents
407
+ avParents: avSidecar.parents,
408
+ avSidecar
380
409
  };
381
410
  }
382
411
  function explorerRows(tree, data) {
383
412
  const rows = [];
384
413
  const walk = (nodes, depth) => {
385
414
  for (const node of nodes) {
386
- const variant = data.variantOf.get(node.session.id);
415
+ const variant = data.variantOf.get(node.session.id) ?? variantFromTitle(node.session.title, data.avSidecar);
387
416
  const agent = node.session.agent;
388
417
  rows.push({
389
418
  session: node.session,
@@ -1214,7 +1243,11 @@ async function remainingIdleCount(api, directory, rootID) {
1214
1243
  hidden: new Set,
1215
1244
  configAgents: new Set,
1216
1245
  variantOf: new Map,
1217
- avParents: new Map
1246
+ avParents: new Map,
1247
+ avSidecar: {
1248
+ parents: new Map,
1249
+ aliases: new Map
1250
+ }
1218
1251
  });
1219
1252
  return rows.filter((row) => !row.running).length;
1220
1253
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mirrowel/opencode-subagent-explorer",
3
- "version": "0.2.0-dev.4",
3
+ "version": "0.2.0-dev.5",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for exploring and cleaning up subagent sessions of the current session",
6
6
  "license": "MIT",
package/src/sessions.ts CHANGED
@@ -146,29 +146,76 @@ export async function fetchActiveSessionIDs(client: unknown): Promise<Set<string
146
146
  }
147
147
 
148
148
  /** Minimal agent-variants sidecar view: which parents exist and which have
149
- * the base disabled. Absent file / parse errors -> empty (fail-soft). */
149
+ * the base disabled, plus the full alias set (variant name -> parent) used
150
+ * to validate parsed annotations. Absent file / parse errors -> empty
151
+ * (fail-soft). */
150
152
  export type AvSidecar = {
151
153
  parents: Map<string, { disableBase: boolean }>
154
+ aliases: Map<string, string>
152
155
  }
153
156
 
154
157
  export function loadAvSidecar(globalConfigDir: string | undefined): AvSidecar {
155
158
  const parents = new Map<string, { disableBase: boolean }>()
156
- if (!globalConfigDir) return { parents }
159
+ const aliases = new Map<string, string>()
160
+ if (!globalConfigDir) return { parents, aliases }
157
161
  try {
158
162
  const path = `${globalConfigDir.replace(/[\\/]+$/, "")}/agent-variants.jsonc`
159
- if (!existsSync(path)) return { parents }
163
+ if (!existsSync(path)) return { parents, aliases }
160
164
  const raw = readFileSync(path, "utf8")
161
165
  const json = JSON.parse(stripJsoncComments(raw)) as {
162
- agents?: Record<string, { disable_base?: boolean; disable?: boolean }>
166
+ agents?: Record<string, { disable_base?: boolean; disable?: boolean; variants?: Record<string, { name?: string; disable?: boolean }> }>
163
167
  }
164
168
  for (const [parent, entry] of Object.entries(json.agents ?? {})) {
165
169
  if (!entry || typeof entry !== "object") continue
166
170
  parents.set(parent, { disableBase: entry.disable_base === true && entry.disable !== true })
171
+ // Alias = the variant's custom name when set, else parent-key (mirrors
172
+ // AV's variantName) - the validation set for annotation parsing.
173
+ for (const [key, variant] of Object.entries(entry.variants ?? {})) {
174
+ if (!variant || typeof variant !== "object" || variant.disable === true) continue
175
+ const alias = typeof variant.name === "string" && variant.name !== "" ? variant.name : `${parent}-${key}`
176
+ aliases.set(alias, parent)
177
+ }
167
178
  }
168
179
  } catch {
169
180
  // Not our file to complain about - AV may simply not be installed.
170
181
  }
171
- return { parents }
182
+ return { parents, aliases }
183
+ }
184
+
185
+ /** One annotation token in any historical form: legacy "(@x variant)",
186
+ * terse "(@x)", switch "(@x ← y)". Mirrors AV's parseVariantAnnotation. */
187
+ const ANNOTATION_TOKEN = /\(@\s*([^()@←]*?)\s*(?:\s+variant)?\s*(?:\s*←\s*([^()@←]*?))?\s*\)/g
188
+
189
+ /** OpenCode's own task-title suffix " (@agent subagent)" (task.ts appends it
190
+ * on agent-switch resumes, always AFTER our note) matches the terse form
191
+ * grammatically - excluded explicitly or it would override the true alias
192
+ * under last-wins. */
193
+ const NATIVE_TITLE_SUFFIX = /^[^\s]+ subagent$/
194
+
195
+ /** Reads the last VALID variant annotation from a text. Native suffixes are
196
+ * never evidence. */
197
+ export function parseVariantAnnotation(text: string | undefined): { alias: string; switchedFrom?: string } | undefined {
198
+ if (typeof text !== "string") return undefined
199
+ let last: { alias: string; switchedFrom?: string } | undefined
200
+ for (const match of text.matchAll(ANNOTATION_TOKEN)) {
201
+ const alias = (match[1] ?? "").trim()
202
+ if (alias === "" || NATIVE_TITLE_SUFFIX.test(alias)) continue
203
+ const switchedFrom = (match[2] ?? "").trim()
204
+ last = switchedFrom === "" ? { alias } : { alias, switchedFrom }
205
+ }
206
+ return last
207
+ }
208
+
209
+ /** Title-annotation fallback for children beyond the attribution walk's
210
+ * depth: AV writes the annotation into the child's session title at call
211
+ * time and it survives forever, so it attributes what the windowed walk
212
+ * cannot reach. Validated against the sidecar's alias set - an unknown
213
+ * token never attributes. */
214
+ export function variantFromTitle(title: string | undefined, sidecar: AvSidecar): { alias: string; parent: string } | undefined {
215
+ const alias = parseVariantAnnotation(title)?.alias
216
+ if (alias === undefined || sidecar.aliases.size === 0) return undefined
217
+ const parent = sidecar.aliases.get(alias)
218
+ return parent !== undefined ? { alias, parent } : undefined
172
219
  }
173
220
 
174
221
  function stripJsoncComments(text: string): string {
@@ -230,12 +277,10 @@ export async function fetchVariantAttribution(
230
277
  // Two alias sources: the after-hook metadata stamp (terminal
231
278
  // completed parts) and the before-hook description annotation -
232
279
  // aborted/error parts never receive the metadata stamp, but the
233
- // annotation persists. Reads every historical annotation form:
234
- // legacy "(@x variant)", terse "(@x)", switch "(@x ← y)".
280
+ // annotation persists. The shared parser excludes OpenCode's native
281
+ // "(@agent subagent)" suffix, which would otherwise win last-wins.
235
282
  const description: unknown = part.state?.input?.description
236
- const descriptionMatch =
237
- typeof description === "string" ? [...description.matchAll(/\(@\s*([^()@←]*?)\s*(?:\s+variant)?\s*(?:\s*←\s*[^()@←]*?)?\s*\)/g)].at(-1) : undefined
238
- const descriptionAlias = (descriptionMatch?.[1] ?? "").trim()
283
+ const descriptionAlias = parseVariantAnnotation(typeof description === "string" ? description : undefined)?.alias
239
284
  const alias = (typeof variantMeta?.alias === "string" ? variantMeta.alias : undefined) ?? (descriptionAlias !== "" ? descriptionAlias : undefined)
240
285
  const routedAgent = variantMeta?.routedAgent
241
286
  if (typeof child === "string" && typeof alias === "string" && !map.has(child)) {
package/src/wizard.tsx CHANGED
@@ -30,6 +30,8 @@ import {
30
30
  loadAvSidecar,
31
31
  parentCandidates,
32
32
  rowHidden,
33
+ variantFromTitle,
34
+ type AvSidecar,
33
35
  type CleanupPlan,
34
36
  type SessionLite,
35
37
  type SessionTreeNode,
@@ -54,6 +56,8 @@ export type ExplorerData = {
54
56
  variantOf: VariantAttribution
55
57
  /** AV sidecar parents (empty when agent-variants is not installed). */
56
58
  avParents: Map<string, { disableBase: boolean }>
59
+ /** The full sidecar view - the alias set validates title annotations. */
60
+ avSidecar: AvSidecar
57
61
  }
58
62
 
59
63
  export type TreeAction =
@@ -89,6 +93,7 @@ export async function loadExplorerData(api: TuiPluginApi, directory: string, roo
89
93
  configAgents: configAgentIDs(api.state.config),
90
94
  variantOf,
91
95
  avParents: avSidecar.parents,
96
+ avSidecar,
92
97
  }
93
98
  }
94
99
 
@@ -96,7 +101,10 @@ export function explorerRows(tree: SessionTreeNode[], data: ExplorerData): Explo
96
101
  const rows: ExplorerRow[] = []
97
102
  const walk = (nodes: SessionTreeNode[], depth: number) => {
98
103
  for (const node of nodes) {
99
- const variant = data.variantOf.get(node.session.id)
104
+ // Attribution: windowed metadata/description walk first, then the
105
+ // title annotation (AV's call-time writing, carried forever - reaches
106
+ // children the walk cannot).
107
+ const variant = data.variantOf.get(node.session.id) ?? variantFromTitle(node.session.title, data.avSidecar)
100
108
  const agent = node.session.agent
101
109
  rows.push({
102
110
  session: node.session,
@@ -603,6 +611,7 @@ async function remainingIdleCount(api: TuiPluginApi, directory: string, rootID:
603
611
  configAgents: new Set(),
604
612
  variantOf: new Map(),
605
613
  avParents: new Map(),
614
+ avSidecar: { parents: new Map(), aliases: new Map() },
606
615
  })
607
616
  return rows.filter((row) => !row.running).length
608
617
  }