sentinelayer-cli 0.30.2 → 0.30.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentinelayer-cli",
3
- "version": "0.30.2",
3
+ "version": "0.30.3",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -32,6 +32,7 @@ import {
32
32
  registerAgent,
33
33
  unregisterAgent,
34
34
  } from "../session/agent-registry.js";
35
+ import { inferSessionAgentIdentity } from "../session/agent-identity.js";
35
36
  import { startSenti, stopSenti } from "../session/daemon.js";
36
37
  import {
37
38
  getDaemonStatus,
@@ -1607,20 +1608,30 @@ async function publishListenerPresenceEvent({
1607
1608
  agentId,
1608
1609
  agentModel = "cli",
1609
1610
  displayName = "",
1611
+ provider = "",
1612
+ clientKind = "cli",
1610
1613
  listenerId,
1611
1614
  lifecycle = {},
1612
1615
  } = {}) {
1613
1616
  const normalizedType = normalizeString(lifecycle.type) || "heartbeat";
1614
1617
  const eventName = listenerLifecycleEventName(normalizedType);
1618
+ const identity = inferSessionAgentIdentity({
1619
+ agentId,
1620
+ model: agentModel,
1621
+ displayName,
1622
+ provider,
1623
+ clientKind,
1624
+ });
1615
1625
  const event = createAgentEvent({
1616
1626
  event: eventName,
1617
1627
  sessionId,
1618
1628
  agent: {
1619
1629
  id: agentId,
1620
- model: normalizeString(agentModel) || "cli",
1630
+ model: normalizeString(identity.model) || "cli",
1621
1631
  role: "listener",
1622
- displayName: normalizeString(displayName) || agentId,
1623
- clientKind: "cli",
1632
+ displayName: normalizeString(identity.displayName) || agentId,
1633
+ provider: normalizeString(identity.provider) || undefined,
1634
+ clientKind: normalizeString(identity.clientKind) || "cli",
1624
1635
  },
1625
1636
  eventId: `session-listener-${listenerId}-${normalizedType}-${lifecycle.pollCount ?? 0}`,
1626
1637
  idempotencyToken: `session-listener:${listenerId}:${normalizedType}:${lifecycle.pollCount ?? 0}`,
@@ -1676,20 +1687,30 @@ export function buildSessionCoachingEvent({
1676
1687
  agentId,
1677
1688
  agentModel = "cli",
1678
1689
  displayName = "",
1690
+ provider = "",
1691
+ clientKind = "cli",
1679
1692
  listenerId = "",
1680
1693
  tick = 0,
1681
1694
  tips = SESSION_LIVE_SUCCESS_TIPS,
1682
1695
  } = {}) {
1683
1696
  const tipList = Array.isArray(tips) && tips.length ? tips : SESSION_LIVE_SUCCESS_TIPS;
1697
+ const identity = inferSessionAgentIdentity({
1698
+ agentId,
1699
+ model: agentModel,
1700
+ displayName,
1701
+ provider,
1702
+ clientKind,
1703
+ });
1684
1704
  return createAgentEvent({
1685
1705
  event: "session_coaching",
1686
1706
  sessionId,
1687
1707
  agent: {
1688
1708
  id: agentId,
1689
- model: normalizeString(agentModel) || "cli",
1709
+ model: normalizeString(identity.model) || "cli",
1690
1710
  role: "listener",
1691
- displayName: normalizeString(displayName) || agentId,
1692
- clientKind: "cli",
1711
+ displayName: normalizeString(identity.displayName) || agentId,
1712
+ provider: normalizeString(identity.provider) || undefined,
1713
+ clientKind: normalizeString(identity.clientKind) || "cli",
1693
1714
  },
1694
1715
  eventId: `session-coaching-${listenerId || agentId}-${tick}`,
1695
1716
  idempotencyToken: `session-coaching:${listenerId || agentId}:${tick}`,
@@ -1707,20 +1728,30 @@ function buildListenerCatchupEvent({
1707
1728
  agentId,
1708
1729
  agentModel = "cli",
1709
1730
  displayName = "",
1731
+ provider = "",
1732
+ clientKind = "cli",
1710
1733
  listenerId,
1711
1734
  catchup = {},
1712
1735
  } = {}) {
1713
1736
  const message = formatListenerCatchupNotice(catchup);
1714
1737
  const pollCount = Number(catchup.pollCount || 0);
1738
+ const identity = inferSessionAgentIdentity({
1739
+ agentId,
1740
+ model: agentModel,
1741
+ displayName,
1742
+ provider,
1743
+ clientKind,
1744
+ });
1715
1745
  return createAgentEvent({
1716
1746
  event: "session_listen_catchup",
1717
1747
  sessionId,
1718
1748
  agent: {
1719
1749
  id: agentId,
1720
- model: normalizeString(agentModel) || "cli",
1750
+ model: normalizeString(identity.model) || "cli",
1721
1751
  role: "listener",
1722
- displayName: normalizeString(displayName) || agentId,
1723
- clientKind: "cli",
1752
+ displayName: normalizeString(identity.displayName) || agentId,
1753
+ provider: normalizeString(identity.provider) || undefined,
1754
+ clientKind: normalizeString(identity.clientKind) || "cli",
1724
1755
  },
1725
1756
  eventId: `session-listener-${listenerId}-catchup-${pollCount}`,
1726
1757
  idempotencyToken: `session-listener:${listenerId}:catchup:${pollCount}`,
@@ -1959,6 +1990,9 @@ async function ensureSessionSayAgentRegistered(
1959
1990
  const registered = await rememberAgentIdentity(sessionId, {
1960
1991
  agentId,
1961
1992
  model: normalizeString(agent.model) || "cli",
1993
+ displayName: normalizeString(agent.displayName),
1994
+ provider: normalizeString(agent.provider),
1995
+ clientKind: normalizeString(agent.clientKind) || "cli",
1962
1996
  role: sessionSayRegistryRole(agent.role),
1963
1997
  targetPath,
1964
1998
  });
@@ -1995,12 +2029,24 @@ async function resolveSessionAgentEnvelope(
1995
2029
  const resolvedRole = normalizeString(role) || normalizeString(registeredAgent?.role);
1996
2030
  const resolvedDisplayName =
1997
2031
  normalizeString(displayName) || normalizeString(registeredAgent?.displayName);
2032
+ const identity = inferSessionAgentIdentity({
2033
+ agentId: normalizedAgentId,
2034
+ model: resolvedModel,
2035
+ displayName: resolvedDisplayName,
2036
+ provider: registeredAgent?.provider,
2037
+ clientKind: normalizeString(clientKind) || normalizeString(registeredAgent?.clientKind) || "cli",
2038
+ });
1998
2039
  const envelope = {
1999
2040
  id: normalizedAgentId,
2000
- model: resolvedModel || undefined,
2041
+ model: normalizeString(identity.model) || resolvedModel || undefined,
2001
2042
  role: resolvedRole || undefined,
2002
- displayName: resolvedDisplayName || undefined,
2003
- clientKind: normalizeString(clientKind) || undefined,
2043
+ displayName: normalizeString(identity.displayName) || resolvedDisplayName || undefined,
2044
+ provider: normalizeString(identity.provider) || undefined,
2045
+ clientKind:
2046
+ normalizeString(identity.clientKind) ||
2047
+ normalizeString(clientKind) ||
2048
+ normalizeString(registeredAgent?.clientKind) ||
2049
+ undefined,
2004
2050
  };
2005
2051
  return Object.fromEntries(Object.entries(envelope).filter(([, value]) => value !== undefined));
2006
2052
  }
@@ -3021,6 +3067,8 @@ export function registerSessionCommand(program) {
3021
3067
  targetPath,
3022
3068
  agentId: resolvedAgentId,
3023
3069
  model,
3070
+ displayName: normalizeString(acceptedOnboarding?.displayName),
3071
+ clientKind: "cli",
3024
3072
  role,
3025
3073
  trackProcessExit: false,
3026
3074
  awaitRemoteSync: hasConcreteAgentIdentity,
@@ -3059,6 +3107,9 @@ export function registerSessionCommand(program) {
3059
3107
  agentId: joined.agentId,
3060
3108
  role: joined.role,
3061
3109
  model: joined.model,
3110
+ displayName: joined.displayName || null,
3111
+ provider: joined.provider || null,
3112
+ clientKind: joined.clientKind || null,
3062
3113
  status: joined.status,
3063
3114
  joinedAt: joined.joinedAt,
3064
3115
  materializedLocalSession: localSession.materialized,
@@ -3305,12 +3356,19 @@ export function registerSessionCommand(program) {
3305
3356
  if (to) {
3306
3357
  eventPayload.to = to;
3307
3358
  }
3359
+ const agentIdentity = inferSessionAgentIdentity({
3360
+ agentId,
3361
+ model: options.model,
3362
+ displayName: options.displayName,
3363
+ clientKind: "cli",
3364
+ });
3308
3365
  const agent = {
3309
3366
  id: agentId,
3310
- model: normalizeString(options.model) || "cli",
3311
- displayName: normalizeString(options.displayName) || undefined,
3367
+ model: normalizeString(agentIdentity.model) || "cli",
3368
+ displayName: normalizeString(agentIdentity.displayName) || undefined,
3369
+ provider: normalizeString(agentIdentity.provider) || undefined,
3312
3370
  role: normalizeString(options.role) || "coder",
3313
- clientKind: "cli",
3371
+ clientKind: normalizeString(agentIdentity.clientKind) || "cli",
3314
3372
  };
3315
3373
  const event = createAgentEvent({
3316
3374
  event: "session_message",
@@ -3992,8 +4050,16 @@ export function registerSessionCommand(program) {
3992
4050
  "presence-interval",
3993
4051
  60,
3994
4052
  );
3995
- const agentModel = normalizeString(options.model) || "cli";
3996
- const displayName = normalizeString(options.displayName) || agentId;
4053
+ const listenerIdentity = inferSessionAgentIdentity({
4054
+ agentId,
4055
+ model: options.model,
4056
+ displayName: options.displayName,
4057
+ clientKind: "cli",
4058
+ });
4059
+ const agentModel = normalizeString(listenerIdentity.model) || "cli";
4060
+ const displayName = normalizeString(listenerIdentity.displayName) || agentId;
4061
+ const provider = normalizeString(listenerIdentity.provider);
4062
+ const clientKind = normalizeString(listenerIdentity.clientKind) || "cli";
3997
4063
  const limit = parsePositiveInteger(options.limit, "limit", 200);
3998
4064
  const emitFormat = normalizeString(options.emit).toLowerCase() || "ndjson";
3999
4065
  if (!["ndjson", "text"].includes(emitFormat)) {
@@ -4085,6 +4151,8 @@ export function registerSessionCommand(program) {
4085
4151
  agentId,
4086
4152
  agentModel,
4087
4153
  displayName,
4154
+ provider,
4155
+ clientKind,
4088
4156
  listenerId,
4089
4157
  tick: coachingTick++,
4090
4158
  }),
@@ -4155,6 +4223,8 @@ export function registerSessionCommand(program) {
4155
4223
  agentId,
4156
4224
  agentModel,
4157
4225
  displayName,
4226
+ provider,
4227
+ clientKind,
4158
4228
  listenerId,
4159
4229
  catchup,
4160
4230
  }),
@@ -4257,6 +4327,8 @@ export function registerSessionCommand(program) {
4257
4327
  agentId,
4258
4328
  agentModel,
4259
4329
  displayName,
4330
+ provider,
4331
+ clientKind,
4260
4332
  listenerId,
4261
4333
  lifecycle,
4262
4334
  });
@@ -0,0 +1,119 @@
1
+ function normalizeString(value) {
2
+ return String(value || "").trim();
3
+ }
4
+
5
+ function normalizeLookup(value) {
6
+ return normalizeString(value).toLowerCase();
7
+ }
8
+
9
+ function titleFromIdentifier(value) {
10
+ const parts = normalizeString(value)
11
+ .replace(/[_-]+/g, " ")
12
+ .split(/\s+/)
13
+ .map((part) => part.trim())
14
+ .filter(Boolean);
15
+ if (!parts.length) return "";
16
+ return parts
17
+ .map((part) => {
18
+ if (/^[0-9]+$/.test(part)) return part;
19
+ return `${part.charAt(0).toUpperCase()}${part.slice(1)}`;
20
+ })
21
+ .join(" ");
22
+ }
23
+
24
+ function isWeakModel(value) {
25
+ const normalized = normalizeLookup(value);
26
+ return !normalized || ["anonymous", "cli", "unknown", "unreported"].includes(normalized);
27
+ }
28
+
29
+ function inferFamily(agentId, model) {
30
+ const haystack = `${agentId} ${model}`.toLowerCase();
31
+ if (haystack.includes("claude") || haystack.includes("sonnet") || haystack.includes("opus")) {
32
+ return "claude";
33
+ }
34
+ if (haystack.includes("codex") || haystack.includes("gpt-") || haystack.includes("openai")) {
35
+ return "codex";
36
+ }
37
+ if (haystack.includes("gemini") || haystack.includes("google")) return "gemini";
38
+ if (haystack.includes("grok") || haystack.includes("xai")) return "grok";
39
+ if (haystack.includes("senti") || haystack.includes("sentinelayer")) return "senti";
40
+ return "";
41
+ }
42
+
43
+ function inferModel(family) {
44
+ if (family === "codex") return "gpt-5-codex";
45
+ if (family === "claude") return "claude";
46
+ if (family === "gemini") return "gemini";
47
+ if (family === "grok") return "grok";
48
+ if (family === "senti") return "gpt-5.4-mini";
49
+ return "";
50
+ }
51
+
52
+ function inferProvider(family, model) {
53
+ const normalizedModel = normalizeLookup(model);
54
+ if (family === "claude" || normalizedModel.includes("claude")) return "anthropic";
55
+ if (family === "codex" || normalizedModel.includes("gpt") || normalizedModel.includes("openai")) {
56
+ return "openai";
57
+ }
58
+ if (family === "gemini" || normalizedModel.includes("gemini")) return "google";
59
+ if (family === "grok" || normalizedModel.includes("grok")) return "xai";
60
+ if (family === "senti") return "sentinelayer";
61
+ return "";
62
+ }
63
+
64
+ function inferClientKind(family) {
65
+ if (family === "codex") return "codex";
66
+ if (family === "claude") return "claude";
67
+ if (family === "gemini") return "gemini";
68
+ if (family === "grok") return "grok";
69
+ if (family === "senti") return "senti";
70
+ return "";
71
+ }
72
+
73
+ function inferDisplayName(agentId, family) {
74
+ const normalizedId = normalizeLookup(agentId);
75
+ if (normalizedId === "senti") return "Senti";
76
+ if (normalizedId.includes("claude-verifier")) return "Claude Verifier";
77
+ if (normalizedId.startsWith("claude")) return "Claude";
78
+ if (normalizedId.startsWith("codex")) return "Codex";
79
+ if (normalizedId.startsWith("gemini")) return "Gemini";
80
+ if (normalizedId.startsWith("grok")) return "Grok";
81
+ if (family) return titleFromIdentifier(family);
82
+ return titleFromIdentifier(agentId);
83
+ }
84
+
85
+ export function inferSessionAgentIdentity(input = {}) {
86
+ const source =
87
+ input && typeof input === "object" && !Array.isArray(input)
88
+ ? input
89
+ : {};
90
+ const {
91
+ agentId = "",
92
+ model = "",
93
+ displayName = "",
94
+ provider = "",
95
+ clientKind = "",
96
+ } = source;
97
+ const normalizedAgentId = normalizeString(agentId);
98
+ const explicitModel = normalizeString(model);
99
+ const family = inferFamily(normalizedAgentId, explicitModel);
100
+ const resolvedModel = isWeakModel(explicitModel)
101
+ ? inferModel(family) || explicitModel || "unknown"
102
+ : explicitModel;
103
+ const resolvedProvider = normalizeString(provider) || inferProvider(family, resolvedModel);
104
+ const resolvedClientKind = normalizeString(clientKind) || inferClientKind(family) || "cli";
105
+ const resolvedDisplayName =
106
+ normalizeString(displayName) || inferDisplayName(normalizedAgentId, family);
107
+
108
+ return Object.fromEntries(
109
+ Object.entries({
110
+ agentId: normalizedAgentId,
111
+ model: resolvedModel,
112
+ displayName: resolvedDisplayName,
113
+ provider: resolvedProvider,
114
+ clientKind: resolvedClientKind,
115
+ }).filter(([, value]) => normalizeString(value)),
116
+ );
117
+ }
118
+
119
+ export { titleFromIdentifier };
@@ -5,6 +5,7 @@ import process from "node:process";
5
5
 
6
6
  import { STUCK_THRESHOLDS } from "../agents/jules/pulse.js";
7
7
  import { createAgentEvent } from "../events/schema.js";
8
+ import { inferSessionAgentIdentity } from "./agent-identity.js";
8
9
  import { resolveSessionPaths } from "./paths.js";
9
10
  import { emitContextBriefing } from "./recap.js";
10
11
  import {
@@ -120,6 +121,9 @@ function normalizeAgentSnapshot(snapshot = {}, nowIso = new Date().toISOString()
120
121
  sessionId: normalizeString(snapshot.sessionId),
121
122
  agentId: normalizeString(snapshot.agentId),
122
123
  model: normalizeString(snapshot.model) || "unknown",
124
+ displayName: normalizeString(snapshot.displayName) || "",
125
+ provider: normalizeString(snapshot.provider) || "",
126
+ clientKind: normalizeString(snapshot.clientKind) || "",
123
127
  role: normalizeRole(snapshot.role || "observer"),
124
128
  status: normalizeStatus(snapshot.status, "idle"),
125
129
  detail: normalizeString(snapshot.detail) || "",
@@ -149,13 +153,30 @@ async function readAgentSnapshot(snapshotPath) {
149
153
  }
150
154
  }
151
155
 
152
- async function writeAgentSnapshot(snapshotPath, snapshot) {
156
+ const _snapshotWriteQueues = new Map();
157
+
158
+ async function writeAgentSnapshotFile(snapshotPath, snapshot) {
153
159
  await fsp.mkdir(path.dirname(snapshotPath), { recursive: true });
154
- const tmpPath = `${snapshotPath}.${process.pid}.${Date.now()}.tmp`;
160
+ const tmpPath = `${snapshotPath}.${process.pid}.${Date.now()}.${randomBytes(4).toString("hex")}.tmp`;
155
161
  await fsp.writeFile(tmpPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf-8");
156
162
  await fsp.rename(tmpPath, snapshotPath);
157
163
  }
158
164
 
165
+ async function writeAgentSnapshot(snapshotPath, snapshot) {
166
+ const previous = _snapshotWriteQueues.get(snapshotPath) || Promise.resolve();
167
+ const write = previous
168
+ .catch(() => {})
169
+ .then(() => writeAgentSnapshotFile(snapshotPath, snapshot));
170
+ _snapshotWriteQueues.set(snapshotPath, write);
171
+ try {
172
+ await write;
173
+ } finally {
174
+ if (_snapshotWriteQueues.get(snapshotPath) === write) {
175
+ _snapshotWriteQueues.delete(snapshotPath);
176
+ }
177
+ }
178
+ }
179
+
159
180
  async function emitAgentEvent(
160
181
  sessionId,
161
182
  event,
@@ -164,7 +185,14 @@ async function emitAgentEvent(
164
185
  ) {
165
186
  const envelope = createAgentEvent({
166
187
  event,
167
- agentId: payload.agentId,
188
+ agent: {
189
+ id: payload.agentId,
190
+ model: payload.model,
191
+ displayName: payload.displayName,
192
+ provider: payload.provider,
193
+ role: payload.role,
194
+ clientKind: payload.clientKind,
195
+ },
168
196
  sessionId,
169
197
  payload,
170
198
  });
@@ -198,13 +226,15 @@ function isActiveAgentSnapshot(snapshot) {
198
226
  return true;
199
227
  }
200
228
 
201
- function chooseAgentRefreshModel(value, existingModel) {
229
+ function chooseAgentRefreshModel(value, existingModel, sourceValue = value) {
202
230
  const candidate = normalizeString(value);
203
231
  const existing = normalizeString(existingModel);
232
+ const source = normalizeString(sourceValue).toLowerCase();
233
+ const sourceIsWeak = !source || ["anonymous", "cli", "unknown", "unreported"].includes(source);
204
234
  if (!candidate) {
205
235
  return existing || "unknown";
206
236
  }
207
- if (existing && ["cli", "unknown"].includes(candidate.toLowerCase())) {
237
+ if (existing && (sourceIsWeak || ["cli", "unknown"].includes(candidate.toLowerCase()))) {
208
238
  return existing;
209
239
  }
210
240
  return candidate;
@@ -222,6 +252,19 @@ function chooseAgentRefreshRole(value, existingRole) {
222
252
  return candidate;
223
253
  }
224
254
 
255
+ function chooseAgentRefreshMetadata(value, existingValue, sourceValue = value) {
256
+ const candidate = normalizeString(value);
257
+ const existing = normalizeString(existingValue);
258
+ const source = normalizeString(sourceValue);
259
+ if (!candidate) {
260
+ return existing;
261
+ }
262
+ if (existing && !source) {
263
+ return existing;
264
+ }
265
+ return candidate;
266
+ }
267
+
225
268
  // In-process registry of agents registered by *this* CLI process. The
226
269
  // dashboard treats any participant without a terminal agent_leave /
227
270
  // agent_killed / session_killed event as "active". When a CLI exits via
@@ -283,6 +326,9 @@ export async function registerAgent(
283
326
  {
284
327
  agentId = "",
285
328
  model = "",
329
+ displayName = "",
330
+ provider = "",
331
+ clientKind = "",
286
332
  role = "observer",
287
333
  targetPath = process.cwd(),
288
334
  trackProcessExit = true,
@@ -311,6 +357,14 @@ export async function registerAgent(
311
357
  }
312
358
  }
313
359
 
360
+ const identity = inferSessionAgentIdentity({
361
+ agentId: resolvedAgentId,
362
+ model,
363
+ displayName,
364
+ provider,
365
+ clientKind,
366
+ });
367
+
314
368
  const snapshotPath = buildAgentSnapshotPath(paths, resolvedAgentId);
315
369
  const existing = await readAgentSnapshot(snapshotPath);
316
370
 
@@ -320,7 +374,10 @@ export async function registerAgent(
320
374
  ...existing,
321
375
  sessionId: paths.sessionId,
322
376
  agentId: resolvedAgentId,
323
- model: chooseAgentRefreshModel(model, existing.model),
377
+ model: chooseAgentRefreshModel(identity.model, existing.model, model),
378
+ displayName: chooseAgentRefreshMetadata(identity.displayName, existing.displayName, displayName),
379
+ provider: chooseAgentRefreshMetadata(identity.provider, existing.provider, provider),
380
+ clientKind: chooseAgentRefreshMetadata(identity.clientKind, existing.clientKind, clientKind),
324
381
  role: chooseAgentRefreshRole(role, existing.role),
325
382
  status: normalizeString(existing.status) || "idle",
326
383
  detail: normalizeString(existing.detail) || "",
@@ -350,7 +407,10 @@ export async function registerAgent(
350
407
  {
351
408
  sessionId: paths.sessionId,
352
409
  agentId: resolvedAgentId,
353
- model: normalizeString(model) || "unknown",
410
+ model: normalizeString(identity.model) || "unknown",
411
+ displayName: normalizeString(identity.displayName),
412
+ provider: normalizeString(identity.provider),
413
+ clientKind: normalizeString(identity.clientKind),
354
414
  role,
355
415
  status: "idle",
356
416
  detail: "",
@@ -369,6 +429,9 @@ export async function registerAgent(
369
429
  await emitAgentEvent(paths.sessionId, "agent_join", {
370
430
  agentId: snapshot.agentId,
371
431
  model: snapshot.model,
432
+ displayName: snapshot.displayName,
433
+ provider: snapshot.provider,
434
+ clientKind: snapshot.clientKind,
372
435
  role: snapshot.role,
373
436
  status: snapshot.status,
374
437
  }, { targetPath, awaitRemoteSync });
@@ -412,6 +475,9 @@ export async function rememberAgentIdentity(
412
475
  {
413
476
  agentId = "",
414
477
  model = "",
478
+ displayName = "",
479
+ provider = "",
480
+ clientKind = "",
415
481
  role = "observer",
416
482
  targetPath = process.cwd(),
417
483
  } = {}
@@ -422,6 +488,13 @@ export async function rememberAgentIdentity(
422
488
  if (!resolvedAgentId) {
423
489
  throw new Error("agentId is required.");
424
490
  }
491
+ const identity = inferSessionAgentIdentity({
492
+ agentId: resolvedAgentId,
493
+ model,
494
+ displayName,
495
+ provider,
496
+ clientKind,
497
+ });
425
498
 
426
499
  const snapshotPath = buildAgentSnapshotPath(paths, resolvedAgentId);
427
500
  const existing = await readAgentSnapshot(snapshotPath);
@@ -430,7 +503,10 @@ export async function rememberAgentIdentity(
430
503
  ...(existing || {}),
431
504
  sessionId: paths.sessionId,
432
505
  agentId: resolvedAgentId,
433
- model: normalizeString(model) || normalizeString(existing?.model) || "unknown",
506
+ model: chooseAgentRefreshModel(identity.model, existing?.model, model),
507
+ displayName: chooseAgentRefreshMetadata(identity.displayName, existing?.displayName, displayName),
508
+ provider: chooseAgentRefreshMetadata(identity.provider, existing?.provider, provider),
509
+ clientKind: chooseAgentRefreshMetadata(identity.clientKind, existing?.clientKind, clientKind),
434
510
  role: normalizeString(role) || normalizeString(existing?.role) || "observer",
435
511
  status: normalizeString(existing?.status) || "idle",
436
512
  detail: normalizeString(existing?.detail) || "",
@@ -536,6 +612,9 @@ export async function unregisterAgent(
536
612
  reason: normalizedReason,
537
613
  role: snapshot.role,
538
614
  model: snapshot.model,
615
+ displayName: snapshot.displayName,
616
+ provider: snapshot.provider,
617
+ clientKind: snapshot.clientKind,
539
618
  }, { targetPath });
540
619
  // Already left explicitly — don't double-emit on process exit.
541
620
  _untrackLocalAgent(paths.sessionId, snapshot.agentId);