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 +1 -1
- package/src/commands/session.js +89 -17
- package/src/session/agent-identity.js +119 -0
- package/src/session/agent-registry.js +87 -8
package/package.json
CHANGED
package/src/commands/session.js
CHANGED
|
@@ -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(
|
|
1630
|
+
model: normalizeString(identity.model) || "cli",
|
|
1621
1631
|
role: "listener",
|
|
1622
|
-
displayName: normalizeString(displayName) || agentId,
|
|
1623
|
-
|
|
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(
|
|
1709
|
+
model: normalizeString(identity.model) || "cli",
|
|
1690
1710
|
role: "listener",
|
|
1691
|
-
displayName: normalizeString(displayName) || agentId,
|
|
1692
|
-
|
|
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(
|
|
1750
|
+
model: normalizeString(identity.model) || "cli",
|
|
1721
1751
|
role: "listener",
|
|
1722
|
-
displayName: normalizeString(displayName) || agentId,
|
|
1723
|
-
|
|
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
|
-
|
|
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(
|
|
3311
|
-
displayName: normalizeString(
|
|
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
|
|
3996
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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);
|