@bbigbang/runtime-acp 0.1.0
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/acp/client.d.ts +101 -0
- package/dist/acp/client.js +747 -0
- package/dist/acp/jsonrpc.d.ts +31 -0
- package/dist/acp/jsonrpc.js +18 -0
- package/dist/acp/stdio.d.ts +15 -0
- package/dist/acp/stdio.js +201 -0
- package/dist/acp/types.d.ts +171 -0
- package/dist/acp/types.js +1 -0
- package/dist/db/db.d.ts +3 -0
- package/dist/db/db.js +10 -0
- package/dist/db/deliveryCheckpointStore.d.ts +21 -0
- package/dist/db/deliveryCheckpointStore.js +28 -0
- package/dist/db/migrations.d.ts +2 -0
- package/dist/db/migrations.js +6196 -0
- package/dist/db/uiPrefStore.d.ts +4 -0
- package/dist/db/uiPrefStore.js +18 -0
- package/dist/gateway/bindingRuntime.d.ts +113 -0
- package/dist/gateway/bindingRuntime.js +1267 -0
- package/dist/gateway/history.d.ts +7 -0
- package/dist/gateway/history.js +146 -0
- package/dist/gateway/sessionStore.d.ts +79 -0
- package/dist/gateway/sessionStore.js +126 -0
- package/dist/gateway/toolAuth.d.ts +36 -0
- package/dist/gateway/toolAuth.js +372 -0
- package/dist/gateway/types.d.ts +79 -0
- package/dist/gateway/types.js +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +19 -0
- package/dist/logging.d.ts +7 -0
- package/dist/logging.js +28 -0
- package/dist/runtime/lock.d.ts +5 -0
- package/dist/runtime/lock.js +87 -0
- package/dist/runtime/workspaceLockManager.d.ts +23 -0
- package/dist/runtime/workspaceLockManager.js +141 -0
- package/dist/tools/workspace.d.ts +1 -0
- package/dist/tools/workspace.js +13 -0
- package/package.json +38 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { resolveWorkspacePath } from '../tools/workspace.js';
|
|
3
|
+
export const TOOL_KINDS = [
|
|
4
|
+
'read',
|
|
5
|
+
'edit',
|
|
6
|
+
'delete',
|
|
7
|
+
'move',
|
|
8
|
+
'search',
|
|
9
|
+
'execute',
|
|
10
|
+
'think',
|
|
11
|
+
'fetch',
|
|
12
|
+
'switch_mode',
|
|
13
|
+
'other',
|
|
14
|
+
];
|
|
15
|
+
export function parseToolKind(value) {
|
|
16
|
+
if (typeof value !== 'string')
|
|
17
|
+
return null;
|
|
18
|
+
const normalized = value.trim().toLowerCase();
|
|
19
|
+
return TOOL_KINDS.includes(normalized)
|
|
20
|
+
? normalized
|
|
21
|
+
: null;
|
|
22
|
+
}
|
|
23
|
+
const PATH_PREFIX_TOOL_KINDS = new Set([
|
|
24
|
+
'read',
|
|
25
|
+
'edit',
|
|
26
|
+
'delete',
|
|
27
|
+
'move',
|
|
28
|
+
]);
|
|
29
|
+
export class ToolAuth {
|
|
30
|
+
db;
|
|
31
|
+
onceGrants = new Map();
|
|
32
|
+
constructor(db) {
|
|
33
|
+
this.db = db;
|
|
34
|
+
}
|
|
35
|
+
grantOnce(sessionKey, toolKind, count = 1) {
|
|
36
|
+
const perSession = this.onceGrants.get(sessionKey) ?? new Map();
|
|
37
|
+
perSession.set(toolKind, (perSession.get(toolKind) ?? 0) + count);
|
|
38
|
+
this.onceGrants.set(sessionKey, perSession);
|
|
39
|
+
}
|
|
40
|
+
setPersistentPolicy(bindingKey, toolKind, policy) {
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
this.db
|
|
43
|
+
.prepare(`
|
|
44
|
+
INSERT INTO tool_policies(binding_key, tool_kind, policy, created_at, updated_at)
|
|
45
|
+
VALUES(?, ?, ?, ?, ?)
|
|
46
|
+
ON CONFLICT(binding_key, tool_kind) DO UPDATE SET
|
|
47
|
+
policy = excluded.policy,
|
|
48
|
+
updated_at = excluded.updated_at
|
|
49
|
+
`)
|
|
50
|
+
.run(bindingKey, toolKind, policy, now, now);
|
|
51
|
+
}
|
|
52
|
+
getPersistentPolicy(bindingKey, toolKind) {
|
|
53
|
+
const row = this.db
|
|
54
|
+
.prepare('SELECT policy FROM tool_policies WHERE binding_key = ? AND tool_kind = ? LIMIT 1')
|
|
55
|
+
.get(bindingKey, toolKind);
|
|
56
|
+
return row?.policy ?? null;
|
|
57
|
+
}
|
|
58
|
+
listPersistentPolicies(bindingKey, policy) {
|
|
59
|
+
const rows = policy
|
|
60
|
+
? this.db
|
|
61
|
+
.prepare(`
|
|
62
|
+
SELECT tool_kind as toolKind, policy
|
|
63
|
+
FROM tool_policies
|
|
64
|
+
WHERE binding_key = ? AND policy = ?
|
|
65
|
+
ORDER BY tool_kind ASC
|
|
66
|
+
`)
|
|
67
|
+
.all(bindingKey, policy)
|
|
68
|
+
: this.db
|
|
69
|
+
.prepare(`
|
|
70
|
+
SELECT tool_kind as toolKind, policy
|
|
71
|
+
FROM tool_policies
|
|
72
|
+
WHERE binding_key = ?
|
|
73
|
+
ORDER BY tool_kind ASC
|
|
74
|
+
`)
|
|
75
|
+
.all(bindingKey);
|
|
76
|
+
return rows
|
|
77
|
+
.map((row) => {
|
|
78
|
+
const toolKind = parseToolKind(row.toolKind);
|
|
79
|
+
if (!toolKind)
|
|
80
|
+
return null;
|
|
81
|
+
return { toolKind, policy: row.policy };
|
|
82
|
+
})
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
}
|
|
85
|
+
clearPersistentPolicy(bindingKey, toolKind, policy) {
|
|
86
|
+
const result = policy
|
|
87
|
+
? this.db
|
|
88
|
+
.prepare(`
|
|
89
|
+
DELETE FROM tool_policies
|
|
90
|
+
WHERE binding_key = ? AND tool_kind = ? AND policy = ?
|
|
91
|
+
`)
|
|
92
|
+
.run(bindingKey, toolKind, policy)
|
|
93
|
+
: this.db
|
|
94
|
+
.prepare(`
|
|
95
|
+
DELETE FROM tool_policies
|
|
96
|
+
WHERE binding_key = ? AND tool_kind = ?
|
|
97
|
+
`)
|
|
98
|
+
.run(bindingKey, toolKind);
|
|
99
|
+
return result.changes > 0;
|
|
100
|
+
}
|
|
101
|
+
clearPersistentPolicies(bindingKey, policy) {
|
|
102
|
+
const result = policy
|
|
103
|
+
? this.db
|
|
104
|
+
.prepare(`
|
|
105
|
+
DELETE FROM tool_policies
|
|
106
|
+
WHERE binding_key = ? AND policy = ?
|
|
107
|
+
`)
|
|
108
|
+
.run(bindingKey, policy)
|
|
109
|
+
: this.db
|
|
110
|
+
.prepare(`
|
|
111
|
+
DELETE FROM tool_policies
|
|
112
|
+
WHERE binding_key = ?
|
|
113
|
+
`)
|
|
114
|
+
.run(bindingKey);
|
|
115
|
+
return result.changes;
|
|
116
|
+
}
|
|
117
|
+
setAllowPrefixRule(bindingKey, toolKind, argPrefix) {
|
|
118
|
+
const normalizedPrefix = normalizeStoredPrefix(toolKind, argPrefix);
|
|
119
|
+
if (!normalizedPrefix) {
|
|
120
|
+
throw new Error('Invalid allow prefix.');
|
|
121
|
+
}
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
this.db
|
|
124
|
+
.prepare(`
|
|
125
|
+
INSERT INTO tool_allow_prefixes(binding_key, tool_kind, arg_prefix, created_at, updated_at)
|
|
126
|
+
VALUES(?, ?, ?, ?, ?)
|
|
127
|
+
ON CONFLICT(binding_key, tool_kind, arg_prefix) DO UPDATE SET
|
|
128
|
+
updated_at = excluded.updated_at
|
|
129
|
+
`)
|
|
130
|
+
.run(bindingKey, toolKind, normalizedPrefix, now, now);
|
|
131
|
+
}
|
|
132
|
+
listAllowPrefixRules(bindingKey, toolKind) {
|
|
133
|
+
const rows = toolKind
|
|
134
|
+
? this.db
|
|
135
|
+
.prepare(`
|
|
136
|
+
SELECT tool_kind as toolKind, arg_prefix as argPrefix
|
|
137
|
+
FROM tool_allow_prefixes
|
|
138
|
+
WHERE binding_key = ? AND tool_kind = ?
|
|
139
|
+
ORDER BY tool_kind ASC, arg_prefix ASC
|
|
140
|
+
`)
|
|
141
|
+
.all(bindingKey, toolKind)
|
|
142
|
+
: this.db
|
|
143
|
+
.prepare(`
|
|
144
|
+
SELECT tool_kind as toolKind, arg_prefix as argPrefix
|
|
145
|
+
FROM tool_allow_prefixes
|
|
146
|
+
WHERE binding_key = ?
|
|
147
|
+
ORDER BY tool_kind ASC, arg_prefix ASC
|
|
148
|
+
`)
|
|
149
|
+
.all(bindingKey);
|
|
150
|
+
return rows
|
|
151
|
+
.map((row) => {
|
|
152
|
+
const parsedKind = parseToolKind(row.toolKind);
|
|
153
|
+
if (!parsedKind)
|
|
154
|
+
return null;
|
|
155
|
+
const normalizedPrefix = normalizeStoredPrefix(parsedKind, row.argPrefix);
|
|
156
|
+
if (!normalizedPrefix)
|
|
157
|
+
return null;
|
|
158
|
+
return {
|
|
159
|
+
toolKind: parsedKind,
|
|
160
|
+
argPrefix: normalizedPrefix,
|
|
161
|
+
};
|
|
162
|
+
})
|
|
163
|
+
.filter(Boolean);
|
|
164
|
+
}
|
|
165
|
+
clearAllowPrefixRule(bindingKey, toolKind, argPrefix) {
|
|
166
|
+
const normalizedPrefix = normalizeStoredPrefix(toolKind, argPrefix);
|
|
167
|
+
if (!normalizedPrefix)
|
|
168
|
+
return false;
|
|
169
|
+
const result = this.db
|
|
170
|
+
.prepare(`
|
|
171
|
+
DELETE FROM tool_allow_prefixes
|
|
172
|
+
WHERE binding_key = ? AND tool_kind = ? AND arg_prefix = ?
|
|
173
|
+
`)
|
|
174
|
+
.run(bindingKey, toolKind, normalizedPrefix);
|
|
175
|
+
return result.changes > 0;
|
|
176
|
+
}
|
|
177
|
+
clearAllowPrefixRules(bindingKey, toolKind) {
|
|
178
|
+
const result = toolKind
|
|
179
|
+
? this.db
|
|
180
|
+
.prepare(`
|
|
181
|
+
DELETE FROM tool_allow_prefixes
|
|
182
|
+
WHERE binding_key = ? AND tool_kind = ?
|
|
183
|
+
`)
|
|
184
|
+
.run(bindingKey, toolKind)
|
|
185
|
+
: this.db
|
|
186
|
+
.prepare(`
|
|
187
|
+
DELETE FROM tool_allow_prefixes
|
|
188
|
+
WHERE binding_key = ?
|
|
189
|
+
`)
|
|
190
|
+
.run(bindingKey);
|
|
191
|
+
return result.changes;
|
|
192
|
+
}
|
|
193
|
+
evaluatePersistentPolicy(bindingKey, toolKind, context) {
|
|
194
|
+
const policy = this.getPersistentPolicy(bindingKey, toolKind);
|
|
195
|
+
if (policy === 'reject')
|
|
196
|
+
return 'reject';
|
|
197
|
+
if (policy === 'allow')
|
|
198
|
+
return 'allow';
|
|
199
|
+
return this.matchesAllowPrefixRule(bindingKey, toolKind, context)
|
|
200
|
+
? 'allow'
|
|
201
|
+
: null;
|
|
202
|
+
}
|
|
203
|
+
consume(sessionKey, toolKind, context) {
|
|
204
|
+
const bindingRow = this.db
|
|
205
|
+
.prepare('SELECT binding_key as bindingKey FROM bindings WHERE session_key = ? LIMIT 1')
|
|
206
|
+
.get(sessionKey);
|
|
207
|
+
if (!bindingRow)
|
|
208
|
+
return false;
|
|
209
|
+
const persistent = this.evaluatePersistentPolicy(bindingRow.bindingKey, toolKind, context);
|
|
210
|
+
if (persistent === 'reject')
|
|
211
|
+
return false;
|
|
212
|
+
if (persistent === 'allow')
|
|
213
|
+
return true;
|
|
214
|
+
const perSession = this.onceGrants.get(sessionKey);
|
|
215
|
+
const remaining = perSession?.get(toolKind) ?? 0;
|
|
216
|
+
if (remaining <= 0)
|
|
217
|
+
return false;
|
|
218
|
+
perSession.set(toolKind, remaining - 1);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
matchesAllowPrefixRule(bindingKey, toolKind, context) {
|
|
222
|
+
if (!context)
|
|
223
|
+
return false;
|
|
224
|
+
const rules = this.listAllowPrefixRules(bindingKey, toolKind);
|
|
225
|
+
if (rules.length === 0)
|
|
226
|
+
return false;
|
|
227
|
+
const candidates = extractMatchCandidates(toolKind, context);
|
|
228
|
+
if (candidates.length === 0)
|
|
229
|
+
return false;
|
|
230
|
+
for (const rule of rules) {
|
|
231
|
+
if (candidates.some((candidate) => prefixMatches(toolKind, candidate, rule.argPrefix))) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function extractMatchCandidates(toolKind, context) {
|
|
239
|
+
const out = [];
|
|
240
|
+
const seen = new Set();
|
|
241
|
+
const push = (raw) => {
|
|
242
|
+
if (typeof raw !== 'string')
|
|
243
|
+
return;
|
|
244
|
+
const normalized = normalizeCandidate(toolKind, raw, context.workspaceRoot);
|
|
245
|
+
if (!normalized || seen.has(normalized))
|
|
246
|
+
return;
|
|
247
|
+
seen.add(normalized);
|
|
248
|
+
out.push(normalized);
|
|
249
|
+
};
|
|
250
|
+
const params = asRecord(context.params);
|
|
251
|
+
const method = String(context.method ?? '').trim();
|
|
252
|
+
if (method === 'fs/read_text_file' || method === 'fs/write_text_file') {
|
|
253
|
+
push(params?.path);
|
|
254
|
+
}
|
|
255
|
+
if (method === 'terminal/create') {
|
|
256
|
+
push(formatCommandLine(params?.command, params?.args));
|
|
257
|
+
}
|
|
258
|
+
const toolCall = asRecord(context.toolCall);
|
|
259
|
+
if (toolCall) {
|
|
260
|
+
push(toolCall.path);
|
|
261
|
+
push(getPathValue(toolCall, 'arguments.path'));
|
|
262
|
+
push(getPathValue(toolCall, 'input.path'));
|
|
263
|
+
push(formatCommandLine(toolCall.command, toolCall.args));
|
|
264
|
+
push(formatCommandLine(getPathValue(toolCall, 'arguments.command'), getPathValue(toolCall, 'arguments.args')));
|
|
265
|
+
push(extractTargetFromToolTitle(toolKind, toolCall.title));
|
|
266
|
+
}
|
|
267
|
+
if (PATH_PREFIX_TOOL_KINDS.has(toolKind)) {
|
|
268
|
+
push(params?.file);
|
|
269
|
+
push(params?.target);
|
|
270
|
+
push(params?.uri);
|
|
271
|
+
}
|
|
272
|
+
else if (toolKind === 'execute') {
|
|
273
|
+
push(params?.command);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
push(params?.path);
|
|
277
|
+
push(params?.query);
|
|
278
|
+
push(params?.pattern);
|
|
279
|
+
push(params?.text);
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
function normalizeCandidate(toolKind, raw, workspaceRoot) {
|
|
284
|
+
if (PATH_PREFIX_TOOL_KINDS.has(toolKind)) {
|
|
285
|
+
return normalizePathPrefix(raw, workspaceRoot);
|
|
286
|
+
}
|
|
287
|
+
return normalizeTextPrefix(raw);
|
|
288
|
+
}
|
|
289
|
+
function normalizeStoredPrefix(toolKind, raw) {
|
|
290
|
+
if (PATH_PREFIX_TOOL_KINDS.has(toolKind)) {
|
|
291
|
+
return normalizePathPrefix(raw);
|
|
292
|
+
}
|
|
293
|
+
return normalizeTextPrefix(raw);
|
|
294
|
+
}
|
|
295
|
+
function normalizePathPrefix(raw, workspaceRoot) {
|
|
296
|
+
const trimmed = raw.trim();
|
|
297
|
+
if (!trimmed || !path.isAbsolute(trimmed))
|
|
298
|
+
return null;
|
|
299
|
+
if (workspaceRoot) {
|
|
300
|
+
try {
|
|
301
|
+
return resolveWorkspacePath(workspaceRoot, trimmed);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return path.resolve(trimmed);
|
|
308
|
+
}
|
|
309
|
+
function normalizeTextPrefix(raw) {
|
|
310
|
+
const normalized = raw.replace(/\s+/g, ' ').trim();
|
|
311
|
+
return normalized || null;
|
|
312
|
+
}
|
|
313
|
+
function prefixMatches(toolKind, candidate, prefix) {
|
|
314
|
+
if (PATH_PREFIX_TOOL_KINDS.has(toolKind)) {
|
|
315
|
+
return pathPrefixMatches(candidate, prefix);
|
|
316
|
+
}
|
|
317
|
+
return candidate.startsWith(prefix);
|
|
318
|
+
}
|
|
319
|
+
function pathPrefixMatches(candidate, prefix) {
|
|
320
|
+
const normalizedCandidate = path.resolve(candidate);
|
|
321
|
+
const normalizedPrefix = path.resolve(prefix);
|
|
322
|
+
if (normalizedCandidate === normalizedPrefix)
|
|
323
|
+
return true;
|
|
324
|
+
return normalizedCandidate.startsWith(normalizedPrefix + path.sep);
|
|
325
|
+
}
|
|
326
|
+
function asRecord(value) {
|
|
327
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
328
|
+
return null;
|
|
329
|
+
return value;
|
|
330
|
+
}
|
|
331
|
+
function getPathValue(source, pathExpr) {
|
|
332
|
+
const parts = pathExpr.split('.');
|
|
333
|
+
let current = source;
|
|
334
|
+
for (const part of parts) {
|
|
335
|
+
const obj = asRecord(current);
|
|
336
|
+
if (!obj)
|
|
337
|
+
return undefined;
|
|
338
|
+
current = obj[part];
|
|
339
|
+
}
|
|
340
|
+
return current;
|
|
341
|
+
}
|
|
342
|
+
function formatCommandLine(commandRaw, argsRaw) {
|
|
343
|
+
if (typeof commandRaw !== 'string' || !commandRaw.trim())
|
|
344
|
+
return null;
|
|
345
|
+
const command = commandRaw.trim();
|
|
346
|
+
const args = Array.isArray(argsRaw)
|
|
347
|
+
? argsRaw.filter((item) => typeof item === 'string')
|
|
348
|
+
: [];
|
|
349
|
+
const full = args.length > 0 ? `${command} ${args.join(' ')}` : command;
|
|
350
|
+
return normalizeTextPrefix(full);
|
|
351
|
+
}
|
|
352
|
+
function extractTargetFromToolTitle(toolKind, titleRaw) {
|
|
353
|
+
if (typeof titleRaw !== 'string')
|
|
354
|
+
return null;
|
|
355
|
+
const title = titleRaw.trim();
|
|
356
|
+
if (!title)
|
|
357
|
+
return null;
|
|
358
|
+
if (PATH_PREFIX_TOOL_KINDS.has(toolKind)) {
|
|
359
|
+
const match = title.match(/^(?:read|edit|delete|move)\s*:\s*(.+)$/i);
|
|
360
|
+
if (match) {
|
|
361
|
+
return match[1]?.trim() ?? null;
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
if (toolKind === 'execute') {
|
|
366
|
+
const match = title.match(/^run\s*:\s*(.+)$/i);
|
|
367
|
+
if (match) {
|
|
368
|
+
return match[1]?.trim() ?? null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type DeliveryState = {
|
|
2
|
+
text: string;
|
|
3
|
+
messageId: string | null;
|
|
4
|
+
};
|
|
5
|
+
export type UiMode = 'verbose' | 'summary';
|
|
6
|
+
export type ToolUiStage = 'start' | 'update' | 'complete';
|
|
7
|
+
export type PermissionUiRequest = {
|
|
8
|
+
uiMode: UiMode;
|
|
9
|
+
sessionKey: string;
|
|
10
|
+
requestId: string;
|
|
11
|
+
toolTitle: string;
|
|
12
|
+
toolKind: string | null;
|
|
13
|
+
toolName?: string;
|
|
14
|
+
toolArgs?: unknown;
|
|
15
|
+
approvalKind?: 'tool' | 'plan' | 'question';
|
|
16
|
+
title?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
input?: unknown;
|
|
19
|
+
actions?: Array<{
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
variant?: 'primary' | 'secondary' | 'danger';
|
|
23
|
+
requiresInput?: boolean;
|
|
24
|
+
inputPlaceholder?: string;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
export type UiEvent = {
|
|
28
|
+
kind: 'plan' | 'task';
|
|
29
|
+
mode: UiMode;
|
|
30
|
+
title: string;
|
|
31
|
+
detail?: string;
|
|
32
|
+
silent?: boolean;
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'plan_phase';
|
|
35
|
+
mode: UiMode;
|
|
36
|
+
phase: 'planning' | 'implementation';
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'usage';
|
|
39
|
+
mode: UiMode;
|
|
40
|
+
inputTokens?: number;
|
|
41
|
+
cachedInputTokens?: number;
|
|
42
|
+
outputTokens?: number;
|
|
43
|
+
reasoningOutputTokens?: number;
|
|
44
|
+
totalTokens?: number;
|
|
45
|
+
currentInputTokens?: number;
|
|
46
|
+
currentCachedInputTokens?: number;
|
|
47
|
+
modelContextWindow?: number;
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'compact';
|
|
51
|
+
mode: UiMode;
|
|
52
|
+
threadId: string;
|
|
53
|
+
turnId: string;
|
|
54
|
+
itemId?: string;
|
|
55
|
+
source: 'thread_compacted' | 'raw_response_item' | 'thread_item';
|
|
56
|
+
eventKey: string;
|
|
57
|
+
} | {
|
|
58
|
+
kind: 'tool';
|
|
59
|
+
mode: UiMode;
|
|
60
|
+
title: string;
|
|
61
|
+
detail?: string;
|
|
62
|
+
input?: unknown;
|
|
63
|
+
output?: string;
|
|
64
|
+
toolCallId?: string;
|
|
65
|
+
stage?: ToolUiStage;
|
|
66
|
+
status?: string;
|
|
67
|
+
metadata?: Record<string, unknown>;
|
|
68
|
+
};
|
|
69
|
+
export type OutboundSink = {
|
|
70
|
+
sendAgentText?: (text: string) => Promise<void>;
|
|
71
|
+
sendActivityText?: (text: string) => Promise<void>;
|
|
72
|
+
sendThinkingText?: (text: string) => Promise<void>;
|
|
73
|
+
sendText: (text: string) => Promise<void>;
|
|
74
|
+
breakTextStream?: () => Promise<void>;
|
|
75
|
+
flush?: () => Promise<void>;
|
|
76
|
+
getDeliveryState?: () => DeliveryState;
|
|
77
|
+
requestPermission?: (req: PermissionUiRequest) => Promise<void>;
|
|
78
|
+
sendUi?: (event: UiEvent) => Promise<void>;
|
|
79
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { AcpClient } from './acp/client.js';
|
|
2
|
+
export type { PermissionRequest, PermissionDecision, AcpClientEvents, ClientToolEvent } from './acp/client.js';
|
|
3
|
+
export { spawnAcpAgent } from './acp/stdio.js';
|
|
4
|
+
export type { StdioProcess } from './acp/stdio.js';
|
|
5
|
+
export { isRequest, isResponse, isNotification, } from './acp/jsonrpc.js';
|
|
6
|
+
export type { JsonRpcRequest, JsonRpcResponse, JsonRpcNotification, JsonRpcMessage, } from './acp/jsonrpc.js';
|
|
7
|
+
export type { InitializeParams, InitializeResult, NewSessionParams, NewSessionResult, PromptParams, PromptResult, ContentBlock, RequestPermissionParams, McpServerEntry, } from './acp/types.js';
|
|
8
|
+
export { BindingRuntime } from './gateway/bindingRuntime.js';
|
|
9
|
+
export type { RuntimeConfig } from './gateway/bindingRuntime.js';
|
|
10
|
+
export { createSession, createRun, finishRun, getSession, getBinding, upsertBinding, updateAcpSessionId, updateSessionRuntimeState, clearAcpSessionId, updateLoadSupported, updateClaudeSessionControls, insertClaudeSessionUserMessage, getLatestClaudeSessionUserMessage, bindingKeyFromConversationKey, SHARED_CHAT_SCOPE_USER_ID, } from './gateway/sessionStore.js';
|
|
11
|
+
export type { Platform, ConversationKey, SessionBinding, StoredClaudeSessionModeId } from './gateway/sessionStore.js';
|
|
12
|
+
export { ToolAuth, parseToolKind, TOOL_KINDS } from './gateway/toolAuth.js';
|
|
13
|
+
export type { ToolKind } from './gateway/toolAuth.js';
|
|
14
|
+
export { buildReplayContextFromRecentRuns } from './gateway/history.js';
|
|
15
|
+
export type { OutboundSink, PermissionUiRequest, UiEvent, UiMode, ToolUiStage, DeliveryState, } from './gateway/types.js';
|
|
16
|
+
export { openDb } from './db/db.js';
|
|
17
|
+
export type { Db } from './db/db.js';
|
|
18
|
+
export { migrate } from './db/migrations.js';
|
|
19
|
+
export { getUiMode, setUiMode } from './db/uiPrefStore.js';
|
|
20
|
+
export { upsertDeliveryCheckpoint, getDeliveryCheckpoint, } from './db/deliveryCheckpointStore.js';
|
|
21
|
+
export { acquireProcessLock } from './runtime/lock.js';
|
|
22
|
+
export type { ProcessLock } from './runtime/lock.js';
|
|
23
|
+
export { WorkspaceLockManager } from './runtime/workspaceLockManager.js';
|
|
24
|
+
export type { WorkspaceLockLease } from './runtime/workspaceLockManager.js';
|
|
25
|
+
export { resolveWorkspacePath } from './tools/workspace.js';
|
|
26
|
+
export { log } from './logging.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// ACP protocol layer
|
|
2
|
+
export { AcpClient } from './acp/client.js';
|
|
3
|
+
export { spawnAcpAgent } from './acp/stdio.js';
|
|
4
|
+
export { isRequest, isResponse, isNotification, } from './acp/jsonrpc.js';
|
|
5
|
+
// Gateway / runtime layer
|
|
6
|
+
export { BindingRuntime } from './gateway/bindingRuntime.js';
|
|
7
|
+
export { createSession, createRun, finishRun, getSession, getBinding, upsertBinding, updateAcpSessionId, updateSessionRuntimeState, clearAcpSessionId, updateLoadSupported, updateClaudeSessionControls, insertClaudeSessionUserMessage, getLatestClaudeSessionUserMessage, bindingKeyFromConversationKey, SHARED_CHAT_SCOPE_USER_ID, } from './gateway/sessionStore.js';
|
|
8
|
+
export { ToolAuth, parseToolKind, TOOL_KINDS } from './gateway/toolAuth.js';
|
|
9
|
+
export { buildReplayContextFromRecentRuns } from './gateway/history.js';
|
|
10
|
+
// Database layer
|
|
11
|
+
export { openDb } from './db/db.js';
|
|
12
|
+
export { migrate } from './db/migrations.js';
|
|
13
|
+
export { getUiMode, setUiMode } from './db/uiPrefStore.js';
|
|
14
|
+
export { upsertDeliveryCheckpoint, getDeliveryCheckpoint, } from './db/deliveryCheckpointStore.js';
|
|
15
|
+
// Utilities
|
|
16
|
+
export { acquireProcessLock } from './runtime/lock.js';
|
|
17
|
+
export { WorkspaceLockManager } from './runtime/workspaceLockManager.js';
|
|
18
|
+
export { resolveWorkspacePath } from './tools/workspace.js';
|
|
19
|
+
export { log } from './logging.js';
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const levelOrder = {
|
|
2
|
+
debug: 10,
|
|
3
|
+
info: 20,
|
|
4
|
+
warn: 30,
|
|
5
|
+
error: 40,
|
|
6
|
+
};
|
|
7
|
+
const currentLevel = process.env.LOG_LEVEL ?? 'info';
|
|
8
|
+
function shouldLog(level) {
|
|
9
|
+
return levelOrder[level] >= levelOrder[currentLevel];
|
|
10
|
+
}
|
|
11
|
+
export const log = {
|
|
12
|
+
debug: (...args) => {
|
|
13
|
+
if (shouldLog('debug'))
|
|
14
|
+
console.log('[debug]', ...args);
|
|
15
|
+
},
|
|
16
|
+
info: (...args) => {
|
|
17
|
+
if (shouldLog('info'))
|
|
18
|
+
console.log('[info]', ...args);
|
|
19
|
+
},
|
|
20
|
+
warn: (...args) => {
|
|
21
|
+
if (shouldLog('warn'))
|
|
22
|
+
console.warn('[warn]', ...args);
|
|
23
|
+
},
|
|
24
|
+
error: (...args) => {
|
|
25
|
+
if (shouldLog('error'))
|
|
26
|
+
console.error('[error]', ...args);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function acquireProcessLock(lockPath) {
|
|
4
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
5
|
+
try {
|
|
6
|
+
const fd = fs.openSync(lockPath, 'wx');
|
|
7
|
+
try {
|
|
8
|
+
fs.writeFileSync(fd, JSON.stringify({
|
|
9
|
+
pid: process.pid,
|
|
10
|
+
startedAt: Date.now(),
|
|
11
|
+
}, null, 2) + '\n', 'utf8');
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
fs.closeSync(fd);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
path: lockPath,
|
|
18
|
+
release: () => {
|
|
19
|
+
try {
|
|
20
|
+
fs.unlinkSync(lockPath);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// ignore
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (err?.code !== 'EEXIST')
|
|
30
|
+
throw err;
|
|
31
|
+
// If lock exists, verify the process is alive.
|
|
32
|
+
const existing = readLockFile(lockPath);
|
|
33
|
+
if (existing?.pid && isPidAlive(existing.pid)) {
|
|
34
|
+
throw new Error(`Another instance is running (pid=${existing.pid})`, {
|
|
35
|
+
cause: err,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// Stale lock.
|
|
39
|
+
try {
|
|
40
|
+
fs.unlinkSync(lockPath);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// ignore
|
|
44
|
+
}
|
|
45
|
+
// Retry once.
|
|
46
|
+
const fd = fs.openSync(lockPath, 'wx');
|
|
47
|
+
try {
|
|
48
|
+
fs.writeFileSync(fd, JSON.stringify({
|
|
49
|
+
pid: process.pid,
|
|
50
|
+
startedAt: Date.now(),
|
|
51
|
+
}, null, 2) + '\n', 'utf8');
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
fs.closeSync(fd);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
path: lockPath,
|
|
58
|
+
release: () => {
|
|
59
|
+
try {
|
|
60
|
+
fs.unlinkSync(lockPath);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// ignore
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function isPidAlive(pid) {
|
|
70
|
+
try {
|
|
71
|
+
process.kill(pid, 0);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function readLockFile(lockPath) {
|
|
79
|
+
try {
|
|
80
|
+
const raw = fs.readFileSync(lockPath, 'utf8');
|
|
81
|
+
const parsed = JSON.parse(raw);
|
|
82
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type WorkspaceLockLease = {
|
|
2
|
+
key: string;
|
|
3
|
+
waited: boolean;
|
|
4
|
+
release: () => void;
|
|
5
|
+
};
|
|
6
|
+
type WorkspaceLockHooks = {
|
|
7
|
+
onWaitStart?: () => void;
|
|
8
|
+
onAcquired?: (params: {
|
|
9
|
+
waited: boolean;
|
|
10
|
+
}) => void;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
};
|
|
13
|
+
export declare class WorkspaceLockManager {
|
|
14
|
+
private readonly locks;
|
|
15
|
+
normalizeKey(workspaceRoot: string): string;
|
|
16
|
+
acquire(workspaceRoot: string, hooks?: WorkspaceLockHooks): Promise<WorkspaceLockLease>;
|
|
17
|
+
runExclusive<T>(workspaceRoot: string, action: () => Promise<T> | T, hooks?: WorkspaceLockHooks): Promise<T>;
|
|
18
|
+
runAfterPendingWrites<T>(workspaceRoot: string, action: () => Promise<T> | T, hooks?: WorkspaceLockHooks): Promise<T>;
|
|
19
|
+
waitForPendingWrites(workspaceRoot: string, hooks?: WorkspaceLockHooks): Promise<void>;
|
|
20
|
+
private getState;
|
|
21
|
+
private release;
|
|
22
|
+
}
|
|
23
|
+
export {};
|