replicas-engine 0.1.235 → 0.1.237

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 +383 -137
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -1779,11 +1779,11 @@ function parseReplicasConfigString(content, filename) {
1779
1779
  // ../shared/src/claude-auth.ts
1780
1780
  function isClaudeAuthErrorText(text) {
1781
1781
  const lower = text.toLowerCase();
1782
- return lower.includes("failed to authenticate") || lower.includes("authentication_error") || lower.includes("authentication_failed") || lower.includes("authentication failed") || lower.includes("invalid authentication credentials") || lower.includes("not logged in") || lower.includes("please run /login") || lower.includes("401") && lower.includes("authentic");
1782
+ return lower.includes("failed to authenticate") || lower.includes("authentication_error") || lower.includes("authentication_failed") || lower.includes("authentication failed") || lower.includes("invalid authentication credentials") || lower.includes("not logged in") || lower.includes("please run /login") || lower.includes("credit balance is too low") || lower.includes("401") && lower.includes("authentic");
1783
1783
  }
1784
1784
 
1785
1785
  // ../shared/src/engine/environment.ts
1786
- var DAYTONA_SNAPSHOT_ID = "29-05-2026-royal-york-v6";
1786
+ var DAYTONA_SNAPSHOT_ID = "29-05-2026-royal-york-v8";
1787
1787
  var DESKTOP_NOVNC_PORT = 6080;
1788
1788
 
1789
1789
  // ../shared/src/engine/types.ts
@@ -1905,6 +1905,53 @@ var AUDIT_LOG_ACTION = {
1905
1905
  };
1906
1906
  var AUDIT_LOG_ACTIONS = Object.values(AUDIT_LOG_ACTION);
1907
1907
 
1908
+ // ../shared/src/display-message/types.ts
1909
+ var BACKGROUND_TASK_SUBTYPES = /* @__PURE__ */ new Set([
1910
+ "task_started",
1911
+ "task_progress",
1912
+ "task_updated",
1913
+ "task_notification"
1914
+ ]);
1915
+ function isBackgroundTaskSubtype(subtype) {
1916
+ return typeof subtype === "string" && BACKGROUND_TASK_SUBTYPES.has(subtype);
1917
+ }
1918
+ function optionalString(value) {
1919
+ return typeof value === "string" ? value : void 0;
1920
+ }
1921
+ function coerceBackgroundTaskPayload(payload) {
1922
+ if (!isRecord(payload)) return null;
1923
+ const subtype = payload.subtype;
1924
+ if (!isBackgroundTaskSubtype(subtype)) return null;
1925
+ const taskId = payload.task_id;
1926
+ if (typeof taskId !== "string" || !taskId) return null;
1927
+ const patch = isRecord(payload.patch) ? {
1928
+ status: optionalString(payload.patch.status),
1929
+ description: optionalString(payload.patch.description),
1930
+ error: optionalString(payload.patch.error)
1931
+ } : void 0;
1932
+ return {
1933
+ subtype,
1934
+ taskId,
1935
+ description: optionalString(payload.description),
1936
+ summary: optionalString(payload.summary),
1937
+ outputFile: optionalString(payload.output_file),
1938
+ status: optionalString(payload.status),
1939
+ patch
1940
+ };
1941
+ }
1942
+ function normalizeBackgroundTaskStatus(status) {
1943
+ if (status === "completed" || status === "failed" || status === "stopped") {
1944
+ return status;
1945
+ }
1946
+ if (status === "killed") {
1947
+ return "stopped";
1948
+ }
1949
+ return "in_progress";
1950
+ }
1951
+ function isTerminalBackgroundTaskStatus(status) {
1952
+ return normalizeBackgroundTaskStatus(status) !== "in_progress";
1953
+ }
1954
+
1908
1955
  // ../shared/src/agent-event-utils.ts
1909
1956
  function getUserMessage(event) {
1910
1957
  return event.type === "event_msg" && event.payload.type === "user_message" && typeof event.payload.message === "string" ? event.payload.message : null;
@@ -3260,33 +3307,67 @@ var EnvironmentDetailsService = class {
3260
3307
  var environmentDetailsService = new EnvironmentDetailsService();
3261
3308
 
3262
3309
  // src/services/start-hook-logs-service.ts
3263
- import { createHash } from "crypto";
3264
3310
  import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile3, readdir as readdir2 } from "fs/promises";
3265
3311
  import { homedir as homedir6 } from "os";
3266
3312
  import { join as join8 } from "path";
3267
- var LOGS_DIR = join8(homedir6(), ".replicas", "start-hook-logs");
3268
- function sanitizeFilename(name) {
3269
- const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
3270
- const hash = createHash("sha256").update(name).digest("hex").slice(0, 8);
3271
- return `${safe}-${hash}`;
3272
- }
3273
- function repoFilename(repoName) {
3274
- return `repo-${sanitizeFilename(repoName)}.json`;
3313
+
3314
+ // src/services/hook-log-files.ts
3315
+ import { createHash } from "crypto";
3316
+ var ENVIRONMENT_HOOK_LOG_FILENAME = "environment.json";
3317
+ function repoHookLogFilename(repoName) {
3318
+ const safe = repoName.replace(/[^a-zA-Z0-9._-]/g, "_");
3319
+ const hash = createHash("sha256").update(repoName).digest("hex").slice(0, 8);
3320
+ return `repo-${safe}-${hash}.json`;
3275
3321
  }
3322
+
3323
+ // src/services/start-hook-logs-service.ts
3324
+ var LOGS_DIR = join8(homedir6(), ".replicas", "start-hook-logs");
3276
3325
  function withPreview(stored) {
3277
3326
  const preview = buildHookOutputPreview(stored.output);
3278
3327
  return { ...stored, ...preview };
3279
3328
  }
3329
+ function isObject(value) {
3330
+ return typeof value === "object" && value !== null;
3331
+ }
3332
+ function optionalString2(value) {
3333
+ return typeof value === "string" ? value : void 0;
3334
+ }
3335
+ function normalizeStored(raw) {
3336
+ if (!isObject(raw)) return null;
3337
+ if (typeof raw.output !== "string" || typeof raw.exitCode !== "number") return null;
3338
+ const hookType = raw.hookType === "environment" ? "environment" : "repository";
3339
+ const repoNameField = optionalString2(raw.repoName);
3340
+ const hookName = optionalString2(raw.hookName) ?? repoNameField ?? "";
3341
+ const repoName = repoNameField ?? hookName;
3342
+ const hookCommands = Array.isArray(raw.hookCommands) ? raw.hookCommands.filter((c) => typeof c === "string") : [];
3343
+ return {
3344
+ hookType,
3345
+ hookName,
3346
+ repoName,
3347
+ hookCommands,
3348
+ output: raw.output,
3349
+ exitCode: raw.exitCode,
3350
+ timedOut: typeof raw.timedOut === "boolean" ? raw.timedOut : false,
3351
+ executedAt: optionalString2(raw.executedAt) ?? ""
3352
+ };
3353
+ }
3280
3354
  var StartHookLogsService = class {
3281
3355
  async ensureDir() {
3282
3356
  await mkdir4(LOGS_DIR, { recursive: true });
3283
3357
  }
3284
- async saveRepoLog(repoName, entry) {
3358
+ async saveLog(hookType, hookName, entry) {
3285
3359
  await this.ensureDir();
3286
- const log = { repoName, ...entry };
3287
- await writeFile3(join8(LOGS_DIR, repoFilename(repoName)), `${JSON.stringify(log, null, 2)}
3360
+ const log = { hookType, hookName, repoName: hookName, ...entry };
3361
+ const filename = hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
3362
+ await writeFile3(join8(LOGS_DIR, filename), `${JSON.stringify(log, null, 2)}
3288
3363
  `, "utf-8");
3289
3364
  }
3365
+ async saveEnvironmentLog(entry) {
3366
+ await this.saveLog("environment", "environment", entry);
3367
+ }
3368
+ async saveRepoLog(repoName, entry) {
3369
+ await this.saveLog("repository", repoName, entry);
3370
+ }
3290
3371
  async getAllLogs() {
3291
3372
  let files;
3292
3373
  try {
@@ -3304,19 +3385,27 @@ var StartHookLogsService = class {
3304
3385
  }
3305
3386
  try {
3306
3387
  const raw = await readFile3(join8(LOGS_DIR, file), "utf-8");
3307
- const stored = JSON.parse(raw);
3308
- logs.push(withPreview(stored));
3388
+ const stored = normalizeStored(JSON.parse(raw));
3389
+ if (stored) {
3390
+ logs.push(withPreview(stored));
3391
+ }
3309
3392
  } catch {
3310
3393
  }
3311
3394
  }
3312
- logs.sort((a, b) => a.repoName.localeCompare(b.repoName));
3395
+ logs.sort((a, b) => {
3396
+ if (a.hookType !== b.hookType) {
3397
+ return a.hookType === "environment" ? -1 : 1;
3398
+ }
3399
+ return a.hookName.localeCompare(b.hookName);
3400
+ });
3313
3401
  return logs;
3314
3402
  }
3315
- async getFullOutput(repoName) {
3403
+ async getFullOutput(hookType, hookName) {
3404
+ const filename = hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
3316
3405
  try {
3317
- const raw = await readFile3(join8(LOGS_DIR, repoFilename(repoName)), "utf-8");
3318
- const stored = JSON.parse(raw);
3319
- if (stored.repoName !== repoName) {
3406
+ const raw = await readFile3(join8(LOGS_DIR, filename), "utf-8");
3407
+ const stored = normalizeStored(JSON.parse(raw));
3408
+ if (!stored || stored.hookType !== hookType || stored.hookName !== hookName) {
3320
3409
  return null;
3321
3410
  }
3322
3411
  return stored.output;
@@ -3565,6 +3654,13 @@ Repositories: ${hookEntries.length}
3565
3654
  onOutputChunk: (chunk) => onEvent({ type: "output", data: chunk, label: "environment" })
3566
3655
  });
3567
3656
  recordResult(envResult);
3657
+ await startHookLogsService.saveEnvironmentLog({
3658
+ hookCommands: [envHookContent],
3659
+ output: envResult.output.join(""),
3660
+ exitCode: envResult.exitCode,
3661
+ timedOut: envResult.timedOut,
3662
+ executedAt: (/* @__PURE__ */ new Date()).toISOString()
3663
+ });
3568
3664
  } else {
3569
3665
  await environmentDetailsService.setEnvironmentStartHook("n/a");
3570
3666
  }
@@ -5095,7 +5191,14 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5095
5191
  sessionId = null;
5096
5192
  activeQuery = null;
5097
5193
  activePromptStream = null;
5194
+ activeSessionSignature = null;
5195
+ activeSessionModel = null;
5196
+ activeSessionPermissionMode = null;
5197
+ sessionLoop = null;
5198
+ sessionLinearForwarder = null;
5199
+ pendingTurn = null;
5098
5200
  pendingInterrupt = false;
5201
+ activeBackgroundTasks = /* @__PURE__ */ new Set();
5099
5202
  systemPromptOverride;
5100
5203
  toolsOverride;
5101
5204
  mcpServersConfig;
@@ -5116,7 +5219,6 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5116
5219
  }
5117
5220
  async interruptActiveTurn() {
5118
5221
  this.pendingInterrupt = true;
5119
- this.activePromptStream?.close();
5120
5222
  if (this.activeQuery) {
5121
5223
  await this.activeQuery.interrupt();
5122
5224
  }
@@ -5298,117 +5400,271 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5298
5400
  thinkingLevel,
5299
5401
  enableInteractiveTools
5300
5402
  } = request;
5301
- const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
5302
5403
  if (!message || !message.trim()) {
5303
5404
  throw new Error("Message cannot be empty");
5304
5405
  }
5305
5406
  await this.initialized;
5306
- try {
5307
- const content = [
5308
- {
5309
- type: "text",
5310
- text: message
5407
+ this.pendingInterrupt = false;
5408
+ const content = [
5409
+ {
5410
+ type: "text",
5411
+ text: message
5412
+ }
5413
+ ];
5414
+ if (images && images.length > 0) {
5415
+ const normalizedImages = await normalizeImages(images);
5416
+ for (const image of normalizedImages) {
5417
+ content.push({
5418
+ type: "image",
5419
+ source: {
5420
+ type: "base64",
5421
+ media_type: image.source.media_type,
5422
+ data: image.source.data
5423
+ }
5424
+ });
5425
+ }
5426
+ }
5427
+ const userMessage = {
5428
+ type: "user",
5429
+ message: {
5430
+ role: "user",
5431
+ content
5432
+ },
5433
+ parent_tool_use_id: null,
5434
+ session_id: this.sessionId ?? ""
5435
+ };
5436
+ if (!options.skipUserMessageRecord) {
5437
+ await this.recordEvent(userMessage);
5438
+ }
5439
+ const combinedInstructions = this.buildCombinedInstructions(customInstructions);
5440
+ const resolvedModel = normalizeClaudeModel(model) || CLAUDE_OPUS_1M_MODEL;
5441
+ const resolvedPermissionMode = permissionMode === "read" ? "plan" : "bypassPermissions";
5442
+ const signature = {
5443
+ combinedInstructions,
5444
+ thinkingLevel,
5445
+ // enableInteractiveTools only matters when permissionMode is 'read'; the
5446
+ // session signature tracks the effective flag so a permissionMode change
5447
+ // can hot-swap via setPermissionMode without restarting.
5448
+ enableInteractiveTools: Boolean(enableInteractiveTools)
5449
+ };
5450
+ await this.ensureSession({
5451
+ signature,
5452
+ combinedInstructions,
5453
+ resolvedModel,
5454
+ resolvedPermissionMode,
5455
+ enableInteractiveTools: signature.enableInteractiveTools,
5456
+ thinkingLevel
5457
+ });
5458
+ const promptStream = this.activePromptStream;
5459
+ const activeQuery = this.activeQuery;
5460
+ if (!promptStream || !activeQuery) {
5461
+ throw new Error("Claude session unavailable after ensureSession");
5462
+ }
5463
+ if (this.pendingInterrupt) {
5464
+ this.pendingInterrupt = false;
5465
+ return;
5466
+ }
5467
+ await new Promise((resolve3, reject) => {
5468
+ this.pendingTurn = { resolve: resolve3, reject };
5469
+ promptStream.push(userMessage);
5470
+ });
5471
+ }
5472
+ async ensureSession(args) {
5473
+ const {
5474
+ signature,
5475
+ combinedInstructions,
5476
+ resolvedModel,
5477
+ resolvedPermissionMode,
5478
+ enableInteractiveTools,
5479
+ thinkingLevel
5480
+ } = args;
5481
+ if (this.activeQuery && this.activeSessionSignature && this.sessionSignaturesMatch(this.activeSessionSignature, signature)) {
5482
+ if (this.activeSessionModel !== resolvedModel) {
5483
+ try {
5484
+ await this.activeQuery.setModel(resolvedModel);
5485
+ this.activeSessionModel = resolvedModel;
5486
+ } catch (err) {
5487
+ console.warn("[ClaudeManager] setModel failed; recreating session:", err);
5488
+ await this.tearDownSession();
5311
5489
  }
5312
- ];
5313
- if (images && images.length > 0) {
5314
- const normalizedImages = await normalizeImages(images);
5315
- for (const image of normalizedImages) {
5316
- content.push({
5317
- type: "image",
5318
- source: {
5319
- type: "base64",
5320
- media_type: image.source.media_type,
5321
- data: image.source.data
5322
- }
5323
- });
5490
+ }
5491
+ if (this.activeQuery && this.activeSessionPermissionMode !== resolvedPermissionMode) {
5492
+ try {
5493
+ await this.activeQuery.setPermissionMode(resolvedPermissionMode);
5494
+ this.activeSessionPermissionMode = resolvedPermissionMode;
5495
+ } catch (err) {
5496
+ console.warn("[ClaudeManager] setPermissionMode failed; recreating session:", err);
5497
+ await this.tearDownSession();
5324
5498
  }
5325
5499
  }
5326
- const userMessage = {
5327
- type: "user",
5328
- message: {
5329
- role: "user",
5330
- content
5331
- },
5332
- parent_tool_use_id: null,
5333
- session_id: this.sessionId ?? ""
5334
- };
5335
- if (!options.skipUserMessageRecord) {
5336
- await this.recordEvent(userMessage);
5500
+ if (this.activeQuery) return;
5501
+ } else if (this.activeQuery) {
5502
+ await this.tearDownSession();
5503
+ }
5504
+ await this.startSession({
5505
+ combinedInstructions,
5506
+ resolvedModel,
5507
+ resolvedPermissionMode,
5508
+ enableInteractiveTools,
5509
+ thinkingLevel,
5510
+ signature
5511
+ });
5512
+ }
5513
+ sessionSignaturesMatch(a, b) {
5514
+ return a.combinedInstructions === b.combinedInstructions && a.thinkingLevel === b.thinkingLevel && a.enableInteractiveTools === b.enableInteractiveTools;
5515
+ }
5516
+ async startSession(args) {
5517
+ const {
5518
+ combinedInstructions,
5519
+ resolvedModel,
5520
+ resolvedPermissionMode,
5521
+ enableInteractiveTools,
5522
+ thinkingLevel,
5523
+ signature
5524
+ } = args;
5525
+ const systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(combinedInstructions) : {
5526
+ type: "preset",
5527
+ preset: "claude_code",
5528
+ append: combinedInstructions
5529
+ };
5530
+ const queryEnv = buildClaudeAgentEnv(this.envOverrides);
5531
+ const additionalDirectories = await getAgentAdditionalDirectories();
5532
+ const interactiveAllowed = enableInteractiveTools && resolvedPermissionMode === "plan";
5533
+ const useDefaultToolPolicy = !this.toolsOverride;
5534
+ const allowedTools = useDefaultToolPolicy ? [
5535
+ ...ALWAYS_ALLOWED_TOOLS,
5536
+ ...interactiveAllowed ? INTERACTIVE_TOOL_NAMES : []
5537
+ ] : void 0;
5538
+ const disallowedTools = [
5539
+ ...this.disallowedToolsOverride ?? [],
5540
+ ...useDefaultToolPolicy ? ALWAYS_DISALLOWED_TOOLS : [],
5541
+ ...interactiveAllowed ? [] : INTERACTIVE_TOOL_NAMES
5542
+ ];
5543
+ const promptStream = new PromptStream();
5544
+ const response = query({
5545
+ prompt: promptStream,
5546
+ options: {
5547
+ resume: this.sessionId || void 0,
5548
+ cwd: this.workingDirectory,
5549
+ additionalDirectories,
5550
+ permissionMode: resolvedPermissionMode,
5551
+ allowDangerouslySkipPermissions: resolvedPermissionMode === "bypassPermissions",
5552
+ ...this.toolsOverride ? { tools: this.toolsOverride } : {},
5553
+ ...allowedTools ? { allowedTools } : {},
5554
+ ...disallowedTools.length > 0 ? { disallowedTools } : {},
5555
+ settingSources: ["user", "project", "local"],
5556
+ systemPrompt,
5557
+ ...this.mcpServersConfig ? { mcpServers: this.mcpServersConfig } : {},
5558
+ env: queryEnv,
5559
+ model: resolvedModel,
5560
+ ...thinkingLevel ? { effort: thinkingLevel } : {},
5561
+ canUseTool: this.buildCanUseTool()
5337
5562
  }
5338
- const promptStream = new PromptStream();
5339
- promptStream.push(userMessage);
5340
- this.activePromptStream = promptStream;
5341
- const combinedInstructions = this.buildCombinedInstructions(customInstructions);
5342
- const systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(combinedInstructions) : {
5343
- type: "preset",
5344
- preset: "claude_code",
5345
- append: combinedInstructions
5346
- };
5347
- const queryEnv = buildClaudeAgentEnv(this.envOverrides);
5348
- const additionalDirectories = await getAgentAdditionalDirectories();
5349
- const resolvedModel = normalizeClaudeModel(model) || CLAUDE_OPUS_1M_MODEL;
5350
- const interactiveAllowed = enableInteractiveTools && permissionMode === "read";
5351
- const useDefaultToolPolicy = !this.toolsOverride;
5352
- const allowedTools = useDefaultToolPolicy ? [
5353
- ...ALWAYS_ALLOWED_TOOLS,
5354
- ...interactiveAllowed ? INTERACTIVE_TOOL_NAMES : []
5355
- ] : void 0;
5356
- const disallowedTools = [
5357
- ...this.disallowedToolsOverride ?? [],
5358
- ...useDefaultToolPolicy ? ALWAYS_DISALLOWED_TOOLS : [],
5359
- ...interactiveAllowed ? [] : INTERACTIVE_TOOL_NAMES
5360
- ];
5361
- const response = query({
5362
- prompt: promptStream,
5363
- options: {
5364
- resume: this.sessionId || void 0,
5365
- cwd: this.workingDirectory,
5366
- additionalDirectories,
5367
- permissionMode: permissionMode === "read" ? "plan" : "bypassPermissions",
5368
- allowDangerouslySkipPermissions: permissionMode !== "read",
5369
- ...this.toolsOverride ? { tools: this.toolsOverride } : {},
5370
- ...allowedTools ? { allowedTools } : {},
5371
- ...disallowedTools.length > 0 ? { disallowedTools } : {},
5372
- settingSources: ["user", "project", "local"],
5373
- systemPrompt,
5374
- ...this.mcpServersConfig ? { mcpServers: this.mcpServersConfig } : {},
5375
- env: queryEnv,
5376
- model: resolvedModel,
5377
- ...thinkingLevel ? { effort: thinkingLevel } : {},
5378
- canUseTool: this.buildCanUseTool()
5379
- }
5380
- });
5381
- this.activeQuery = response;
5382
- if (this.pendingInterrupt) {
5383
- this.pendingInterrupt = false;
5384
- this.activePromptStream?.close();
5385
- await this.activeQuery.interrupt();
5386
- }
5387
- const linearForwarder = new LinearEventForwarder(linearSessionId);
5388
- for await (const msg of response) {
5563
+ });
5564
+ this.activeQuery = response;
5565
+ this.activePromptStream = promptStream;
5566
+ this.activeSessionSignature = signature;
5567
+ this.activeSessionModel = resolvedModel;
5568
+ this.activeSessionPermissionMode = resolvedPermissionMode;
5569
+ this.sessionLinearForwarder = new LinearEventForwarder(ENGINE_ENV.LINEAR_SESSION_ID);
5570
+ this.activeBackgroundTasks.clear();
5571
+ this.sessionLoop = this.runSessionLoop(response).catch((err) => {
5572
+ console.error("[ClaudeManager] Session loop crashed:", err);
5573
+ });
5574
+ }
5575
+ async runSessionLoop(response) {
5576
+ const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
5577
+ const iterator = response[Symbol.asyncIterator]();
5578
+ try {
5579
+ while (true) {
5580
+ const next = await iterator.next();
5581
+ if (next.done) break;
5582
+ const msg = next.value;
5389
5583
  const authErrorMessage = _ClaudeManager.detectAuthErrorInMessage(msg);
5390
5584
  if (authErrorMessage) {
5391
- this.activePromptStream?.close();
5392
- throw new ClaudeAuthError(authErrorMessage);
5585
+ this.failPendingTurn(new ClaudeAuthError(authErrorMessage));
5586
+ try {
5587
+ response.close();
5588
+ } catch (err) {
5589
+ console.warn("[ClaudeManager] query.close() during auth error failed:", err);
5590
+ }
5591
+ return;
5393
5592
  }
5394
5593
  this.setAuthRetrying(false);
5395
5594
  await this.handleMessage(msg);
5396
- if (linearSessionId) {
5397
- linearForwarder.sendPlan(extractPlanFromClaudeEvent(msg));
5398
- linearForwarder.sendEvent(convertClaudeEvent(msg, linearSessionId));
5595
+ _ClaudeManager.updateBackgroundTaskState(msg, this.activeBackgroundTasks);
5596
+ if (linearSessionId && this.sessionLinearForwarder) {
5597
+ this.sessionLinearForwarder.sendPlan(extractPlanFromClaudeEvent(msg));
5598
+ this.sessionLinearForwarder.sendEvent(convertClaudeEvent(msg, linearSessionId));
5399
5599
  }
5400
5600
  if (msg.type === "result") {
5401
5601
  await this.recordContextUsage(response);
5402
- this.activePromptStream?.close();
5403
- break;
5602
+ this.sessionLinearForwarder?.flushThoughtAsResponse();
5603
+ this.resolvePendingTurn();
5404
5604
  }
5405
5605
  }
5406
- linearForwarder.flushThoughtAsResponse();
5407
5606
  } finally {
5408
- this.activeQuery = null;
5409
- this.activePromptStream?.close();
5410
- this.activePromptStream = null;
5411
- this.pendingInterrupt = false;
5607
+ this.sessionLinearForwarder?.flushThoughtAsResponse();
5608
+ this.failPendingTurn(new Error("Claude session ended unexpectedly"));
5609
+ if (this.activeQuery === response) {
5610
+ this.clearSessionState();
5611
+ }
5612
+ }
5613
+ }
5614
+ clearSessionState() {
5615
+ this.activeQuery = null;
5616
+ this.activePromptStream = null;
5617
+ this.activeSessionSignature = null;
5618
+ this.activeSessionModel = null;
5619
+ this.activeSessionPermissionMode = null;
5620
+ this.sessionLinearForwarder = null;
5621
+ this.sessionLoop = null;
5622
+ this.activeBackgroundTasks.clear();
5623
+ }
5624
+ resolvePendingTurn() {
5625
+ const pending = this.pendingTurn;
5626
+ if (!pending) return;
5627
+ this.pendingTurn = null;
5628
+ this.pendingInterrupt = false;
5629
+ pending.resolve();
5630
+ }
5631
+ failPendingTurn(err) {
5632
+ const pending = this.pendingTurn;
5633
+ if (!pending) return;
5634
+ this.pendingTurn = null;
5635
+ this.pendingInterrupt = false;
5636
+ pending.reject(err);
5637
+ }
5638
+ async tearDownSession() {
5639
+ const query2 = this.activeQuery;
5640
+ const stream = this.activePromptStream;
5641
+ const loop = this.sessionLoop;
5642
+ this.failPendingTurn(new Error("Claude session torn down"));
5643
+ this.clearSessionState();
5644
+ if (query2) {
5645
+ try {
5646
+ query2.close();
5647
+ } catch (err) {
5648
+ console.warn("[ClaudeManager] query.close() failed:", err);
5649
+ }
5650
+ }
5651
+ stream?.close();
5652
+ if (loop) {
5653
+ try {
5654
+ await loop;
5655
+ } catch {
5656
+ }
5657
+ }
5658
+ }
5659
+ static updateBackgroundTaskState(message, activeTasks) {
5660
+ if (message.type !== "system") return;
5661
+ const payload = coerceBackgroundTaskPayload(message);
5662
+ if (!payload) return;
5663
+ const status = payload.subtype === "task_updated" ? payload.patch?.status : payload.status;
5664
+ if (isTerminalBackgroundTaskStatus(status)) {
5665
+ activeTasks.delete(payload.taskId);
5666
+ } else {
5667
+ activeTasks.add(payload.taskId);
5412
5668
  }
5413
5669
  }
5414
5670
  getContextUsageProvider() {
@@ -5726,7 +5982,7 @@ var AspClient = class {
5726
5982
  // src/managers/codex-asp/app-server-process.ts
5727
5983
  var DEFAULT_CODEX_BINARY = "codex";
5728
5984
  var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
5729
- var ENGINE_PACKAGE_VERSION = "0.1.213";
5985
+ var ENGINE_PACKAGE_VERSION = "0.1.237";
5730
5986
  var INITIALIZE_METHOD = "initialize";
5731
5987
  var INITIALIZED_NOTIFICATION = "initialized";
5732
5988
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -9224,26 +9480,12 @@ import { existsSync as existsSync8 } from "fs";
9224
9480
  import { join as join19 } from "path";
9225
9481
 
9226
9482
  // src/services/warm-hook-logs-service.ts
9227
- import { createHash as createHash2 } from "crypto";
9228
9483
  import { mkdir as mkdir12, readFile as readFile11, writeFile as writeFile7, readdir as readdir5, appendFile as appendFile6, unlink as unlink3 } from "fs/promises";
9229
9484
  import { homedir as homedir15 } from "os";
9230
9485
  import { join as join18 } from "path";
9231
9486
  var LOGS_DIR2 = join18(homedir15(), ".replicas", "warm-hook-logs");
9232
9487
  var CURRENT_RUN_LOG = join18(LOGS_DIR2, "current-run.log");
9233
- function sanitizeFilename2(name) {
9234
- const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
9235
- const hash = createHash2("sha256").update(name).digest("hex").slice(0, 8);
9236
- return `${safe}-${hash}`;
9237
- }
9238
- function globalFilename() {
9239
- return "global.json";
9240
- }
9241
- function environmentFilename() {
9242
- return "environment.json";
9243
- }
9244
- function repoFilename2(repoName) {
9245
- return `repo-${sanitizeFilename2(repoName)}.json`;
9246
- }
9488
+ var GLOBAL_FILENAME = "global.json";
9247
9489
  function withPreview2(stored) {
9248
9490
  const preview = buildHookOutputPreview(stored.output);
9249
9491
  return { ...stored, ...preview };
@@ -9259,7 +9501,7 @@ var WarmHookLogsService = class {
9259
9501
  hookName: "organization",
9260
9502
  ...entry
9261
9503
  };
9262
- await writeFile7(join18(LOGS_DIR2, globalFilename()), `${JSON.stringify(log, null, 2)}
9504
+ await writeFile7(join18(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
9263
9505
  `, "utf-8");
9264
9506
  }
9265
9507
  async saveEnvironmentHookLog(entry) {
@@ -9269,7 +9511,7 @@ var WarmHookLogsService = class {
9269
9511
  hookName: "environment",
9270
9512
  ...entry
9271
9513
  };
9272
- await writeFile7(join18(LOGS_DIR2, environmentFilename()), `${JSON.stringify(log, null, 2)}
9514
+ await writeFile7(join18(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
9273
9515
  `, "utf-8");
9274
9516
  }
9275
9517
  async saveRepoHookLog(repoName, entry) {
@@ -9279,7 +9521,7 @@ var WarmHookLogsService = class {
9279
9521
  hookName: repoName,
9280
9522
  ...entry
9281
9523
  };
9282
- await writeFile7(join18(LOGS_DIR2, repoFilename2(repoName)), `${JSON.stringify(log, null, 2)}
9524
+ await writeFile7(join18(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
9283
9525
  `, "utf-8");
9284
9526
  }
9285
9527
  async getAllLogs() {
@@ -9334,7 +9576,7 @@ var WarmHookLogsService = class {
9334
9576
  }
9335
9577
  }
9336
9578
  async getFullOutput(hookType, hookName) {
9337
- const filename = hookType === "global" ? globalFilename() : hookType === "environment" ? environmentFilename() : repoFilename2(hookName);
9579
+ const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
9338
9580
  try {
9339
9581
  const raw = await readFile11(join18(LOGS_DIR2, filename), "utf-8");
9340
9582
  const stored = JSON.parse(raw);
@@ -10172,10 +10414,14 @@ function createV1Routes(deps) {
10172
10414
  );
10173
10415
  }
10174
10416
  });
10175
- app2.get("/start-hooks/logs/:repoName/full", async (c) => {
10417
+ app2.get("/start-hooks/logs/:hookType/:hookName/full", async (c) => {
10176
10418
  try {
10177
- const repoName = c.req.param("repoName");
10178
- const output = await startHookLogsService.getFullOutput(repoName);
10419
+ const hookType = c.req.param("hookType");
10420
+ const hookName = c.req.param("hookName");
10421
+ if (hookType !== "environment" && hookType !== "repository") {
10422
+ return c.json(jsonError("Invalid hookType", 'Must be "environment" or "repository"'), 400);
10423
+ }
10424
+ const output = await startHookLogsService.getFullOutput(hookType, hookName);
10179
10425
  if (output === null) {
10180
10426
  return c.json(jsonError("Start hook log not found"), 404);
10181
10427
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.235",
3
+ "version": "0.1.237",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",