@vellumai/assistant 0.3.16 → 0.3.18
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/ARCHITECTURE.md +70 -13
- package/README.md +6 -0
- package/docs/architecture/http-token-refresh.md +23 -1
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +4 -7
- package/src/__tests__/channel-guardian.test.ts +3 -1
- package/src/__tests__/checker.test.ts +79 -48
- package/src/__tests__/config-watcher.test.ts +11 -13
- package/src/__tests__/conversation-pairing.test.ts +103 -3
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -1
- package/src/__tests__/guardian-action-followup-executor.test.ts +1 -1
- package/src/__tests__/guardian-action-late-reply.test.ts +131 -0
- package/src/__tests__/guardian-action-store.test.ts +182 -0
- package/src/__tests__/guardian-dispatch.test.ts +120 -0
- package/src/__tests__/ipc-snapshot.test.ts +21 -0
- package/src/__tests__/non-member-access-request.test.ts +1 -2
- package/src/__tests__/notification-broadcaster.test.ts +115 -4
- package/src/__tests__/notification-decision-strategy.test.ts +2 -1
- package/src/__tests__/notification-deep-link.test.ts +44 -1
- package/src/__tests__/notification-guardian-path.test.ts +157 -0
- package/src/__tests__/notification-thread-candidate-validation.test.ts +215 -0
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/trust-store.test.ts +21 -21
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +5 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
- package/src/__tests__/trusted-contact-verification.test.ts +9 -9
- package/src/__tests__/update-bulletin-state.test.ts +1 -1
- package/src/__tests__/update-bulletin.test.ts +66 -3
- package/src/__tests__/update-template-contract.test.ts +6 -11
- package/src/__tests__/voice-session-bridge.test.ts +109 -9
- package/src/calls/call-controller.ts +129 -8
- package/src/calls/guardian-action-sweep.ts +1 -1
- package/src/calls/guardian-dispatch.ts +8 -0
- package/src/calls/voice-session-bridge.ts +4 -2
- package/src/cli/core-commands.ts +41 -1
- package/src/config/templates/UPDATES.md +5 -6
- package/src/config/update-bulletin-format.ts +2 -0
- package/src/config/update-bulletin-state.ts +1 -1
- package/src/config/update-bulletin-template-path.ts +6 -0
- package/src/config/update-bulletin.ts +21 -6
- package/src/daemon/config-watcher.ts +3 -2
- package/src/daemon/daemon-control.ts +64 -10
- package/src/daemon/handlers/config-slack-channel.ts +1 -1
- package/src/daemon/handlers/identity.ts +45 -25
- package/src/daemon/handlers/sessions.ts +1 -1
- package/src/daemon/ipc-contract/sessions.ts +1 -1
- package/src/daemon/ipc-contract/workspace.ts +12 -1
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +8 -0
- package/src/daemon/server.ts +25 -3
- package/src/daemon/session-process.ts +438 -184
- package/src/daemon/tls-certs.ts +17 -12
- package/src/daemon/tool-side-effects.ts +1 -1
- package/src/memory/channel-delivery-store.ts +18 -20
- package/src/memory/channel-guardian-store.ts +39 -42
- package/src/memory/conversation-crud.ts +2 -2
- package/src/memory/conversation-queries.ts +2 -2
- package/src/memory/conversation-store.ts +24 -25
- package/src/memory/db-init.ts +9 -1
- package/src/memory/fts-reconciler.ts +41 -26
- package/src/memory/guardian-action-store.ts +57 -7
- package/src/memory/guardian-verification.ts +1 -0
- package/src/memory/jobs-worker.ts +2 -2
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +15 -0
- package/src/memory/migrations/032-notification-delivery-thread-decision.ts +20 -0
- package/src/memory/migrations/index.ts +4 -2
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/schema.ts +6 -1
- package/src/memory/search/semantic.ts +3 -3
- package/src/notifications/README.md +158 -17
- package/src/notifications/broadcaster.ts +68 -50
- package/src/notifications/conversation-pairing.ts +96 -18
- package/src/notifications/decision-engine.ts +6 -3
- package/src/notifications/deliveries-store.ts +12 -0
- package/src/notifications/emit-signal.ts +1 -0
- package/src/notifications/thread-candidates.ts +60 -25
- package/src/notifications/types.ts +2 -1
- package/src/permissions/checker.ts +1 -16
- package/src/permissions/defaults.ts +14 -4
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/http-server.ts +11 -11
- package/src/runtime/routes/access-request-decision.ts +1 -1
- package/src/runtime/routes/debug-routes.ts +4 -4
- package/src/runtime/routes/guardian-approval-interception.ts +4 -4
- package/src/runtime/routes/inbound-message-handler.ts +6 -6
- package/src/runtime/routes/integration-routes.ts +2 -2
- package/src/tools/permission-checker.ts +1 -2
- package/src/tools/secret-detection-handler.ts +1 -1
- package/src/tools/system/voice-config.ts +1 -1
- package/src/version.ts +29 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
1
|
+
import { execSync, spawn } from 'node:child_process';
|
|
2
2
|
import { closeSync,existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { createConnection } from 'node:net';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
@@ -70,9 +70,17 @@ function killStaleDaemon(): void {
|
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
73
|
+
// Guard against stale PID reuse: if the PID has been recycled by the OS
|
|
74
|
+
// and now belongs to an unrelated process, we must not signal it.
|
|
75
|
+
if (!isVellumDaemonProcess(pid)) {
|
|
76
|
+
log.info({ pid }, 'PID file references a non-vellum process (stale PID reuse) — cleaning up PID file only');
|
|
77
|
+
cleanupPidFile();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// The PID file references a live vellum daemon process, but getDaemonStatus()
|
|
82
|
+
// (called earlier in startDaemon) already returns early when the daemon is
|
|
83
|
+
// healthy. If we reach here, the recorded process is alive but non-responsive.
|
|
76
84
|
try {
|
|
77
85
|
log.info({ pid }, 'Killing stale daemon process from PID file');
|
|
78
86
|
process.kill(pid, 'SIGKILL');
|
|
@@ -91,6 +99,27 @@ function isProcessRunning(pid: number): boolean {
|
|
|
91
99
|
}
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Check whether a PID belongs to a vellum daemon process (a bun process
|
|
104
|
+
* running the daemon's main.ts). Prevents signaling an unrelated process
|
|
105
|
+
* that reused a stale PID.
|
|
106
|
+
*/
|
|
107
|
+
function isVellumDaemonProcess(pid: number): boolean {
|
|
108
|
+
try {
|
|
109
|
+
const cmd = execSync(`ps -p ${pid} -o command=`, {
|
|
110
|
+
encoding: 'utf-8',
|
|
111
|
+
timeout: 3000,
|
|
112
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
113
|
+
}).trim();
|
|
114
|
+
// The daemon is spawned as `bun run <path>/main.ts` — look for bun
|
|
115
|
+
// running our daemon entry point.
|
|
116
|
+
return cmd.includes('bun') && cmd.includes('daemon/main.ts');
|
|
117
|
+
} catch {
|
|
118
|
+
// Process exited or ps failed — treat as not ours.
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
94
123
|
/** Try a TCP connect to the Unix socket. Returns true if the connection
|
|
95
124
|
* handshake completes within the timeout — false on connection refused,
|
|
96
125
|
* timeout, or missing socket file. */
|
|
@@ -166,10 +195,17 @@ export async function getDaemonStatus(): Promise<{ running: boolean; pid?: numbe
|
|
|
166
195
|
cleanupPidFile();
|
|
167
196
|
return { running: false };
|
|
168
197
|
}
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
198
|
+
// Guard against stale PID reuse: if the OS recycled the PID and it now
|
|
199
|
+
// belongs to an unrelated process, discard the stale PID file.
|
|
200
|
+
if (!isVellumDaemonProcess(pid)) {
|
|
201
|
+
log.info({ pid }, 'PID file references a non-vellum process (stale PID reuse) — cleaning up');
|
|
202
|
+
cleanupPidFile();
|
|
203
|
+
return { running: false };
|
|
204
|
+
}
|
|
205
|
+
// Process is alive and is ours — verify the socket is responsive. A
|
|
206
|
+
// deadlocked or wedged daemon will pass the PID liveness check but fail
|
|
207
|
+
// to accept connections, and should be treated as not running so
|
|
208
|
+
// killStaleDaemon() can clean it up.
|
|
173
209
|
const responsive = await isSocketResponsive();
|
|
174
210
|
if (!responsive) {
|
|
175
211
|
log.warn({ pid }, 'Daemon process alive but socket unresponsive');
|
|
@@ -188,6 +224,11 @@ function getStartupLockPath(): string {
|
|
|
188
224
|
function acquireStartupLock(): boolean {
|
|
189
225
|
const lockPath = getStartupLockPath();
|
|
190
226
|
try {
|
|
227
|
+
// Ensure the root directory exists before attempting the lock file write.
|
|
228
|
+
// On a first-time run, getRootDir() may not exist yet, and writeFileSync
|
|
229
|
+
// with 'wx' would throw ENOENT — which the catch block misinterprets as
|
|
230
|
+
// "lock already held."
|
|
231
|
+
mkdirSync(getRootDir(), { recursive: true });
|
|
191
232
|
// O_CREAT | O_EXCL — fails atomically if the file already exists.
|
|
192
233
|
writeFileSync(lockPath, String(Date.now()), { flag: 'wx' });
|
|
193
234
|
return true;
|
|
@@ -226,12 +267,16 @@ export async function startDaemon(): Promise<{
|
|
|
226
267
|
const lockWaitMs = 10_000;
|
|
227
268
|
const lockInterval = 200;
|
|
228
269
|
let lockWaited = 0;
|
|
270
|
+
let lockAcquired = false;
|
|
229
271
|
while (lockWaited < lockWaitMs) {
|
|
230
272
|
await new Promise((r) => setTimeout(r, lockInterval));
|
|
231
273
|
lockWaited += lockInterval;
|
|
232
|
-
if (acquireStartupLock())
|
|
274
|
+
if (acquireStartupLock()) {
|
|
275
|
+
lockAcquired = true;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
233
278
|
}
|
|
234
|
-
if (
|
|
279
|
+
if (!lockAcquired) {
|
|
235
280
|
// Timed out waiting for the lock — re-check status in case the
|
|
236
281
|
// other caller succeeded.
|
|
237
282
|
const recheck = await getDaemonStatus();
|
|
@@ -358,6 +403,15 @@ export async function stopDaemon(): Promise<StopResult> {
|
|
|
358
403
|
return { stopped: false, reason: 'not_running' };
|
|
359
404
|
}
|
|
360
405
|
|
|
406
|
+
// Guard against stale PID reuse: if the PID has been recycled by the OS
|
|
407
|
+
// and now belongs to an unrelated process, clean up the PID file but
|
|
408
|
+
// never signal it.
|
|
409
|
+
if (!isVellumDaemonProcess(pid)) {
|
|
410
|
+
log.info({ pid }, 'PID file references a non-vellum process (stale PID reuse) — cleaning up PID file only');
|
|
411
|
+
cleanupPidFile();
|
|
412
|
+
return { stopped: false, reason: 'not_running' };
|
|
413
|
+
}
|
|
414
|
+
|
|
361
415
|
process.kill(pid, 'SIGTERM');
|
|
362
416
|
|
|
363
417
|
const timeouts = readDaemonTimeouts();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { deleteSecureKey, getSecureKey, setSecureKey } from '../../security/secure-keys.js';
|
|
2
2
|
import { deleteCredentialMetadata, getCredentialMetadata, upsertCredentialMetadata } from '../../tools/credentials/metadata-store.js';
|
|
3
|
-
import { log } from './shared.js';
|
|
3
|
+
import { log as _log } from './shared.js';
|
|
4
4
|
|
|
5
5
|
// -- Result type --
|
|
6
6
|
|
|
@@ -6,6 +6,45 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { getWorkspacePromptPath, readLockfile } from '../../util/platform.js';
|
|
7
7
|
import { defineHandlers, type HandlerContext,log } from './shared.js';
|
|
8
8
|
|
|
9
|
+
export interface IdentityFields {
|
|
10
|
+
name: string;
|
|
11
|
+
role: string;
|
|
12
|
+
personality: string;
|
|
13
|
+
emoji: string;
|
|
14
|
+
home: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Parse the core identity fields from IDENTITY.md content. */
|
|
18
|
+
export function parseIdentityFields(content: string): IdentityFields {
|
|
19
|
+
const fields: Record<string, string> = {};
|
|
20
|
+
for (const line of content.split('\n')) {
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
const lower = trimmed.toLowerCase();
|
|
23
|
+
const extract = (prefix: string): string | null => {
|
|
24
|
+
if (!lower.startsWith(prefix)) return null;
|
|
25
|
+
return trimmed.split(':**').pop()?.trim() ?? null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const name = extract('- **name:**');
|
|
29
|
+
if (name) { fields.name = name; continue; }
|
|
30
|
+
const role = extract('- **role:**');
|
|
31
|
+
if (role) { fields.role = role; continue; }
|
|
32
|
+
const personality = extract('- **personality:**') ?? extract('- **vibe:**');
|
|
33
|
+
if (personality) { fields.personality = personality; continue; }
|
|
34
|
+
const emoji = extract('- **emoji:**');
|
|
35
|
+
if (emoji) { fields.emoji = emoji; continue; }
|
|
36
|
+
const home = extract('- **home:**');
|
|
37
|
+
if (home) { fields.home = home; continue; }
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
name: fields.name ?? '',
|
|
41
|
+
role: fields.role ?? '',
|
|
42
|
+
personality: fields.personality ?? '',
|
|
43
|
+
emoji: fields.emoji ?? '',
|
|
44
|
+
home: fields.home ?? '',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
9
48
|
function handleIdentityGet(socket: net.Socket, ctx: HandlerContext): void {
|
|
10
49
|
const identityPath = getWorkspacePromptPath('IDENTITY.md');
|
|
11
50
|
|
|
@@ -24,26 +63,7 @@ function handleIdentityGet(socket: net.Socket, ctx: HandlerContext): void {
|
|
|
24
63
|
|
|
25
64
|
try {
|
|
26
65
|
const content = readFileSync(identityPath, 'utf-8');
|
|
27
|
-
const fields
|
|
28
|
-
for (const line of content.split('\n')) {
|
|
29
|
-
const trimmed = line.trim();
|
|
30
|
-
const lower = trimmed.toLowerCase();
|
|
31
|
-
const extract = (prefix: string): string | null => {
|
|
32
|
-
if (!lower.startsWith(prefix)) return null;
|
|
33
|
-
return trimmed.split(':**').pop()?.trim() ?? null;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const name = extract('- **name:**');
|
|
37
|
-
if (name) { fields.name = name; continue; }
|
|
38
|
-
const role = extract('- **role:**');
|
|
39
|
-
if (role) { fields.role = role; continue; }
|
|
40
|
-
const personality = extract('- **personality:**') ?? extract('- **vibe:**');
|
|
41
|
-
if (personality) { fields.personality = personality; continue; }
|
|
42
|
-
const emoji = extract('- **emoji:**');
|
|
43
|
-
if (emoji) { fields.emoji = emoji; continue; }
|
|
44
|
-
const home = extract('- **home:**');
|
|
45
|
-
if (home) { fields.home = home; continue; }
|
|
46
|
-
}
|
|
66
|
+
const fields = parseIdentityFields(content);
|
|
47
67
|
|
|
48
68
|
// Read version from package.json
|
|
49
69
|
let version: string | undefined;
|
|
@@ -90,11 +110,11 @@ function handleIdentityGet(socket: net.Socket, ctx: HandlerContext): void {
|
|
|
90
110
|
ctx.send(socket, {
|
|
91
111
|
type: 'identity_get_response',
|
|
92
112
|
found: true,
|
|
93
|
-
name: fields.name
|
|
94
|
-
role: fields.role
|
|
95
|
-
personality: fields.personality
|
|
96
|
-
emoji: fields.emoji
|
|
97
|
-
home: fields.home
|
|
113
|
+
name: fields.name,
|
|
114
|
+
role: fields.role,
|
|
115
|
+
personality: fields.personality,
|
|
116
|
+
emoji: fields.emoji,
|
|
117
|
+
home: fields.home,
|
|
98
118
|
version,
|
|
99
119
|
assistantId,
|
|
100
120
|
createdAt,
|
|
@@ -38,8 +38,8 @@ import { normalizeThreadType } from '../ipc-protocol.js';
|
|
|
38
38
|
import { executeRecordingIntent } from '../recording-executor.js';
|
|
39
39
|
import { resolveRecordingIntent } from '../recording-intent.js';
|
|
40
40
|
import { classifyRecordingIntentFallback, containsRecordingKeywords } from '../recording-intent-fallback.js';
|
|
41
|
-
import { resolveChannelCapabilities } from '../session-runtime-assembly.js';
|
|
42
41
|
import { buildSessionErrorMessage,classifySessionError } from '../session-error.js';
|
|
42
|
+
import { resolveChannelCapabilities } from '../session-runtime-assembly.js';
|
|
43
43
|
import { generateVideoThumbnail } from '../video-thumbnail.js';
|
|
44
44
|
import { handleRecordingPause, handleRecordingRestart, handleRecordingResume, handleRecordingStart, handleRecordingStop } from './recording.js';
|
|
45
45
|
import {
|
|
@@ -213,7 +213,7 @@ export interface AssistantAttention {
|
|
|
213
213
|
|
|
214
214
|
export interface SessionListResponse {
|
|
215
215
|
type: 'session_list_response';
|
|
216
|
-
sessions: Array<{ id: string; title: string; createdAt
|
|
216
|
+
sessions: Array<{ id: string; title: string; createdAt?: number; updatedAt: number; threadType?: ThreadType; source?: string; channelBinding?: ChannelBinding; conversationOriginChannel?: ChannelId; conversationOriginInterface?: InterfaceId; assistantAttention?: AssistantAttention }>;
|
|
217
217
|
/** Whether more sessions exist beyond the returned page. */
|
|
218
218
|
hasMore?: boolean;
|
|
219
219
|
}
|
|
@@ -112,6 +112,16 @@ export interface ToolNamesListResponse {
|
|
|
112
112
|
schemas?: Record<string, ToolInputSchema>;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/** Server push — broadcast when IDENTITY.md changes on disk. */
|
|
116
|
+
export interface IdentityChanged {
|
|
117
|
+
type: 'identity_changed';
|
|
118
|
+
name: string;
|
|
119
|
+
role: string;
|
|
120
|
+
personality: string;
|
|
121
|
+
emoji: string;
|
|
122
|
+
home: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
115
125
|
// --- Domain-level union aliases (consumed by the barrel file) ---
|
|
116
126
|
|
|
117
127
|
export type _WorkspaceClientMessages =
|
|
@@ -126,4 +136,5 @@ export type _WorkspaceServerMessages =
|
|
|
126
136
|
| WorkspaceFileReadResponse
|
|
127
137
|
| IdentityGetResponse
|
|
128
138
|
| ToolPermissionSimulateResponse
|
|
129
|
-
| ToolNamesListResponse
|
|
139
|
+
| ToolNamesListResponse
|
|
140
|
+
| IdentityChanged;
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '../config/env.js';
|
|
18
18
|
import { loadConfig } from '../config/loader.js';
|
|
19
19
|
import { ensurePromptFiles } from '../config/system-prompt.js';
|
|
20
|
+
import { syncUpdateBulletinOnStartup } from '../config/update-bulletin.js';
|
|
20
21
|
import { HeartbeatService } from '../heartbeat/heartbeat-service.js';
|
|
21
22
|
import { getHookManager } from '../hooks/manager.js';
|
|
22
23
|
import { installTemplates } from '../hooks/templates.js';
|
|
@@ -63,6 +64,7 @@ import { installShutdownHandlers } from './shutdown-handlers.js';
|
|
|
63
64
|
// Re-export public API so existing consumers don't need to change imports
|
|
64
65
|
export type { StopResult } from './daemon-control.js';
|
|
65
66
|
export {
|
|
67
|
+
cleanupPidFile,
|
|
66
68
|
ensureDaemonRunning,
|
|
67
69
|
getDaemonStatus,
|
|
68
70
|
isDaemonRunning,
|
|
@@ -139,6 +141,12 @@ export async function runDaemon(): Promise<void> {
|
|
|
139
141
|
initializeDb();
|
|
140
142
|
log.info('Daemon startup: DB initialized');
|
|
141
143
|
|
|
144
|
+
try {
|
|
145
|
+
syncUpdateBulletinOnStartup();
|
|
146
|
+
} catch (err) {
|
|
147
|
+
log.warn({ err }, 'Bulletin sync failed — continuing startup');
|
|
148
|
+
}
|
|
149
|
+
|
|
142
150
|
// Recover orphaned work items that were left in 'running' state when the
|
|
143
151
|
// daemon previously crashed or was killed mid-task.
|
|
144
152
|
const orphanedRunning = listWorkItems({ status: 'running' });
|
package/src/daemon/server.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmodSync, readFileSync,statSync } from 'node:fs';
|
|
1
|
+
import { chmodSync, existsSync, readFileSync,statSync } from 'node:fs';
|
|
2
2
|
import * as net from 'node:net';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import * as tls from 'node:tls';
|
|
@@ -20,12 +20,13 @@ import { getSubagentManager } from '../subagent/index.js';
|
|
|
20
20
|
import { IngressBlockedError } from '../util/errors.js';
|
|
21
21
|
import { getLogger } from '../util/logger.js';
|
|
22
22
|
import { getLocalIPv4 } from '../util/network-info.js';
|
|
23
|
-
import { getSandboxWorkingDir, getSocketPath, getTCPHost, getTCPPort, isIOSPairingEnabled,isTCPEnabled, removeSocketFile } from '../util/platform.js';
|
|
23
|
+
import { getSandboxWorkingDir, getSocketPath, getTCPHost, getTCPPort, getWorkspacePromptPath, isIOSPairingEnabled,isTCPEnabled, removeSocketFile } from '../util/platform.js';
|
|
24
24
|
import { registerDaemonCallbacks } from '../work-items/work-item-runner.js';
|
|
25
25
|
import { AuthManager } from './auth-manager.js';
|
|
26
26
|
import { ComputerUseSession } from './computer-use-session.js';
|
|
27
27
|
import { ConfigWatcher } from './config-watcher.js';
|
|
28
28
|
import { handleMessage, type HandlerContext, type SessionCreateOptions } from './handlers.js';
|
|
29
|
+
import { parseIdentityFields } from './handlers/identity.js';
|
|
29
30
|
import { cleanupRecordingsOnDisconnect } from './handlers/recording.js';
|
|
30
31
|
import { ensureBlobDir, sweepStaleBlobs } from './ipc-blob-store.js';
|
|
31
32
|
import { IpcSender } from './ipc-handler.js';
|
|
@@ -226,6 +227,24 @@ export class DaemonServer {
|
|
|
226
227
|
);
|
|
227
228
|
}
|
|
228
229
|
|
|
230
|
+
private broadcastIdentityChanged(): void {
|
|
231
|
+
try {
|
|
232
|
+
const identityPath = getWorkspacePromptPath('IDENTITY.md');
|
|
233
|
+
const content = existsSync(identityPath) ? readFileSync(identityPath, 'utf-8') : '';
|
|
234
|
+
const fields = parseIdentityFields(content);
|
|
235
|
+
this.broadcast({
|
|
236
|
+
type: 'identity_changed',
|
|
237
|
+
name: fields.name,
|
|
238
|
+
role: fields.role,
|
|
239
|
+
personality: fields.personality,
|
|
240
|
+
emoji: fields.emoji,
|
|
241
|
+
home: fields.home,
|
|
242
|
+
});
|
|
243
|
+
} catch (err) {
|
|
244
|
+
log.error({ err }, 'Failed to broadcast identity change');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
229
248
|
// ── Server lifecycle ────────────────────────────────────────────────
|
|
230
249
|
|
|
231
250
|
async start(): Promise<void> {
|
|
@@ -255,7 +274,10 @@ export class DaemonServer {
|
|
|
255
274
|
});
|
|
256
275
|
}, 5 * 60 * 1000);
|
|
257
276
|
|
|
258
|
-
this.configWatcher.start(
|
|
277
|
+
this.configWatcher.start(
|
|
278
|
+
() => this.evictSessionsForReload(),
|
|
279
|
+
() => this.broadcastIdentityChanged(),
|
|
280
|
+
);
|
|
259
281
|
this.auth.initToken();
|
|
260
282
|
|
|
261
283
|
let tlsCreds: { cert: string; key: string; fingerprint: string } | null = null;
|