replicas-engine 0.1.133 → 0.1.135

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 +175 -30
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -967,6 +967,9 @@ import { homedir as homedir5 } from "os";
967
967
  import { exec } from "child_process";
968
968
  import { promisify as promisify2 } from "util";
969
969
 
970
+ // ../shared/src/event.ts
971
+ var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
972
+
970
973
  // ../shared/src/pricing.ts
971
974
  var PLANS = {
972
975
  hobby: {
@@ -1166,7 +1169,7 @@ function parseReplicasConfigString(content, filename) {
1166
1169
  }
1167
1170
 
1168
1171
  // ../shared/src/engine/environment.ts
1169
- var DAYTONA_SNAPSHOT_ID = "29-04-2026-royal-york-v1";
1172
+ var DAYTONA_SNAPSHOT_ID = "02-05-2026-royal-york-v1";
1170
1173
 
1171
1174
  // ../shared/src/engine/types.ts
1172
1175
  var DEFAULT_CHAT_TITLES = {
@@ -1180,6 +1183,16 @@ var IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
1180
1183
  var WORKSPACE_FILE_UPLOAD_MAX_SIZE_BYTES = 20 * 1024 * 1024;
1181
1184
  var WORKSPACE_FILE_CONTENT_MAX_SIZE_BYTES = 1 * 1024 * 1024;
1182
1185
 
1186
+ // ../shared/src/audit-log.ts
1187
+ var AUDIT_LOG_ACTION = {
1188
+ CREATE: "create",
1189
+ UPDATE: "update",
1190
+ DELETE: "delete",
1191
+ CONNECT: "connect",
1192
+ DISCONNECT: "disconnect"
1193
+ };
1194
+ var AUDIT_LOG_ACTIONS = Object.values(AUDIT_LOG_ACTION);
1195
+
1183
1196
  // ../shared/src/routes/environment.ts
1184
1197
  var RESERVED_MCP_NAME_PREFIXES = ["relay-", "replicas-"];
1185
1198
  function isReservedMcpName(name) {
@@ -2828,12 +2841,40 @@ function isJsonlEvent2(value) {
2828
2841
  function sleep(ms) {
2829
2842
  return new Promise((resolve2) => setTimeout(resolve2, ms));
2830
2843
  }
2844
+ function extractRateLimitsSnapshot(parsed) {
2845
+ if (!isRecord(parsed)) return null;
2846
+ const payload = isRecord(parsed.payload) ? parsed.payload : parsed;
2847
+ const rateLimits = isRecord(payload.rate_limits) ? payload.rate_limits : null;
2848
+ if (!rateLimits) return null;
2849
+ const credits = isRecord(rateLimits.credits) ? rateLimits.credits : null;
2850
+ const hasCredits = credits && typeof credits.has_credits === "boolean" ? credits.has_credits : null;
2851
+ const unlimited = credits && typeof credits.unlimited === "boolean" ? credits.unlimited : null;
2852
+ const balance = credits && typeof credits.balance === "string" ? credits.balance : null;
2853
+ if (unlimited === true) return null;
2854
+ const rateLimitResetType = typeof rateLimits.rate_limit_reached_type === "string" && rateLimits.rate_limit_reached_type.length > 0 ? rateLimits.rate_limit_reached_type : null;
2855
+ const planType = typeof rateLimits.plan_type === "string" ? rateLimits.plan_type : null;
2856
+ let state = "ok";
2857
+ if (hasCredits === false) {
2858
+ state = "out_of_credits";
2859
+ } else if (rateLimitResetType !== null) {
2860
+ state = "rate_limited";
2861
+ }
2862
+ return { state, balance, rateLimitResetType, planType };
2863
+ }
2831
2864
  var CodexManager = class extends CodingAgentManager {
2832
2865
  codex;
2833
2866
  currentThreadId = null;
2834
2867
  currentThread = null;
2835
2868
  tempImageDir;
2836
2869
  activeAbortController = null;
2870
+ /** Most recent quota state observed from a rollout `rate_limits` payload. */
2871
+ latestQuotaState = "ok";
2872
+ /** Last state actually emitted to the UI. Drives dedup so seeded historical state still emits the first time it surfaces in a turn. */
2873
+ lastEmittedQuotaState = "ok";
2874
+ /** Last full snapshot, retained so we can re-emit when a user retries while blocked. */
2875
+ latestQuotaSnapshot = null;
2876
+ /** When true, new turns short-circuit instead of running. Set by `out_of_credits`. */
2877
+ quotaBlocked = false;
2837
2878
  constructor(options) {
2838
2879
  super(options);
2839
2880
  const codexApiKey = resolveCodexApiKey();
@@ -2850,6 +2891,73 @@ var CodexManager = class extends CodingAgentManager {
2850
2891
  console.log(`[CodexManager] Restored thread ID from persisted state: ${this.currentThreadId}`);
2851
2892
  }
2852
2893
  }
2894
+ /**
2895
+ * One-shot pass over the current session's rollout jsonl to find the most
2896
+ * recent `rate_limits` entry and emit a quota state if it has changed.
2897
+ * Used after `runStreamed` throws — the tail loop may not have pumped the
2898
+ * fatal line before the SDK exited.
2899
+ */
2900
+ async flushQuotaSnapshotFromCurrentSession() {
2901
+ if (!this.currentThreadId) return;
2902
+ try {
2903
+ const sessionFile = await this.findSessionFile(this.currentThreadId);
2904
+ if (!sessionFile) return;
2905
+ const content = await readFile7(sessionFile, "utf-8");
2906
+ const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
2907
+ let latest = null;
2908
+ for (const line of lines) {
2909
+ try {
2910
+ const parsed = JSON.parse(line);
2911
+ const snapshot = extractRateLimitsSnapshot(parsed);
2912
+ if (snapshot) {
2913
+ latest = snapshot;
2914
+ }
2915
+ } catch {
2916
+ }
2917
+ }
2918
+ if (latest) {
2919
+ this.emitQuotaStatus(latest);
2920
+ }
2921
+ } catch (error) {
2922
+ console.warn("[CodexManager] Failed to flush quota snapshot from session:", error);
2923
+ }
2924
+ }
2925
+ /**
2926
+ * Emit a synthetic codex-quota-status AgentEvent and update local state.
2927
+ * Dedupes against `lastEmittedQuotaState` (what the UI has actually seen),
2928
+ * not `latestQuotaState`, so a state primed silently by `seedSeenLines` on
2929
+ * engine restart still emits the first time it surfaces in a turn. Pass
2930
+ * `force` to re-emit on a retry so users see the banner reappear.
2931
+ */
2932
+ emitQuotaStatus(snapshot, force = false) {
2933
+ const stateChanged = snapshot.state !== this.lastEmittedQuotaState;
2934
+ if (!stateChanged && !force) {
2935
+ return;
2936
+ }
2937
+ this.latestQuotaState = snapshot.state;
2938
+ this.lastEmittedQuotaState = snapshot.state;
2939
+ this.quotaBlocked = snapshot.state === "out_of_credits";
2940
+ this.latestQuotaSnapshot = snapshot;
2941
+ const payload = {
2942
+ state: snapshot.state,
2943
+ balance: snapshot.balance,
2944
+ rateLimitResetType: snapshot.rateLimitResetType,
2945
+ planType: snapshot.planType
2946
+ };
2947
+ this.onEvent({
2948
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2949
+ type: CODEX_QUOTA_STATUS_EVENT_TYPE,
2950
+ payload
2951
+ });
2952
+ if (!stateChanged) return;
2953
+ if (snapshot.state === "out_of_credits") {
2954
+ console.warn("[CodexManager] Codex account out of credits \u2014 pausing turn processing.");
2955
+ } else if (snapshot.state === "rate_limited") {
2956
+ console.warn(`[CodexManager] Codex rate limit reached (${snapshot.rateLimitResetType}).`);
2957
+ } else {
2958
+ console.log("[CodexManager] Codex quota recovered.");
2959
+ }
2960
+ }
2853
2961
  async interruptActiveTurn() {
2854
2962
  if (this.activeAbortController) {
2855
2963
  this.activeAbortController.abort();
@@ -2908,6 +3016,18 @@ var CodexManager = class extends CodingAgentManager {
2908
3016
  * Internal method that actually processes the message
2909
3017
  */
2910
3018
  async processMessageInternal(request) {
3019
+ if (this.quotaBlocked && this.latestQuotaSnapshot) {
3020
+ await this.flushQuotaSnapshotFromCurrentSession();
3021
+ if (this.quotaBlocked && this.latestQuotaSnapshot) {
3022
+ this.emitQuotaStatus(this.latestQuotaSnapshot, true);
3023
+ try {
3024
+ await this.onTurnComplete();
3025
+ } catch (error) {
3026
+ console.error("[CodexManager] onTurnComplete failed during quota-blocked turn:", error);
3027
+ }
3028
+ return;
3029
+ }
3030
+ }
2911
3031
  const {
2912
3032
  message,
2913
3033
  model,
@@ -2944,8 +3064,8 @@ var CodexManager = class extends CodingAgentManager {
2944
3064
  this.currentThread = this.codex.resumeThread(this.currentThreadId, threadOptions);
2945
3065
  } else {
2946
3066
  this.currentThread = this.codex.startThread(threadOptions);
2947
- const { events: events2 } = await this.currentThread.runStreamed("Hello", { signal: abortController.signal });
2948
- for await (const event of events2) {
3067
+ const { events } = await this.currentThread.runStreamed("Hello", { signal: abortController.signal });
3068
+ for await (const event of events) {
2949
3069
  if (event.type === "thread.started") {
2950
3070
  this.currentThreadId = event.thread_id;
2951
3071
  await this.onSaveSessionId(this.currentThreadId);
@@ -2969,38 +3089,47 @@ var CodexManager = class extends CodingAgentManager {
2969
3089
  } else {
2970
3090
  input = message;
2971
3091
  }
2972
- const { events } = await this.currentThread.runStreamed(input, { signal: abortController.signal });
2973
- let latestThoughtEvent = null;
2974
- for await (const event of events) {
2975
- if (linearSessionId) {
2976
- const plan = extractPlanFromCodexEvent(event);
2977
- if (plan) {
2978
- monolithService.sendEvent({
2979
- type: "agent_plan_update",
2980
- payload: { linearSessionId, plan }
2981
- }).catch(() => {
2982
- });
2983
- }
2984
- const linearEvent = convertCodexEvent(event, linearSessionId);
2985
- if (linearEvent) {
2986
- if (latestThoughtEvent) {
2987
- monolithService.sendEvent({ type: "agent_update", payload: latestThoughtEvent }).catch(() => {
3092
+ try {
3093
+ const { events } = await this.currentThread.runStreamed(input, { signal: abortController.signal });
3094
+ let latestThoughtEvent = null;
3095
+ for await (const event of events) {
3096
+ if (linearSessionId) {
3097
+ const plan = extractPlanFromCodexEvent(event);
3098
+ if (plan) {
3099
+ monolithService.sendEvent({
3100
+ type: "agent_plan_update",
3101
+ payload: { linearSessionId, plan }
3102
+ }).catch(() => {
2988
3103
  });
2989
3104
  }
2990
- if (isLinearThoughtEvent2(linearEvent)) {
2991
- latestThoughtEvent = linearEvent;
2992
- } else {
2993
- latestThoughtEvent = null;
2994
- monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
2995
- });
3105
+ const linearEvent = convertCodexEvent(event, linearSessionId);
3106
+ if (linearEvent) {
3107
+ if (latestThoughtEvent) {
3108
+ monolithService.sendEvent({ type: "agent_update", payload: latestThoughtEvent }).catch(() => {
3109
+ });
3110
+ }
3111
+ if (isLinearThoughtEvent2(linearEvent)) {
3112
+ latestThoughtEvent = linearEvent;
3113
+ } else {
3114
+ latestThoughtEvent = null;
3115
+ monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
3116
+ });
3117
+ }
2996
3118
  }
2997
3119
  }
2998
3120
  }
2999
- }
3000
- if (linearSessionId && latestThoughtEvent) {
3001
- const responseEvent = linearThoughtToResponse(latestThoughtEvent);
3002
- monolithService.sendEvent({ type: "agent_update", payload: responseEvent }).catch(() => {
3003
- });
3121
+ if (linearSessionId && latestThoughtEvent) {
3122
+ const responseEvent = linearThoughtToResponse(latestThoughtEvent);
3123
+ monolithService.sendEvent({ type: "agent_update", payload: responseEvent }).catch(() => {
3124
+ });
3125
+ }
3126
+ } catch (error) {
3127
+ await this.flushQuotaSnapshotFromCurrentSession();
3128
+ if (this.quotaBlocked) {
3129
+ console.warn("[CodexManager] runStreamed failed while quota was blocked \u2014 surfacing as quota state.");
3130
+ return;
3131
+ }
3132
+ throw error;
3004
3133
  }
3005
3134
  } finally {
3006
3135
  if (stopTail) {
@@ -3100,8 +3229,20 @@ var CodexManager = class extends CodingAgentManager {
3100
3229
  try {
3101
3230
  const content = await readFile7(sessionFile, "utf-8");
3102
3231
  const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
3232
+ let latest = null;
3103
3233
  for (const line of lines) {
3104
3234
  seenLines.add(line);
3235
+ try {
3236
+ const parsed = JSON.parse(line);
3237
+ const snapshot = extractRateLimitsSnapshot(parsed);
3238
+ if (snapshot) latest = snapshot;
3239
+ } catch {
3240
+ }
3241
+ }
3242
+ if (latest) {
3243
+ this.latestQuotaState = latest.state;
3244
+ this.latestQuotaSnapshot = latest;
3245
+ this.quotaBlocked = latest.state === "out_of_credits";
3105
3246
  }
3106
3247
  } catch {
3107
3248
  }
@@ -3121,6 +3262,10 @@ var CodexManager = class extends CodingAgentManager {
3121
3262
  seenLines.add(trimmed);
3122
3263
  try {
3123
3264
  const parsed = JSON.parse(trimmed);
3265
+ const snapshot = extractRateLimitsSnapshot(parsed);
3266
+ if (snapshot) {
3267
+ this.emitQuotaStatus(snapshot);
3268
+ }
3124
3269
  if (isJsonlEvent2(parsed)) {
3125
3270
  this.onEvent(parsed);
3126
3271
  emitted += 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.133",
3
+ "version": "0.1.135",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",