@axiom-lattice/cli-a2a 0.1.4 → 0.1.6
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/.turbo/turbo-build.log +34 -32
- package/CHANGELOG.md +26 -0
- package/README.md +85 -2
- package/__tests__/opencode-executor.test.ts +631 -31
- package/__tests__/session-store-path.test.ts +40 -0
- package/dist/{chunk-NQIDRU47.mjs → chunk-54SKDK3D.mjs} +78 -32
- package/dist/chunk-54SKDK3D.mjs.map +1 -0
- package/dist/chunk-BSNQOP2W.mjs +350 -0
- package/dist/chunk-BSNQOP2W.mjs.map +1 -0
- package/dist/chunk-DRFFQJII.mjs +195 -0
- package/dist/chunk-DRFFQJII.mjs.map +1 -0
- package/dist/chunk-URJDRXJW.mjs +378 -0
- package/dist/chunk-URJDRXJW.mjs.map +1 -0
- package/dist/{chunk-G7AGL2QA.mjs → chunk-WZB7FBSX.mjs} +23 -5
- package/dist/chunk-WZB7FBSX.mjs.map +1 -0
- package/dist/{chunk-LXL47XMZ.mjs → chunk-YGUPSPZO.mjs} +9 -1
- package/dist/chunk-YGUPSPZO.mjs.map +1 -0
- package/dist/{chunk-35NFMGMS.mjs → chunk-ZP3JNJQJ.mjs} +3 -3
- package/dist/chunk-ZP3JNJQJ.mjs.map +1 -0
- package/dist/cli.js +769 -156
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +7 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/executor-LLE5VEFJ.mjs +12 -0
- package/dist/executor-LO7P3KUW.mjs +12 -0
- package/dist/{executor-RVBGAWUF.mjs → executor-OOLGGXHS.mjs} +4 -3
- package/dist/{executors-QIIKBUMJ.mjs → executors-AQHOPGSR.mjs} +2 -2
- package/dist/index.d.mts +92 -21
- package/dist/index.d.ts +92 -21
- package/dist/index.js +731 -123
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +15 -7
- package/package.json +11 -4
- package/runtime/cli-path.cjs +3 -0
- package/runtime/cli-path.d.ts +1 -0
- package/runtime/cli-path.mjs +5 -0
- package/src/config/defaults.ts +3 -1
- package/src/config/types.ts +8 -3
- package/src/executors/claude/client.ts +44 -14
- package/src/executors/claude/executor.ts +65 -30
- package/src/executors/codex/client.ts +239 -16
- package/src/executors/codex/executor.ts +70 -37
- package/src/executors/events.ts +15 -1
- package/src/executors/index.ts +13 -6
- package/src/executors/opencode/client.ts +192 -18
- package/src/executors/opencode/executor.ts +65 -30
- package/src/index.ts +8 -0
- package/src/local-state.ts +53 -0
- package/src/server/agent-card.ts +1 -0
- package/src/server/index.ts +22 -7
- package/src/session-store.ts +262 -0
- package/dist/chunk-35NFMGMS.mjs.map +0 -1
- package/dist/chunk-G7AGL2QA.mjs.map +0 -1
- package/dist/chunk-LXL47XMZ.mjs.map +0 -1
- package/dist/chunk-NQIDRU47.mjs.map +0 -1
- package/dist/chunk-VSZ3DACI.mjs +0 -179
- package/dist/chunk-VSZ3DACI.mjs.map +0 -1
- package/dist/chunk-WNCDOYZS.mjs +0 -187
- package/dist/chunk-WNCDOYZS.mjs.map +0 -1
- package/dist/executor-OWPVDUXH.mjs +0 -11
- package/dist/executor-XWHWUVQ3.mjs +0 -11
- /package/dist/{executor-OWPVDUXH.mjs.map → executor-LLE5VEFJ.mjs.map} +0 -0
- /package/dist/{executor-RVBGAWUF.mjs.map → executor-LO7P3KUW.mjs.map} +0 -0
- /package/dist/{executor-XWHWUVQ3.mjs.map → executor-OOLGGXHS.mjs.map} +0 -0
- /package/dist/{executors-QIIKBUMJ.mjs.map → executors-AQHOPGSR.mjs.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -102,13 +102,13 @@ function registerExecutor(provider, factory) {
|
|
|
102
102
|
function getExecutor(provider) {
|
|
103
103
|
return registry.get(provider);
|
|
104
104
|
}
|
|
105
|
-
function createExecutor(config) {
|
|
105
|
+
function createExecutor(config, sessionStore) {
|
|
106
106
|
const factory = registry.get(config.provider);
|
|
107
107
|
if (!factory) {
|
|
108
108
|
const available = Array.from(registry.keys()).join(", ");
|
|
109
109
|
throw new Error(`Unknown provider: ${config.provider}. Available: ${available}`);
|
|
110
110
|
}
|
|
111
|
-
return factory(config);
|
|
111
|
+
return factory(config, sessionStore);
|
|
112
112
|
}
|
|
113
113
|
var import_sdk, import_uuid, registry;
|
|
114
114
|
var init_executors = __esm({
|
|
@@ -120,20 +120,211 @@ var init_executors = __esm({
|
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
+
// src/session-store.ts
|
|
124
|
+
function resolveSessionStorePath(options) {
|
|
125
|
+
if (options.configuredPath && options.configuredPath.length > 0) {
|
|
126
|
+
return options.configuredPath;
|
|
127
|
+
}
|
|
128
|
+
return (0, import_node_path2.join)(
|
|
129
|
+
options.storeDir ?? DEFAULT_STORE_DIR,
|
|
130
|
+
`sessions-${options.provider}-${options.port}.json`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
function generateContextId() {
|
|
134
|
+
return (0, import_node_crypto.randomUUID)();
|
|
135
|
+
}
|
|
136
|
+
var import_node_path2, import_node_os, import_node_fs2, import_node_crypto, log3, DEFAULT_STORE_DIR, DEFAULT_STORE_PATH, DEFAULT_CLEANUP_INTERVAL_MS, SessionBindingStore;
|
|
137
|
+
var init_session_store = __esm({
|
|
138
|
+
"src/session-store.ts"() {
|
|
139
|
+
"use strict";
|
|
140
|
+
import_node_path2 = require("path");
|
|
141
|
+
import_node_os = require("os");
|
|
142
|
+
import_node_fs2 = require("fs");
|
|
143
|
+
import_node_crypto = require("crypto");
|
|
144
|
+
init_logger();
|
|
145
|
+
log3 = logger.child("session-store");
|
|
146
|
+
DEFAULT_STORE_DIR = (0, import_node_path2.join)(
|
|
147
|
+
(0, import_node_os.homedir)(),
|
|
148
|
+
"Library",
|
|
149
|
+
"Application Support",
|
|
150
|
+
"axiom-lattice",
|
|
151
|
+
"cli-a2a"
|
|
152
|
+
);
|
|
153
|
+
DEFAULT_STORE_PATH = (0, import_node_path2.join)(DEFAULT_STORE_DIR, "sessions.json");
|
|
154
|
+
DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1e3;
|
|
155
|
+
SessionBindingStore = class {
|
|
156
|
+
constructor(opts) {
|
|
157
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
158
|
+
this.cleanupTimer = null;
|
|
159
|
+
this.dirty = false;
|
|
160
|
+
this.flushTimeout = null;
|
|
161
|
+
this.storePath = opts?.storePath && opts.storePath.length > 0 ? opts.storePath : DEFAULT_STORE_PATH;
|
|
162
|
+
this.persist = opts?.persist ?? true;
|
|
163
|
+
this.loadFromFile();
|
|
164
|
+
this.startCleanup(opts?.cleanupIntervalMs ?? DEFAULT_CLEANUP_INTERVAL_MS);
|
|
165
|
+
}
|
|
166
|
+
// ── Public API ────────────────────────────────────────────────────────
|
|
167
|
+
get(contextId, provider) {
|
|
168
|
+
const key = this.makeKey(contextId, provider);
|
|
169
|
+
const binding = this.cache.get(key);
|
|
170
|
+
if (!binding) return null;
|
|
171
|
+
if (this.isExpired(binding)) {
|
|
172
|
+
this.cache.delete(key);
|
|
173
|
+
this.markDirty();
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return binding;
|
|
177
|
+
}
|
|
178
|
+
set(binding) {
|
|
179
|
+
const key = this.makeKey(binding.contextId, binding.provider);
|
|
180
|
+
this.cache.set(key, binding);
|
|
181
|
+
this.markDirty();
|
|
182
|
+
}
|
|
183
|
+
delete(contextId, provider) {
|
|
184
|
+
const key = this.makeKey(contextId, provider);
|
|
185
|
+
const existed = this.cache.delete(key);
|
|
186
|
+
if (existed) this.markDirty();
|
|
187
|
+
return existed;
|
|
188
|
+
}
|
|
189
|
+
clearActiveTask(contextId, provider) {
|
|
190
|
+
const binding = this.get(contextId, provider);
|
|
191
|
+
if (binding) {
|
|
192
|
+
binding.activeTaskId = void 0;
|
|
193
|
+
binding.activeTaskState = void 0;
|
|
194
|
+
binding.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
195
|
+
this.markDirty();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Remove all expired entries.
|
|
200
|
+
* Called automatically on the cleanup interval; call manually before shutdown.
|
|
201
|
+
*/
|
|
202
|
+
cleanup() {
|
|
203
|
+
let removed = 0;
|
|
204
|
+
for (const [key, binding] of this.cache) {
|
|
205
|
+
if (this.isExpired(binding)) {
|
|
206
|
+
this.cache.delete(key);
|
|
207
|
+
removed++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (removed > 0) {
|
|
211
|
+
this.markDirty();
|
|
212
|
+
log3.info("TTL cleanup completed", { removed, remaining: this.cache.size });
|
|
213
|
+
}
|
|
214
|
+
return removed;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Graceful shutdown: stop cleanup timer, flush pending writes.
|
|
218
|
+
*/
|
|
219
|
+
shutdown() {
|
|
220
|
+
if (this.cleanupTimer) {
|
|
221
|
+
clearInterval(this.cleanupTimer);
|
|
222
|
+
this.cleanupTimer = null;
|
|
223
|
+
}
|
|
224
|
+
if (this.flushTimeout) {
|
|
225
|
+
clearTimeout(this.flushTimeout);
|
|
226
|
+
this.flushTimeout = null;
|
|
227
|
+
}
|
|
228
|
+
this.flushToFile();
|
|
229
|
+
log3.info("Session store shut down", { bindings: this.cache.size });
|
|
230
|
+
}
|
|
231
|
+
/** For testing: return the number of cached bindings. */
|
|
232
|
+
get size() {
|
|
233
|
+
return this.cache.size;
|
|
234
|
+
}
|
|
235
|
+
// ── Internal ──────────────────────────────────────────────────────────
|
|
236
|
+
makeKey(contextId, provider) {
|
|
237
|
+
return `${provider}:${contextId}`;
|
|
238
|
+
}
|
|
239
|
+
isExpired(binding) {
|
|
240
|
+
if (!binding.expiresAt) return false;
|
|
241
|
+
return Date.now() >= new Date(binding.expiresAt).getTime();
|
|
242
|
+
}
|
|
243
|
+
startCleanup(intervalMs) {
|
|
244
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), intervalMs);
|
|
245
|
+
this.cleanupTimer.unref();
|
|
246
|
+
}
|
|
247
|
+
// ── Persistence ───────────────────────────────────────────────────────
|
|
248
|
+
markDirty() {
|
|
249
|
+
if (!this.persist) return;
|
|
250
|
+
this.dirty = true;
|
|
251
|
+
if (!this.flushTimeout) {
|
|
252
|
+
this.flushTimeout = setTimeout(() => {
|
|
253
|
+
this.flushToFile();
|
|
254
|
+
}, 1e3);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
flushToFile() {
|
|
258
|
+
this.flushTimeout = null;
|
|
259
|
+
if (!this.persist || !this.dirty) return;
|
|
260
|
+
this.dirty = false;
|
|
261
|
+
this.writeAtomically();
|
|
262
|
+
}
|
|
263
|
+
writeAtomically() {
|
|
264
|
+
const tmpPath = this.storePath + ".tmp";
|
|
265
|
+
const data = {
|
|
266
|
+
version: 1,
|
|
267
|
+
bindings: Array.from(this.cache.values())
|
|
268
|
+
};
|
|
269
|
+
try {
|
|
270
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(this.storePath), { recursive: true });
|
|
271
|
+
(0, import_node_fs2.writeFileSync)(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
272
|
+
(0, import_node_fs2.renameSync)(tmpPath, this.storePath);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
log3.error("Failed to write session store", { error: err.message });
|
|
275
|
+
try {
|
|
276
|
+
(0, import_node_fs2.unlinkSync)(tmpPath);
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
loadFromFile() {
|
|
282
|
+
if (!this.persist || !(0, import_node_fs2.existsSync)(this.storePath)) return;
|
|
283
|
+
try {
|
|
284
|
+
const raw = (0, import_node_fs2.readFileSync)(this.storePath, "utf-8");
|
|
285
|
+
const data = JSON.parse(raw);
|
|
286
|
+
if (!data || !Array.isArray(data.bindings)) {
|
|
287
|
+
throw new Error("Invalid store format: bindings array missing");
|
|
288
|
+
}
|
|
289
|
+
let loaded = 0;
|
|
290
|
+
let expired = 0;
|
|
291
|
+
for (const b of data.bindings) {
|
|
292
|
+
if (!b.provider || !b.contextId || !b.sessionId) continue;
|
|
293
|
+
if (this.isExpired(b)) {
|
|
294
|
+
expired++;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const key = this.makeKey(b.contextId, b.provider);
|
|
298
|
+
this.cache.set(key, b);
|
|
299
|
+
loaded++;
|
|
300
|
+
}
|
|
301
|
+
log3.info("Session store loaded", { path: this.storePath, loaded, expired });
|
|
302
|
+
} catch (err) {
|
|
303
|
+
log3.error("Failed to load session store \u2014 starting fresh", {
|
|
304
|
+
path: this.storePath,
|
|
305
|
+
error: err.message
|
|
306
|
+
});
|
|
307
|
+
this.cache.clear();
|
|
308
|
+
this.writeAtomically();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
123
315
|
// src/executors/opencode/client.ts
|
|
124
|
-
var import_node_child_process,
|
|
316
|
+
var import_node_child_process, log5, OpenCodeClient;
|
|
125
317
|
var init_client = __esm({
|
|
126
318
|
"src/executors/opencode/client.ts"() {
|
|
127
319
|
"use strict";
|
|
128
320
|
import_node_child_process = require("child_process");
|
|
129
321
|
init_logger();
|
|
130
|
-
|
|
322
|
+
log5 = logger.child("opencode:client");
|
|
131
323
|
OpenCodeClient = class {
|
|
132
324
|
constructor(config) {
|
|
133
325
|
this.config = config;
|
|
134
326
|
this.currentChild = null;
|
|
135
327
|
}
|
|
136
|
-
/** Abort the currently running child process (if any). */
|
|
137
328
|
abort() {
|
|
138
329
|
if (this.currentChild) {
|
|
139
330
|
this.currentChild.kill("SIGTERM");
|
|
@@ -146,14 +337,117 @@ var init_client = __esm({
|
|
|
146
337
|
}
|
|
147
338
|
}
|
|
148
339
|
/**
|
|
149
|
-
* Execute a prompt via the OpenCode CLI
|
|
340
|
+
* Execute a prompt via the OpenCode CLI.
|
|
341
|
+
*
|
|
342
|
+
* @param prompt - The user prompt text.
|
|
343
|
+
* @param sessionId - If provided, continues the session with `--session`.
|
|
344
|
+
* Otherwise creates a new session using `--format json` to capture the
|
|
345
|
+
* session ID.
|
|
346
|
+
* @param workingDirectory - Optional per-request working directory override.
|
|
347
|
+
* Falls back to config.workdir if not provided.
|
|
150
348
|
*/
|
|
151
|
-
async execute(prompt) {
|
|
349
|
+
async execute(prompt, sessionId, workingDirectory) {
|
|
350
|
+
if (sessionId) {
|
|
351
|
+
const text = await this.runResume(prompt, sessionId, workingDirectory);
|
|
352
|
+
return { text, sessionId };
|
|
353
|
+
}
|
|
354
|
+
return this.runNew(prompt, workingDirectory);
|
|
355
|
+
}
|
|
356
|
+
resolveWorkdir(override) {
|
|
357
|
+
return override || this.config.workdir || process.cwd();
|
|
358
|
+
}
|
|
359
|
+
// ── New session (first turn) ──────────────────────────────────────────
|
|
360
|
+
async runNew(prompt, workingDirectory) {
|
|
361
|
+
const cliPath = this.config.cliPath || "opencode";
|
|
362
|
+
const timeout = this.config.timeout ?? 6e5;
|
|
363
|
+
const MAX_PROMPT = 1e5;
|
|
364
|
+
if (prompt.length > MAX_PROMPT) {
|
|
365
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}).`);
|
|
366
|
+
}
|
|
367
|
+
const env = {
|
|
368
|
+
PATH: process.env.PATH ?? "",
|
|
369
|
+
HOME: process.env.HOME ?? ""
|
|
370
|
+
};
|
|
371
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
372
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
373
|
+
}
|
|
374
|
+
for (const key of ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENCODE_SERVER_PASSWORD", "OPENCODE_SERVER_USERNAME"]) {
|
|
375
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
376
|
+
}
|
|
377
|
+
const args = ["run", "--format", "json"];
|
|
378
|
+
if (this.config.model) {
|
|
379
|
+
args.push("--model", this.config.model);
|
|
380
|
+
}
|
|
381
|
+
if (this.config.agent) {
|
|
382
|
+
args.push("--agent", this.config.agent);
|
|
383
|
+
}
|
|
384
|
+
if (this.config.attachUrl) {
|
|
385
|
+
args.push("--attach", this.config.attachUrl);
|
|
386
|
+
}
|
|
387
|
+
args.push("--dangerously-skip-permissions", prompt);
|
|
388
|
+
const workdir = this.resolveWorkdir(workingDirectory);
|
|
389
|
+
log5.info("Spawning opencode (new session, json)", { cliPath, workdir });
|
|
390
|
+
return new Promise((resolve3, reject) => {
|
|
391
|
+
const child = (0, import_node_child_process.spawn)(cliPath, args, {
|
|
392
|
+
cwd: workdir,
|
|
393
|
+
env,
|
|
394
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
395
|
+
});
|
|
396
|
+
this.currentChild = child;
|
|
397
|
+
let stdout = "";
|
|
398
|
+
let stderr = "";
|
|
399
|
+
const MAX_OUTPUT = 1e7;
|
|
400
|
+
const timer = setTimeout(() => {
|
|
401
|
+
child.kill("SIGTERM");
|
|
402
|
+
const forceTimer = setTimeout(() => {
|
|
403
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
404
|
+
}, 5e3);
|
|
405
|
+
child.on("close", () => clearTimeout(forceTimer));
|
|
406
|
+
reject(new Error(`OpenCode execution timed out after ${timeout}ms`));
|
|
407
|
+
}, timeout);
|
|
408
|
+
child.stdout?.on("data", (chunk) => {
|
|
409
|
+
stdout += chunk.toString();
|
|
410
|
+
if (stdout.length > MAX_OUTPUT) {
|
|
411
|
+
child.kill("SIGTERM");
|
|
412
|
+
setTimeout(() => {
|
|
413
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
414
|
+
}, 5e3);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
child.stderr?.on("data", (chunk) => {
|
|
418
|
+
stderr += chunk.toString();
|
|
419
|
+
log5.debug("OpenCode stderr", { text: chunk.toString().trim() });
|
|
420
|
+
});
|
|
421
|
+
child.on("close", (code) => {
|
|
422
|
+
clearTimeout(timer);
|
|
423
|
+
this.currentChild = null;
|
|
424
|
+
if (code !== 0) {
|
|
425
|
+
const errMsg = stderr.trim() || stdout.trim() || `OpenCode exited with code ${code}`;
|
|
426
|
+
log5.warn("OpenCode non-zero exit", { code, stderr: errMsg });
|
|
427
|
+
reject(new Error(errMsg));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
const { text, sessionId } = this.parseJsonEvents(stdout);
|
|
432
|
+
resolve3({ text, sessionId });
|
|
433
|
+
} catch (err) {
|
|
434
|
+
reject(new Error(`Failed to parse OpenCode JSON output: ${err.message}`));
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
child.on("error", (err) => {
|
|
438
|
+
clearTimeout(timer);
|
|
439
|
+
log5.error("OpenCode spawn failed", { error: err.message });
|
|
440
|
+
reject(new Error(`Failed to start opencode: ${err.message}. Is it installed?`));
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
// ── Resume session ────────────────────────────────────────────────────
|
|
445
|
+
async runResume(prompt, sessionId, workingDirectory) {
|
|
152
446
|
const cliPath = this.config.cliPath || "opencode";
|
|
153
447
|
const timeout = this.config.timeout ?? 6e5;
|
|
154
448
|
const MAX_PROMPT = 1e5;
|
|
155
449
|
if (prompt.length > MAX_PROMPT) {
|
|
156
|
-
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT})
|
|
450
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}).`);
|
|
157
451
|
}
|
|
158
452
|
const env = {
|
|
159
453
|
PATH: process.env.PATH ?? "",
|
|
@@ -165,7 +459,7 @@ var init_client = __esm({
|
|
|
165
459
|
for (const key of ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENCODE_SERVER_PASSWORD", "OPENCODE_SERVER_USERNAME"]) {
|
|
166
460
|
if (process.env[key]) env[key] = process.env[key];
|
|
167
461
|
}
|
|
168
|
-
const args = ["run",
|
|
462
|
+
const args = ["run", "--session", sessionId];
|
|
169
463
|
if (this.config.model) {
|
|
170
464
|
args.push("--model", this.config.model);
|
|
171
465
|
}
|
|
@@ -175,12 +469,12 @@ var init_client = __esm({
|
|
|
175
469
|
if (this.config.attachUrl) {
|
|
176
470
|
args.push("--attach", this.config.attachUrl);
|
|
177
471
|
}
|
|
178
|
-
args.push("--format", "default");
|
|
179
|
-
|
|
180
|
-
|
|
472
|
+
args.push("--format", "default", "--dangerously-skip-permissions", prompt);
|
|
473
|
+
const workdir = this.resolveWorkdir(workingDirectory);
|
|
474
|
+
log5.info("Spawning opencode (resume)", { sessionId, workdir });
|
|
181
475
|
return new Promise((resolve3, reject) => {
|
|
182
476
|
const child = (0, import_node_child_process.spawn)(cliPath, args, {
|
|
183
|
-
cwd:
|
|
477
|
+
cwd: workdir,
|
|
184
478
|
env,
|
|
185
479
|
stdio: ["ignore", "pipe", "pipe"]
|
|
186
480
|
});
|
|
@@ -207,7 +501,7 @@ var init_client = __esm({
|
|
|
207
501
|
});
|
|
208
502
|
child.stderr?.on("data", (chunk) => {
|
|
209
503
|
stderr += chunk.toString();
|
|
210
|
-
|
|
504
|
+
log5.debug("OpenCode stderr", { text: chunk.toString().trim() });
|
|
211
505
|
});
|
|
212
506
|
child.on("close", (code) => {
|
|
213
507
|
clearTimeout(timer);
|
|
@@ -216,22 +510,66 @@ var init_client = __esm({
|
|
|
216
510
|
resolve3(stdout.trim());
|
|
217
511
|
} else {
|
|
218
512
|
const errMsg = stderr.trim() || stdout.trim() || `OpenCode exited with code ${code}`;
|
|
219
|
-
|
|
513
|
+
log5.warn("OpenCode non-zero exit", { code, stderr: errMsg });
|
|
220
514
|
reject(new Error(errMsg));
|
|
221
515
|
}
|
|
222
516
|
});
|
|
223
517
|
child.on("error", (err) => {
|
|
224
518
|
clearTimeout(timer);
|
|
225
|
-
|
|
519
|
+
log5.error("OpenCode spawn failed", { error: err.message });
|
|
226
520
|
reject(new Error(`Failed to start opencode: ${err.message}. Is it installed?`));
|
|
227
521
|
});
|
|
228
522
|
});
|
|
229
523
|
}
|
|
524
|
+
// ── Parser ────────────────────────────────────────────────────────────
|
|
525
|
+
/**
|
|
526
|
+
* Parse newline-delimited JSON events from OpenCode's `--format json` output.
|
|
527
|
+
* Extracts the sessionID from the first event that contains it, and
|
|
528
|
+
* accumulates text content from relevant events.
|
|
529
|
+
*/
|
|
530
|
+
parseJsonEvents(stdout) {
|
|
531
|
+
const lines = stdout.split("\n").filter((l) => l.trim());
|
|
532
|
+
let sessionId = "";
|
|
533
|
+
const textParts = [];
|
|
534
|
+
for (const line of lines) {
|
|
535
|
+
let event;
|
|
536
|
+
try {
|
|
537
|
+
event = JSON.parse(line);
|
|
538
|
+
} catch {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (!sessionId && event.sessionID) {
|
|
542
|
+
sessionId = event.sessionID;
|
|
543
|
+
}
|
|
544
|
+
if (event.type === "error") continue;
|
|
545
|
+
const text = event.text || event.content || event.message;
|
|
546
|
+
if (text) {
|
|
547
|
+
textParts.push(text);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (!sessionId) {
|
|
551
|
+
return {
|
|
552
|
+
text: textParts.join("\n").trim() || stdout.trim(),
|
|
553
|
+
sessionId: ""
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
text: textParts.join("\n").trim() || "No response from OpenCode.",
|
|
558
|
+
sessionId
|
|
559
|
+
};
|
|
560
|
+
}
|
|
230
561
|
};
|
|
231
562
|
}
|
|
232
563
|
});
|
|
233
564
|
|
|
234
565
|
// src/executors/events.ts
|
|
566
|
+
function extractText(message) {
|
|
567
|
+
return message.parts.filter((p) => {
|
|
568
|
+
const part = p;
|
|
569
|
+
const text = part.text;
|
|
570
|
+
return text !== void 0 && text !== null;
|
|
571
|
+
}).map((p) => p.text).join("\n");
|
|
572
|
+
}
|
|
235
573
|
function publishTask(bus, taskId, contextId) {
|
|
236
574
|
bus.publish({
|
|
237
575
|
kind: "task",
|
|
@@ -280,22 +618,24 @@ __export(executor_exports, {
|
|
|
280
618
|
OpenCodeExecutor: () => OpenCodeExecutor,
|
|
281
619
|
createOpenCodeExecutor: () => createOpenCodeExecutor
|
|
282
620
|
});
|
|
283
|
-
function createOpenCodeExecutor(config) {
|
|
284
|
-
return new OpenCodeExecutor(config);
|
|
621
|
+
function createOpenCodeExecutor(config, sessionStore) {
|
|
622
|
+
return new OpenCodeExecutor(config, sessionStore);
|
|
285
623
|
}
|
|
286
|
-
var
|
|
624
|
+
var log6, OpenCodeExecutor;
|
|
287
625
|
var init_executor = __esm({
|
|
288
626
|
"src/executors/opencode/executor.ts"() {
|
|
289
627
|
"use strict";
|
|
628
|
+
init_session_store();
|
|
290
629
|
init_logger();
|
|
291
630
|
init_client();
|
|
292
631
|
init_events();
|
|
293
|
-
|
|
632
|
+
log6 = logger.child("opencode:executor");
|
|
294
633
|
OpenCodeExecutor = class {
|
|
295
|
-
constructor(config) {
|
|
634
|
+
constructor(config, sessionStore) {
|
|
296
635
|
this.client = null;
|
|
297
636
|
this.initialized = false;
|
|
298
637
|
this.config = config;
|
|
638
|
+
this.sessionStore = sessionStore;
|
|
299
639
|
}
|
|
300
640
|
async initialize() {
|
|
301
641
|
if (this.initialized) return;
|
|
@@ -309,62 +649,81 @@ var init_executor = __esm({
|
|
|
309
649
|
timeout: this.config.timeouts.prompt ?? 6e5
|
|
310
650
|
});
|
|
311
651
|
this.initialized = true;
|
|
312
|
-
|
|
652
|
+
log6.info("Executor initialized", { workdir: oc.projectDirectory, model: oc.model });
|
|
313
653
|
}
|
|
314
654
|
async shutdown() {
|
|
315
655
|
this.client = null;
|
|
316
656
|
this.initialized = false;
|
|
317
|
-
|
|
657
|
+
log6.info("Executor shut down");
|
|
318
658
|
}
|
|
319
659
|
async execute(ctx, bus) {
|
|
320
|
-
const { taskId,
|
|
660
|
+
const { taskId, userMessage } = ctx;
|
|
321
661
|
await this.initialize();
|
|
662
|
+
const reuseByContext = this.config.session?.reuseByContext ?? true;
|
|
663
|
+
let contextId = ctx.contextId;
|
|
664
|
+
if (!contextId) {
|
|
665
|
+
contextId = generateContextId();
|
|
666
|
+
log6.info("Generated new contextId", { contextId, taskId });
|
|
667
|
+
}
|
|
668
|
+
const binding = reuseByContext ? this.sessionStore.get(contextId, "opencode") : null;
|
|
669
|
+
const sessionId = binding?.sessionId;
|
|
322
670
|
try {
|
|
323
|
-
|
|
671
|
+
const existingTask = ctx.task;
|
|
672
|
+
if (!existingTask) {
|
|
324
673
|
publishTask(bus, taskId, contextId);
|
|
325
674
|
publishStatus(bus, taskId, contextId, "submitted");
|
|
326
675
|
}
|
|
327
|
-
publishStatus(bus, taskId, contextId, "working"
|
|
328
|
-
const promptText =
|
|
329
|
-
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
676
|
+
publishStatus(bus, taskId, contextId, "working");
|
|
677
|
+
const promptText = extractText(userMessage);
|
|
678
|
+
const userMsg = userMessage;
|
|
679
|
+
const workingDirectory = userMsg.metadata && typeof userMsg.metadata === "object" ? userMsg.metadata.workingDirectory : void 0;
|
|
680
|
+
log6.info("Sending prompt to OpenCode", {
|
|
681
|
+
taskId,
|
|
682
|
+
contextId,
|
|
683
|
+
sessionId: sessionId || "(new)",
|
|
684
|
+
len: promptText.length,
|
|
685
|
+
workingDirectory: workingDirectory || "(default)"
|
|
686
|
+
});
|
|
687
|
+
const result = await this.client.execute(promptText, sessionId, workingDirectory);
|
|
688
|
+
this.sessionStore.set({
|
|
689
|
+
provider: "opencode",
|
|
690
|
+
contextId,
|
|
691
|
+
sessionId: result.sessionId,
|
|
692
|
+
activeTaskId: void 0,
|
|
693
|
+
activeTaskState: void 0,
|
|
694
|
+
createdAt: binding?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
695
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
696
|
+
expiresAt: new Date(Date.now() + (this.config.session?.ttl ?? 24 * 60 * 60 * 1e3)).toISOString()
|
|
697
|
+
});
|
|
698
|
+
publishFinalArtifact(bus, taskId, contextId, result.text || "No response from OpenCode.");
|
|
333
699
|
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
334
700
|
bus.finished();
|
|
335
|
-
|
|
701
|
+
log6.info("Task completed", { taskId, contextId, sessionId: result.sessionId });
|
|
336
702
|
} catch (error) {
|
|
337
703
|
const msg = error.message ?? String(error);
|
|
338
|
-
|
|
704
|
+
log6.error("Execution failed", { taskId, contextId, error: msg });
|
|
339
705
|
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
340
706
|
bus.finished();
|
|
341
707
|
}
|
|
342
708
|
}
|
|
343
709
|
async cancelTask(taskId, bus) {
|
|
344
|
-
|
|
710
|
+
log6.info("Cancel requested for OpenCode task", { taskId });
|
|
345
711
|
this.client?.abort();
|
|
346
712
|
publishStatus(bus, taskId, "", "canceled", "OpenCode task cancelled", true);
|
|
347
713
|
bus.finished();
|
|
348
714
|
}
|
|
349
|
-
extractText(message) {
|
|
350
|
-
return message.parts.filter((p) => {
|
|
351
|
-
const part = p;
|
|
352
|
-
const text = part.text;
|
|
353
|
-
return text !== void 0 && text !== null;
|
|
354
|
-
}).map((p) => p.text).join("\n");
|
|
355
|
-
}
|
|
356
715
|
};
|
|
357
716
|
}
|
|
358
717
|
});
|
|
359
718
|
|
|
360
719
|
// src/executors/codex/client.ts
|
|
361
|
-
var import_node_child_process2,
|
|
720
|
+
var import_node_child_process2, log7, CodexClient;
|
|
362
721
|
var init_client2 = __esm({
|
|
363
722
|
"src/executors/codex/client.ts"() {
|
|
364
723
|
"use strict";
|
|
365
724
|
import_node_child_process2 = require("child_process");
|
|
366
725
|
init_logger();
|
|
367
|
-
|
|
726
|
+
log7 = logger.child("codex:client");
|
|
368
727
|
CodexClient = class {
|
|
369
728
|
constructor(config) {
|
|
370
729
|
this.config = config;
|
|
@@ -382,9 +741,24 @@ var init_client2 = __esm({
|
|
|
382
741
|
}
|
|
383
742
|
}
|
|
384
743
|
/**
|
|
385
|
-
* Execute a prompt via the Codex CLI
|
|
744
|
+
* Execute a prompt via the Codex CLI.
|
|
745
|
+
*
|
|
746
|
+
* @param prompt - The user prompt text.
|
|
747
|
+
* @param sessionId - If provided, resumes an existing session.
|
|
748
|
+
* Otherwise creates a new session and extracts the thread_id from JSONL.
|
|
749
|
+
* @param workingDirectory - Optional per-request working directory override.
|
|
386
750
|
*/
|
|
387
|
-
async execute(prompt) {
|
|
751
|
+
async execute(prompt, sessionId, workingDirectory) {
|
|
752
|
+
if (sessionId) {
|
|
753
|
+
return this.executeResume(prompt, sessionId, workingDirectory);
|
|
754
|
+
}
|
|
755
|
+
return this.executeNew(prompt, workingDirectory);
|
|
756
|
+
}
|
|
757
|
+
resolveWorkdir(override) {
|
|
758
|
+
return override || this.config.workdir || process.cwd();
|
|
759
|
+
}
|
|
760
|
+
// ── New session (first turn) ──────────────────────────────────────────
|
|
761
|
+
async executeNew(prompt, workingDirectory) {
|
|
388
762
|
const cliPath = this.config.cliPath || "codex";
|
|
389
763
|
const timeout = this.config.timeout ?? 3e5;
|
|
390
764
|
const MAX_PROMPT = 1e5;
|
|
@@ -401,15 +775,20 @@ var init_client2 = __esm({
|
|
|
401
775
|
if (this.config.apiKey) {
|
|
402
776
|
env["OPENAI_API_KEY"] = this.config.apiKey;
|
|
403
777
|
}
|
|
404
|
-
const args = [];
|
|
778
|
+
const args = ["exec", "--json"];
|
|
405
779
|
if (this.config.model) {
|
|
406
780
|
args.push("--model", this.config.model);
|
|
407
781
|
}
|
|
408
|
-
args.push(
|
|
409
|
-
|
|
782
|
+
args.push(
|
|
783
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
784
|
+
"--skip-git-repo-check",
|
|
785
|
+
prompt
|
|
786
|
+
);
|
|
787
|
+
const workdir = this.resolveWorkdir(workingDirectory);
|
|
788
|
+
log7.info("Spawning codex (new session)", { cliPath, workdir, model: this.config.model });
|
|
410
789
|
return new Promise((resolve3, reject) => {
|
|
411
790
|
const child = (0, import_node_child_process2.spawn)(cliPath, args, {
|
|
412
|
-
cwd:
|
|
791
|
+
cwd: workdir,
|
|
413
792
|
env,
|
|
414
793
|
stdio: ["ignore", "pipe", "pipe"]
|
|
415
794
|
});
|
|
@@ -436,26 +815,182 @@ var init_client2 = __esm({
|
|
|
436
815
|
});
|
|
437
816
|
child.stderr?.on("data", (chunk) => {
|
|
438
817
|
stderr += chunk.toString();
|
|
439
|
-
|
|
818
|
+
log7.debug("Codex stderr", { text: chunk.toString().trim() });
|
|
440
819
|
});
|
|
441
820
|
child.on("close", (code) => {
|
|
442
821
|
clearTimeout(timer);
|
|
443
822
|
this.currentChild = null;
|
|
444
|
-
if (code
|
|
445
|
-
|
|
446
|
-
|
|
823
|
+
if (code !== 0) {
|
|
824
|
+
const errMsg = stderr.trim() || stdout.trim() || `Codex exited with code ${code}`;
|
|
825
|
+
log7.warn("Codex non-zero exit", { code, stderr: errMsg });
|
|
826
|
+
reject(new Error(errMsg));
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
const { text, sessionId } = this.parseJsonl(stdout);
|
|
831
|
+
resolve3({ text, sessionId });
|
|
832
|
+
} catch (err) {
|
|
833
|
+
reject(new Error(`Failed to parse Codex JSONL output: ${err.message}`));
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
child.on("error", (err) => {
|
|
837
|
+
clearTimeout(timer);
|
|
838
|
+
log7.error("Codex spawn failed", { error: err.message });
|
|
839
|
+
reject(new Error(`Failed to start codex: ${err.message}. Is it installed? (npm i -g @openai/codex)`));
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
// ── Resume session (subsequent turns) ─────────────────────────────────
|
|
844
|
+
async executeResume(prompt, sessionId, workingDirectory) {
|
|
845
|
+
const cliPath = this.config.cliPath || "codex";
|
|
846
|
+
const timeout = this.config.timeout ?? 3e5;
|
|
847
|
+
const MAX_PROMPT = 1e5;
|
|
848
|
+
if (prompt.length > MAX_PROMPT) {
|
|
849
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages.`);
|
|
850
|
+
}
|
|
851
|
+
const env = {
|
|
852
|
+
PATH: process.env.PATH ?? "",
|
|
853
|
+
HOME: process.env.HOME ?? ""
|
|
854
|
+
};
|
|
855
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
856
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
857
|
+
}
|
|
858
|
+
if (this.config.apiKey) {
|
|
859
|
+
env["OPENAI_API_KEY"] = this.config.apiKey;
|
|
860
|
+
}
|
|
861
|
+
const args = ["exec", "resume", sessionId];
|
|
862
|
+
if (this.config.model) {
|
|
863
|
+
args.push("--model", this.config.model);
|
|
864
|
+
}
|
|
865
|
+
args.push(
|
|
866
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
867
|
+
"--skip-git-repo-check",
|
|
868
|
+
prompt
|
|
869
|
+
);
|
|
870
|
+
const workdir = this.resolveWorkdir(workingDirectory);
|
|
871
|
+
log7.info("Spawning codex (resume)", { sessionId, workdir });
|
|
872
|
+
return new Promise((resolve3, reject) => {
|
|
873
|
+
const child = (0, import_node_child_process2.spawn)(cliPath, args, {
|
|
874
|
+
cwd: workdir,
|
|
875
|
+
env,
|
|
876
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
877
|
+
});
|
|
878
|
+
this.currentChild = child;
|
|
879
|
+
let stdout = "";
|
|
880
|
+
let stderr = "";
|
|
881
|
+
const MAX_OUTPUT = 1e7;
|
|
882
|
+
const timer = setTimeout(() => {
|
|
883
|
+
child.kill("SIGTERM");
|
|
884
|
+
const forceTimer = setTimeout(() => {
|
|
885
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
886
|
+
}, 5e3);
|
|
887
|
+
child.on("close", () => clearTimeout(forceTimer));
|
|
888
|
+
reject(new Error(`Codex execution timed out after ${timeout}ms`));
|
|
889
|
+
}, timeout);
|
|
890
|
+
child.stdout?.on("data", (chunk) => {
|
|
891
|
+
stdout += chunk.toString();
|
|
892
|
+
if (stdout.length > MAX_OUTPUT) {
|
|
893
|
+
child.kill("SIGTERM");
|
|
894
|
+
setTimeout(() => {
|
|
895
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
896
|
+
}, 5e3);
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
child.stderr?.on("data", (chunk) => {
|
|
900
|
+
stderr += chunk.toString();
|
|
901
|
+
log7.debug("Codex stderr", { text: chunk.toString().trim() });
|
|
902
|
+
});
|
|
903
|
+
child.on("close", (code) => {
|
|
904
|
+
clearTimeout(timer);
|
|
905
|
+
this.currentChild = null;
|
|
906
|
+
if (code !== 0) {
|
|
447
907
|
const errMsg = stderr.trim() || stdout.trim() || `Codex exited with code ${code}`;
|
|
448
|
-
|
|
908
|
+
log7.warn("Codex non-zero exit", { code, stderr: errMsg });
|
|
449
909
|
reject(new Error(errMsg));
|
|
910
|
+
return;
|
|
450
911
|
}
|
|
912
|
+
const text = this.extractResumeText(stdout);
|
|
913
|
+
resolve3({ text, sessionId });
|
|
451
914
|
});
|
|
452
915
|
child.on("error", (err) => {
|
|
453
916
|
clearTimeout(timer);
|
|
454
|
-
|
|
917
|
+
log7.error("Codex spawn failed", { error: err.message });
|
|
455
918
|
reject(new Error(`Failed to start codex: ${err.message}. Is it installed? (npm i -g @openai/codex)`));
|
|
456
919
|
});
|
|
457
920
|
});
|
|
458
921
|
}
|
|
922
|
+
// ── Parsers ───────────────────────────────────────────────────────────
|
|
923
|
+
/**
|
|
924
|
+
* Parse JSONL output to extract session thread_id and agent response text.
|
|
925
|
+
*/
|
|
926
|
+
parseJsonl(stdout) {
|
|
927
|
+
const lines = stdout.split("\n").filter((l) => l.trim());
|
|
928
|
+
let sessionId = "";
|
|
929
|
+
const textParts = [];
|
|
930
|
+
for (const line of lines) {
|
|
931
|
+
let event;
|
|
932
|
+
try {
|
|
933
|
+
event = JSON.parse(line);
|
|
934
|
+
} catch {
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
if (event.type === "thread.started" && event.thread_id) {
|
|
938
|
+
sessionId = event.thread_id;
|
|
939
|
+
}
|
|
940
|
+
if (event.type === "item.completed") {
|
|
941
|
+
const item = event.item;
|
|
942
|
+
if (item && item.type === "agent_message" && item.text) {
|
|
943
|
+
textParts.push(item.text);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
if (!sessionId) {
|
|
948
|
+
throw new Error("No thread_id found in Codex JSONL output");
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
text: textParts.join("\n").trim() || "No response from Codex.",
|
|
952
|
+
sessionId
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Extract the agent response text from the resume mode output.
|
|
957
|
+
*
|
|
958
|
+
* The resume output includes a CLI header followed by the conversation.
|
|
959
|
+
* We extract everything after the last "codex" marker line as the
|
|
960
|
+
* latest agent response.
|
|
961
|
+
*/
|
|
962
|
+
extractResumeText(stdout) {
|
|
963
|
+
const lines = stdout.split("\n");
|
|
964
|
+
let responseStart = -1;
|
|
965
|
+
for (let i = 0; i < lines.length; i++) {
|
|
966
|
+
if (lines[i].trim() === "codex") {
|
|
967
|
+
responseStart = i;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (responseStart >= 0 && responseStart + 1 < lines.length) {
|
|
971
|
+
return lines.slice(responseStart + 1).join("\n").trim();
|
|
972
|
+
}
|
|
973
|
+
let skipHeader = true;
|
|
974
|
+
const resultLines = [];
|
|
975
|
+
for (const line of lines) {
|
|
976
|
+
const trimmed = line.trim();
|
|
977
|
+
if (skipHeader) {
|
|
978
|
+
if (trimmed === "--------" || trimmed === "codex") {
|
|
979
|
+
skipHeader = false;
|
|
980
|
+
}
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (trimmed === "user" || trimmed === "codex") {
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (trimmed.startsWith("tokens used")) {
|
|
987
|
+
if (resultLines.length > 0) resultLines.pop();
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
resultLines.push(line);
|
|
991
|
+
}
|
|
992
|
+
return resultLines.join("\n").trim() || stdout.trim();
|
|
993
|
+
}
|
|
459
994
|
};
|
|
460
995
|
}
|
|
461
996
|
});
|
|
@@ -466,22 +1001,24 @@ __export(executor_exports2, {
|
|
|
466
1001
|
CodexExecutor: () => CodexExecutor,
|
|
467
1002
|
createCodexExecutor: () => createCodexExecutor
|
|
468
1003
|
});
|
|
469
|
-
function createCodexExecutor(config) {
|
|
470
|
-
return new CodexExecutor(config);
|
|
1004
|
+
function createCodexExecutor(config, sessionStore) {
|
|
1005
|
+
return new CodexExecutor(config, sessionStore);
|
|
471
1006
|
}
|
|
472
|
-
var
|
|
1007
|
+
var log8, CodexExecutor;
|
|
473
1008
|
var init_executor2 = __esm({
|
|
474
1009
|
"src/executors/codex/executor.ts"() {
|
|
475
1010
|
"use strict";
|
|
1011
|
+
init_session_store();
|
|
476
1012
|
init_logger();
|
|
477
1013
|
init_client2();
|
|
478
1014
|
init_events();
|
|
479
|
-
|
|
1015
|
+
log8 = logger.child("codex:executor");
|
|
480
1016
|
CodexExecutor = class {
|
|
481
|
-
constructor(config) {
|
|
1017
|
+
constructor(config, sessionStore) {
|
|
482
1018
|
this.client = null;
|
|
483
1019
|
this.initialized = false;
|
|
484
1020
|
this.config = config;
|
|
1021
|
+
this.sessionStore = sessionStore;
|
|
485
1022
|
}
|
|
486
1023
|
async initialize() {
|
|
487
1024
|
if (this.initialized) return;
|
|
@@ -494,63 +1031,82 @@ var init_executor2 = __esm({
|
|
|
494
1031
|
timeout: this.config.timeouts.prompt ?? 6e5
|
|
495
1032
|
});
|
|
496
1033
|
this.initialized = true;
|
|
497
|
-
|
|
1034
|
+
log8.info("Executor initialized", { cliPath: cx.cliPath, workdir: cx.workdir });
|
|
498
1035
|
}
|
|
499
1036
|
async shutdown() {
|
|
500
1037
|
this.client = null;
|
|
501
1038
|
this.initialized = false;
|
|
502
|
-
|
|
1039
|
+
log8.info("Executor shut down");
|
|
503
1040
|
}
|
|
504
1041
|
async execute(ctx, bus) {
|
|
505
|
-
const { taskId,
|
|
1042
|
+
const { taskId, userMessage } = ctx;
|
|
506
1043
|
await this.initialize();
|
|
1044
|
+
const reuseByContext = this.config.session?.reuseByContext ?? true;
|
|
1045
|
+
let contextId = ctx.contextId;
|
|
1046
|
+
if (!contextId) {
|
|
1047
|
+
contextId = generateContextId();
|
|
1048
|
+
log8.info("Generated new contextId", { contextId, taskId });
|
|
1049
|
+
}
|
|
1050
|
+
const binding = reuseByContext ? this.sessionStore.get(contextId, "codex") : null;
|
|
1051
|
+
const sessionId = binding?.sessionId;
|
|
507
1052
|
try {
|
|
508
|
-
|
|
1053
|
+
const existingTask = ctx.task;
|
|
1054
|
+
if (!existingTask) {
|
|
509
1055
|
publishTask(bus, taskId, contextId);
|
|
510
1056
|
publishStatus(bus, taskId, contextId, "submitted");
|
|
511
1057
|
}
|
|
512
|
-
publishStatus(bus, taskId, contextId, "working"
|
|
513
|
-
const promptText =
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
1058
|
+
publishStatus(bus, taskId, contextId, "working");
|
|
1059
|
+
const promptText = extractText(userMessage);
|
|
1060
|
+
const userMsg = userMessage;
|
|
1061
|
+
const workingDirectory = userMsg.metadata && typeof userMsg.metadata === "object" ? userMsg.metadata.workingDirectory : void 0;
|
|
1062
|
+
log8.info("Sending prompt to Codex", {
|
|
1063
|
+
taskId,
|
|
1064
|
+
contextId,
|
|
1065
|
+
sessionId: sessionId || "(new)",
|
|
1066
|
+
len: promptText.length,
|
|
1067
|
+
workingDirectory: workingDirectory || "(default)"
|
|
1068
|
+
});
|
|
1069
|
+
const result = await this.client.execute(promptText, sessionId, workingDirectory);
|
|
1070
|
+
this.sessionStore.set({
|
|
1071
|
+
provider: "codex",
|
|
1072
|
+
contextId,
|
|
1073
|
+
sessionId: result.sessionId,
|
|
1074
|
+
activeTaskId: void 0,
|
|
1075
|
+
activeTaskState: void 0,
|
|
1076
|
+
createdAt: binding?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1077
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1078
|
+
expiresAt: new Date(Date.now() + (this.config.session?.ttl ?? 24 * 60 * 60 * 1e3)).toISOString()
|
|
1079
|
+
});
|
|
1080
|
+
publishFinalArtifact(bus, taskId, contextId, result.text || "No response from Codex.");
|
|
518
1081
|
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
519
1082
|
bus.finished();
|
|
520
|
-
|
|
1083
|
+
log8.info("Task completed", { taskId, contextId, sessionId: result.sessionId });
|
|
521
1084
|
} catch (error) {
|
|
522
1085
|
const msg = error.message ?? String(error);
|
|
523
|
-
|
|
1086
|
+
log8.error("Execution failed", { taskId, contextId, error: msg });
|
|
524
1087
|
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
525
1088
|
bus.finished();
|
|
526
1089
|
}
|
|
527
1090
|
}
|
|
528
1091
|
async cancelTask(taskId, bus) {
|
|
529
|
-
|
|
1092
|
+
log8.info("Cancel requested for Codex task", { taskId });
|
|
530
1093
|
this.client?.abort();
|
|
531
1094
|
publishStatus(bus, taskId, "", "canceled", "Codex task cancelled", true);
|
|
532
1095
|
bus.finished();
|
|
533
1096
|
}
|
|
534
|
-
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
535
|
-
extractText(message) {
|
|
536
|
-
return message.parts.filter((p) => {
|
|
537
|
-
const part = p;
|
|
538
|
-
const text = part.text;
|
|
539
|
-
return text !== void 0 && text !== null;
|
|
540
|
-
}).map((p) => p.text).join("\n");
|
|
541
|
-
}
|
|
542
1097
|
};
|
|
543
1098
|
}
|
|
544
1099
|
});
|
|
545
1100
|
|
|
546
1101
|
// src/executors/claude/client.ts
|
|
547
|
-
var import_node_child_process3,
|
|
1102
|
+
var import_node_child_process3, import_uuid3, log9, ClaudeClient;
|
|
548
1103
|
var init_client3 = __esm({
|
|
549
1104
|
"src/executors/claude/client.ts"() {
|
|
550
1105
|
"use strict";
|
|
551
1106
|
import_node_child_process3 = require("child_process");
|
|
1107
|
+
import_uuid3 = require("uuid");
|
|
552
1108
|
init_logger();
|
|
553
|
-
|
|
1109
|
+
log9 = logger.child("claude:client");
|
|
554
1110
|
ClaudeClient = class {
|
|
555
1111
|
constructor(config) {
|
|
556
1112
|
this.config = config;
|
|
@@ -568,14 +1124,31 @@ var init_client3 = __esm({
|
|
|
568
1124
|
}
|
|
569
1125
|
}
|
|
570
1126
|
/**
|
|
571
|
-
* Execute a prompt via the Claude Code CLI
|
|
1127
|
+
* Execute a prompt via the Claude Code CLI.
|
|
1128
|
+
*
|
|
1129
|
+
* @param prompt - The user prompt text.
|
|
1130
|
+
* @param sessionId - If provided, resumes the session with `--resume`.
|
|
1131
|
+
* Otherwise creates a new session with `--session-id <new-uuid>`.
|
|
1132
|
+
* @param workingDirectory - Optional per-request working directory override.
|
|
1133
|
+
* @returns The response text and the session ID (new or existing).
|
|
572
1134
|
*/
|
|
573
|
-
async execute(prompt) {
|
|
1135
|
+
async execute(prompt, sessionId, workingDirectory) {
|
|
1136
|
+
if (sessionId) {
|
|
1137
|
+
const text2 = await this.run(["-p", "--resume", sessionId, prompt], workingDirectory);
|
|
1138
|
+
return { text: text2, sessionId };
|
|
1139
|
+
}
|
|
1140
|
+
const newSessionId = (0, import_uuid3.v4)();
|
|
1141
|
+
const text = await this.run(["-p", "--session-id", newSessionId, prompt], workingDirectory);
|
|
1142
|
+
return { text, sessionId: newSessionId };
|
|
1143
|
+
}
|
|
1144
|
+
// ── Internal ──────────────────────────────────────────────────────────
|
|
1145
|
+
async run(args, workingDirectory) {
|
|
574
1146
|
const cliPath = this.config.cliPath || "claude";
|
|
575
1147
|
const timeout = this.config.timeout ?? 3e5;
|
|
576
1148
|
const MAX_PROMPT = 1e5;
|
|
577
|
-
|
|
578
|
-
|
|
1149
|
+
const promptArg = args[args.length - 1];
|
|
1150
|
+
if (promptArg && promptArg.length > MAX_PROMPT) {
|
|
1151
|
+
throw new Error(`Prompt too large (${promptArg.length} chars, max ${MAX_PROMPT}). Split into smaller messages.`);
|
|
579
1152
|
}
|
|
580
1153
|
const env = {
|
|
581
1154
|
PATH: process.env.PATH ?? "",
|
|
@@ -587,15 +1160,19 @@ var init_client3 = __esm({
|
|
|
587
1160
|
if (this.config.apiKey) {
|
|
588
1161
|
env["ANTHROPIC_API_KEY"] = this.config.apiKey;
|
|
589
1162
|
}
|
|
590
|
-
const
|
|
1163
|
+
const fullArgs = [];
|
|
1164
|
+
for (const arg of args.slice(0, -1)) {
|
|
1165
|
+
fullArgs.push(arg);
|
|
1166
|
+
}
|
|
591
1167
|
if (this.config.model) {
|
|
592
|
-
|
|
1168
|
+
fullArgs.push("--model", this.config.model);
|
|
593
1169
|
}
|
|
594
|
-
|
|
595
|
-
|
|
1170
|
+
fullArgs.push(promptArg);
|
|
1171
|
+
const workdir = workingDirectory || this.config.workdir || process.cwd();
|
|
1172
|
+
log9.info("Spawning claude", { cliPath, workdir, model: this.config.model });
|
|
596
1173
|
return new Promise((resolve3, reject) => {
|
|
597
|
-
const child = (0, import_node_child_process3.spawn)(cliPath,
|
|
598
|
-
cwd:
|
|
1174
|
+
const child = (0, import_node_child_process3.spawn)(cliPath, fullArgs, {
|
|
1175
|
+
cwd: workdir,
|
|
599
1176
|
env,
|
|
600
1177
|
stdio: ["ignore", "pipe", "pipe"]
|
|
601
1178
|
});
|
|
@@ -622,7 +1199,7 @@ var init_client3 = __esm({
|
|
|
622
1199
|
});
|
|
623
1200
|
child.stderr?.on("data", (chunk) => {
|
|
624
1201
|
stderr += chunk.toString();
|
|
625
|
-
|
|
1202
|
+
log9.debug("Claude stderr", { text: chunk.toString().trim() });
|
|
626
1203
|
});
|
|
627
1204
|
child.on("close", (code) => {
|
|
628
1205
|
clearTimeout(timer);
|
|
@@ -631,13 +1208,13 @@ var init_client3 = __esm({
|
|
|
631
1208
|
resolve3(stdout.trim());
|
|
632
1209
|
} else {
|
|
633
1210
|
const errMsg = stderr.trim() || stdout.trim() || `Claude Code exited with code ${code}`;
|
|
634
|
-
|
|
1211
|
+
log9.warn("Claude non-zero exit", { code, stderr: errMsg });
|
|
635
1212
|
reject(new Error(errMsg));
|
|
636
1213
|
}
|
|
637
1214
|
});
|
|
638
1215
|
child.on("error", (err) => {
|
|
639
1216
|
clearTimeout(timer);
|
|
640
|
-
|
|
1217
|
+
log9.error("Claude spawn failed", { error: err.message });
|
|
641
1218
|
reject(new Error(`Failed to start claude: ${err.message}. Install: npm i -g @anthropic-ai/claude-code`));
|
|
642
1219
|
});
|
|
643
1220
|
});
|
|
@@ -652,22 +1229,24 @@ __export(executor_exports3, {
|
|
|
652
1229
|
ClaudeExecutor: () => ClaudeExecutor,
|
|
653
1230
|
createClaudeExecutor: () => createClaudeExecutor
|
|
654
1231
|
});
|
|
655
|
-
function createClaudeExecutor(config) {
|
|
656
|
-
return new ClaudeExecutor(config);
|
|
1232
|
+
function createClaudeExecutor(config, sessionStore) {
|
|
1233
|
+
return new ClaudeExecutor(config, sessionStore);
|
|
657
1234
|
}
|
|
658
|
-
var
|
|
1235
|
+
var log10, ClaudeExecutor;
|
|
659
1236
|
var init_executor3 = __esm({
|
|
660
1237
|
"src/executors/claude/executor.ts"() {
|
|
661
1238
|
"use strict";
|
|
1239
|
+
init_session_store();
|
|
662
1240
|
init_logger();
|
|
663
1241
|
init_client3();
|
|
664
1242
|
init_events();
|
|
665
|
-
|
|
1243
|
+
log10 = logger.child("claude:executor");
|
|
666
1244
|
ClaudeExecutor = class {
|
|
667
|
-
constructor(config) {
|
|
1245
|
+
constructor(config, sessionStore) {
|
|
668
1246
|
this.client = null;
|
|
669
1247
|
this.initialized = false;
|
|
670
1248
|
this.config = config;
|
|
1249
|
+
this.sessionStore = sessionStore;
|
|
671
1250
|
}
|
|
672
1251
|
async initialize() {
|
|
673
1252
|
if (this.initialized) return;
|
|
@@ -680,50 +1259,69 @@ var init_executor3 = __esm({
|
|
|
680
1259
|
timeout: this.config.timeouts.prompt ?? 6e5
|
|
681
1260
|
});
|
|
682
1261
|
this.initialized = true;
|
|
683
|
-
|
|
1262
|
+
log10.info("Executor initialized", { cliPath: cc.cliPath, workdir: cc.workdir });
|
|
684
1263
|
}
|
|
685
1264
|
async shutdown() {
|
|
686
1265
|
this.client = null;
|
|
687
1266
|
this.initialized = false;
|
|
688
|
-
|
|
1267
|
+
log10.info("Executor shut down");
|
|
689
1268
|
}
|
|
690
1269
|
async execute(ctx, bus) {
|
|
691
|
-
const { taskId,
|
|
1270
|
+
const { taskId, userMessage } = ctx;
|
|
692
1271
|
await this.initialize();
|
|
1272
|
+
const reuseByContext = this.config.session?.reuseByContext ?? true;
|
|
1273
|
+
let contextId = ctx.contextId;
|
|
1274
|
+
if (!contextId) {
|
|
1275
|
+
contextId = generateContextId();
|
|
1276
|
+
log10.info("Generated new contextId", { contextId, taskId });
|
|
1277
|
+
}
|
|
1278
|
+
const binding = reuseByContext ? this.sessionStore.get(contextId, "claude") : null;
|
|
1279
|
+
const sessionId = binding?.sessionId;
|
|
693
1280
|
try {
|
|
694
|
-
|
|
1281
|
+
const existingTask = ctx.task;
|
|
1282
|
+
if (!existingTask) {
|
|
695
1283
|
publishTask(bus, taskId, contextId);
|
|
696
1284
|
publishStatus(bus, taskId, contextId, "submitted");
|
|
697
1285
|
}
|
|
698
|
-
publishStatus(bus, taskId, contextId, "working"
|
|
699
|
-
const promptText =
|
|
700
|
-
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
1286
|
+
publishStatus(bus, taskId, contextId, "working");
|
|
1287
|
+
const promptText = extractText(userMessage);
|
|
1288
|
+
const userMsg = userMessage;
|
|
1289
|
+
const workingDirectory = userMsg.metadata && typeof userMsg.metadata === "object" ? userMsg.metadata.workingDirectory : void 0;
|
|
1290
|
+
log10.info("Sending prompt to Claude Code", {
|
|
1291
|
+
taskId,
|
|
1292
|
+
contextId,
|
|
1293
|
+
sessionId: sessionId || "(new)",
|
|
1294
|
+
len: promptText.length,
|
|
1295
|
+
workingDirectory: workingDirectory || "(default)"
|
|
1296
|
+
});
|
|
1297
|
+
const result = await this.client.execute(promptText, sessionId, workingDirectory);
|
|
1298
|
+
this.sessionStore.set({
|
|
1299
|
+
provider: "claude",
|
|
1300
|
+
contextId,
|
|
1301
|
+
sessionId: result.sessionId,
|
|
1302
|
+
activeTaskId: void 0,
|
|
1303
|
+
activeTaskState: void 0,
|
|
1304
|
+
createdAt: binding?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1305
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1306
|
+
expiresAt: new Date(Date.now() + (this.config.session?.ttl ?? 24 * 60 * 60 * 1e3)).toISOString()
|
|
1307
|
+
});
|
|
1308
|
+
publishFinalArtifact(bus, taskId, contextId, result.text || "No response from Claude Code.");
|
|
704
1309
|
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
705
1310
|
bus.finished();
|
|
706
|
-
|
|
1311
|
+
log10.info("Task completed", { taskId, contextId, sessionId: result.sessionId });
|
|
707
1312
|
} catch (error) {
|
|
708
1313
|
const msg = error.message ?? String(error);
|
|
709
|
-
|
|
1314
|
+
log10.error("Execution failed", { taskId, contextId, error: msg });
|
|
710
1315
|
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
711
1316
|
bus.finished();
|
|
712
1317
|
}
|
|
713
1318
|
}
|
|
714
1319
|
async cancelTask(taskId, bus) {
|
|
715
|
-
|
|
1320
|
+
log10.info("Cancel requested for Claude Code task", { taskId });
|
|
716
1321
|
this.client?.abort();
|
|
717
1322
|
publishStatus(bus, taskId, "", "canceled", "Claude Code task cancelled", true);
|
|
718
1323
|
bus.finished();
|
|
719
1324
|
}
|
|
720
|
-
extractText(message) {
|
|
721
|
-
return message.parts.filter((p) => {
|
|
722
|
-
const part = p;
|
|
723
|
-
const text = part.text;
|
|
724
|
-
return text !== void 0 && text !== null;
|
|
725
|
-
}).map((p) => p.text).join("\n");
|
|
726
|
-
}
|
|
727
1325
|
};
|
|
728
1326
|
}
|
|
729
1327
|
});
|
|
@@ -733,14 +1331,14 @@ var bridge_exports = {};
|
|
|
733
1331
|
__export(bridge_exports, {
|
|
734
1332
|
BridgeClient: () => BridgeClient
|
|
735
1333
|
});
|
|
736
|
-
var import_node_net,
|
|
1334
|
+
var import_node_net, import_node_crypto2, log11, SimpleWs, BridgeClient;
|
|
737
1335
|
var init_bridge = __esm({
|
|
738
1336
|
"src/bridge.ts"() {
|
|
739
1337
|
"use strict";
|
|
740
1338
|
import_node_net = require("net");
|
|
741
|
-
|
|
1339
|
+
import_node_crypto2 = require("crypto");
|
|
742
1340
|
init_logger();
|
|
743
|
-
|
|
1341
|
+
log11 = logger.child("bridge");
|
|
744
1342
|
SimpleWs = class {
|
|
745
1343
|
constructor() {
|
|
746
1344
|
this.socket = null;
|
|
@@ -753,7 +1351,7 @@ var init_bridge = __esm({
|
|
|
753
1351
|
const host = url.hostname;
|
|
754
1352
|
const port = parseInt(url.port) || (url.protocol === "wss:" ? 443 : 80);
|
|
755
1353
|
const path = url.pathname + url.search;
|
|
756
|
-
const key = (0,
|
|
1354
|
+
const key = (0, import_node_crypto2.randomBytes)(16).toString("base64");
|
|
757
1355
|
const req = (0, import_node_net.connect)({ host, port }, () => {
|
|
758
1356
|
const headers = [
|
|
759
1357
|
`GET ${path} HTTP/1.1`,
|
|
@@ -847,7 +1445,7 @@ var init_bridge = __esm({
|
|
|
847
1445
|
if (!this.socket) return;
|
|
848
1446
|
const payload = Buffer.from(data, "utf-8");
|
|
849
1447
|
const len = payload.length;
|
|
850
|
-
const maskKey = (0,
|
|
1448
|
+
const maskKey = (0, import_node_crypto2.randomBytes)(4);
|
|
851
1449
|
const masked = Buffer.alloc(len);
|
|
852
1450
|
for (let i = 0; i < len; i++) {
|
|
853
1451
|
masked[i] = payload[i] ^ maskKey[i % 4];
|
|
@@ -899,7 +1497,7 @@ var init_bridge = __esm({
|
|
|
899
1497
|
this.ws = new SimpleWs();
|
|
900
1498
|
let firstConnect = true;
|
|
901
1499
|
this.ws.onopen = () => {
|
|
902
|
-
|
|
1500
|
+
log11.info("Bridge connected");
|
|
903
1501
|
this.ws.send(JSON.stringify({
|
|
904
1502
|
type: "agent:register",
|
|
905
1503
|
agentId: this.config.agentId,
|
|
@@ -914,7 +1512,7 @@ var init_bridge = __esm({
|
|
|
914
1512
|
return;
|
|
915
1513
|
}
|
|
916
1514
|
if (msg.type === "agent:registered") {
|
|
917
|
-
|
|
1515
|
+
log11.info("Agent registered via bridge", { agentId: this.config.agentId });
|
|
918
1516
|
if (firstConnect) {
|
|
919
1517
|
firstConnect = false;
|
|
920
1518
|
resolve3();
|
|
@@ -926,7 +1524,7 @@ var init_bridge = __esm({
|
|
|
926
1524
|
}
|
|
927
1525
|
};
|
|
928
1526
|
this.ws.onclose = () => {
|
|
929
|
-
|
|
1527
|
+
log11.warn("Bridge disconnected, reconnecting...");
|
|
930
1528
|
if (this.running) {
|
|
931
1529
|
this.reconnectTimer = setTimeout(
|
|
932
1530
|
() => this.start().catch(() => {
|
|
@@ -936,13 +1534,13 @@ var init_bridge = __esm({
|
|
|
936
1534
|
}
|
|
937
1535
|
};
|
|
938
1536
|
this.ws.onerror = (err) => {
|
|
939
|
-
|
|
1537
|
+
log11.error("Bridge error", { error: err.message });
|
|
940
1538
|
if (firstConnect) {
|
|
941
1539
|
firstConnect = false;
|
|
942
1540
|
reject(err);
|
|
943
1541
|
}
|
|
944
1542
|
};
|
|
945
|
-
|
|
1543
|
+
log11.info("Connecting bridge", { url: this.config.bridgeUrl });
|
|
946
1544
|
this.ws.connect(this.config.bridgeUrl, this.config.apiKey);
|
|
947
1545
|
});
|
|
948
1546
|
}
|
|
@@ -957,7 +1555,7 @@ var init_bridge = __esm({
|
|
|
957
1555
|
}
|
|
958
1556
|
this.ws?.close();
|
|
959
1557
|
this.ws = null;
|
|
960
|
-
|
|
1558
|
+
log11.info("Bridge stopped");
|
|
961
1559
|
}
|
|
962
1560
|
// ── Internal ────────────────────────────────────────────────────────────
|
|
963
1561
|
async handleHttpRequest(msg) {
|
|
@@ -991,7 +1589,7 @@ var init_bridge = __esm({
|
|
|
991
1589
|
body: responseBody
|
|
992
1590
|
}));
|
|
993
1591
|
} catch (err) {
|
|
994
|
-
|
|
1592
|
+
log11.error("Proxy request failed", { url, error: err.message });
|
|
995
1593
|
this.ws?.send(JSON.stringify({
|
|
996
1594
|
type: "http:error",
|
|
997
1595
|
requestId,
|
|
@@ -1005,7 +1603,7 @@ var init_bridge = __esm({
|
|
|
1005
1603
|
|
|
1006
1604
|
// src/cli.ts
|
|
1007
1605
|
var import_node_util = require("util");
|
|
1008
|
-
var
|
|
1606
|
+
var import_node_path3 = require("path");
|
|
1009
1607
|
|
|
1010
1608
|
// src/config/loader.ts
|
|
1011
1609
|
var import_node_fs = require("fs");
|
|
@@ -1051,6 +1649,8 @@ var DEFAULTS = Object.freeze({
|
|
|
1051
1649
|
session: {
|
|
1052
1650
|
titlePrefix: "A2A Session",
|
|
1053
1651
|
reuseByContext: true,
|
|
1652
|
+
persist: true,
|
|
1653
|
+
storePath: "",
|
|
1054
1654
|
ttl: 36e5,
|
|
1055
1655
|
cleanupInterval: 3e5
|
|
1056
1656
|
},
|
|
@@ -1069,7 +1669,7 @@ var DEFAULTS = Object.freeze({
|
|
|
1069
1669
|
level: "info"
|
|
1070
1670
|
},
|
|
1071
1671
|
opencode: {
|
|
1072
|
-
baseUrl: "
|
|
1672
|
+
baseUrl: "",
|
|
1073
1673
|
projectDirectory: "",
|
|
1074
1674
|
model: "",
|
|
1075
1675
|
agent: "",
|
|
@@ -1195,7 +1795,8 @@ function buildAgentCard(config) {
|
|
|
1195
1795
|
skills,
|
|
1196
1796
|
capabilities: {
|
|
1197
1797
|
streaming: agentCard.streaming ?? true,
|
|
1198
|
-
pushNotifications: agentCard.pushNotifications ?? false
|
|
1798
|
+
pushNotifications: agentCard.pushNotifications ?? false,
|
|
1799
|
+
stateTransitionHistory: true
|
|
1199
1800
|
},
|
|
1200
1801
|
// Additional interfaces advertised to orchestrators
|
|
1201
1802
|
additionalInterfaces: [
|
|
@@ -1209,15 +1810,26 @@ function buildAgentCard(config) {
|
|
|
1209
1810
|
|
|
1210
1811
|
// src/server/index.ts
|
|
1211
1812
|
init_executors();
|
|
1813
|
+
init_session_store();
|
|
1212
1814
|
init_logger();
|
|
1213
|
-
var
|
|
1815
|
+
var log4 = logger.child("server");
|
|
1214
1816
|
async function createA2AServer(config) {
|
|
1215
1817
|
const srv = config.server;
|
|
1216
1818
|
const port = srv.port ?? 3e3;
|
|
1217
1819
|
const hostname = srv.hostname ?? "0.0.0.0";
|
|
1218
1820
|
const advertiseHost = srv.advertiseHost ?? "localhost";
|
|
1219
1821
|
const advertiseProto = srv.advertiseProtocol ?? "http";
|
|
1220
|
-
const
|
|
1822
|
+
const sessionConf = config.session;
|
|
1823
|
+
const sessionStore = new SessionBindingStore({
|
|
1824
|
+
persist: sessionConf.persist ?? true,
|
|
1825
|
+
storePath: resolveSessionStorePath({
|
|
1826
|
+
configuredPath: sessionConf.storePath,
|
|
1827
|
+
provider: config.provider,
|
|
1828
|
+
port
|
|
1829
|
+
}),
|
|
1830
|
+
cleanupIntervalMs: sessionConf.cleanupInterval
|
|
1831
|
+
});
|
|
1832
|
+
const executor = createExecutor(config, sessionStore);
|
|
1221
1833
|
await executor.initialize();
|
|
1222
1834
|
const agentCard = buildAgentCard(config);
|
|
1223
1835
|
const taskStore = new import_server.InMemoryTaskStore();
|
|
@@ -1249,7 +1861,7 @@ async function createA2AServer(config) {
|
|
|
1249
1861
|
app.use("/a2a/jsonrpc", (0, import_express2.jsonRpcHandler)({ requestHandler, userBuilder: import_express2.UserBuilder.noAuthentication }));
|
|
1250
1862
|
app.use("/a2a/rest", (0, import_express2.restHandler)({ requestHandler, userBuilder: import_express2.UserBuilder.noAuthentication }));
|
|
1251
1863
|
const httpServer = app.listen(port, hostname, () => {
|
|
1252
|
-
|
|
1864
|
+
log4.info("A2A server started", { bind: hostname, port, proto: advertiseProto });
|
|
1253
1865
|
const banner = [
|
|
1254
1866
|
"",
|
|
1255
1867
|
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
@@ -1273,14 +1885,15 @@ async function createA2AServer(config) {
|
|
|
1273
1885
|
async shutdown() {
|
|
1274
1886
|
httpServer.close();
|
|
1275
1887
|
await executor.shutdown();
|
|
1276
|
-
|
|
1888
|
+
sessionStore.shutdown();
|
|
1889
|
+
log4.info("Server shut down");
|
|
1277
1890
|
}
|
|
1278
1891
|
};
|
|
1279
1892
|
}
|
|
1280
1893
|
|
|
1281
1894
|
// src/cli.ts
|
|
1282
1895
|
init_logger();
|
|
1283
|
-
var
|
|
1896
|
+
var log12 = logger.child("cli");
|
|
1284
1897
|
var PKG_VERSION = "0.1.0";
|
|
1285
1898
|
function printUsage() {
|
|
1286
1899
|
console.log(`
|
|
@@ -1431,7 +2044,7 @@ async function registerWithGateway(registerUrl, registerKey, config, agentCardUr
|
|
|
1431
2044
|
};
|
|
1432
2045
|
try {
|
|
1433
2046
|
const url = registerUrl.replace(/\/$/, "") + "/api/assistants";
|
|
1434
|
-
|
|
2047
|
+
log12.info("Registering with gateway", { url, agentCardUrl });
|
|
1435
2048
|
const resp = await fetch(url, {
|
|
1436
2049
|
method: "POST",
|
|
1437
2050
|
headers: {
|
|
@@ -1442,24 +2055,24 @@ async function registerWithGateway(registerUrl, registerKey, config, agentCardUr
|
|
|
1442
2055
|
});
|
|
1443
2056
|
if (!resp.ok) {
|
|
1444
2057
|
const text = await resp.text().catch(() => "");
|
|
1445
|
-
|
|
2058
|
+
log12.error("Gateway registration failed", { status: resp.status, body: text });
|
|
1446
2059
|
return null;
|
|
1447
2060
|
}
|
|
1448
2061
|
const result = await resp.json();
|
|
1449
2062
|
const assistantId = result.data?.id ?? body.graphDefinition.key;
|
|
1450
|
-
|
|
2063
|
+
log12.info("Registered with gateway", { assistantId, agentCardUrl });
|
|
1451
2064
|
console.log(`Registered as agent: ${assistantId}`);
|
|
1452
2065
|
console.log(`Agent card: ${agentCardUrl}`);
|
|
1453
2066
|
return { assistantId, agentCardUrl };
|
|
1454
2067
|
} catch (err) {
|
|
1455
|
-
|
|
2068
|
+
log12.error("Gateway registration error", { error: err.message });
|
|
1456
2069
|
return null;
|
|
1457
2070
|
}
|
|
1458
2071
|
}
|
|
1459
2072
|
async function unregisterFromGateway(registerUrl, registerKey, assistantId) {
|
|
1460
2073
|
try {
|
|
1461
2074
|
const url = `${registerUrl.replace(/\/$/, "")}/api/assistants/${assistantId}`;
|
|
1462
|
-
|
|
2075
|
+
log12.info("Unregistering from gateway", { assistantId });
|
|
1463
2076
|
const resp = await fetch(url, {
|
|
1464
2077
|
method: "DELETE",
|
|
1465
2078
|
headers: {
|
|
@@ -1467,19 +2080,19 @@ async function unregisterFromGateway(registerUrl, registerKey, assistantId) {
|
|
|
1467
2080
|
}
|
|
1468
2081
|
});
|
|
1469
2082
|
if (!resp.ok && resp.status !== 404) {
|
|
1470
|
-
|
|
2083
|
+
log12.warn("Gateway unregistration failed", { status: resp.status });
|
|
1471
2084
|
} else {
|
|
1472
|
-
|
|
2085
|
+
log12.info("Unregistered from gateway", { assistantId });
|
|
1473
2086
|
}
|
|
1474
2087
|
} catch (err) {
|
|
1475
|
-
|
|
2088
|
+
log12.warn("Gateway unregistration error", { error: err.message });
|
|
1476
2089
|
}
|
|
1477
2090
|
}
|
|
1478
2091
|
async function main() {
|
|
1479
2092
|
const { configPath, overrides, registerUrl, registerKey, unregisterOnShutdown, bridgeUrl, bridgeKey } = parseCliArgs();
|
|
1480
2093
|
const config = resolveConfig(configPath, overrides);
|
|
1481
2094
|
if (configPath) {
|
|
1482
|
-
config.configDir = (0,
|
|
2095
|
+
config.configDir = (0, import_node_path3.dirname)((0, import_node_path3.resolve)(configPath));
|
|
1483
2096
|
}
|
|
1484
2097
|
const levelStr = config.logging.level ?? "info";
|
|
1485
2098
|
const levelMap = {
|
|
@@ -1490,9 +2103,9 @@ async function main() {
|
|
|
1490
2103
|
};
|
|
1491
2104
|
logger.setLevel(levelMap[levelStr] ?? 20 /* INFO */);
|
|
1492
2105
|
if (!configPath) {
|
|
1493
|
-
|
|
2106
|
+
log12.info("No --config provided \u2014 running with defaults + CLI args.");
|
|
1494
2107
|
}
|
|
1495
|
-
|
|
2108
|
+
log12.info("Starting CLI A2A Gateway", {
|
|
1496
2109
|
provider: config.provider,
|
|
1497
2110
|
config: configPath ?? "(defaults)",
|
|
1498
2111
|
agent: config.agentCard.name,
|
|
@@ -1524,10 +2137,10 @@ async function main() {
|
|
|
1524
2137
|
localBaseUrl: `http://localhost:${listenPort}`
|
|
1525
2138
|
});
|
|
1526
2139
|
bridgeClient.start().then(() => {
|
|
1527
|
-
|
|
2140
|
+
log12.info("Bridge connected \u2014 agent reachable via gateway proxy");
|
|
1528
2141
|
console.log(`Bridge: ws://gateway \u2192 localhost:${listenPort}`);
|
|
1529
2142
|
}).catch((err) => {
|
|
1530
|
-
|
|
2143
|
+
log12.error("Bridge connection failed", { error: err.message });
|
|
1531
2144
|
});
|
|
1532
2145
|
}
|
|
1533
2146
|
let registeredAssistantId = null;
|
|
@@ -1544,7 +2157,7 @@ async function main() {
|
|
|
1544
2157
|
}
|
|
1545
2158
|
}
|
|
1546
2159
|
const shutdown = async (signal) => {
|
|
1547
|
-
|
|
2160
|
+
log12.info(`${signal} received, shutting down...`);
|
|
1548
2161
|
bridgeClient?.stop();
|
|
1549
2162
|
if (registeredAssistantId && registerUrl && registerKey && unregisterOnShutdown) {
|
|
1550
2163
|
await unregisterFromGateway(registerUrl, registerKey, registeredAssistantId);
|
|
@@ -1556,7 +2169,7 @@ async function main() {
|
|
|
1556
2169
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
1557
2170
|
}
|
|
1558
2171
|
main().catch((err) => {
|
|
1559
|
-
|
|
2172
|
+
log12.error("Fatal error", { error: err.message, stack: err.stack });
|
|
1560
2173
|
process.exit(1);
|
|
1561
2174
|
});
|
|
1562
2175
|
//# sourceMappingURL=cli.js.map
|