deplyze-code 0.1.0 → 0.1.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/README.en.md +161 -210
- package/README.md +161 -210
- package/bin/deplyze +2 -0
- package/package.json +13 -10
- package/preload.ts +2 -2
- package/scripts/smoke.ts +491 -0
- package/src/QueryEngine.ts +2 -3
- package/src/commands/peers/index.ts +50 -0
- package/src/components/PromptInput/Notifications.tsx +9 -4
- package/src/components/PromptInput/PromptInputFooterLeftSide.tsx +3 -4
- package/src/components/PromptInput/footerAuthStatus.ts +41 -0
- package/src/components/memory/MemoryFileSelector.tsx +9 -7
- package/src/coordinator/coordinatorMode.ts +1 -5
- package/src/coordinator/workerAgent.ts +30 -0
- package/src/hooks/useApiKeyVerification.ts +71 -39
- package/src/main.tsx +22 -25
- package/src/screens/REPL.tsx +35 -40
- package/src/screens/ResumeConversation.tsx +30 -34
- package/src/services/autoDream/autoDream.ts +21 -3
- package/src/services/autoDream/config.ts +35 -3
- package/src/tools/AgentTool/AgentTool.tsx +3 -3
- package/src/tools/AgentTool/builtInAgents.ts +6 -8
- package/src/tools/ListPeersTool/ListPeersTool.ts +79 -0
- package/src/tools.ts +4 -11
- package/src/utils/concurrentSessions.ts +182 -32
- package/src/utils/sessionRestore.ts +5 -9
- package/src/utils/systemPrompt.ts +0 -1
- package/src/utils/toolPool.ts +4 -8
- package/src/utils/udsClient.ts +40 -0
- package/src/utils/udsMessaging.ts +98 -0
- package/tests/autoDream.test.ts +140 -0
- package/tests/coordinatorMode.test.ts +80 -0
- package/tests/footerAuthStatus.test.ts +54 -0
- package/tests/udsInbox.test.ts +124 -0
|
@@ -32,14 +32,12 @@ export function getBuiltInAgents(): AgentDefinition[] {
|
|
|
32
32
|
// Use lazy require inside the function body to avoid circular dependency
|
|
33
33
|
// issues at module init time. The coordinatorMode module depends on tools
|
|
34
34
|
// which depend on AgentTool which imports this file.
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return getCoordinatorAgents()
|
|
42
|
-
}
|
|
35
|
+
if (isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)) {
|
|
36
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
37
|
+
const { getCoordinatorAgents } =
|
|
38
|
+
require('../../coordinator/workerAgent.js') as typeof import('../../coordinator/workerAgent.js')
|
|
39
|
+
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
40
|
+
return getCoordinatorAgents()
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
const agents: AgentDefinition[] = [
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from 'zod/v4'
|
|
2
|
+
import { buildTool, type ToolDef } from '../../Tool.js'
|
|
3
|
+
import { listLocalPeers } from '../../utils/concurrentSessions.js'
|
|
4
|
+
import { lazySchema } from '../../utils/lazySchema.js'
|
|
5
|
+
import { jsonStringify } from '../../utils/slowOperations.js'
|
|
6
|
+
|
|
7
|
+
const inputSchema = lazySchema(() => z.strictObject({}))
|
|
8
|
+
type InputSchema = ReturnType<typeof inputSchema>
|
|
9
|
+
|
|
10
|
+
const outputSchema = lazySchema(() =>
|
|
11
|
+
z.object({
|
|
12
|
+
peers: z.array(
|
|
13
|
+
z.object({
|
|
14
|
+
sessionId: z.string(),
|
|
15
|
+
pid: z.number(),
|
|
16
|
+
cwd: z.string(),
|
|
17
|
+
startedAt: z.number().optional(),
|
|
18
|
+
kind: z.enum(['interactive', 'bg', 'daemon', 'daemon-worker']),
|
|
19
|
+
entrypoint: z.string().optional(),
|
|
20
|
+
name: z.string().optional(),
|
|
21
|
+
status: z.enum(['busy', 'idle', 'waiting']).optional(),
|
|
22
|
+
transport: z.enum(['uds', 'deferred']),
|
|
23
|
+
address: z.string().optional(),
|
|
24
|
+
canMessage: z.boolean(),
|
|
25
|
+
reason: z.string().optional(),
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
}),
|
|
29
|
+
)
|
|
30
|
+
type OutputSchema = ReturnType<typeof outputSchema>
|
|
31
|
+
|
|
32
|
+
export type Output = z.infer<OutputSchema>
|
|
33
|
+
|
|
34
|
+
export const ListPeersTool = buildTool({
|
|
35
|
+
name: 'ListPeers',
|
|
36
|
+
searchHint: 'list local peer sessions for inbox coordination',
|
|
37
|
+
maxResultSizeChars: 100_000,
|
|
38
|
+
async description() {
|
|
39
|
+
return 'List discoverable local Deplyze Code peer sessions and whether they can receive local messages.'
|
|
40
|
+
},
|
|
41
|
+
async prompt() {
|
|
42
|
+
return 'Use this tool to inspect discoverable local peer sessions before sending a cross-session message.'
|
|
43
|
+
},
|
|
44
|
+
get inputSchema(): InputSchema {
|
|
45
|
+
return inputSchema()
|
|
46
|
+
},
|
|
47
|
+
get outputSchema(): OutputSchema {
|
|
48
|
+
return outputSchema()
|
|
49
|
+
},
|
|
50
|
+
userFacingName() {
|
|
51
|
+
return 'ListPeers'
|
|
52
|
+
},
|
|
53
|
+
shouldDefer: true,
|
|
54
|
+
isConcurrencySafe() {
|
|
55
|
+
return true
|
|
56
|
+
},
|
|
57
|
+
isReadOnly() {
|
|
58
|
+
return true
|
|
59
|
+
},
|
|
60
|
+
renderToolUseMessage() {
|
|
61
|
+
return null
|
|
62
|
+
},
|
|
63
|
+
async call() {
|
|
64
|
+
return {
|
|
65
|
+
data: {
|
|
66
|
+
peers: await listLocalPeers(),
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
mapToolResultToToolResultBlockParam(content, toolUseID) {
|
|
71
|
+
const { peers } = content as Output
|
|
72
|
+
return {
|
|
73
|
+
tool_use_id: toolUseID,
|
|
74
|
+
type: 'tool_result',
|
|
75
|
+
content:
|
|
76
|
+
peers.length === 0 ? 'No local peers found' : jsonStringify(peers),
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
} satisfies ToolDef<InputSchema, Output>)
|
package/src/tools.ts
CHANGED
|
@@ -117,9 +117,8 @@ const TerminalCaptureTool = feature('TERMINAL_PANEL')
|
|
|
117
117
|
const WebBrowserTool = feature('WEB_BROWSER_TOOL')
|
|
118
118
|
? require('./tools/WebBrowserTool/WebBrowserTool.js').WebBrowserTool
|
|
119
119
|
: null
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
: null
|
|
120
|
+
const getCoordinatorModeModule = () =>
|
|
121
|
+
require('./coordinator/coordinatorMode.js') as typeof import('./coordinator/coordinatorMode.js')
|
|
123
122
|
const SnipTool = feature('HISTORY_SNIP')
|
|
124
123
|
? require('./tools/SnipTool/SnipTool.js').SnipTool
|
|
125
124
|
: null
|
|
@@ -276,10 +275,7 @@ export const getTools = (permissionContext: ToolPermissionContext): Tools => {
|
|
|
276
275
|
// below which also hides REPL_ONLY_TOOLS when REPL is enabled.
|
|
277
276
|
if (isReplModeEnabled() && REPLTool) {
|
|
278
277
|
const replSimple: Tool[] = [REPLTool]
|
|
279
|
-
if (
|
|
280
|
-
feature('COORDINATOR_MODE') &&
|
|
281
|
-
coordinatorModeModule?.isCoordinatorMode()
|
|
282
|
-
) {
|
|
278
|
+
if (getCoordinatorModeModule().isCoordinatorMode()) {
|
|
283
279
|
replSimple.push(TaskStopTool, getSendMessageTool())
|
|
284
280
|
}
|
|
285
281
|
return filterToolsByDenyRules(replSimple, permissionContext)
|
|
@@ -288,10 +284,7 @@ export const getTools = (permissionContext: ToolPermissionContext): Tools => {
|
|
|
288
284
|
// When coordinator mode is also active, include AgentTool and TaskStopTool
|
|
289
285
|
// so the coordinator gets Task+TaskStop (via useMergedTools filtering) and
|
|
290
286
|
// workers get Bash/Read/Edit (via filterToolsForAgent filtering).
|
|
291
|
-
if (
|
|
292
|
-
feature('COORDINATOR_MODE') &&
|
|
293
|
-
coordinatorModeModule?.isCoordinatorMode()
|
|
294
|
-
) {
|
|
287
|
+
if (getCoordinatorModeModule().isCoordinatorMode()) {
|
|
295
288
|
simpleTools.push(AgentTool, TaskStopTool, getSendMessageTool())
|
|
296
289
|
}
|
|
297
290
|
return filterToolsByDenyRules(simpleTools, permissionContext)
|
|
@@ -11,17 +11,146 @@ import { logForDebugging } from './debug.js'
|
|
|
11
11
|
import { getClaudeConfigHomeDir } from './envUtils.js'
|
|
12
12
|
import { errorMessage, isFsInaccessible } from './errors.js'
|
|
13
13
|
import { isProcessRunning } from './genericProcessUtils.js'
|
|
14
|
-
import { getPlatform } from './platform.js'
|
|
14
|
+
import { getPlatform, SUPPORTED_PLATFORMS } from './platform.js'
|
|
15
15
|
import { jsonParse, jsonStringify } from './slowOperations.js'
|
|
16
16
|
import { getAgentId } from './teammate.js'
|
|
17
17
|
|
|
18
18
|
export type SessionKind = 'interactive' | 'bg' | 'daemon' | 'daemon-worker'
|
|
19
19
|
export type SessionStatus = 'busy' | 'idle' | 'waiting'
|
|
20
|
+
export type RegisteredSessionRecord = {
|
|
21
|
+
pid: number
|
|
22
|
+
sessionId?: string
|
|
23
|
+
cwd?: string
|
|
24
|
+
startedAt?: number
|
|
25
|
+
kind: SessionKind
|
|
26
|
+
entrypoint?: string
|
|
27
|
+
messagingSocketPath?: string
|
|
28
|
+
name?: string
|
|
29
|
+
logPath?: string
|
|
30
|
+
agent?: string
|
|
31
|
+
bridgeSessionId?: string | null
|
|
32
|
+
status?: SessionStatus
|
|
33
|
+
waitingFor?: string
|
|
34
|
+
updatedAt?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type LocalPeerTransport = 'uds' | 'deferred'
|
|
38
|
+
|
|
39
|
+
export type LocalPeer = {
|
|
40
|
+
sessionId: string
|
|
41
|
+
pid: number
|
|
42
|
+
cwd: string
|
|
43
|
+
startedAt?: number
|
|
44
|
+
kind: SessionKind
|
|
45
|
+
entrypoint?: string
|
|
46
|
+
name?: string
|
|
47
|
+
status?: SessionStatus
|
|
48
|
+
transport: LocalPeerTransport
|
|
49
|
+
address?: string
|
|
50
|
+
canMessage: boolean
|
|
51
|
+
reason?: string
|
|
52
|
+
}
|
|
20
53
|
|
|
21
54
|
function getSessionsDir(): string {
|
|
22
55
|
return join(getClaudeConfigHomeDir(), 'sessions')
|
|
23
56
|
}
|
|
24
57
|
|
|
58
|
+
function isSessionRegistryFile(file: string): boolean {
|
|
59
|
+
return /^\d+\.json$/.test(file)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function sanitizeSessionRecord(
|
|
63
|
+
pid: number,
|
|
64
|
+
raw: unknown,
|
|
65
|
+
): RegisteredSessionRecord | null {
|
|
66
|
+
if (!raw || typeof raw !== 'object') return null
|
|
67
|
+
const record = raw as Record<string, unknown>
|
|
68
|
+
return {
|
|
69
|
+
pid,
|
|
70
|
+
sessionId:
|
|
71
|
+
typeof record.sessionId === 'string' ? record.sessionId : undefined,
|
|
72
|
+
cwd: typeof record.cwd === 'string' ? record.cwd : undefined,
|
|
73
|
+
startedAt:
|
|
74
|
+
typeof record.startedAt === 'number' ? record.startedAt : undefined,
|
|
75
|
+
kind:
|
|
76
|
+
record.kind === 'bg' ||
|
|
77
|
+
record.kind === 'daemon' ||
|
|
78
|
+
record.kind === 'daemon-worker'
|
|
79
|
+
? record.kind
|
|
80
|
+
: 'interactive',
|
|
81
|
+
entrypoint:
|
|
82
|
+
typeof record.entrypoint === 'string' ? record.entrypoint : undefined,
|
|
83
|
+
messagingSocketPath:
|
|
84
|
+
typeof record.messagingSocketPath === 'string'
|
|
85
|
+
? record.messagingSocketPath
|
|
86
|
+
: undefined,
|
|
87
|
+
name: typeof record.name === 'string' ? record.name : undefined,
|
|
88
|
+
logPath: typeof record.logPath === 'string' ? record.logPath : undefined,
|
|
89
|
+
agent: typeof record.agent === 'string' ? record.agent : undefined,
|
|
90
|
+
bridgeSessionId:
|
|
91
|
+
typeof record.bridgeSessionId === 'string' ||
|
|
92
|
+
record.bridgeSessionId === null
|
|
93
|
+
? record.bridgeSessionId
|
|
94
|
+
: undefined,
|
|
95
|
+
status:
|
|
96
|
+
record.status === 'busy' ||
|
|
97
|
+
record.status === 'idle' ||
|
|
98
|
+
record.status === 'waiting'
|
|
99
|
+
? record.status
|
|
100
|
+
: undefined,
|
|
101
|
+
waitingFor:
|
|
102
|
+
typeof record.waitingFor === 'string' ? record.waitingFor : undefined,
|
|
103
|
+
updatedAt:
|
|
104
|
+
typeof record.updatedAt === 'number' ? record.updatedAt : undefined,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function readLiveSessionRecords(options?: {
|
|
109
|
+
includeCurrentProcess?: boolean
|
|
110
|
+
}): Promise<RegisteredSessionRecord[]> {
|
|
111
|
+
const dir = getSessionsDir()
|
|
112
|
+
let files: string[]
|
|
113
|
+
try {
|
|
114
|
+
files = await readdir(dir)
|
|
115
|
+
} catch (e) {
|
|
116
|
+
if (!isFsInaccessible(e)) {
|
|
117
|
+
logForDebugging(`[concurrentSessions] readdir failed: ${errorMessage(e)}`)
|
|
118
|
+
}
|
|
119
|
+
return []
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const includeCurrentProcess = options?.includeCurrentProcess ?? true
|
|
123
|
+
const records: RegisteredSessionRecord[] = []
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
if (!isSessionRegistryFile(file)) continue
|
|
126
|
+
|
|
127
|
+
const pid = parseInt(file.slice(0, -5), 10)
|
|
128
|
+
const isCurrentProcess = pid === process.pid
|
|
129
|
+
const isLive = isCurrentProcess || isProcessRunning(pid)
|
|
130
|
+
if (!isLive) {
|
|
131
|
+
if (getPlatform() !== 'wsl') {
|
|
132
|
+
void unlink(join(dir, file)).catch(() => {})
|
|
133
|
+
}
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
if (!includeCurrentProcess && isCurrentProcess) continue
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const parsed = jsonParse(await readFile(join(dir, file), 'utf8'))
|
|
140
|
+
const record = sanitizeSessionRecord(pid, parsed)
|
|
141
|
+
if (record) {
|
|
142
|
+
records.push(record)
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
logForDebugging(
|
|
146
|
+
`[concurrentSessions] failed to read ${file}: ${errorMessage(e)}`,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return records.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0))
|
|
152
|
+
}
|
|
153
|
+
|
|
25
154
|
/**
|
|
26
155
|
* Kind override from env. Set by the spawner (`claude --bg`, daemon
|
|
27
156
|
* supervisor) so the child can register without the parent having to
|
|
@@ -166,39 +295,60 @@ export async function updateSessionActivity(patch: {
|
|
|
166
295
|
* Returns 0 on any error (conservative).
|
|
167
296
|
*/
|
|
168
297
|
export async function countConcurrentSessions(): Promise<number> {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
files = await readdir(dir)
|
|
173
|
-
} catch (e) {
|
|
174
|
-
if (!isFsInaccessible(e)) {
|
|
175
|
-
logForDebugging(`[concurrentSessions] readdir failed: ${errorMessage(e)}`)
|
|
176
|
-
}
|
|
177
|
-
return 0
|
|
178
|
-
}
|
|
298
|
+
return (await readLiveSessionRecords()).length
|
|
299
|
+
}
|
|
179
300
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
301
|
+
export async function listAllLiveSessions(options?: {
|
|
302
|
+
includeCurrentProcess?: boolean
|
|
303
|
+
}): Promise<RegisteredSessionRecord[]> {
|
|
304
|
+
return readLiveSessionRecords(options)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export async function listLocalPeers(): Promise<LocalPeer[]> {
|
|
308
|
+
const platform = getPlatform()
|
|
309
|
+
const supportsUds = SUPPORTED_PLATFORMS.includes(platform)
|
|
310
|
+
const sessions = await listAllLiveSessions({ includeCurrentProcess: false })
|
|
311
|
+
const peersBySessionId = new Map<string, LocalPeer>()
|
|
312
|
+
|
|
313
|
+
for (const session of sessions) {
|
|
314
|
+
if (!session.sessionId || !session.cwd) continue
|
|
315
|
+
|
|
316
|
+
const hasSocket =
|
|
317
|
+
typeof session.messagingSocketPath === 'string' &&
|
|
318
|
+
session.messagingSocketPath.trim().length > 0
|
|
319
|
+
const canMessage = supportsUds && hasSocket
|
|
320
|
+
const peer: LocalPeer = {
|
|
321
|
+
sessionId: session.sessionId,
|
|
322
|
+
pid: session.pid,
|
|
323
|
+
cwd: session.cwd,
|
|
324
|
+
startedAt: session.startedAt,
|
|
325
|
+
kind: session.kind,
|
|
326
|
+
entrypoint: session.entrypoint,
|
|
327
|
+
name: session.name,
|
|
328
|
+
status: session.status,
|
|
329
|
+
transport: canMessage ? 'uds' : 'deferred',
|
|
330
|
+
address: canMessage ? `uds:${session.messagingSocketPath}` : undefined,
|
|
331
|
+
canMessage,
|
|
332
|
+
reason: canMessage
|
|
333
|
+
? undefined
|
|
334
|
+
: platform === 'windows'
|
|
335
|
+
? 'Local UDS messaging is deferred on native Windows in this phase.'
|
|
336
|
+
: hasSocket
|
|
337
|
+
? 'This session is not advertising a usable local messaging socket.'
|
|
338
|
+
: 'This session is not advertising a local messaging socket.',
|
|
191
339
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
void unlink(join(dir, file)).catch(() => {})
|
|
340
|
+
|
|
341
|
+
const existing = peersBySessionId.get(peer.sessionId)
|
|
342
|
+
if (
|
|
343
|
+
!existing ||
|
|
344
|
+
(!existing.canMessage && peer.canMessage) ||
|
|
345
|
+
(existing.startedAt ?? 0) < (peer.startedAt ?? 0)
|
|
346
|
+
) {
|
|
347
|
+
peersBySessionId.set(peer.sessionId, peer)
|
|
201
348
|
}
|
|
202
349
|
}
|
|
203
|
-
|
|
350
|
+
|
|
351
|
+
return [...peersBySessionId.values()].sort(
|
|
352
|
+
(a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0),
|
|
353
|
+
)
|
|
204
354
|
}
|
|
@@ -254,7 +254,7 @@ export async function refreshAgentDefinitionsForModeSwitch(
|
|
|
254
254
|
cliAgents: AgentDefinition[],
|
|
255
255
|
currentAgentDefinitions: AgentDefinitionsResult,
|
|
256
256
|
): Promise<AgentDefinitionsResult> {
|
|
257
|
-
if (!
|
|
257
|
+
if (!modeWasSwitched) {
|
|
258
258
|
return currentAgentDefinitions
|
|
259
259
|
}
|
|
260
260
|
|
|
@@ -425,11 +425,9 @@ export async function processResumedConversation(
|
|
|
425
425
|
): Promise<ProcessedResume> {
|
|
426
426
|
// Match coordinator/normal mode to the resumed session
|
|
427
427
|
let modeWarning: string | undefined
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
result.messages.push(createSystemMessage(modeWarning, 'warning'))
|
|
432
|
-
}
|
|
428
|
+
modeWarning = context.modeApi?.matchSessionMode(result.mode)
|
|
429
|
+
if (modeWarning) {
|
|
430
|
+
result.messages.push(createSystemMessage(modeWarning, 'warning'))
|
|
433
431
|
}
|
|
434
432
|
|
|
435
433
|
// Reuse the resumed session's ID unless --fork-session is specified
|
|
@@ -511,9 +509,7 @@ export async function processResumedConversation(
|
|
|
511
509
|
)
|
|
512
510
|
|
|
513
511
|
// Persist the current mode so future resumes know what mode this session was in
|
|
514
|
-
|
|
515
|
-
saveMode(context.modeApi?.isCoordinatorMode() ? 'coordinator' : 'normal')
|
|
516
|
-
}
|
|
512
|
+
saveMode(context.modeApi?.isCoordinatorMode() ? 'coordinator' : 'normal')
|
|
517
513
|
|
|
518
514
|
// Compute initial state before render (per CLAUDE.md guidelines)
|
|
519
515
|
const restoredAttribution = opts.includeAttribution
|
|
@@ -60,7 +60,6 @@ export function buildEffectiveSystemPrompt({
|
|
|
60
60
|
// Use inline env check instead of coordinatorModule to avoid circular
|
|
61
61
|
// dependency issues during test module loading.
|
|
62
62
|
if (
|
|
63
|
-
feature('COORDINATOR_MODE') &&
|
|
64
63
|
isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE) &&
|
|
65
64
|
!mainThreadAgentDefinition
|
|
66
65
|
) {
|
package/src/utils/toolPool.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { feature } from 'bun:bundle'
|
|
2
1
|
import partition from 'lodash-es/partition.js'
|
|
3
2
|
import uniqBy from 'lodash-es/uniqBy.js'
|
|
4
3
|
import { COORDINATOR_MODE_ALLOWED_TOOLS } from '../constants/tools.js'
|
|
@@ -19,9 +18,8 @@ export function isPrActivitySubscriptionTool(name: string): boolean {
|
|
|
19
18
|
|
|
20
19
|
// Dead code elimination: conditional imports for feature-gated modules
|
|
21
20
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
: null
|
|
21
|
+
const getCoordinatorModeModule = () =>
|
|
22
|
+
require('../coordinator/coordinatorMode.js') as typeof import('../coordinator/coordinatorMode.js')
|
|
25
23
|
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
26
24
|
|
|
27
25
|
/**
|
|
@@ -69,10 +67,8 @@ export function mergeAndFilterTools(
|
|
|
69
67
|
const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
|
|
70
68
|
const tools = [...builtIn.sort(byName), ...mcp.sort(byName)]
|
|
71
69
|
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
return applyCoordinatorToolFilter(tools)
|
|
75
|
-
}
|
|
70
|
+
if (getCoordinatorModeModule().isCoordinatorMode()) {
|
|
71
|
+
return applyCoordinatorToolFilter(tools)
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
return tools
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import net from 'net'
|
|
2
|
+
import {
|
|
3
|
+
listAllLiveSessions as listLiveSessionsFromRegistry,
|
|
4
|
+
listLocalPeers as listPeersFromRegistry,
|
|
5
|
+
} from './concurrentSessions.js'
|
|
6
|
+
import { getPlatform, SUPPORTED_PLATFORMS } from './platform.js'
|
|
7
|
+
|
|
8
|
+
export const listAllLiveSessions = listLiveSessionsFromRegistry
|
|
9
|
+
export const listLocalPeers = listPeersFromRegistry
|
|
10
|
+
|
|
11
|
+
export async function sendToUdsSocket(
|
|
12
|
+
socketPath: string,
|
|
13
|
+
message: string,
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const platform = getPlatform()
|
|
16
|
+
if (!SUPPORTED_PLATFORMS.includes(platform)) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'Local UDS messaging is not supported on native windows in this phase.',
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
await new Promise<void>((resolve, reject) => {
|
|
23
|
+
const client = net.createConnection(socketPath)
|
|
24
|
+
const timeout = setTimeout(() => {
|
|
25
|
+
client.destroy(new Error(`Timed out connecting to ${socketPath}`))
|
|
26
|
+
}, 3_000)
|
|
27
|
+
|
|
28
|
+
client.once('error', error => {
|
|
29
|
+
clearTimeout(timeout)
|
|
30
|
+
reject(error)
|
|
31
|
+
})
|
|
32
|
+
client.once('connect', () => {
|
|
33
|
+
client.end(message, 'utf8')
|
|
34
|
+
})
|
|
35
|
+
client.once('close', hadError => {
|
|
36
|
+
clearTimeout(timeout)
|
|
37
|
+
if (!hadError) resolve()
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import net from 'net'
|
|
2
|
+
import { mkdir, unlink } from 'fs/promises'
|
|
3
|
+
import { dirname, join } from 'path'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
import { registerCleanup } from './cleanupRegistry.js'
|
|
6
|
+
import { logForDebugging } from './debug.js'
|
|
7
|
+
import { errorMessage } from './errors.js'
|
|
8
|
+
import { getPlatform, SUPPORTED_PLATFORMS } from './platform.js'
|
|
9
|
+
|
|
10
|
+
let activeSocketPath: string | undefined
|
|
11
|
+
let server: net.Server | null = null
|
|
12
|
+
let onEnqueue: (() => void) | undefined
|
|
13
|
+
|
|
14
|
+
export function setOnEnqueue(callback: (() => void) | undefined): void {
|
|
15
|
+
onEnqueue = callback
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getDefaultUdsSocketPath(): string {
|
|
19
|
+
return join(tmpdir(), 'deplyze-code', `uds-inbox-${process.pid}.sock`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getUdsMessagingSocketPath(): string | undefined {
|
|
23
|
+
return activeSocketPath
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function closeServer(): Promise<void> {
|
|
27
|
+
const currentServer = server
|
|
28
|
+
const socketPath = activeSocketPath
|
|
29
|
+
server = null
|
|
30
|
+
activeSocketPath = undefined
|
|
31
|
+
delete process.env.CLAUDE_CODE_MESSAGING_SOCKET
|
|
32
|
+
|
|
33
|
+
if (currentServer) {
|
|
34
|
+
await new Promise<void>(resolve => {
|
|
35
|
+
currentServer.close(() => resolve())
|
|
36
|
+
}).catch(() => {})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (socketPath) {
|
|
40
|
+
await unlink(socketPath).catch(() => {})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function startUdsMessaging(
|
|
45
|
+
socketPath: string,
|
|
46
|
+
_options?: { isExplicit?: boolean },
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
if (server || activeSocketPath) return
|
|
49
|
+
|
|
50
|
+
const platform = getPlatform()
|
|
51
|
+
if (!SUPPORTED_PLATFORMS.includes(platform)) {
|
|
52
|
+
delete process.env.CLAUDE_CODE_MESSAGING_SOCKET
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
await mkdir(dirname(socketPath), { recursive: true })
|
|
58
|
+
await unlink(socketPath).catch(() => {})
|
|
59
|
+
|
|
60
|
+
const nextServer = net.createServer(socket => {
|
|
61
|
+
let payload = ''
|
|
62
|
+
socket.setEncoding('utf8')
|
|
63
|
+
socket.on('data', chunk => {
|
|
64
|
+
payload += chunk
|
|
65
|
+
})
|
|
66
|
+
socket.on('end', () => {
|
|
67
|
+
if (payload.trim().length > 0) {
|
|
68
|
+
onEnqueue?.()
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
socket.on('error', error => {
|
|
72
|
+
logForDebugging(
|
|
73
|
+
`[udsMessaging] socket error: ${errorMessage(error)}`,
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
await new Promise<void>((resolve, reject) => {
|
|
79
|
+
nextServer.once('error', reject)
|
|
80
|
+
nextServer.listen(socketPath, () => {
|
|
81
|
+
nextServer.off('error', reject)
|
|
82
|
+
resolve()
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
server = nextServer
|
|
87
|
+
activeSocketPath = socketPath
|
|
88
|
+
process.env.CLAUDE_CODE_MESSAGING_SOCKET = socketPath
|
|
89
|
+
registerCleanup(async () => {
|
|
90
|
+
await closeServer()
|
|
91
|
+
})
|
|
92
|
+
} catch (error) {
|
|
93
|
+
logForDebugging(
|
|
94
|
+
`[udsMessaging] failed to start socket ${socketPath}: ${errorMessage(error)}`,
|
|
95
|
+
)
|
|
96
|
+
await closeServer()
|
|
97
|
+
}
|
|
98
|
+
}
|