@agent-link/agent 0.1.204 → 0.1.206
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/dist/backends/claude.d.ts +90 -0
- package/dist/backends/claude.js +356 -0
- package/dist/backends/claude.js.map +1 -0
- package/dist/backends/index.d.ts +27 -0
- package/dist/backends/index.js +52 -0
- package/dist/backends/index.js.map +1 -0
- package/dist/backends/types.d.ts +379 -0
- package/dist/backends/types.js +120 -0
- package/dist/backends/types.js.map +1 -0
- package/dist/claude.d.ts +22 -2
- package/dist/claude.js +54 -6
- package/dist/claude.js.map +1 -1
- package/dist/cli.js +62 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/connection.d.ts +4 -1
- package/dist/connection.js +274 -134
- package/dist/connection.js.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/recap.d.ts +1 -0
- package/dist/recap.js.map +1 -1
- package/dist/runtime.d.ts +176 -0
- package/dist/runtime.js +286 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend abstraction for AgentLink.
|
|
3
|
+
*
|
|
4
|
+
* Stage 1: defines the interface only. ClaudeBackend implementation lives in
|
|
5
|
+
* agent/src/claude.ts (refactor to backends/claude/* is a follow-up PR).
|
|
6
|
+
*/
|
|
7
|
+
export type BackendType = 'claude' | 'codex';
|
|
8
|
+
export declare const KNOWN_BACKEND_TYPES: readonly BackendType[];
|
|
9
|
+
/**
|
|
10
|
+
* Backend-neutral file attachment for `UserTurnInput.files`.
|
|
11
|
+
* Defined here (not in claude.ts) so the boundary stays clean.
|
|
12
|
+
* (PR3 will re-export this as `ChatFile` from claude.ts for back-compat;
|
|
13
|
+
* not yet wired in Stage 1.)
|
|
14
|
+
*/
|
|
15
|
+
export interface BackendFileAttachment {
|
|
16
|
+
name: string;
|
|
17
|
+
mimeType: string;
|
|
18
|
+
/** base64-encoded content */
|
|
19
|
+
data: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Backend-neutral history message — fully opaque from the backend interface's
|
|
23
|
+
* point of view. Concrete backends (Claude's `{ role, content, ... }`,
|
|
24
|
+
* Codex's event records) all flow through unchanged; the wire/UI layer
|
|
25
|
+
* narrows as needed. PR3 will define the normalized shape if/when needed.
|
|
26
|
+
*/
|
|
27
|
+
export type BackendHistoryMessage = Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Backend-neutral session listing entry. Carries identity required for
|
|
30
|
+
* cross-backend routing — `backendType` + `backendSessionId` together
|
|
31
|
+
* uniquely identify a session.
|
|
32
|
+
*/
|
|
33
|
+
export interface BackendSessionInfo {
|
|
34
|
+
/** Composite identity for routing / resume. */
|
|
35
|
+
session: BackendSessionRef;
|
|
36
|
+
/** Display title (custom title > derived). */
|
|
37
|
+
title: string;
|
|
38
|
+
/** Optional user-set custom title (separate from derived). */
|
|
39
|
+
customTitle?: string;
|
|
40
|
+
/** Short preview of last message (UI hint). */
|
|
41
|
+
preview?: string;
|
|
42
|
+
/** Epoch ms of last activity. */
|
|
43
|
+
lastModified: number;
|
|
44
|
+
}
|
|
45
|
+
/** Backend-neutral session identifier. Avoids leaking `claudeSessionId` into shared code. */
|
|
46
|
+
export interface BackendSessionRef {
|
|
47
|
+
backendType: BackendType;
|
|
48
|
+
/** Stable id used for routing, listing, resume. For Claude: claudeSessionId. For Codex: Thread.id. */
|
|
49
|
+
backendSessionId: string;
|
|
50
|
+
/** `backendThreadId === backendSessionId` for Claude (single-thread sessions). Codex sets it to `Thread.id`, distinct from session id. */
|
|
51
|
+
backendThreadId?: string | null;
|
|
52
|
+
/** Provider's raw id, kept for debugging / cross-backend migration. */
|
|
53
|
+
providerSessionId?: string | null;
|
|
54
|
+
}
|
|
55
|
+
/** Backend-neutral sandbox / permission mode. Each backend maps internally. */
|
|
56
|
+
export type SandboxMode = 'safe' | 'workspace' | 'unrestricted';
|
|
57
|
+
/**
|
|
58
|
+
* Named union for per-conversation permission modes (PR4-C, Codex finding C).
|
|
59
|
+
* Mirrors claude.ts:806 — backends declare which values they support via
|
|
60
|
+
* `capabilities.controls.permissionMode`.
|
|
61
|
+
*/
|
|
62
|
+
export type PermissionMode = 'normal' | 'acceptEdits' | 'auto' | 'default' | 'plan';
|
|
63
|
+
/**
|
|
64
|
+
* Result of a `restartSession` call (PR4-C). `wasTurnActive` drives
|
|
65
|
+
* `execution_cancelled` emission in connection.ts.
|
|
66
|
+
*/
|
|
67
|
+
export interface BackendRestartResult {
|
|
68
|
+
/** New backend session id after restart, or null if conv was empty. */
|
|
69
|
+
claudeSessionId: string | null;
|
|
70
|
+
/** Whether a turn was in flight when restart fired. */
|
|
71
|
+
wasTurnActive: boolean;
|
|
72
|
+
/** Backend-internal history snapshot (opaque). */
|
|
73
|
+
history?: unknown;
|
|
74
|
+
}
|
|
75
|
+
/** Options for `restartSession` / `createPlaceholderSession` (PR4-C). */
|
|
76
|
+
export interface RestartOpts {
|
|
77
|
+
planMode?: boolean;
|
|
78
|
+
brainMode?: boolean;
|
|
79
|
+
permissionMode?: PermissionMode;
|
|
80
|
+
}
|
|
81
|
+
/** Capability descriptor with optional per-feature details. */
|
|
82
|
+
export interface Capability<TDetails = unknown> {
|
|
83
|
+
supported: boolean;
|
|
84
|
+
details?: TDetails;
|
|
85
|
+
}
|
|
86
|
+
export interface BackendCapabilities {
|
|
87
|
+
lifecycle: {
|
|
88
|
+
persistentProcess: boolean;
|
|
89
|
+
resumableSessions: boolean;
|
|
90
|
+
concurrentTurns: boolean;
|
|
91
|
+
backgroundTurns: boolean;
|
|
92
|
+
fork: Capability;
|
|
93
|
+
compaction: Capability;
|
|
94
|
+
};
|
|
95
|
+
approvals: {
|
|
96
|
+
command: Capability;
|
|
97
|
+
fileChange: Capability;
|
|
98
|
+
permissions: Capability;
|
|
99
|
+
genericTool: Capability;
|
|
100
|
+
userInput: Capability;
|
|
101
|
+
};
|
|
102
|
+
streaming: {
|
|
103
|
+
text: boolean;
|
|
104
|
+
reasoning: Capability;
|
|
105
|
+
plan: Capability;
|
|
106
|
+
usage: Capability<{
|
|
107
|
+
incremental: boolean;
|
|
108
|
+
}>;
|
|
109
|
+
processOutput: Capability;
|
|
110
|
+
fsWatch: Capability;
|
|
111
|
+
remoteControl: Capability<{
|
|
112
|
+
statusEvents: boolean;
|
|
113
|
+
}>;
|
|
114
|
+
};
|
|
115
|
+
history: {
|
|
116
|
+
list: boolean;
|
|
117
|
+
read: boolean;
|
|
118
|
+
rename: boolean;
|
|
119
|
+
delete: boolean;
|
|
120
|
+
search: boolean;
|
|
121
|
+
};
|
|
122
|
+
models: {
|
|
123
|
+
list: Capability;
|
|
124
|
+
switchPerSession: boolean;
|
|
125
|
+
switchPerTurn: boolean;
|
|
126
|
+
};
|
|
127
|
+
tools: {
|
|
128
|
+
/** Item kinds this backend emits (e.g. 'Read','Write','Bash' for Claude; 'command_execution','file_change' for Codex). */
|
|
129
|
+
itemTypes: string[];
|
|
130
|
+
fallbackRenderer: boolean;
|
|
131
|
+
specializedRenderers: string[];
|
|
132
|
+
};
|
|
133
|
+
integrations: {
|
|
134
|
+
accountInfo: Capability;
|
|
135
|
+
codeReview: Capability;
|
|
136
|
+
mcpManagement: Capability;
|
|
137
|
+
hooks: Capability;
|
|
138
|
+
personalitySwitch: Capability;
|
|
139
|
+
skills: Capability;
|
|
140
|
+
};
|
|
141
|
+
sandboxModes: SandboxMode[];
|
|
142
|
+
/** PR4-C: per-conversation user-action controls. */
|
|
143
|
+
controls: {
|
|
144
|
+
setModel: Capability;
|
|
145
|
+
restartSession: Capability;
|
|
146
|
+
brainMode: Capability;
|
|
147
|
+
planMode: Capability;
|
|
148
|
+
permissionMode: Capability<{
|
|
149
|
+
modes: PermissionMode[];
|
|
150
|
+
}>;
|
|
151
|
+
isCompacting: Capability;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/** Approval / confirmation requests from the backend to the user. */
|
|
155
|
+
export type ApprovalRequest = {
|
|
156
|
+
kind: 'command';
|
|
157
|
+
command: string;
|
|
158
|
+
cwd?: string;
|
|
159
|
+
reason?: string;
|
|
160
|
+
} | {
|
|
161
|
+
kind: 'file_change';
|
|
162
|
+
summary: string;
|
|
163
|
+
changes?: unknown;
|
|
164
|
+
} | {
|
|
165
|
+
kind: 'permissions';
|
|
166
|
+
permissions: unknown;
|
|
167
|
+
reason?: string;
|
|
168
|
+
} | {
|
|
169
|
+
kind: 'tool';
|
|
170
|
+
toolName: string;
|
|
171
|
+
input: unknown;
|
|
172
|
+
reason?: string;
|
|
173
|
+
};
|
|
174
|
+
export type UserInputSource = 'ask_user_question' | 'mcp_elicitation' | 'tool_request_user_input';
|
|
175
|
+
export interface UserInputQuestion {
|
|
176
|
+
question: string;
|
|
177
|
+
header?: string;
|
|
178
|
+
options: Array<{
|
|
179
|
+
label: string;
|
|
180
|
+
description?: string;
|
|
181
|
+
}>;
|
|
182
|
+
multiSelect?: boolean;
|
|
183
|
+
}
|
|
184
|
+
export interface UserInputRequest {
|
|
185
|
+
questions: UserInputQuestion[];
|
|
186
|
+
source: UserInputSource;
|
|
187
|
+
}
|
|
188
|
+
export interface UsageInfo {
|
|
189
|
+
inputTokens?: number;
|
|
190
|
+
outputTokens?: number;
|
|
191
|
+
cacheReadInputTokens?: number;
|
|
192
|
+
cacheCreationInputTokens?: number;
|
|
193
|
+
totalCostUsd?: number;
|
|
194
|
+
}
|
|
195
|
+
/** Normalized tool event (start / update / result). */
|
|
196
|
+
export interface NormalizedToolEvent {
|
|
197
|
+
id: string;
|
|
198
|
+
name: string;
|
|
199
|
+
/** Backend item kind (e.g. 'Read','Bash','command_execution','file_change'). */
|
|
200
|
+
itemKind?: string;
|
|
201
|
+
input?: unknown;
|
|
202
|
+
output?: unknown;
|
|
203
|
+
isError?: boolean;
|
|
204
|
+
status?: 'in_progress' | 'completed' | 'error';
|
|
205
|
+
}
|
|
206
|
+
export type NormalizedEvent = {
|
|
207
|
+
type: 'session_started';
|
|
208
|
+
session: BackendSessionRef;
|
|
209
|
+
} | {
|
|
210
|
+
type: 'turn_started';
|
|
211
|
+
session: BackendSessionRef;
|
|
212
|
+
turnId?: string;
|
|
213
|
+
} | {
|
|
214
|
+
type: 'text_delta';
|
|
215
|
+
session: BackendSessionRef;
|
|
216
|
+
text: string;
|
|
217
|
+
} | {
|
|
218
|
+
type: 'tool_started';
|
|
219
|
+
session: BackendSessionRef;
|
|
220
|
+
tool: NormalizedToolEvent;
|
|
221
|
+
} | {
|
|
222
|
+
type: 'tool_updated';
|
|
223
|
+
session: BackendSessionRef;
|
|
224
|
+
tool: NormalizedToolEvent;
|
|
225
|
+
} | {
|
|
226
|
+
type: 'tool_completed';
|
|
227
|
+
session: BackendSessionRef;
|
|
228
|
+
tool: NormalizedToolEvent;
|
|
229
|
+
} | {
|
|
230
|
+
type: 'plan_delta';
|
|
231
|
+
session: BackendSessionRef;
|
|
232
|
+
plan: unknown;
|
|
233
|
+
} | {
|
|
234
|
+
type: 'reasoning_delta';
|
|
235
|
+
session: BackendSessionRef;
|
|
236
|
+
text: string;
|
|
237
|
+
} | {
|
|
238
|
+
type: 'compact_started';
|
|
239
|
+
session: BackendSessionRef;
|
|
240
|
+
} | {
|
|
241
|
+
type: 'compact_completed';
|
|
242
|
+
session: BackendSessionRef;
|
|
243
|
+
} | {
|
|
244
|
+
type: 'usage_update';
|
|
245
|
+
session: BackendSessionRef;
|
|
246
|
+
usage: UsageInfo;
|
|
247
|
+
} | {
|
|
248
|
+
type: 'fs_changed';
|
|
249
|
+
session: BackendSessionRef;
|
|
250
|
+
paths: string[];
|
|
251
|
+
} | {
|
|
252
|
+
type: 'process_output';
|
|
253
|
+
session: BackendSessionRef;
|
|
254
|
+
processId: string;
|
|
255
|
+
chunk: string;
|
|
256
|
+
} | {
|
|
257
|
+
type: 'process_exited';
|
|
258
|
+
session: BackendSessionRef;
|
|
259
|
+
processId: string;
|
|
260
|
+
exitCode: number;
|
|
261
|
+
} | {
|
|
262
|
+
type: 'approval_requested';
|
|
263
|
+
session: BackendSessionRef;
|
|
264
|
+
requestId: string;
|
|
265
|
+
request: ApprovalRequest;
|
|
266
|
+
} | {
|
|
267
|
+
type: 'user_input_requested';
|
|
268
|
+
session: BackendSessionRef;
|
|
269
|
+
requestId: string;
|
|
270
|
+
request: UserInputRequest;
|
|
271
|
+
} | {
|
|
272
|
+
type: 'turn_completed';
|
|
273
|
+
session: BackendSessionRef;
|
|
274
|
+
usage?: UsageInfo;
|
|
275
|
+
} | {
|
|
276
|
+
type: 'turn_cancelled';
|
|
277
|
+
session: BackendSessionRef;
|
|
278
|
+
} | {
|
|
279
|
+
type: 'error';
|
|
280
|
+
session?: BackendSessionRef;
|
|
281
|
+
message: string;
|
|
282
|
+
};
|
|
283
|
+
export interface StartOpts {
|
|
284
|
+
workDir: string;
|
|
285
|
+
/** Backend-neutral permission mode; backend maps to its own sandbox/permission flags. */
|
|
286
|
+
permissions?: {
|
|
287
|
+
mode: SandboxMode;
|
|
288
|
+
additionalWritableDirs?: string[];
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
export interface EnsureSessionOpts {
|
|
292
|
+
conversationId: string;
|
|
293
|
+
workDir: string;
|
|
294
|
+
resumeSessionId?: string;
|
|
295
|
+
/** Optional metadata that backends with rich session metadata (Claude's `recapId`, `briefingDate` etc.) can store. */
|
|
296
|
+
metadata?: Record<string, unknown>;
|
|
297
|
+
}
|
|
298
|
+
export interface UserTurnInput {
|
|
299
|
+
text: string;
|
|
300
|
+
files?: BackendFileAttachment[];
|
|
301
|
+
/**
|
|
302
|
+
* Backend-specific per-turn metadata. ClaudeBackend forwards these as
|
|
303
|
+
* `HandleChatOptions` (brainMode, recapId, briefingDate, devops*, projectName,
|
|
304
|
+
* icmId, etc.). Codex/other backends may ignore unknown keys. The runtime
|
|
305
|
+
* passes this through opaquely; only the backend interprets it.
|
|
306
|
+
*/
|
|
307
|
+
metadata?: Record<string, unknown>;
|
|
308
|
+
}
|
|
309
|
+
export interface ApprovalAnswer {
|
|
310
|
+
requestId: string;
|
|
311
|
+
decision: 'allow' | 'deny';
|
|
312
|
+
reason?: string;
|
|
313
|
+
}
|
|
314
|
+
export interface UserInputAnswer {
|
|
315
|
+
requestId: string;
|
|
316
|
+
/** questionText -> selected option label */
|
|
317
|
+
answers: Record<string, string>;
|
|
318
|
+
}
|
|
319
|
+
export type EventListener = (event: NormalizedEvent) => void;
|
|
320
|
+
/**
|
|
321
|
+
* Backend interface. Methods marked optional are capability-gated: callers must
|
|
322
|
+
* check `capabilities` before invoking. Required methods all backends must
|
|
323
|
+
* implement (even if as no-ops or throws).
|
|
324
|
+
*/
|
|
325
|
+
export interface AgentBackend {
|
|
326
|
+
readonly type: BackendType;
|
|
327
|
+
readonly capabilities: BackendCapabilities;
|
|
328
|
+
start(opts: StartOpts): Promise<void>;
|
|
329
|
+
shutdown(): Promise<void>;
|
|
330
|
+
ensureSession(opts: EnsureSessionOpts): Promise<BackendSessionRef>;
|
|
331
|
+
startTurn(session: BackendSessionRef, input: UserTurnInput): Promise<void>;
|
|
332
|
+
interruptTurn(session: BackendSessionRef): Promise<void>;
|
|
333
|
+
shutdownSession?(session: BackendSessionRef): Promise<void>;
|
|
334
|
+
answerUserInput(answer: UserInputAnswer): void;
|
|
335
|
+
answerApproval(answer: ApprovalAnswer): void;
|
|
336
|
+
listSessions(workDir: string): Promise<BackendSessionInfo[]>;
|
|
337
|
+
readSession(workDir: string, session: BackendSessionRef): Promise<BackendHistoryMessage[]>;
|
|
338
|
+
renameSession?(workDir: string, session: BackendSessionRef, title: string): Promise<boolean>;
|
|
339
|
+
deleteSession?(workDir: string, session: BackendSessionRef): Promise<boolean>;
|
|
340
|
+
compact?(session: BackendSessionRef): Promise<void>;
|
|
341
|
+
fork?(session: BackendSessionRef, fromMessageId?: string): Promise<BackendSessionRef>;
|
|
342
|
+
getAccountInfo?(): Promise<{
|
|
343
|
+
usage?: UsageInfo;
|
|
344
|
+
limits?: unknown;
|
|
345
|
+
}>;
|
|
346
|
+
listModels?(): Promise<Array<{
|
|
347
|
+
id: string;
|
|
348
|
+
label?: string;
|
|
349
|
+
}>>;
|
|
350
|
+
/**
|
|
351
|
+
* PR4-C (Codex finding B): keyed by conversationId, NOT BackendSessionRef.
|
|
352
|
+
* Passing null targets the global default model. claude.ts uses
|
|
353
|
+
* `pendingModels.set(convId, model)` for not-yet-started conversations
|
|
354
|
+
* — the convId must be passed straight through.
|
|
355
|
+
*/
|
|
356
|
+
setModel?(conversationId: string | null, model: string | null): void;
|
|
357
|
+
getEffectiveModel?(conversationId?: string): string | null;
|
|
358
|
+
isCompacting?(conversationId?: string): boolean;
|
|
359
|
+
/** Returns null if no conversation exists for convId (caller should fall
|
|
360
|
+
* back to createPlaceholderSession). */
|
|
361
|
+
restartSession?(conversationId: string, opts?: RestartOpts): BackendRestartResult | null;
|
|
362
|
+
/** Create a placeholder + workDir entry so the next ensureSession picks
|
|
363
|
+
* up sticky preferences. Used when no live conversation exists. */
|
|
364
|
+
createPlaceholderSession?(conversationId: string, workDir: string, opts?: RestartOpts): void;
|
|
365
|
+
/** True if the backend currently has a live conversation for convId. */
|
|
366
|
+
hasConversation?(conversationId: string): boolean;
|
|
367
|
+
on(listener: EventListener): () => void;
|
|
368
|
+
}
|
|
369
|
+
/** Helper: minimal capabilities object for a stub/unknown backend. */
|
|
370
|
+
export declare function emptyCapabilities(): BackendCapabilities;
|
|
371
|
+
/**
|
|
372
|
+
* Capabilities declared by the ClaudeBackend adapter (current implementation
|
|
373
|
+
* as of 2026-05-09). These reflect what the adapter exposes through the
|
|
374
|
+
* AgentBackend interface — not the full surface of the underlying claude CLI.
|
|
375
|
+
* For example, claude.ts has restartConversation and an interactive /compact
|
|
376
|
+
* flow, but neither is wired through AgentBackend.fork / AgentBackend.compact
|
|
377
|
+
* yet, so both are reported as unsupported.
|
|
378
|
+
*/
|
|
379
|
+
export declare function claudeCapabilities(): BackendCapabilities;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend abstraction for AgentLink.
|
|
3
|
+
*
|
|
4
|
+
* Stage 1: defines the interface only. ClaudeBackend implementation lives in
|
|
5
|
+
* agent/src/claude.ts (refactor to backends/claude/* is a follow-up PR).
|
|
6
|
+
*/
|
|
7
|
+
export const KNOWN_BACKEND_TYPES = ['claude', 'codex'];
|
|
8
|
+
/** Helper: minimal capabilities object for a stub/unknown backend. */
|
|
9
|
+
export function emptyCapabilities() {
|
|
10
|
+
const cap = () => ({ supported: false });
|
|
11
|
+
return {
|
|
12
|
+
lifecycle: {
|
|
13
|
+
persistentProcess: false,
|
|
14
|
+
resumableSessions: false,
|
|
15
|
+
concurrentTurns: false,
|
|
16
|
+
backgroundTurns: false,
|
|
17
|
+
fork: cap(),
|
|
18
|
+
compaction: cap(),
|
|
19
|
+
},
|
|
20
|
+
approvals: {
|
|
21
|
+
command: cap(),
|
|
22
|
+
fileChange: cap(),
|
|
23
|
+
permissions: cap(),
|
|
24
|
+
genericTool: cap(),
|
|
25
|
+
userInput: cap(),
|
|
26
|
+
},
|
|
27
|
+
streaming: {
|
|
28
|
+
text: false,
|
|
29
|
+
reasoning: cap(),
|
|
30
|
+
plan: cap(),
|
|
31
|
+
usage: { supported: false },
|
|
32
|
+
processOutput: cap(),
|
|
33
|
+
fsWatch: cap(),
|
|
34
|
+
remoteControl: { supported: false },
|
|
35
|
+
},
|
|
36
|
+
history: { list: false, read: false, rename: false, delete: false, search: false },
|
|
37
|
+
models: { list: cap(), switchPerSession: false, switchPerTurn: false },
|
|
38
|
+
tools: { itemTypes: [], fallbackRenderer: false, specializedRenderers: [] },
|
|
39
|
+
integrations: {
|
|
40
|
+
accountInfo: cap(),
|
|
41
|
+
codeReview: cap(),
|
|
42
|
+
mcpManagement: cap(),
|
|
43
|
+
hooks: cap(),
|
|
44
|
+
personalitySwitch: cap(),
|
|
45
|
+
skills: cap(),
|
|
46
|
+
},
|
|
47
|
+
sandboxModes: [],
|
|
48
|
+
controls: {
|
|
49
|
+
setModel: cap(),
|
|
50
|
+
restartSession: cap(),
|
|
51
|
+
brainMode: cap(),
|
|
52
|
+
planMode: cap(),
|
|
53
|
+
permissionMode: { supported: false },
|
|
54
|
+
isCompacting: cap(),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Capabilities declared by the ClaudeBackend adapter (current implementation
|
|
60
|
+
* as of 2026-05-09). These reflect what the adapter exposes through the
|
|
61
|
+
* AgentBackend interface — not the full surface of the underlying claude CLI.
|
|
62
|
+
* For example, claude.ts has restartConversation and an interactive /compact
|
|
63
|
+
* flow, but neither is wired through AgentBackend.fork / AgentBackend.compact
|
|
64
|
+
* yet, so both are reported as unsupported.
|
|
65
|
+
*/
|
|
66
|
+
export function claudeCapabilities() {
|
|
67
|
+
const yes = (details) => ({ supported: true, details });
|
|
68
|
+
const no = () => ({ supported: false });
|
|
69
|
+
return {
|
|
70
|
+
lifecycle: {
|
|
71
|
+
persistentProcess: true,
|
|
72
|
+
resumableSessions: true,
|
|
73
|
+
concurrentTurns: true, // multiple conversations = multiple processes
|
|
74
|
+
backgroundTurns: false,
|
|
75
|
+
fork: no(), // restartConversation exists but its signature doesn't match AgentBackend.fork
|
|
76
|
+
compaction: no(), // claude.ts has no programmatic compact API
|
|
77
|
+
},
|
|
78
|
+
approvals: {
|
|
79
|
+
command: no(), // Claude uses bypassPermissions; no per-command approval
|
|
80
|
+
fileChange: no(),
|
|
81
|
+
permissions: no(),
|
|
82
|
+
genericTool: yes(), // tool permission via permission-prompt-tool stdio
|
|
83
|
+
userInput: yes(), // AskUserQuestion via control_request
|
|
84
|
+
},
|
|
85
|
+
streaming: {
|
|
86
|
+
text: true,
|
|
87
|
+
reasoning: yes(), // thinking blocks
|
|
88
|
+
plan: yes(), // EnterPlanMode tool
|
|
89
|
+
usage: { supported: true, details: { incremental: false } },
|
|
90
|
+
processOutput: no(), // Bash background mode is approximated, not first-class
|
|
91
|
+
fsWatch: no(),
|
|
92
|
+
remoteControl: { supported: false },
|
|
93
|
+
},
|
|
94
|
+
history: { list: true, read: true, rename: true, delete: true, search: true },
|
|
95
|
+
models: { list: no(), switchPerSession: true, switchPerTurn: false },
|
|
96
|
+
tools: {
|
|
97
|
+
itemTypes: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'Task', 'Agent', 'WebFetch', 'WebSearch', 'TodoWrite'],
|
|
98
|
+
fallbackRenderer: true,
|
|
99
|
+
specializedRenderers: ['Edit', 'EnterPlanMode'],
|
|
100
|
+
},
|
|
101
|
+
integrations: {
|
|
102
|
+
accountInfo: no(),
|
|
103
|
+
codeReview: no(),
|
|
104
|
+
mcpManagement: no(),
|
|
105
|
+
hooks: no(),
|
|
106
|
+
personalitySwitch: no(),
|
|
107
|
+
skills: no(),
|
|
108
|
+
},
|
|
109
|
+
sandboxModes: ['unrestricted'], // Claude runs with bypassPermissions
|
|
110
|
+
controls: {
|
|
111
|
+
setModel: yes(),
|
|
112
|
+
restartSession: yes(),
|
|
113
|
+
brainMode: yes(),
|
|
114
|
+
planMode: yes(),
|
|
115
|
+
permissionMode: { supported: true, details: { modes: ['normal', 'acceptEdits', 'auto', 'default', 'plan'] } },
|
|
116
|
+
isCompacting: yes(),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/backends/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,CAAC,MAAM,mBAAmB,GAA2B,CAAC,QAAQ,EAAE,OAAO,CAAU,CAAC;AA0TxF,sEAAsE;AACtE,MAAM,UAAU,iBAAiB;IAC/B,MAAM,GAAG,GAAG,GAAe,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IACrD,OAAO;QACL,SAAS,EAAE;YACT,iBAAiB,EAAE,KAAK;YACxB,iBAAiB,EAAE,KAAK;YACxB,eAAe,EAAE,KAAK;YACtB,eAAe,EAAE,KAAK;YACtB,IAAI,EAAE,GAAG,EAAE;YACX,UAAU,EAAE,GAAG,EAAE;SAClB;QACD,SAAS,EAAE;YACT,OAAO,EAAE,GAAG,EAAE;YACd,UAAU,EAAE,GAAG,EAAE;YACjB,WAAW,EAAE,GAAG,EAAE;YAClB,WAAW,EAAE,GAAG,EAAE;YAClB,SAAS,EAAE,GAAG,EAAE;SACjB;QACD,SAAS,EAAE;YACT,IAAI,EAAE,KAAK;YACX,SAAS,EAAE,GAAG,EAAE;YAChB,IAAI,EAAE,GAAG,EAAE;YACX,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;YAC3B,aAAa,EAAE,GAAG,EAAE;YACpB,OAAO,EAAE,GAAG,EAAE;YACd,aAAa,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;SACpC;QACD,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;QAClF,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE;QACtE,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,EAAE;QAC3E,YAAY,EAAE;YACZ,WAAW,EAAE,GAAG,EAAE;YAClB,UAAU,EAAE,GAAG,EAAE;YACjB,aAAa,EAAE,GAAG,EAAE;YACpB,KAAK,EAAE,GAAG,EAAE;YACZ,iBAAiB,EAAE,GAAG,EAAE;YACxB,MAAM,EAAE,GAAG,EAAE;SACd;QACD,YAAY,EAAE,EAAE;QAChB,QAAQ,EAAE;YACR,QAAQ,EAAE,GAAG,EAAE;YACf,cAAc,EAAE,GAAG,EAAE;YACrB,SAAS,EAAE,GAAG,EAAE;YAChB,QAAQ,EAAE,GAAG,EAAE;YACf,cAAc,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;YACpC,YAAY,EAAE,GAAG,EAAE;SACpB;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,CAAC,OAAiB,EAAc,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9E,MAAM,EAAE,GAAG,GAAe,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,OAAO;QACL,SAAS,EAAE;YACT,iBAAiB,EAAE,IAAI;YACvB,iBAAiB,EAAE,IAAI;YACvB,eAAe,EAAE,IAAI,EAAE,8CAA8C;YACrE,eAAe,EAAE,KAAK;YACtB,IAAI,EAAE,EAAE,EAAE,EAAW,+EAA+E;YACpG,UAAU,EAAE,EAAE,EAAE,EAAK,4CAA4C;SAClE;QACD,SAAS,EAAE;YACT,OAAO,EAAE,EAAE,EAAE,EAAQ,yDAAyD;YAC9E,UAAU,EAAE,EAAE,EAAE;YAChB,WAAW,EAAE,EAAE,EAAE;YACjB,WAAW,EAAE,GAAG,EAAE,EAAG,mDAAmD;YACxE,SAAS,EAAE,GAAG,EAAE,EAAK,sCAAsC;SAC5D;QACD,SAAS,EAAE;YACT,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,GAAG,EAAE,EAAK,kBAAkB;YACvC,IAAI,EAAE,GAAG,EAAE,EAAU,qBAAqB;YAC1C,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE;YAC3D,aAAa,EAAE,EAAE,EAAE,EAAE,wDAAwD;YAC7E,OAAO,EAAE,EAAE,EAAE;YACb,aAAa,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;SACpC;QACD,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAC7E,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE;QACpE,KAAK,EAAE;YACL,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,CAAC;YACnH,gBAAgB,EAAE,IAAI;YACtB,oBAAoB,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC;SAChD;QACD,YAAY,EAAE;YACZ,WAAW,EAAE,EAAE,EAAE;YACjB,UAAU,EAAE,EAAE,EAAE;YAChB,aAAa,EAAE,EAAE,EAAE;YACnB,KAAK,EAAE,EAAE,EAAE;YACX,iBAAiB,EAAE,EAAE,EAAE;YACvB,MAAM,EAAE,EAAE,EAAE;SACb;QACD,YAAY,EAAE,CAAC,cAAc,CAAC,EAAE,qCAAqC;QACrE,QAAQ,EAAE;YACR,QAAQ,EAAE,GAAG,EAAE;YACf,cAAc,EAAE,GAAG,EAAE;YACrB,SAAS,EAAE,GAAG,EAAE;YAChB,QAAQ,EAAE,GAAG,EAAE;YACf,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAA4B,EAAE,EAAE;YACxI,YAAY,EAAE,GAAG,EAAE;SACpB;KACF,CAAC;AACJ,CAAC"}
|
package/dist/claude.d.ts
CHANGED
|
@@ -74,9 +74,29 @@ export declare function removeCloseObserver(fn: CloseObserverFn): void;
|
|
|
74
74
|
export declare function setCloseObserver(fn: CloseObserverFn): void;
|
|
75
75
|
/** @deprecated Use removeCloseObserver() instead. Clears ALL close observers. */
|
|
76
76
|
export declare function clearCloseObserver(): void;
|
|
77
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Subscribe a SendFn additively. Returns an unsubscribe function. Use this
|
|
79
|
+
* when multiple subscribers should each receive every outbound frame (e.g.
|
|
80
|
+
* the legacy connection.ts path and the ClaudeBackend adapter coexisting).
|
|
81
|
+
*/
|
|
82
|
+
export declare function addSendFn(fn: SendFn): () => void;
|
|
83
|
+
/**
|
|
84
|
+
* Legacy replace-one semantics: clear all existing subscribers then install
|
|
85
|
+
* just this one. Returns an unsubscribe function. Existing call sites should
|
|
86
|
+
* migrate to {@link addSendFn} when they need additive behavior.
|
|
87
|
+
*/
|
|
88
|
+
export declare function setSendFn(fn: SendFn): () => void;
|
|
78
89
|
type SessionStartedFn = (conversationId: string, claudeSessionId: string) => void;
|
|
79
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Subscribe a callback for every Claude session-started event additively.
|
|
92
|
+
* Returns an unsubscribe function.
|
|
93
|
+
*/
|
|
94
|
+
export declare function addOnSessionStarted(fn: SessionStartedFn): () => void;
|
|
95
|
+
/**
|
|
96
|
+
* Legacy replace-one semantics for session-started subscriber. Existing call
|
|
97
|
+
* sites should migrate to {@link addOnSessionStarted}.
|
|
98
|
+
*/
|
|
99
|
+
export declare function setOnSessionStarted(fn: SessionStartedFn): () => void;
|
|
80
100
|
/**
|
|
81
101
|
* Set the model override for a conversation.
|
|
82
102
|
* If the conversation has a claudeSessionId, persists to disk.
|
package/dist/claude.js
CHANGED
|
@@ -53,7 +53,21 @@ const conversations = new Map();
|
|
|
53
53
|
const lastSessionIdsByConv = new Map();
|
|
54
54
|
/** Pending model override per conversation (set before first message, before sessionId is known). */
|
|
55
55
|
const pendingModels = new Map();
|
|
56
|
-
|
|
56
|
+
// Multi-subscriber send fanout. Historically a single closure; switched to a
|
|
57
|
+
// Set so multiple callers (e.g. ClaudeBackend adapter + connection.ts) can
|
|
58
|
+
// each subscribe and receive every outbound frame. Each subscriber is invoked
|
|
59
|
+
// in a try/catch so one throwing handler doesn't block the others.
|
|
60
|
+
const sendFns = new Set();
|
|
61
|
+
const sendFn = (msg) => {
|
|
62
|
+
for (const fn of sendFns) {
|
|
63
|
+
try {
|
|
64
|
+
fn(msg);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
console.error('[claude] sendFn subscriber threw', e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
57
71
|
const pendingControlRequests = new Map();
|
|
58
72
|
const outputObservers = [];
|
|
59
73
|
export function addOutputObserver(fn) {
|
|
@@ -89,12 +103,40 @@ export function setCloseObserver(fn) {
|
|
|
89
103
|
export function clearCloseObserver() {
|
|
90
104
|
closeObservers.length = 0;
|
|
91
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Subscribe a SendFn additively. Returns an unsubscribe function. Use this
|
|
108
|
+
* when multiple subscribers should each receive every outbound frame (e.g.
|
|
109
|
+
* the legacy connection.ts path and the ClaudeBackend adapter coexisting).
|
|
110
|
+
*/
|
|
111
|
+
export function addSendFn(fn) {
|
|
112
|
+
sendFns.add(fn);
|
|
113
|
+
return () => { sendFns.delete(fn); };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Legacy replace-one semantics: clear all existing subscribers then install
|
|
117
|
+
* just this one. Returns an unsubscribe function. Existing call sites should
|
|
118
|
+
* migrate to {@link addSendFn} when they need additive behavior.
|
|
119
|
+
*/
|
|
92
120
|
export function setSendFn(fn) {
|
|
93
|
-
|
|
121
|
+
sendFns.clear();
|
|
122
|
+
return addSendFn(fn);
|
|
94
123
|
}
|
|
95
|
-
|
|
124
|
+
const sessionStartedFns = new Set();
|
|
125
|
+
/**
|
|
126
|
+
* Subscribe a callback for every Claude session-started event additively.
|
|
127
|
+
* Returns an unsubscribe function.
|
|
128
|
+
*/
|
|
129
|
+
export function addOnSessionStarted(fn) {
|
|
130
|
+
sessionStartedFns.add(fn);
|
|
131
|
+
return () => { sessionStartedFns.delete(fn); };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Legacy replace-one semantics for session-started subscriber. Existing call
|
|
135
|
+
* sites should migrate to {@link addOnSessionStarted}.
|
|
136
|
+
*/
|
|
96
137
|
export function setOnSessionStarted(fn) {
|
|
97
|
-
|
|
138
|
+
sessionStartedFns.clear();
|
|
139
|
+
return addOnSessionStarted(fn);
|
|
98
140
|
}
|
|
99
141
|
/**
|
|
100
142
|
* Set the model override for a conversation.
|
|
@@ -897,8 +939,14 @@ async function processOutput(child, state, stderr) {
|
|
|
897
939
|
// Notify web client so sidebar can refresh session list
|
|
898
940
|
sendWithConvId({ type: 'session_started', claudeSessionId: state.claudeSessionId });
|
|
899
941
|
// Check if this chat is linked to an action item → call CLI start
|
|
900
|
-
|
|
901
|
-
|
|
942
|
+
for (const fn of sessionStartedFns) {
|
|
943
|
+
try {
|
|
944
|
+
fn(state.conversationId, state.claudeSessionId);
|
|
945
|
+
}
|
|
946
|
+
catch (e) {
|
|
947
|
+
console.error('[claude] onSessionStarted subscriber threw', e);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
902
950
|
// Persist pending model to conversation-models.json now that we have a sessionId
|
|
903
951
|
const pendingModel = pendingModels.get(state.conversationId);
|
|
904
952
|
if (pendingModel) {
|