replicas-engine 0.1.212 → 0.1.214

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.
Files changed (2) hide show
  1. package/dist/src/index.js +615 -111
  2. package/package.json +2 -1
package/dist/src/index.js CHANGED
@@ -1493,7 +1493,7 @@ function parseReplicasConfigString(content, filename) {
1493
1493
  }
1494
1494
 
1495
1495
  // ../shared/src/engine/environment.ts
1496
- var DAYTONA_SNAPSHOT_ID = "25-05-2026-royal-york-v3";
1496
+ var DAYTONA_SNAPSHOT_ID = "26-05-2026-royal-york-v2";
1497
1497
 
1498
1498
  // ../shared/src/engine/types.ts
1499
1499
  var DEFAULT_CHAT_TITLES = {
@@ -1677,7 +1677,8 @@ function loadEngineEnv() {
1677
1677
  AWS_REGION: readEnv("AWS_REGION"),
1678
1678
  REPLICAS_CLAUDE_AUTH_METHOD: parseClaudeAuthMethod(readEnv("REPLICAS_CLAUDE_AUTH_METHOD")),
1679
1679
  REPLICAS_CODEX_AUTH_METHOD: parseCodexAuthMethod(readEnv("REPLICAS_CODEX_AUTH_METHOD")),
1680
- REPLICAS_ENV_SYSTEM_PROMPT: readEnv("REPLICAS_ENV_SYSTEM_PROMPT")
1680
+ REPLICAS_ENV_SYSTEM_PROMPT: readEnv("REPLICAS_ENV_SYSTEM_PROMPT"),
1681
+ CODEX_ASP_ENABLED: readEnv("CODEX_ASP_ENABLED")?.toLowerCase() === "true"
1681
1682
  };
1682
1683
  if (!IS_WARMING_MODE && !env.WORKSPACE_ID) {
1683
1684
  console.error("WORKSPACE_ID is not set \u2014 this is required in normal (non-warming) mode");
@@ -4023,20 +4024,6 @@ var MessageQueueService = class {
4023
4024
  };
4024
4025
 
4025
4026
  // src/managers/coding-agent-manager.ts
4026
- var COMPACT_COMMAND_REGEX = /^\/compact\b\s*(.*)$/s;
4027
- var COMPACT_TURN_MARKER = "\0__replicas_compact_turn__\0";
4028
- var COMPACT_SUMMARY_PROMPT = [
4029
- "The user has requested a session compaction. Produce a thorough summary of this conversation that captures everything needed to continue the work without the prior context.",
4030
- "",
4031
- "Cover at least:",
4032
- "1. The high-level goal we are working toward and the user's most recent intent.",
4033
- "2. Key decisions made and the rationale behind them.",
4034
- "3. Files inspected or modified (paths + the relevant changes).",
4035
- "4. Open questions, blockers, and pending follow-ups.",
4036
- "5. Any environment details, credentials, or external systems already configured.",
4037
- "",
4038
- "Format the response as Markdown with clear headings. The session will be reset after your reply and your summary re-injected as the prelude to the next user turn; write it so a future agent can resume using only that prelude."
4039
- ].join("\n");
4040
4027
  var MAX_INTERRUPT_QUEUE_ITEMS = 1e3;
4041
4028
  var MAX_INTERRUPT_QUEUE_CHARS = 2e5;
4042
4029
  var CodingAgentManager = class {
@@ -4047,9 +4034,7 @@ var CodingAgentManager = class {
4047
4034
  onSaveSessionId;
4048
4035
  onEvent;
4049
4036
  hostOnTurnComplete;
4050
- compactionInFlight = false;
4051
- compactionBuffer = "";
4052
- pendingSessionPrelude = null;
4037
+ compacting = false;
4053
4038
  constructor(options) {
4054
4039
  this.workingDirectory = options.workingDirectory ?? ENGINE_ENV.WORKSPACE_ROOT;
4055
4040
  this.initialSessionId = options.initialSessionId;
@@ -4058,65 +4043,26 @@ var CodingAgentManager = class {
4058
4043
  this.hostOnTurnComplete = options.onTurnComplete;
4059
4044
  }
4060
4045
  onTurnComplete = async () => {
4061
- if (this.compactionInFlight) {
4062
- this.compactionInFlight = false;
4063
- const summary = this.compactionBuffer.trim();
4064
- this.compactionBuffer = "";
4065
- this.pendingSessionPrelude = summary.length > 0 ? summary : null;
4066
- this.emitCompactionStatus("completed");
4067
- try {
4068
- await this.clearSessionState();
4069
- } catch (error) {
4070
- console.error("[CodingAgentManager] Failed to clear session after compaction:", error);
4071
- }
4046
+ if (this.compacting) {
4047
+ this.setCompacting(false);
4072
4048
  }
4073
4049
  await this.hostOnTurnComplete();
4074
4050
  };
4075
4051
  isCompacting() {
4076
- return this.compactionInFlight;
4077
- }
4078
- captureCompactionText(text) {
4079
- if (!this.compactionInFlight) return;
4080
- const trimmed = text.trim();
4081
- if (!trimmed) return;
4082
- this.compactionBuffer = this.compactionBuffer.length > 0 ? `${this.compactionBuffer}
4083
-
4084
- ${trimmed}` : trimmed;
4085
- }
4086
- consumePendingPrelude(message) {
4087
- if (!this.pendingSessionPrelude) return message;
4088
- const prelude = this.pendingSessionPrelude;
4089
- this.pendingSessionPrelude = null;
4090
- return `<prior-session-summary>
4091
- ${prelude}
4092
- </prior-session-summary>
4093
-
4094
- ${message}`;
4052
+ return this.compacting;
4095
4053
  }
4096
- emitCompactionStatus(state) {
4054
+ // Dedup so a noisy stream of native compaction events doesn't spam the UI with redundant status flips.
4055
+ setCompacting(active) {
4056
+ if (this.compacting === active) return;
4057
+ this.compacting = active;
4097
4058
  this.onEvent({
4098
4059
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4099
4060
  type: COMPACTION_STATUS_EVENT_TYPE,
4100
- payload: { state }
4061
+ payload: { state: active ? "in_progress" : "completed" }
4101
4062
  });
4102
4063
  }
4103
4064
  initializeManager(processMessage) {
4104
- const wrappedProcessMessage = async (request) => {
4105
- if (request.message.startsWith(COMPACT_TURN_MARKER)) {
4106
- this.compactionInFlight = true;
4107
- this.compactionBuffer = "";
4108
- this.emitCompactionStatus("in_progress");
4109
- const summaryPrompt = this.consumePendingPrelude(
4110
- request.message.slice(COMPACT_TURN_MARKER.length)
4111
- );
4112
- return processMessage({ ...request, message: summaryPrompt });
4113
- }
4114
- return processMessage({
4115
- ...request,
4116
- message: this.consumePendingPrelude(request.message)
4117
- });
4118
- };
4119
- this.messageQueue = new MessageQueueService(wrappedProcessMessage);
4065
+ this.messageQueue = new MessageQueueService(processMessage);
4120
4066
  this.initialized = this.initialize();
4121
4067
  }
4122
4068
  async interrupt() {
@@ -4136,7 +4082,7 @@ ${message}`;
4136
4082
  getQueue() {
4137
4083
  return this.messageQueue.getQueue().map((m) => ({
4138
4084
  id: m.id,
4139
- message: m.message.startsWith(COMPACT_TURN_MARKER) ? "/compact" : m.message,
4085
+ message: m.message,
4140
4086
  queuedAt: m.queuedAt,
4141
4087
  ...m.senderUserId ? { senderUserId: m.senderUserId } : {},
4142
4088
  ...m.senderEmail ? { senderEmail: m.senderEmail } : {},
@@ -4151,18 +4097,6 @@ ${message}`;
4151
4097
  }
4152
4098
  async enqueueMessage(request) {
4153
4099
  await this.initialized;
4154
- const compact = request.message.trimStart().match(COMPACT_COMMAND_REGEX);
4155
- if (compact) {
4156
- const guidance = compact[1].trim();
4157
- const prompt = guidance ? `${COMPACT_SUMMARY_PROMPT}
4158
-
4159
- Additional guidance from the user for the summary:
4160
- ${guidance}` : COMPACT_SUMMARY_PROMPT;
4161
- return this.messageQueue.enqueue({
4162
- ...request,
4163
- message: `${COMPACT_TURN_MARKER}${prompt}`
4164
- });
4165
- }
4166
4100
  return this.messageQueue.enqueue(request);
4167
4101
  }
4168
4102
  emitContextUsage(payload) {
@@ -4201,7 +4135,7 @@ ${guidance}` : COMPACT_SUMMARY_PROMPT;
4201
4135
  const queue = this.messageQueue.drainQueue({
4202
4136
  maxItems: MAX_INTERRUPT_QUEUE_ITEMS,
4203
4137
  maxChars: MAX_INTERRUPT_QUEUE_CHARS
4204
- }).map((m) => m.startsWith(COMPACT_TURN_MARKER) ? "/compact" : m);
4138
+ });
4205
4139
  return {
4206
4140
  queue,
4207
4141
  isProcessing: this.messageQueue.isProcessing()
@@ -4478,10 +4412,6 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4478
4412
  }
4479
4413
  await this.abortAllPendingToolInputs();
4480
4414
  }
4481
- async clearSessionState() {
4482
- this.sessionId = null;
4483
- await this.onSaveSessionId(null);
4484
- }
4485
4415
  async abortAllPendingToolInputs() {
4486
4416
  const pending = Array.from(this.pendingToolInputs.values());
4487
4417
  this.pendingToolInputs.clear();
@@ -4839,15 +4769,20 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4839
4769
  console.log(`[ClaudeManager] Captured and persisted session ID: ${this.sessionId}`);
4840
4770
  }
4841
4771
  await this.checkForAuthFailure(message);
4842
- if (this.isCompacting() && message.type === "assistant") {
4843
- for (const block of message.message.content) {
4844
- if (block.type === "text") {
4845
- this.captureCompactionText(block.text);
4846
- }
4847
- }
4848
- }
4772
+ this.trackNativeCompaction(message);
4849
4773
  await this.recordEvent(message);
4850
4774
  }
4775
+ // Claude Code intercepts /compact and auto-compaction inside the SDK process; we just observe the status messages to mirror state to the UI.
4776
+ trackNativeCompaction(message) {
4777
+ if (message.type !== "system") return;
4778
+ if (message.subtype === "status" && message.status === "compacting") {
4779
+ this.setCompacting(true);
4780
+ return;
4781
+ }
4782
+ if (message.subtype === "compact_boundary") {
4783
+ this.setCompacting(false);
4784
+ }
4785
+ }
4851
4786
  async recordEvent(event) {
4852
4787
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
4853
4788
  const jsonEvent = {
@@ -5011,11 +4946,6 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5011
4946
  this.activeAbortController.abort();
5012
4947
  }
5013
4948
  }
5014
- async clearSessionState() {
5015
- this.currentThreadId = null;
5016
- this.currentThread = null;
5017
- await this.onSaveSessionId(null);
5018
- }
5019
4949
  /**
5020
4950
  * Update the developer_instructions in ~/.codex/config.toml
5021
4951
  * This sets the system prompt that Codex will use for this turn
@@ -5289,16 +5219,21 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5289
5219
  }
5290
5220
  return null;
5291
5221
  }
5292
- bufferCompactionFromEvent(event) {
5293
- if (!this.isCompacting()) return;
5294
- if (event.type !== "response_item") return;
5295
- const payload = event.payload;
5296
- if (payload?.type !== "message" || payload?.role !== "assistant") return;
5297
- const blocks = Array.isArray(payload.content) ? payload.content : [];
5298
- for (const block of blocks) {
5299
- if (block.type === "output_text" && typeof block.text === "string") {
5300
- this.captureCompactionText(block.text);
5301
- }
5222
+ // @openai/codex-sdk doesn't expose manual /compact (TUI-only); we only mirror the auto-compaction rollout entries to the UI.
5223
+ trackNativeCompaction(event) {
5224
+ if (event.type === "compacted") {
5225
+ this.setCompacting(false);
5226
+ return;
5227
+ }
5228
+ if (event.type !== "event_msg") return;
5229
+ const msg = event.payload.msg;
5230
+ if (!msg) return;
5231
+ const itemType = msg.payload?.item?.type;
5232
+ if (itemType !== "context_compaction") return;
5233
+ if (msg.type === "item_started") {
5234
+ this.setCompacting(true);
5235
+ } else if (msg.type === "item_completed") {
5236
+ this.setCompacting(false);
5302
5237
  }
5303
5238
  }
5304
5239
  async startSessionTail(threadId) {
@@ -5351,7 +5286,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5351
5286
  this.emitQuotaStatus(snapshot);
5352
5287
  }
5353
5288
  if (isJsonlEvent2(parsed)) {
5354
- this.bufferCompactionFromEvent(parsed);
5289
+ this.trackNativeCompaction(parsed);
5355
5290
  this.onEvent(parsed);
5356
5291
  emitted += 1;
5357
5292
  }
@@ -5384,6 +5319,574 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5384
5319
  }
5385
5320
  };
5386
5321
 
5322
+ // src/managers/codex-asp/app-server-process.ts
5323
+ import { spawn } from "child_process";
5324
+ import { EventEmitter as EventEmitter2 } from "events";
5325
+
5326
+ // src/managers/codex-asp/asp-client.ts
5327
+ import { EventEmitter } from "events";
5328
+ var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
5329
+ function hasOwn(record, key) {
5330
+ return Object.prototype.hasOwnProperty.call(record, key);
5331
+ }
5332
+ var AspClient = class {
5333
+ stdin;
5334
+ stdout;
5335
+ emitter = new EventEmitter();
5336
+ pending = /* @__PURE__ */ new Map();
5337
+ nextId = 1;
5338
+ lineBuffer = "";
5339
+ disposed = false;
5340
+ get isDisposed() {
5341
+ return this.disposed;
5342
+ }
5343
+ constructor(options) {
5344
+ this.stdin = options.stdin;
5345
+ this.stdout = options.stdout;
5346
+ this.stdout.setEncoding("utf8");
5347
+ this.stdout.on("data", this.handleStdoutData);
5348
+ this.stdin.on("error", this.handleStdinError);
5349
+ }
5350
+ on(event, listener) {
5351
+ this.emitter.on(event, listener);
5352
+ }
5353
+ off(event, listener) {
5354
+ this.emitter.off(event, listener);
5355
+ }
5356
+ async request(method, params, opts) {
5357
+ if (this.disposed) {
5358
+ throw new Error(`Cannot send ${method}: ASP client disposed`);
5359
+ }
5360
+ const id = this.nextId;
5361
+ this.nextId += 1;
5362
+ const promise = new Promise((resolve3, reject) => {
5363
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
5364
+ const timer = timeoutMs > 0 ? setTimeout(() => {
5365
+ this.pending.delete(id);
5366
+ reject(new Error(`ASP request timed out for ${method}`));
5367
+ }, timeoutMs) : null;
5368
+ this.pending.set(id, { resolve: resolve3, reject, method, timer });
5369
+ });
5370
+ this.write({ method, id, params });
5371
+ return promise;
5372
+ }
5373
+ notify(method, params) {
5374
+ if (this.disposed) {
5375
+ return;
5376
+ }
5377
+ try {
5378
+ this.write(params === void 0 ? { method } : { method, params });
5379
+ } catch (error) {
5380
+ console.warn(`[AspClient] Failed to send notification ${method}:`, error);
5381
+ }
5382
+ }
5383
+ respond(id, result) {
5384
+ if (this.disposed) {
5385
+ return;
5386
+ }
5387
+ try {
5388
+ this.write({ id, result });
5389
+ } catch (error) {
5390
+ console.warn(`[AspClient] Failed to send response ${String(id)}:`, error);
5391
+ }
5392
+ }
5393
+ dispose(reason = new Error("ASP client disposed")) {
5394
+ if (this.disposed) {
5395
+ return;
5396
+ }
5397
+ this.disposed = true;
5398
+ this.stdout.off("data", this.handleStdoutData);
5399
+ this.stdin.removeListener("error", this.handleStdinError);
5400
+ for (const [id, pending] of this.pending) {
5401
+ if (pending.timer) {
5402
+ clearTimeout(pending.timer);
5403
+ }
5404
+ pending.reject(new Error(`${reason.message} while waiting for ${pending.method}`));
5405
+ this.pending.delete(id);
5406
+ }
5407
+ this.lineBuffer = "";
5408
+ this.emitter.emit("dispose", reason);
5409
+ this.emitter.removeAllListeners();
5410
+ }
5411
+ handleStdoutData = (chunk) => {
5412
+ this.lineBuffer += chunk.toString();
5413
+ let newlineIndex = this.lineBuffer.indexOf("\n");
5414
+ while (newlineIndex >= 0) {
5415
+ const line = this.lineBuffer.slice(0, newlineIndex).trim();
5416
+ this.lineBuffer = this.lineBuffer.slice(newlineIndex + 1);
5417
+ if (line.length > 0) {
5418
+ this.handleLine(line);
5419
+ }
5420
+ newlineIndex = this.lineBuffer.indexOf("\n");
5421
+ }
5422
+ };
5423
+ handleStdinError = (error) => {
5424
+ this.dispose(new Error(`ASP stdin error: ${error.message}`));
5425
+ };
5426
+ handleLine(line) {
5427
+ let parsed;
5428
+ try {
5429
+ parsed = JSON.parse(line);
5430
+ } catch (error) {
5431
+ console.warn("[AspClient] Failed to parse ASP JSON line:", error);
5432
+ return;
5433
+ }
5434
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
5435
+ console.warn("[AspClient] Ignoring non-object ASP message");
5436
+ return;
5437
+ }
5438
+ const message = parsed;
5439
+ const hasRequestId = typeof message.id === "number" || typeof message.id === "string";
5440
+ if (hasRequestId && (hasOwn(message, "result") || hasOwn(message, "error"))) {
5441
+ this.handleResponse(message);
5442
+ return;
5443
+ }
5444
+ if (hasRequestId && typeof message.method === "string") {
5445
+ this.emitter.emit("serverRequest", message);
5446
+ return;
5447
+ }
5448
+ if (!hasOwn(message, "id") && typeof message.method === "string") {
5449
+ this.emitter.emit("notification", message);
5450
+ }
5451
+ }
5452
+ handleResponse(message) {
5453
+ if (typeof message.id !== "number") {
5454
+ console.warn("[AspClient] Ignoring response with non-numeric request id");
5455
+ return;
5456
+ }
5457
+ const pending = this.pending.get(message.id);
5458
+ if (!pending) {
5459
+ console.warn(`[AspClient] Ignoring response for unknown request id ${message.id}`);
5460
+ return;
5461
+ }
5462
+ this.pending.delete(message.id);
5463
+ if (pending.timer) {
5464
+ clearTimeout(pending.timer);
5465
+ }
5466
+ if (hasOwn(message, "error")) {
5467
+ pending.reject(this.createRpcError(pending.method, message.error));
5468
+ return;
5469
+ }
5470
+ pending.resolve(message.result);
5471
+ }
5472
+ createRpcError(method, error) {
5473
+ if (typeof error !== "object" || error === null || Array.isArray(error)) {
5474
+ return new Error(`ASP request failed for ${method}`);
5475
+ }
5476
+ const rpcError = error;
5477
+ const code = typeof rpcError.code === "number" ? ` ${rpcError.code}` : "";
5478
+ const message = typeof rpcError.message === "string" ? rpcError.message : "Unknown ASP error";
5479
+ const data = hasOwn(rpcError, "data") ? ` data=${JSON.stringify(rpcError.data)}` : "";
5480
+ return new Error(`ASP request failed for ${method}:${code} ${message}${data}`);
5481
+ }
5482
+ write(message) {
5483
+ try {
5484
+ this.stdin.write(`${JSON.stringify(message)}
5485
+ `, (error) => {
5486
+ if (error) {
5487
+ this.dispose(new Error(`ASP write failed: ${error.message}`));
5488
+ }
5489
+ });
5490
+ } catch (error) {
5491
+ const writeError = error instanceof Error ? error : new Error("ASP write failed");
5492
+ this.dispose(writeError);
5493
+ throw writeError;
5494
+ }
5495
+ }
5496
+ };
5497
+
5498
+ // src/managers/codex-asp/app-server-process.ts
5499
+ var DEFAULT_CODEX_BINARY = "codex";
5500
+ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
5501
+ var ENGINE_PACKAGE_VERSION = "0.1.213";
5502
+ var INITIALIZE_METHOD = "initialize";
5503
+ var INITIALIZED_NOTIFICATION = "initialized";
5504
+ var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
5505
+ var AppServerProcess = class {
5506
+ binary;
5507
+ args;
5508
+ env;
5509
+ cwd;
5510
+ emitter = new EventEmitter2();
5511
+ child = null;
5512
+ client = null;
5513
+ shuttingDown = false;
5514
+ constructor(options = {}) {
5515
+ this.binary = options.binary ?? DEFAULT_CODEX_BINARY;
5516
+ this.args = options.args ?? DEFAULT_CODEX_ARGS;
5517
+ this.env = options.env ?? buildCodexAgentEnv();
5518
+ this.cwd = options.cwd ?? ENGINE_ENV.WORKSPACE_ROOT;
5519
+ }
5520
+ on(event, listener) {
5521
+ this.emitter.on(event, listener);
5522
+ }
5523
+ async start() {
5524
+ if (this.child && this.client) {
5525
+ return { client: this.client };
5526
+ }
5527
+ this.shuttingDown = false;
5528
+ const child = spawn(this.binary, this.args, {
5529
+ cwd: this.cwd,
5530
+ env: this.env,
5531
+ stdio: ["pipe", "pipe", "pipe"]
5532
+ });
5533
+ this.child = child;
5534
+ child.stderr.setEncoding("utf8");
5535
+ child.stderr.on("data", (chunk) => {
5536
+ for (const line of chunk.toString().split("\n")) {
5537
+ if (line.trim().length > 0) {
5538
+ console.error(`[codex-app-server] ${line}`);
5539
+ }
5540
+ }
5541
+ });
5542
+ child.on("exit", (code, signal) => {
5543
+ this.client?.dispose();
5544
+ this.client = null;
5545
+ this.child = null;
5546
+ if (!this.shuttingDown) {
5547
+ console.warn(`[AppServerProcess] codex app-server exited unexpectedly code=${code ?? "null"} signal=${signal ?? "null"}`);
5548
+ this.emitter.emit("exit", code, signal);
5549
+ }
5550
+ });
5551
+ const client = new AspClient({ stdin: child.stdin, stdout: child.stdout });
5552
+ this.client = client;
5553
+ let cleanupEarlyFailureHandlers = () => {
5554
+ };
5555
+ const earlyFailure = new Promise((_resolve, reject) => {
5556
+ const onError = (error) => {
5557
+ reject(error);
5558
+ };
5559
+ const onExit = (code, signal) => {
5560
+ reject(new Error(`codex app-server exited before initialize completed code=${code ?? "null"} signal=${signal ?? "null"}`));
5561
+ };
5562
+ child.once("error", onError);
5563
+ child.once("exit", onExit);
5564
+ cleanupEarlyFailureHandlers = () => {
5565
+ child.off("error", onError);
5566
+ child.off("exit", onExit);
5567
+ };
5568
+ });
5569
+ try {
5570
+ const initializeParams = {
5571
+ clientInfo: {
5572
+ name: "replicas_engine",
5573
+ title: "Replicas Engine",
5574
+ version: ENGINE_PACKAGE_VERSION
5575
+ },
5576
+ capabilities: {
5577
+ experimentalApi: true,
5578
+ requestAttestation: false,
5579
+ optOutNotificationMethods: null
5580
+ }
5581
+ };
5582
+ await Promise.race([
5583
+ client.request(INITIALIZE_METHOD, initializeParams),
5584
+ earlyFailure
5585
+ ]);
5586
+ cleanupEarlyFailureHandlers();
5587
+ client.notify(INITIALIZED_NOTIFICATION);
5588
+ await this.loginWithConfiguredApiKey(client);
5589
+ return { client };
5590
+ } catch (error) {
5591
+ cleanupEarlyFailureHandlers();
5592
+ client.dispose();
5593
+ await this.killAfterFailedStart();
5594
+ throw error;
5595
+ }
5596
+ }
5597
+ async loginWithConfiguredApiKey(client) {
5598
+ if (ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD !== "api_key" || !ENGINE_ENV.OPENAI_API_KEY) {
5599
+ return;
5600
+ }
5601
+ const params = {
5602
+ type: "apiKey",
5603
+ apiKey: ENGINE_ENV.OPENAI_API_KEY
5604
+ };
5605
+ await client.request(ACCOUNT_LOGIN_START_METHOD, params);
5606
+ }
5607
+ async killAfterFailedStart() {
5608
+ const child = this.child;
5609
+ this.child = null;
5610
+ this.client = null;
5611
+ if (!child || child.killed) {
5612
+ return;
5613
+ }
5614
+ this.shuttingDown = true;
5615
+ child.kill("SIGKILL");
5616
+ await new Promise((resolve3) => {
5617
+ child.once("exit", () => resolve3());
5618
+ });
5619
+ }
5620
+ };
5621
+
5622
+ // src/managers/codex-asp/asp-host.ts
5623
+ var hostPromise = null;
5624
+ async function getCodexAspHost() {
5625
+ hostPromise ??= (async () => {
5626
+ try {
5627
+ const process2 = new AppServerProcess();
5628
+ const { client } = await process2.start();
5629
+ process2.on("exit", () => {
5630
+ hostPromise = null;
5631
+ });
5632
+ return { client };
5633
+ } catch (error) {
5634
+ hostPromise = null;
5635
+ throw error;
5636
+ }
5637
+ })();
5638
+ return hostPromise;
5639
+ }
5640
+
5641
+ // src/managers/codex-asp/codex-asp-manager.ts
5642
+ var DEFAULT_MODEL2 = "gpt-5.5";
5643
+ var THREAD_START_METHOD = "thread/start";
5644
+ var TURN_START_METHOD = "turn/start";
5645
+ var TURN_INTERRUPT_METHOD = "turn/interrupt";
5646
+ var TURN_COMPLETED_METHOD = "turn/completed";
5647
+ var ITEM_COMPLETED_METHOD = "item/completed";
5648
+ var AGENT_MESSAGE_DELTA_METHOD = "item/agentMessage/delta";
5649
+ var COMMAND_APPROVAL_METHOD = "item/commandExecution/requestApproval";
5650
+ var FILE_CHANGE_APPROVAL_METHOD = "item/fileChange/requestApproval";
5651
+ var CodexAspManager = class extends CodingAgentManager {
5652
+ currentThreadId = null;
5653
+ activeTurnId = null;
5654
+ historyEvents = [];
5655
+ constructor(options) {
5656
+ super(options);
5657
+ this.initializeManager(this.processMessageInternal.bind(this));
5658
+ }
5659
+ async initialize() {
5660
+ if (this.initialSessionId) {
5661
+ this.currentThreadId = this.initialSessionId;
5662
+ }
5663
+ }
5664
+ async interruptActiveTurn() {
5665
+ if (!this.currentThreadId || !this.activeTurnId) {
5666
+ return;
5667
+ }
5668
+ try {
5669
+ const host = await getCodexAspHost();
5670
+ const params = {
5671
+ threadId: this.currentThreadId,
5672
+ turnId: this.activeTurnId
5673
+ };
5674
+ await host.client.request(TURN_INTERRUPT_METHOD, params);
5675
+ } catch (error) {
5676
+ console.warn("[CodexAspManager] Failed to interrupt active turn:", error);
5677
+ }
5678
+ }
5679
+ async getHistory() {
5680
+ return {
5681
+ thread_id: this.currentThreadId,
5682
+ events: [...this.historyEvents]
5683
+ };
5684
+ }
5685
+ async processMessageInternal(request) {
5686
+ let host = null;
5687
+ try {
5688
+ host = await getCodexAspHost();
5689
+ const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
5690
+ this.recordHistoryEvent("event_msg", {
5691
+ type: "user_message",
5692
+ message: request.message
5693
+ });
5694
+ if (!this.currentThreadId) {
5695
+ const threadStartResponse = await host.client.request(
5696
+ THREAD_START_METHOD,
5697
+ await this.buildThreadStartParams(request, developerInstructions)
5698
+ );
5699
+ this.currentThreadId = threadStartResponse.thread.id;
5700
+ await this.onSaveSessionId(this.currentThreadId);
5701
+ }
5702
+ const threadId = this.currentThreadId;
5703
+ const completedTurn = await this.runTurn(host, threadId, request, developerInstructions);
5704
+ if (completedTurn.status === "failed") {
5705
+ const turnError = completedTurn.error;
5706
+ if (!turnError) {
5707
+ throw new Error("Codex ASP turn failed without error details");
5708
+ }
5709
+ const parts = [`Codex ASP turn failed: ${turnError.message}`];
5710
+ if (turnError.codexErrorInfo !== null) {
5711
+ parts.push(`codexErrorInfo=${JSON.stringify(turnError.codexErrorInfo)}`);
5712
+ }
5713
+ if (turnError.additionalDetails) {
5714
+ parts.push(`details=${turnError.additionalDetails}`);
5715
+ }
5716
+ throw new Error(parts.join(" "));
5717
+ }
5718
+ if (completedTurn.status === "completed") {
5719
+ this.emitFinalAgentMessage(completedTurn);
5720
+ }
5721
+ } finally {
5722
+ this.activeTurnId = null;
5723
+ if (host?.client.isDisposed) {
5724
+ this.currentThreadId = null;
5725
+ void this.onSaveSessionId(null).catch(() => {
5726
+ });
5727
+ }
5728
+ await this.onTurnComplete();
5729
+ }
5730
+ }
5731
+ async buildThreadStartParams(request, developerInstructions) {
5732
+ const additionalDirectories = await getAgentAdditionalDirectories();
5733
+ return {
5734
+ model: request.model ?? DEFAULT_MODEL2,
5735
+ cwd: this.workingDirectory,
5736
+ runtimeWorkspaceRoots: additionalDirectories,
5737
+ sandbox: "danger-full-access",
5738
+ developerInstructions: developerInstructions ?? null,
5739
+ config: { web_search: "live" },
5740
+ experimentalRawEvents: false,
5741
+ persistExtendedHistory: false
5742
+ };
5743
+ }
5744
+ buildTurnStartParams(threadId, request, developerInstructions) {
5745
+ const effort = request.thinkingLevel === "max" ? "xhigh" : request.thinkingLevel;
5746
+ const model = request.model ?? DEFAULT_MODEL2;
5747
+ return {
5748
+ threadId,
5749
+ input: [{
5750
+ type: "text",
5751
+ text: request.message,
5752
+ text_elements: []
5753
+ }],
5754
+ model,
5755
+ ...effort ? { effort } : {},
5756
+ ...developerInstructions ? {
5757
+ collaborationMode: {
5758
+ mode: "default",
5759
+ settings: {
5760
+ model,
5761
+ reasoning_effort: effort ?? null,
5762
+ developer_instructions: developerInstructions
5763
+ }
5764
+ }
5765
+ } : {}
5766
+ };
5767
+ }
5768
+ async runTurn(host, threadId, request, developerInstructions) {
5769
+ let resolveCompleted;
5770
+ const completed = new Promise((resolve3) => {
5771
+ resolveCompleted = resolve3;
5772
+ });
5773
+ let rejectDisposed = () => {
5774
+ };
5775
+ const disposed = new Promise((_resolve, reject) => {
5776
+ rejectDisposed = reject;
5777
+ });
5778
+ void disposed.catch(() => {
5779
+ });
5780
+ let observedTurnId = null;
5781
+ const completedItems = [];
5782
+ const agentMessageDeltas = /* @__PURE__ */ new Map();
5783
+ const onNotification = (notification) => {
5784
+ if (notification.method === ITEM_COMPLETED_METHOD) {
5785
+ if (notification.params.threadId === threadId && (!observedTurnId || notification.params.turnId === observedTurnId)) {
5786
+ completedItems.push(notification.params.item);
5787
+ }
5788
+ return;
5789
+ }
5790
+ if (notification.method === AGENT_MESSAGE_DELTA_METHOD) {
5791
+ if (notification.params.threadId === threadId && (!observedTurnId || notification.params.turnId === observedTurnId)) {
5792
+ const currentText = agentMessageDeltas.get(notification.params.itemId) ?? "";
5793
+ agentMessageDeltas.set(notification.params.itemId, currentText + notification.params.delta);
5794
+ }
5795
+ return;
5796
+ }
5797
+ if (notification.method === TURN_COMPLETED_METHOD) {
5798
+ if (notification.params.threadId !== threadId) {
5799
+ return;
5800
+ }
5801
+ observedTurnId = notification.params.turn.id;
5802
+ const turn = notification.params.turn;
5803
+ const items = turn.items.length > 0 ? [...turn.items] : [];
5804
+ const itemIds = new Set(items.map((item) => item.id));
5805
+ for (const item of completedItems) {
5806
+ if (!itemIds.has(item.id)) {
5807
+ items.push(item);
5808
+ itemIds.add(item.id);
5809
+ }
5810
+ }
5811
+ for (const [itemId, text] of agentMessageDeltas) {
5812
+ if (!itemIds.has(itemId)) {
5813
+ items.push({
5814
+ type: "agentMessage",
5815
+ id: itemId,
5816
+ text,
5817
+ phase: null,
5818
+ memoryCitation: null
5819
+ });
5820
+ }
5821
+ }
5822
+ resolveCompleted(items.length > 0 ? { ...turn, items, itemsView: "full" } : turn);
5823
+ }
5824
+ };
5825
+ const onServerRequest = (serverRequest) => {
5826
+ if (serverRequest.method !== COMMAND_APPROVAL_METHOD && serverRequest.method !== FILE_CHANGE_APPROVAL_METHOD) {
5827
+ return;
5828
+ }
5829
+ console.warn("[CodexAspManager] approval requested while sandbox is danger-full-access");
5830
+ host.client.respond(serverRequest.id, { decision: "accept" });
5831
+ };
5832
+ const onDispose = (reason) => {
5833
+ this.currentThreadId = null;
5834
+ this.activeTurnId = null;
5835
+ void this.onSaveSessionId(null).catch(() => {
5836
+ });
5837
+ const turnLabel = observedTurnId ? ` ${observedTurnId}` : "";
5838
+ rejectDisposed(new Error(`Codex ASP client disposed before turn${turnLabel} completed: ${reason.message}`));
5839
+ };
5840
+ host.client.on("notification", onNotification);
5841
+ host.client.on("serverRequest", onServerRequest);
5842
+ host.client.on("dispose", onDispose);
5843
+ try {
5844
+ const turnStartResponse = await host.client.request(
5845
+ TURN_START_METHOD,
5846
+ this.buildTurnStartParams(threadId, request, developerInstructions)
5847
+ );
5848
+ observedTurnId = turnStartResponse.turn.id;
5849
+ this.activeTurnId = turnStartResponse.turn.id;
5850
+ return await Promise.race([completed, disposed]);
5851
+ } finally {
5852
+ host.client.off("notification", onNotification);
5853
+ host.client.off("serverRequest", onServerRequest);
5854
+ host.client.off("dispose", onDispose);
5855
+ }
5856
+ }
5857
+ emitFinalAgentMessage(turn) {
5858
+ let text = null;
5859
+ for (let index = turn.items.length - 1; index >= 0; index -= 1) {
5860
+ const item = turn.items[index];
5861
+ if (item?.type === "agentMessage") {
5862
+ text = item.text;
5863
+ break;
5864
+ }
5865
+ }
5866
+ if (!text) {
5867
+ return;
5868
+ }
5869
+ const event = this.recordHistoryEvent("response_item", {
5870
+ type: "message",
5871
+ role: "assistant",
5872
+ content: [{
5873
+ type: "output_text",
5874
+ text
5875
+ }]
5876
+ });
5877
+ this.onEvent(event);
5878
+ }
5879
+ recordHistoryEvent(type, payload) {
5880
+ const event = {
5881
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5882
+ type,
5883
+ payload
5884
+ };
5885
+ this.historyEvents.push(event);
5886
+ return event;
5887
+ }
5888
+ };
5889
+
5387
5890
  // src/managers/relay-tools.ts
5388
5891
  import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
5389
5892
  import { z } from "zod";
@@ -6299,7 +6802,8 @@ var ChatService = class {
6299
6802
  codexAvailable: isCodexAvailable()
6300
6803
  });
6301
6804
  } else {
6302
- provider = new CodexManager({
6805
+ const CodexProviderCtor = ENGINE_ENV.CODEX_ASP_ENABLED ? CodexAspManager : CodexManager;
6806
+ provider = new CodexProviderCtor({
6303
6807
  workingDirectory: this.workingDirectory,
6304
6808
  initialSessionId: persisted.providerSessionId,
6305
6809
  onSaveSessionId: saveSession,
@@ -6805,7 +7309,7 @@ var PlanService = class {
6805
7309
  var planService = new PlanService();
6806
7310
 
6807
7311
  // src/services/warm-hooks-service.ts
6808
- import { spawn } from "child_process";
7312
+ import { spawn as spawn2 } from "child_process";
6809
7313
  import { readFile as readFile12 } from "fs/promises";
6810
7314
  import { existsSync as existsSync8 } from "fs";
6811
7315
  import { join as join18 } from "path";
@@ -6973,7 +7477,7 @@ async function executeHookScriptStreaming(params) {
6973
7477
  params.onChunk(`$ ${params.label}
6974
7478
  `);
6975
7479
  return new Promise((resolve3) => {
6976
- const proc = spawn("bash", ["-lc", params.content], {
7480
+ const proc = spawn2("bash", ["-lc", params.content], {
6977
7481
  cwd: params.cwd,
6978
7482
  env: process.env,
6979
7483
  stdio: ["pipe", "pipe", "pipe"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.212",
3
+ "version": "0.1.214",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -38,6 +38,7 @@
38
38
  "zod": "^4.0.0"
39
39
  },
40
40
  "devDependencies": {
41
+ "@replicas/codex-asp-types": "file:../codex-asp-types",
41
42
  "@replicas/shared": "workspace:*",
42
43
  "@types/node": "^20.11.17",
43
44
  "tsup": "^8.5.0",