agent-director 0.4.1 → 0.4.2
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/client.d.ts +99 -0
- package/dist/errors.d.ts +173 -0
- package/dist/ffi.d.ts +42 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +603 -0
- package/dist/internal/bindingSpec.d.ts +37 -0
- package/dist/internal/bootstrapFfi.d.ts +37 -0
- package/dist/internal/freeGuard.d.ts +39 -0
- package/dist/internal/tilde.d.ts +16 -0
- package/dist/internal/tsOnlyErrors.d.ts +25 -0
- package/dist/internal/verbs.d.ts +57 -0
- package/dist/internal/worker.d.ts +36 -0
- package/dist/internal/workerProxy.d.ts +45 -0
- package/dist/platform.d.ts +80 -0
- package/dist/types.d.ts +301 -0
- package/package.json +4 -4
- package/skills/install-agent-director/SKILL.md +1 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/internal/tilde.ts
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
function expandTilde(p) {
|
|
5
|
+
if (p === "")
|
|
6
|
+
return "";
|
|
7
|
+
if (p !== "~" && !p.startsWith("~/"))
|
|
8
|
+
return p;
|
|
9
|
+
const home = process.env["HOME"] ?? os.homedir();
|
|
10
|
+
if (p === "~")
|
|
11
|
+
return home;
|
|
12
|
+
return home + p.slice(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/internal/bootstrapFfi.ts
|
|
16
|
+
import { dlopen, FFIType, CString, ptr } from "bun:ffi";
|
|
17
|
+
|
|
18
|
+
// src/platform.ts
|
|
19
|
+
import { existsSync } from "fs";
|
|
20
|
+
import { fileURLToPath } from "url";
|
|
21
|
+
import { join, dirname } from "path";
|
|
22
|
+
|
|
23
|
+
// src/errors.ts
|
|
24
|
+
class AgentDirectorError extends Error {
|
|
25
|
+
verb;
|
|
26
|
+
errName;
|
|
27
|
+
errDescription;
|
|
28
|
+
constructor(verb, err_name, err_description) {
|
|
29
|
+
super(`${err_name}: ${err_description}`);
|
|
30
|
+
this.verb = verb;
|
|
31
|
+
this.errName = err_name;
|
|
32
|
+
this.errDescription = err_description;
|
|
33
|
+
this.name = this.constructor.name;
|
|
34
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class ErrClientClosed extends AgentDirectorError {
|
|
39
|
+
constructor() {
|
|
40
|
+
super("", "ErrClientClosed", "client is closed: call new Client() to obtain a fresh handle");
|
|
41
|
+
this.name = "ErrClientClosed";
|
|
42
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class ErrUnsupportedPlatform extends AgentDirectorError {
|
|
47
|
+
constructor(tuple) {
|
|
48
|
+
super("", "ErrUnsupportedPlatform", `platform/arch tuple "${tuple}" is not supported; supported: linux-x64, darwin-arm64`);
|
|
49
|
+
this.name = "ErrUnsupportedPlatform";
|
|
50
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class ErrPlatformPackageMissing extends AgentDirectorError {
|
|
55
|
+
constructor(pkgName, detail) {
|
|
56
|
+
const desc = detail ? `native sub-package "${pkgName}" is not usable: ${detail}` : `native sub-package "${pkgName}" is not installed or its binary is absent`;
|
|
57
|
+
super("", "ErrPlatformPackageMissing", desc);
|
|
58
|
+
this.name = "ErrPlatformPackageMissing";
|
|
59
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class ErrBunVersionTooOld extends AgentDirectorError {
|
|
64
|
+
constructor(actual, minimum) {
|
|
65
|
+
super("", "ErrBunVersionTooOld", `Bun ${actual} is below the minimum required version ${minimum}; upgrade Bun to continue`);
|
|
66
|
+
this.name = "ErrBunVersionTooOld";
|
|
67
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class ErrCwdMissing extends AgentDirectorError {
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class ErrCwdNotAPath extends AgentDirectorError {
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class ErrCwdNotFound extends AgentDirectorError {
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class ErrCwdNotADirectory extends AgentDirectorError {
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class ErrRelayModeInvalid extends AgentDirectorError {
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class ErrSpawnDeniedFlag extends AgentDirectorError {
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class ErrReservedEnvKey extends AgentDirectorError {
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class ErrInstanceIdCollision extends AgentDirectorError {
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class ErrTmuxSessionNameEmpty extends AgentDirectorError {
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class ErrTmuxSessionNameInvalid extends AgentDirectorError {
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class ErrTmuxSessionNameTooLong extends AgentDirectorError {
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class ErrSpawnNotFound extends AgentDirectorError {
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
class ErrTmuxNotAvailable extends AgentDirectorError {
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class ErrTmuxSessionCreate extends AgentDirectorError {
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class ErrTmuxSendKeys extends AgentDirectorError {
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class ErrTmuxCaptureFailed extends AgentDirectorError {
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
class ErrSpawnNotInteractive extends AgentDirectorError {
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class ErrSendKeysWhileRelayed extends AgentDirectorError {
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
class ErrSpawnNotPausable extends AgentDirectorError {
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class ErrPauseTimeout extends AgentDirectorError {
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
class ErrListInvalidLabel extends AgentDirectorError {
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
class ErrTemplateNameUnsafe extends AgentDirectorError {
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
class ErrTemplateNotFound extends AgentDirectorError {
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
class ErrTemplateMalformed extends AgentDirectorError {
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class ErrTemplateExists extends AgentDirectorError {
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
class ErrProbeUnsupported extends AgentDirectorError {
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
class ErrSpawnNotResumable extends AgentDirectorError {
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
class ErrNoSessionId extends AgentDirectorError {
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
class ErrJsonlMissing extends AgentDirectorError {
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
class ErrRelayModeOff extends AgentDirectorError {
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
class ErrInvalidDecision extends AgentDirectorError {
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
class ErrNoOpenPermissionRequest extends AgentDirectorError {
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
class ErrAlreadyDecided extends AgentDirectorError {
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
class ErrUnknownHandle extends AgentDirectorError {
|
|
171
|
+
}
|
|
172
|
+
var ERROR_TABLE = {
|
|
173
|
+
ErrCwdMissing,
|
|
174
|
+
ErrCwdNotAPath,
|
|
175
|
+
ErrCwdNotFound,
|
|
176
|
+
ErrCwdNotADirectory,
|
|
177
|
+
ErrRelayModeInvalid,
|
|
178
|
+
ErrSpawnDeniedFlag,
|
|
179
|
+
ErrReservedEnvKey,
|
|
180
|
+
ErrInstanceIdCollision,
|
|
181
|
+
ErrTmuxSessionNameEmpty,
|
|
182
|
+
ErrTmuxSessionNameInvalid,
|
|
183
|
+
ErrTmuxSessionNameTooLong,
|
|
184
|
+
ErrSpawnNotFound,
|
|
185
|
+
ErrNoOpenPermissionRequest,
|
|
186
|
+
ErrAlreadyDecided,
|
|
187
|
+
ErrTmuxNotAvailable,
|
|
188
|
+
ErrTmuxSessionCreate,
|
|
189
|
+
ErrTmuxSendKeys,
|
|
190
|
+
ErrTmuxCaptureFailed,
|
|
191
|
+
ErrSpawnNotInteractive,
|
|
192
|
+
ErrSendKeysWhileRelayed,
|
|
193
|
+
ErrSpawnNotPausable,
|
|
194
|
+
ErrPauseTimeout,
|
|
195
|
+
ErrListInvalidLabel,
|
|
196
|
+
ErrSpawnNotResumable,
|
|
197
|
+
ErrNoSessionId,
|
|
198
|
+
ErrJsonlMissing,
|
|
199
|
+
ErrRelayModeOff,
|
|
200
|
+
ErrInvalidDecision,
|
|
201
|
+
ErrTemplateNameUnsafe,
|
|
202
|
+
ErrTemplateNotFound,
|
|
203
|
+
ErrTemplateMalformed,
|
|
204
|
+
ErrTemplateExists,
|
|
205
|
+
ErrProbeUnsupported,
|
|
206
|
+
ErrUnknownHandle
|
|
207
|
+
};
|
|
208
|
+
function errorFromEnvelope(verb, err_name, err_description) {
|
|
209
|
+
const Ctor = ERROR_TABLE[err_name];
|
|
210
|
+
if (Ctor) {
|
|
211
|
+
return new Ctor(verb, err_name, err_description);
|
|
212
|
+
}
|
|
213
|
+
console.warn(`agent-director: unknown err_name "${err_name}" (verb=${verb}); returning base AgentDirectorError`);
|
|
214
|
+
return new AgentDirectorError(verb, err_name, err_description);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/internal/verbs.ts
|
|
218
|
+
var VERBS = [
|
|
219
|
+
"spawn",
|
|
220
|
+
"status",
|
|
221
|
+
"get",
|
|
222
|
+
"send-keys",
|
|
223
|
+
"read-pane",
|
|
224
|
+
"kill",
|
|
225
|
+
"decide",
|
|
226
|
+
"resume",
|
|
227
|
+
"find-missing",
|
|
228
|
+
"expire",
|
|
229
|
+
"delete",
|
|
230
|
+
"make-template",
|
|
231
|
+
"list",
|
|
232
|
+
"pause",
|
|
233
|
+
"version"
|
|
234
|
+
];
|
|
235
|
+
function kebabToUnderscore(v) {
|
|
236
|
+
return v.replace(/-/g, "_");
|
|
237
|
+
}
|
|
238
|
+
var HANDLE_FREE_VERBS = new Set(["version"]);
|
|
239
|
+
|
|
240
|
+
// src/internal/bindingSpec.ts
|
|
241
|
+
var BINDING_SYMBOL_NAMES = [
|
|
242
|
+
"ad_open",
|
|
243
|
+
"ad_close",
|
|
244
|
+
"ad_free_cstring",
|
|
245
|
+
...VERBS.map((v) => "ad_" + kebabToUnderscore(v))
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
// src/platform.ts
|
|
249
|
+
var MIN_BUN_VERSION = "1.0.21";
|
|
250
|
+
var SUPPORTED_TUPLES = new Map([
|
|
251
|
+
["linux-x64", "@agent-director/linux-x64"],
|
|
252
|
+
["darwin-arm64", "@agent-director/darwin-arm64"]
|
|
253
|
+
]);
|
|
254
|
+
function compareSemver(a, b) {
|
|
255
|
+
const parse = (s) => s.split(".").map((p) => parseInt(p, 10) || 0);
|
|
256
|
+
const pa = parse(a);
|
|
257
|
+
const pb = parse(b);
|
|
258
|
+
for (let i = 0;i < 3; i++) {
|
|
259
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
260
|
+
if (diff !== 0)
|
|
261
|
+
return diff;
|
|
262
|
+
}
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
function resolveNativePath(opts = {}) {
|
|
266
|
+
const platform = opts.platform ?? process.platform;
|
|
267
|
+
const arch = opts.arch ?? process.arch;
|
|
268
|
+
const bunVersion = opts.bunVersion ?? Bun.version;
|
|
269
|
+
if (compareSemver(bunVersion, MIN_BUN_VERSION) < 0) {
|
|
270
|
+
throw new ErrBunVersionTooOld(bunVersion, MIN_BUN_VERSION);
|
|
271
|
+
}
|
|
272
|
+
const tuple = `${platform}-${arch}`;
|
|
273
|
+
const subpkgName = SUPPORTED_TUPLES.get(tuple);
|
|
274
|
+
if (!subpkgName) {
|
|
275
|
+
throw new ErrUnsupportedPlatform(tuple);
|
|
276
|
+
}
|
|
277
|
+
const binaryExt = platform === "darwin" ? "dylib" : "so";
|
|
278
|
+
const binaryName = `libagent_director.${binaryExt}`;
|
|
279
|
+
const cabiDir = process.env.AD_CABI_DIR;
|
|
280
|
+
if (cabiDir) {
|
|
281
|
+
const cabiPath = join(cabiDir, binaryName);
|
|
282
|
+
if (!existsSync(cabiPath)) {
|
|
283
|
+
throw new ErrPlatformPackageMissing(subpkgName, `AD_CABI_DIR is set but "${binaryName}" not found in ${cabiDir}`);
|
|
284
|
+
}
|
|
285
|
+
return cabiPath;
|
|
286
|
+
}
|
|
287
|
+
let pkgDir;
|
|
288
|
+
try {
|
|
289
|
+
const resolved = import.meta.resolve(`${subpkgName}/package.json`);
|
|
290
|
+
pkgDir = dirname(fileURLToPath(resolved));
|
|
291
|
+
} catch {
|
|
292
|
+
throw new ErrPlatformPackageMissing(subpkgName);
|
|
293
|
+
}
|
|
294
|
+
const libPath = join(pkgDir, binaryName);
|
|
295
|
+
if (!existsSync(libPath)) {
|
|
296
|
+
throw new ErrPlatformPackageMissing(subpkgName, `binary "${binaryName}" not found in package directory ${pkgDir}`);
|
|
297
|
+
}
|
|
298
|
+
return libPath;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/internal/bootstrapFfi.ts
|
|
302
|
+
var _soPath = resolveNativePath();
|
|
303
|
+
var _lib = dlopen(_soPath, {
|
|
304
|
+
ad_open: {
|
|
305
|
+
args: [FFIType.cstring],
|
|
306
|
+
returns: FFIType.ptr
|
|
307
|
+
},
|
|
308
|
+
ad_close: {
|
|
309
|
+
args: [FFIType.cstring],
|
|
310
|
+
returns: FFIType.ptr
|
|
311
|
+
},
|
|
312
|
+
ad_free_cstring: {
|
|
313
|
+
args: [FFIType.ptr],
|
|
314
|
+
returns: FFIType.void
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
function callOpen(params) {
|
|
318
|
+
const jsonBytes = Buffer.from(JSON.stringify(params) + "\x00");
|
|
319
|
+
const rawPtr = _lib.symbols.ad_open(ptr(jsonBytes));
|
|
320
|
+
return _readAndFree(rawPtr);
|
|
321
|
+
}
|
|
322
|
+
function callClose(params) {
|
|
323
|
+
const jsonBytes = Buffer.from(JSON.stringify(params) + "\x00");
|
|
324
|
+
const rawPtr = _lib.symbols.ad_close(ptr(jsonBytes));
|
|
325
|
+
return _readAndFree(rawPtr);
|
|
326
|
+
}
|
|
327
|
+
function _readAndFree(rawPtr) {
|
|
328
|
+
if (rawPtr === null || rawPtr === 0) {
|
|
329
|
+
throw new Error("bootstrapFfi: ad_* returned a null pointer");
|
|
330
|
+
}
|
|
331
|
+
const result = new CString(rawPtr).toString();
|
|
332
|
+
_lib.symbols.ad_free_cstring(rawPtr);
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/internal/workerProxy.ts
|
|
337
|
+
import { Worker } from "worker_threads";
|
|
338
|
+
function isErrorEnvelope(v) {
|
|
339
|
+
return typeof v === "object" && v !== null && typeof v.err_name === "string";
|
|
340
|
+
}
|
|
341
|
+
var _worker = null;
|
|
342
|
+
var _nextId = 1;
|
|
343
|
+
var _pending = new Map;
|
|
344
|
+
var _readyResolve = null;
|
|
345
|
+
var _readyReject = null;
|
|
346
|
+
var _readyPromise = null;
|
|
347
|
+
function _rejectAll(reason) {
|
|
348
|
+
for (const entry of _pending.values()) {
|
|
349
|
+
entry.reject(reason);
|
|
350
|
+
}
|
|
351
|
+
_pending.clear();
|
|
352
|
+
}
|
|
353
|
+
function _spawnWorker() {
|
|
354
|
+
const worker = new Worker(new URL("./worker.ts", import.meta.url));
|
|
355
|
+
if (typeof worker.unref === "function") {
|
|
356
|
+
worker.unref();
|
|
357
|
+
}
|
|
358
|
+
_readyPromise = new Promise((resolve, reject) => {
|
|
359
|
+
_readyResolve = resolve;
|
|
360
|
+
_readyReject = reject;
|
|
361
|
+
});
|
|
362
|
+
worker.on("message", (msg) => {
|
|
363
|
+
if (msg.type === "ready") {
|
|
364
|
+
_readyResolve?.();
|
|
365
|
+
_readyResolve = null;
|
|
366
|
+
_readyReject = null;
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (msg.type === "startup-error") {
|
|
370
|
+
const err = new Error(`agent-director worker failed to start: ${msg.message ?? "(unknown)"}`);
|
|
371
|
+
_readyReject?.(err);
|
|
372
|
+
_readyResolve = null;
|
|
373
|
+
_readyReject = null;
|
|
374
|
+
_rejectAll(err);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const entry = _pending.get(msg.id);
|
|
378
|
+
if (!entry)
|
|
379
|
+
return;
|
|
380
|
+
_pending.delete(msg.id);
|
|
381
|
+
if (msg.type === "ffi-error") {
|
|
382
|
+
entry.reject(new Error(`FFI error: ${msg.message ?? "(unknown)"}`));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const jsonString = msg.jsonString ?? "";
|
|
386
|
+
let parsed;
|
|
387
|
+
try {
|
|
388
|
+
parsed = JSON.parse(jsonString);
|
|
389
|
+
} catch (e) {
|
|
390
|
+
entry.reject(new Error(`Failed to parse C-ABI response JSON: ${e instanceof Error ? e.message : String(e)}`));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (isErrorEnvelope(parsed)) {
|
|
394
|
+
entry.reject(errorFromEnvelope(entry.op, parsed.err_name, parsed.err_description));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
entry.resolve(parsed);
|
|
398
|
+
});
|
|
399
|
+
worker.on("error", (err) => {
|
|
400
|
+
const reason = new Error(`agent-director worker error: ${err.message}`);
|
|
401
|
+
_readyReject?.(reason);
|
|
402
|
+
_readyResolve = null;
|
|
403
|
+
_readyReject = null;
|
|
404
|
+
_rejectAll(reason);
|
|
405
|
+
});
|
|
406
|
+
worker.on("exit", (code) => {
|
|
407
|
+
const reason = new Error(`agent-director worker exited with code ${code}`);
|
|
408
|
+
_readyReject?.(reason);
|
|
409
|
+
_readyResolve = null;
|
|
410
|
+
_readyReject = null;
|
|
411
|
+
if (_pending.size > 0) {
|
|
412
|
+
_rejectAll(reason);
|
|
413
|
+
}
|
|
414
|
+
_worker = null;
|
|
415
|
+
_readyPromise = null;
|
|
416
|
+
});
|
|
417
|
+
_worker = worker;
|
|
418
|
+
}
|
|
419
|
+
async function _ensureWorker() {
|
|
420
|
+
if (_worker === null) {
|
|
421
|
+
_spawnWorker();
|
|
422
|
+
}
|
|
423
|
+
await _readyPromise;
|
|
424
|
+
}
|
|
425
|
+
var workerProxy = {
|
|
426
|
+
async dispatch(op, handle, params) {
|
|
427
|
+
await _ensureWorker();
|
|
428
|
+
const id = _nextId++;
|
|
429
|
+
const paramsJSON = JSON.stringify(params ?? {});
|
|
430
|
+
return new Promise((resolve, reject) => {
|
|
431
|
+
_pending.set(id, { resolve, reject, op });
|
|
432
|
+
_worker.postMessage({ id, op, handle, paramsJSON });
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// src/ffi.ts
|
|
438
|
+
async function callVerb(verb, handle, params) {
|
|
439
|
+
return workerProxy.dispatch(verb, handle, params);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/client.ts
|
|
443
|
+
function isErrorEnvelope2(env) {
|
|
444
|
+
return typeof env === "object" && env !== null && "err_name" in env && typeof env.err_name === "string";
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
class Client {
|
|
448
|
+
_handle;
|
|
449
|
+
_open;
|
|
450
|
+
_logger;
|
|
451
|
+
constructor(opts) {
|
|
452
|
+
this._handle = null;
|
|
453
|
+
this._open = false;
|
|
454
|
+
this._logger = opts.logger;
|
|
455
|
+
const storePath = expandTilde(opts.storePath);
|
|
456
|
+
const configPath = opts.configPath !== undefined ? expandTilde(opts.configPath) : undefined;
|
|
457
|
+
const params = { store_path: storePath };
|
|
458
|
+
if (configPath !== undefined)
|
|
459
|
+
params.config_path = configPath;
|
|
460
|
+
if (opts.tmuxCommand !== undefined)
|
|
461
|
+
params.tmux_command = opts.tmuxCommand;
|
|
462
|
+
if (opts.createIfMissing !== undefined)
|
|
463
|
+
params.create_if_missing = opts.createIfMissing;
|
|
464
|
+
const envelopeJSON = callOpen(params);
|
|
465
|
+
const env = JSON.parse(envelopeJSON);
|
|
466
|
+
if (isErrorEnvelope2(env)) {
|
|
467
|
+
throw errorFromEnvelope("open", env.err_name, env.err_description);
|
|
468
|
+
}
|
|
469
|
+
this._handle = env.handle;
|
|
470
|
+
this._open = true;
|
|
471
|
+
}
|
|
472
|
+
_assertOpen() {
|
|
473
|
+
if (!this._open) {
|
|
474
|
+
throw new ErrClientClosed;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
close() {
|
|
478
|
+
if (!this._open)
|
|
479
|
+
return;
|
|
480
|
+
const handle = this._handle;
|
|
481
|
+
try {
|
|
482
|
+
const envelopeJSON = callClose({ handle });
|
|
483
|
+
const env = JSON.parse(envelopeJSON);
|
|
484
|
+
if (isErrorEnvelope2(env)) {
|
|
485
|
+
this._logger?.warn?.(`Client.close(): ad_close returned error envelope`, { err_name: env.err_name, err_description: env.err_description });
|
|
486
|
+
}
|
|
487
|
+
} catch (e) {
|
|
488
|
+
this._logger?.warn?.("Client.close(): unexpected error calling ad_close", e);
|
|
489
|
+
} finally {
|
|
490
|
+
this._handle = null;
|
|
491
|
+
this._open = false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
[Symbol.dispose]() {
|
|
495
|
+
this.close();
|
|
496
|
+
}
|
|
497
|
+
async spawn(params) {
|
|
498
|
+
this._assertOpen();
|
|
499
|
+
return callVerb("spawn", this._handle, params);
|
|
500
|
+
}
|
|
501
|
+
async status(params) {
|
|
502
|
+
this._assertOpen();
|
|
503
|
+
return callVerb("status", this._handle, params);
|
|
504
|
+
}
|
|
505
|
+
async get(params) {
|
|
506
|
+
this._assertOpen();
|
|
507
|
+
return callVerb("get", this._handle, params);
|
|
508
|
+
}
|
|
509
|
+
async sendKeys(params) {
|
|
510
|
+
this._assertOpen();
|
|
511
|
+
return callVerb("send-keys", this._handle, params);
|
|
512
|
+
}
|
|
513
|
+
async readPane(params) {
|
|
514
|
+
this._assertOpen();
|
|
515
|
+
return callVerb("read-pane", this._handle, params);
|
|
516
|
+
}
|
|
517
|
+
async kill(params) {
|
|
518
|
+
this._assertOpen();
|
|
519
|
+
return callVerb("kill", this._handle, params);
|
|
520
|
+
}
|
|
521
|
+
async decide(params) {
|
|
522
|
+
this._assertOpen();
|
|
523
|
+
return callVerb("decide", this._handle, params);
|
|
524
|
+
}
|
|
525
|
+
async resume(params) {
|
|
526
|
+
this._assertOpen();
|
|
527
|
+
return callVerb("resume", this._handle, params);
|
|
528
|
+
}
|
|
529
|
+
async findMissing(params) {
|
|
530
|
+
this._assertOpen();
|
|
531
|
+
return callVerb("find-missing", this._handle, params);
|
|
532
|
+
}
|
|
533
|
+
async expire(params) {
|
|
534
|
+
this._assertOpen();
|
|
535
|
+
return callVerb("expire", this._handle, params);
|
|
536
|
+
}
|
|
537
|
+
async delete(params) {
|
|
538
|
+
this._assertOpen();
|
|
539
|
+
return callVerb("delete", this._handle, params);
|
|
540
|
+
}
|
|
541
|
+
async makeTemplate(params) {
|
|
542
|
+
this._assertOpen();
|
|
543
|
+
return callVerb("make-template", this._handle, params);
|
|
544
|
+
}
|
|
545
|
+
async list(params) {
|
|
546
|
+
this._assertOpen();
|
|
547
|
+
return callVerb("list", this._handle, params);
|
|
548
|
+
}
|
|
549
|
+
async pause(params) {
|
|
550
|
+
this._assertOpen();
|
|
551
|
+
return callVerb("pause", this._handle, params);
|
|
552
|
+
}
|
|
553
|
+
async version(params) {
|
|
554
|
+
this._assertOpen();
|
|
555
|
+
return callVerb("version", null, params);
|
|
556
|
+
}
|
|
557
|
+
_assertOpenForTests() {
|
|
558
|
+
this._assertOpen();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
export {
|
|
562
|
+
errorFromEnvelope,
|
|
563
|
+
ErrUnsupportedPlatform,
|
|
564
|
+
ErrUnknownHandle,
|
|
565
|
+
ErrTmuxSessionNameTooLong,
|
|
566
|
+
ErrTmuxSessionNameInvalid,
|
|
567
|
+
ErrTmuxSessionNameEmpty,
|
|
568
|
+
ErrTmuxSessionCreate,
|
|
569
|
+
ErrTmuxSendKeys,
|
|
570
|
+
ErrTmuxNotAvailable,
|
|
571
|
+
ErrTmuxCaptureFailed,
|
|
572
|
+
ErrTemplateNotFound,
|
|
573
|
+
ErrTemplateNameUnsafe,
|
|
574
|
+
ErrTemplateMalformed,
|
|
575
|
+
ErrTemplateExists,
|
|
576
|
+
ErrSpawnNotResumable,
|
|
577
|
+
ErrSpawnNotPausable,
|
|
578
|
+
ErrSpawnNotInteractive,
|
|
579
|
+
ErrSpawnNotFound,
|
|
580
|
+
ErrSpawnDeniedFlag,
|
|
581
|
+
ErrSendKeysWhileRelayed,
|
|
582
|
+
ErrReservedEnvKey,
|
|
583
|
+
ErrRelayModeOff,
|
|
584
|
+
ErrRelayModeInvalid,
|
|
585
|
+
ErrProbeUnsupported,
|
|
586
|
+
ErrPlatformPackageMissing,
|
|
587
|
+
ErrPauseTimeout,
|
|
588
|
+
ErrNoSessionId,
|
|
589
|
+
ErrNoOpenPermissionRequest,
|
|
590
|
+
ErrListInvalidLabel,
|
|
591
|
+
ErrJsonlMissing,
|
|
592
|
+
ErrInvalidDecision,
|
|
593
|
+
ErrInstanceIdCollision,
|
|
594
|
+
ErrCwdNotFound,
|
|
595
|
+
ErrCwdNotAPath,
|
|
596
|
+
ErrCwdNotADirectory,
|
|
597
|
+
ErrCwdMissing,
|
|
598
|
+
ErrClientClosed,
|
|
599
|
+
ErrBunVersionTooOld,
|
|
600
|
+
ErrAlreadyDecided,
|
|
601
|
+
Client,
|
|
602
|
+
AgentDirectorError
|
|
603
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bindingSpec — canonical set of C-ABI symbol names expected in the dlopen
|
|
3
|
+
* binding object, plus a factory that builds the runtime FFI binding spec.
|
|
4
|
+
*
|
|
5
|
+
* BINDING_SYMBOL_NAMES is exported as a plain string array so the
|
|
6
|
+
* binding-coverage test can import it WITHOUT spawning a worker or loading
|
|
7
|
+
* the native library.
|
|
8
|
+
*
|
|
9
|
+
* buildBindingSpec() returns the full FFI binding object suitable for passing
|
|
10
|
+
* directly to Bun's dlopen(). It is the single source of truth consumed by
|
|
11
|
+
* platform.ts::loadNative() so that worker.ts and bootstrapFfi.ts both
|
|
12
|
+
* resolve the same symbols without duplication.
|
|
13
|
+
*
|
|
14
|
+
* Any drift between BINDING_SYMBOL_NAMES and buildBindingSpec() is caught
|
|
15
|
+
* by test/ffi-binding-coverage.test.ts.
|
|
16
|
+
*/
|
|
17
|
+
import { FFIType } from "bun:ffi";
|
|
18
|
+
/**
|
|
19
|
+
* BINDING_SYMBOL_NAMES is the exhaustive list of C symbols that the worker's
|
|
20
|
+
* dlopen binding object must declare.
|
|
21
|
+
*/
|
|
22
|
+
export declare const BINDING_SYMBOL_NAMES: readonly string[];
|
|
23
|
+
/** A single FFI function declaration (args + return type). */
|
|
24
|
+
export interface FFISymbolDef {
|
|
25
|
+
args: FFIType[];
|
|
26
|
+
returns: FFIType;
|
|
27
|
+
}
|
|
28
|
+
/** The full dlopen binding spec as a plain object — pass to Bun's dlopen(). */
|
|
29
|
+
export type BindingSpec = Record<string, FFISymbolDef>;
|
|
30
|
+
/**
|
|
31
|
+
* buildBindingSpec constructs the full FFI binding map for the native library.
|
|
32
|
+
*
|
|
33
|
+
* Includes lifecycle symbols (ad_open, ad_close, ad_free_cstring) plus one
|
|
34
|
+
* entry per callable verb. All verb symbols take a single cstring argument and
|
|
35
|
+
* return a ptr (raw pointer, freed via ad_free_cstring).
|
|
36
|
+
*/
|
|
37
|
+
export declare function buildBindingSpec(): BindingSpec;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bootstrapFfi — thin synchronous dlopen wrapper used by the Client
|
|
3
|
+
* constructor and close().
|
|
4
|
+
*
|
|
5
|
+
* Only three symbols are needed here: ad_open, ad_close, ad_free_cstring.
|
|
6
|
+
* The full binding spec (all 18 symbols) is used by worker.ts via loadNative().
|
|
7
|
+
*
|
|
8
|
+
* Library path resolution delegates to platform.ts::resolveNativePath() so
|
|
9
|
+
* that all platforms (linux-x64, darwin-arm64) resolve their binary from the
|
|
10
|
+
* correct optional sub-package, and so this file shares the same path logic
|
|
11
|
+
* as the worker.
|
|
12
|
+
*
|
|
13
|
+
* Internal — NOT re-exported from src/index.ts.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* callOpen encodes params as JSON, calls ad_open, copies the returned C
|
|
17
|
+
* string into a JS string, frees the C pointer, and returns the envelope
|
|
18
|
+
* JSON string.
|
|
19
|
+
*
|
|
20
|
+
* Throws if the library call itself fails (i.e. the function pointer is
|
|
21
|
+
* null) — normal Go-level errors are returned as error envelopes, not
|
|
22
|
+
* thrown.
|
|
23
|
+
*/
|
|
24
|
+
export declare function callOpen(params: {
|
|
25
|
+
store_path: string;
|
|
26
|
+
config_path?: string;
|
|
27
|
+
tmux_command?: string;
|
|
28
|
+
create_if_missing?: boolean;
|
|
29
|
+
}): string;
|
|
30
|
+
/**
|
|
31
|
+
* callClose encodes params as JSON, calls ad_close, copies the returned C
|
|
32
|
+
* string into a JS string, frees the C pointer, and returns the envelope
|
|
33
|
+
* JSON string.
|
|
34
|
+
*/
|
|
35
|
+
export declare function callClose(params: {
|
|
36
|
+
handle: string;
|
|
37
|
+
}): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FreeGuard — single-free wrapper for native pointers.
|
|
3
|
+
*
|
|
4
|
+
* Every C-ABI call that returns a pointer must pass it to `ad_free_cstring`
|
|
5
|
+
* exactly once. FreeGuard enforces this:
|
|
6
|
+
*
|
|
7
|
+
* const guard = new FreeGuard(rawPtr, (p) => lib.symbols.ad_free_cstring(p));
|
|
8
|
+
* try {
|
|
9
|
+
* const str = new CString(guard.ptr!).toString();
|
|
10
|
+
* return str;
|
|
11
|
+
* } finally {
|
|
12
|
+
* guard.free(); // safe to call even on error path; no-op on second call
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* Design constraints:
|
|
16
|
+
* - No bun:ffi imports — accepts the free function as a constructor arg so
|
|
17
|
+
* tests can stub it without loading a native library.
|
|
18
|
+
* - Generic over T so the same guard works for Pointer (branded number) or
|
|
19
|
+
* any other pointer-like primitive without widening to `any`.
|
|
20
|
+
* - `free()` nulls the internal reference before calling the underlying
|
|
21
|
+
* free function, so a synchronous exception inside the free function
|
|
22
|
+
* cannot cause a double-free on a subsequent call.
|
|
23
|
+
*/
|
|
24
|
+
export declare class FreeGuard<T> {
|
|
25
|
+
private _ptr;
|
|
26
|
+
private readonly _free;
|
|
27
|
+
constructor(ptr: T, free: (p: T) => void);
|
|
28
|
+
/**
|
|
29
|
+
* ptr returns the guarded pointer, or null if `free()` has already been
|
|
30
|
+
* called. Callers must check for null before dereferencing.
|
|
31
|
+
*/
|
|
32
|
+
get ptr(): T | null;
|
|
33
|
+
/**
|
|
34
|
+
* free calls the underlying free function with the guarded pointer exactly
|
|
35
|
+
* once. After the first call, the internal reference is nulled and
|
|
36
|
+
* subsequent calls are silent no-ops.
|
|
37
|
+
*/
|
|
38
|
+
free(): void;
|
|
39
|
+
}
|