replicas-engine 0.1.411 → 0.1.413

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 +290 -6
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -481,7 +481,7 @@ var WORKSPACE_SIZES = ["small", "large"];
481
481
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
482
482
 
483
483
  // ../shared/src/e2b.ts
484
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-09-v1";
484
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-09-v3";
485
485
 
486
486
  // ../shared/src/runtime-env.ts
487
487
  function parsePosixEnvFile(content) {
@@ -2003,6 +2003,7 @@ In agent mode the CLI hides commands that don't make sense for in-workspace agen
2003
2003
  | "Make an automation that runs every weekday at 9am" | \`replicas automation create\` (interactive) or \`--trigger-cron "0 9 * * 1-5"\` |
2004
2004
  | "Make an automation that runs when a PR opens on \`acme/web\`" | \`replicas automation create ... --trigger-github pull_request.opened --trigger-github-repos acme/web\` |
2005
2005
  | "Make an automation that runs when an MR opens on \`acme/web\`" | \`replicas automation create ... --trigger-gitlab merge_request.opened --trigger-gitlab-repos acme/web\` |
2006
+ | "Ignore Dependabot for this PR automation" | \`replicas automation edit <id> --trigger-github-exclude-users 'dependabot[bot]'\` |
2006
2007
  | "What repos are connected?" | \`replicas repos\` |
2007
2008
  | "Set up a \`replicas.json\` in this repo" | \`replicas init\` (\`-y\` for YAML) |
2008
2009
 
@@ -2089,13 +2090,15 @@ replicas automation create "Review my PRs" \\
2089
2090
  --prompt "Leave a code review on this PR" \\
2090
2091
  --environment <env-name-or-id> \\
2091
2092
  --trigger-github pull_request.opened \\
2092
- --trigger-github-repos acme/web,acme/api
2093
+ --trigger-github-repos acme/web,acme/api \\
2094
+ --trigger-github-exclude-users 'dependabot[bot],renovate[bot]'
2093
2095
 
2094
2096
  replicas automation create "Review my MRs" \\
2095
2097
  --prompt "Leave a code review on this MR" \\
2096
2098
  --environment <env-name-or-id> \\
2097
2099
  --trigger-gitlab merge_request.opened \\
2098
- --trigger-gitlab-repos acme/web,acme/api
2100
+ --trigger-gitlab-repos acme/web,acme/api \\
2101
+ --trigger-gitlab-exclude-users 'bot-user'
2099
2102
 
2100
2103
  # Disable on creation
2101
2104
  replicas automation create ... --disabled
@@ -2119,6 +2122,8 @@ replicas automation edit <id> \\
2119
2122
  --trigger-cron "0 4 * * *" # replaces existing triggers
2120
2123
  \`\`\`
2121
2124
 
2125
+ \`replicas automation edit <id> --trigger-github-exclude-users "dependabot[bot],renovate[bot]"\` updates existing GitHub triggers without replacing the rest of the trigger list. The GitLab equivalent is \`--trigger-gitlab-exclude-users\`. Passing an empty string (for example, \`--trigger-github-exclude-users ""\`) clears existing exclusions for that code host. Passing \`--trigger-github\` or \`--trigger-gitlab\` still replaces triggers, and the matching exclude flag is applied to the replacement trigger.
2126
+
2122
2127
  \`replicas automation edit <id>\` with no flags drops into interactive mode.
2123
2128
 
2124
2129
  ## Repositories
@@ -2293,6 +2298,27 @@ var REPLICAS_AGENT_ABILITIES = Object.keys(
2293
2298
  );
2294
2299
 
2295
2300
  // ../shared/src/routes/preferences.ts
2301
+ var EMPTY_AGENT_DEFAULT_SETTINGS = {
2302
+ agent_provider: null,
2303
+ model: null,
2304
+ thinking_level: null,
2305
+ goal_mode: null,
2306
+ fast_mode: null
2307
+ };
2308
+ var DEFAULT_USER_PREFERENCES = {
2309
+ default_agent: "codex",
2310
+ default_model: null,
2311
+ cursor_models: null,
2312
+ close_workspace_on_pr_close: true,
2313
+ default_environment_id: null,
2314
+ respond_to_ci_workflows: true,
2315
+ respond_to_ci_checks: true,
2316
+ respond_to_human_pr_reviews: false,
2317
+ pr_user_attribution: false,
2318
+ auto_draft_prs: false,
2319
+ default_fast_mode: false,
2320
+ agent_defaults: { ...EMPTY_AGENT_DEFAULT_SETTINGS }
2321
+ };
2296
2322
  var DEFAULT_REPLICAS_AGENT_ABILITIES = Object.fromEntries(
2297
2323
  REPLICAS_AGENT_ABILITIES.map((key) => [key, true])
2298
2324
  );
@@ -2710,6 +2736,11 @@ function extractCommandProtectionCommandText(request, options = {}) {
2710
2736
  for (const key of COMMAND_PROTECTION_COMMAND_KEYS) {
2711
2737
  const value = input[key];
2712
2738
  if (typeof value === "string" && value.trim()) return value;
2739
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) return value.join(" ");
2740
+ }
2741
+ if (isRecord(input.args)) {
2742
+ const nested = extractCommandProtectionCommandText({ toolInput: input.args }, options);
2743
+ if (nested) return nested;
2713
2744
  }
2714
2745
  }
2715
2746
  if (typeof input === "string") return input;
@@ -6515,6 +6546,7 @@ var LinearEventForwarder = class {
6515
6546
  };
6516
6547
 
6517
6548
  // src/services/command-protection-service.ts
6549
+ var DEFAULT_COMMAND_PROTECTION_BLOCK_MESSAGE = "Blocked by organization policy: agents may not merge pull requests.";
6518
6550
  function asCommandProtectionResponse(value) {
6519
6551
  if (typeof value !== "object" || value === null) return null;
6520
6552
  const candidate = value;
@@ -6559,6 +6591,17 @@ async function evaluateCommandProtection(request, signal) {
6559
6591
  function extractToolCommand(input) {
6560
6592
  return extractCommandProtectionCommandText({ toolInput: input }, { stringifyFallback: false });
6561
6593
  }
6594
+ function reportCommandProtectionBlock(options) {
6595
+ const message = options.result.reason || DEFAULT_COMMAND_PROTECTION_BLOCK_MESSAGE;
6596
+ console.warn(`[${options.managerName}] blocked ${options.action} by PR merge policy: ${message}`);
6597
+ options.recordHistoryEvent(options.eventType, {
6598
+ type: "error",
6599
+ message,
6600
+ ...options.context,
6601
+ command: options.command
6602
+ }, options.historyFile);
6603
+ return message;
6604
+ }
6562
6605
 
6563
6606
  // src/services/skill-registry-service.ts
6564
6607
  import { readFile as readFile8, readdir as readdir3, stat as stat2 } from "fs/promises";
@@ -8206,7 +8249,7 @@ var AspClient = class {
8206
8249
  // src/managers/codex-asp/app-server-process.ts
8207
8250
  var DEFAULT_CODEX_BINARY = "codex";
8208
8251
  var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
8209
- var ENGINE_PACKAGE_VERSION = "0.1.411";
8252
+ var ENGINE_PACKAGE_VERSION = "0.1.413";
8210
8253
  var INITIALIZE_METHOD = "initialize";
8211
8254
  var INITIALIZED_NOTIFICATION = "initialized";
8212
8255
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -10322,6 +10365,24 @@ function extractCursorCommandDescription(content) {
10322
10365
  }
10323
10366
  return void 0;
10324
10367
  }
10368
+ function cursorToolInputFromDelta(update) {
10369
+ if (update.type !== "tool-call-started") return null;
10370
+ const toolCall = update.toolCall;
10371
+ return {
10372
+ toolName: toolCall.type,
10373
+ toolInput: toolCall,
10374
+ command: extractToolCommand(toolCall),
10375
+ toolUseId: update.callId
10376
+ };
10377
+ }
10378
+ function cursorToolInputFromMessage(event) {
10379
+ return {
10380
+ toolName: event.name,
10381
+ toolInput: event.args ?? {},
10382
+ command: extractToolCommand(event.args),
10383
+ toolUseId: event.call_id
10384
+ };
10385
+ }
10325
10386
  async function listCursorCommandsInDirectory(directory) {
10326
10387
  let entries;
10327
10388
  try {
@@ -10347,6 +10408,8 @@ var CursorManager = class extends CodingAgentManager {
10347
10408
  agent = null;
10348
10409
  activeRun = null;
10349
10410
  activeModel = null;
10411
+ blockedToolCallIds = /* @__PURE__ */ new Set();
10412
+ pendingCursorBlockReason = null;
10350
10413
  historyFilePath;
10351
10414
  historyFile;
10352
10415
  slashCommandsCache = null;
@@ -10425,12 +10488,22 @@ var CursorManager = class extends CodingAgentManager {
10425
10488
  }, this.historyFile);
10426
10489
  const run = await agent.send(message, {
10427
10490
  model: { id: model },
10428
- mode: request.planMode ? "plan" : "agent"
10491
+ mode: request.planMode ? "plan" : "agent",
10492
+ onDelta: async (args) => {
10493
+ await this.handleCursorToolCall(cursorToolInputFromDelta(args.update));
10494
+ }
10429
10495
  });
10430
10496
  this.activeRun = run;
10431
10497
  this.activeModel = model;
10498
+ if (this.pendingCursorBlockReason) {
10499
+ await run.cancel();
10500
+ throw new Error(this.pendingCursorBlockReason);
10501
+ }
10432
10502
  for await (const event of run.stream()) {
10433
10503
  this.recordCursorEvent(event);
10504
+ if (event.type === "tool_call" && event.status === "running") {
10505
+ await this.handleCursorToolCall(cursorToolInputFromMessage(event));
10506
+ }
10434
10507
  if (linearSessionId) {
10435
10508
  linearForwarder.sendEvent(convertCursorEvent(event, linearSessionId));
10436
10509
  }
@@ -10453,10 +10526,45 @@ var CursorManager = class extends CodingAgentManager {
10453
10526
  } finally {
10454
10527
  this.activeRun = null;
10455
10528
  this.activeModel = null;
10529
+ this.blockedToolCallIds.clear();
10530
+ this.pendingCursorBlockReason = null;
10456
10531
  await this.historyFile.flush();
10457
10532
  await this.onTurnComplete();
10458
10533
  }
10459
10534
  }
10535
+ async handleCursorToolCall(toolCall) {
10536
+ if (!ENGINE_ENV.REPLICAS_DISABLE_GH_PR_MERGE || !toolCall) return;
10537
+ if (this.blockedToolCallIds.has(toolCall.toolUseId)) return;
10538
+ const result = await evaluateCommandProtection({
10539
+ provider: "cursor",
10540
+ source: "cursor_tool_call",
10541
+ toolName: toolCall.toolName,
10542
+ toolInput: toolCall.toolInput,
10543
+ command: toolCall.command,
10544
+ cwd: this.workingDirectory,
10545
+ toolUseId: toolCall.toolUseId
10546
+ });
10547
+ if (result.allowed) return;
10548
+ this.blockedToolCallIds.add(toolCall.toolUseId);
10549
+ const message = reportCommandProtectionBlock({
10550
+ managerName: "CursorManager",
10551
+ action: "tool call",
10552
+ eventType: "cursor-command-protection.blocked",
10553
+ result,
10554
+ command: toolCall.command,
10555
+ context: {
10556
+ toolName: toolCall.toolName,
10557
+ toolUseId: toolCall.toolUseId
10558
+ },
10559
+ historyFile: this.historyFile,
10560
+ recordHistoryEvent: this.recordHistoryEvent.bind(this)
10561
+ });
10562
+ if (this.activeRun) {
10563
+ await this.activeRun.cancel();
10564
+ throw new Error(message);
10565
+ }
10566
+ this.pendingCursorBlockReason = message;
10567
+ }
10460
10568
  async toCursorMessage(request) {
10461
10569
  if (!request.images || request.images.length === 0) {
10462
10570
  return request.message;
@@ -10552,7 +10660,23 @@ var OPENCODE_SHIM_DIR = dirname6(fileURLToPath(new URL("../../scripts/opencode",
10552
10660
  var OPENCODE_CONFIG_PATH = join18(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
10553
10661
  var OPENCODE_FETCH_DISPATCHER = new Agent({ headersTimeout: 0, bodyTimeout: 0 });
10554
10662
  var OPENCODE_SERVER_STARTUP_TIMEOUT_MS = 3e4;
10555
- var OPENCODE_WORKSPACE_PERMISSION = "allow";
10663
+ var OPENCODE_WORKSPACE_PERMISSION = {
10664
+ read: "allow",
10665
+ edit: "allow",
10666
+ glob: "allow",
10667
+ grep: "allow",
10668
+ list: "allow",
10669
+ bash: "ask",
10670
+ task: "allow",
10671
+ external_directory: "allow",
10672
+ todowrite: "allow",
10673
+ question: "allow",
10674
+ webfetch: "allow",
10675
+ websearch: "allow",
10676
+ lsp: "allow",
10677
+ doom_loop: "allow",
10678
+ skill: "allow"
10679
+ };
10556
10680
  var OPENCODE_SLASH_COMMANDS_CACHE_MS = 3e4;
10557
10681
  var OPENCODE_VARIANT_CANDIDATES_BY_THINKING_LEVEL = {
10558
10682
  low: ["low"],
@@ -10684,6 +10808,27 @@ function opencodeErrorPayload(error) {
10684
10808
  function opencodeFetch(input, init) {
10685
10809
  return fetch(input, { ...init, dispatcher: OPENCODE_FETCH_DISPATCHER });
10686
10810
  }
10811
+ function parseOpencodeToolInputText(text) {
10812
+ try {
10813
+ return JSON.parse(text);
10814
+ } catch {
10815
+ return { command: text };
10816
+ }
10817
+ }
10818
+ function opencodePermissionCommand(payload, toolInput) {
10819
+ const toolCommand = extractToolCommand(toolInput);
10820
+ if (toolCommand) return toolCommand;
10821
+ const payloadCommand = extractToolCommand(payload);
10822
+ if (payloadCommand) return payloadCommand;
10823
+ const resources = Array.isArray(payload.resources) ? payload.resources.filter((item) => typeof item === "string") : [];
10824
+ if (resources.length > 0) return resources.join(" ");
10825
+ const metadata = isRecord4(payload.metadata) ? payload.metadata : null;
10826
+ for (const key of ["command", "cmd", "pattern", "resource"]) {
10827
+ const value = metadata?.[key];
10828
+ if (typeof value === "string" && value.trim()) return value;
10829
+ }
10830
+ return null;
10831
+ }
10687
10832
  var OpencodeManager = class extends CodingAgentManager {
10688
10833
  client = null;
10689
10834
  server = null;
@@ -10701,6 +10846,11 @@ var OpencodeManager = class extends CodingAgentManager {
10701
10846
  activeLinearForwarder = null;
10702
10847
  forwardedLinearPartKeys = /* @__PURE__ */ new Set();
10703
10848
  modelVariants = /* @__PURE__ */ new Map();
10849
+ handledPermissionRequestIds = /* @__PURE__ */ new Set();
10850
+ pendingPermissionRequestIds = /* @__PURE__ */ new Set();
10851
+ handledToolCallIds = /* @__PURE__ */ new Set();
10852
+ pendingToolCallIds = /* @__PURE__ */ new Set();
10853
+ opencodeToolInputs = /* @__PURE__ */ new Map();
10704
10854
  slashCommandsCache = null;
10705
10855
  slashCommandsRequest = null;
10706
10856
  constructor(options) {
@@ -10905,6 +11055,11 @@ var OpencodeManager = class extends CodingAgentManager {
10905
11055
  this.activeAbortController = null;
10906
11056
  this.activeLinearForwarder = null;
10907
11057
  this.forwardedLinearPartKeys.clear();
11058
+ this.handledPermissionRequestIds.clear();
11059
+ this.pendingPermissionRequestIds.clear();
11060
+ this.handledToolCallIds.clear();
11061
+ this.pendingToolCallIds.clear();
11062
+ this.opencodeToolInputs.clear();
10908
11063
  await this.historyFile.flush();
10909
11064
  await this.onTurnComplete();
10910
11065
  }
@@ -10942,12 +11097,141 @@ var OpencodeManager = class extends CodingAgentManager {
10942
11097
  if (typeof partSessionID === "string" && this.sessionId && partSessionID !== this.sessionId) return;
10943
11098
  if (event.type === "message.part.updated" && isOpencodePart(part)) {
10944
11099
  if (this.shouldRecordOpencodePart(part)) this.recordOpencodePart(part);
11100
+ this.handleOpencodePartProtection(part).catch((error) => {
11101
+ console.error("[OpencodeManager] Failed to evaluate Opencode tool part:", error);
11102
+ });
10945
11103
  return;
10946
11104
  }
10947
11105
  if (event.type === "message.updated") this.recordOpencodeMessageRole(payload);
11106
+ if (event.type === "session.next.tool.input.ended") {
11107
+ const callId = typeof payload.callID === "string" ? payload.callID : void 0;
11108
+ if (callId && typeof payload.text === "string") {
11109
+ this.opencodeToolInputs.set(callId, parseOpencodeToolInputText(payload.text));
11110
+ }
11111
+ }
11112
+ if (event.type === "permission.v2.asked" || event.type === "permission.asked") {
11113
+ this.respondToOpencodePermission(event.type, payload).catch((error) => {
11114
+ console.error("[OpencodeManager] Failed to respond to Opencode permission:", error);
11115
+ });
11116
+ }
11117
+ if (event.type === "session.next.tool.called") {
11118
+ this.handleOpencodeToolCallProtection(payload).catch((error) => {
11119
+ console.error("[OpencodeManager] Failed to evaluate Opencode tool call:", error);
11120
+ });
11121
+ }
10948
11122
  if (this.recordOpencodeNextPart(event.type, payload)) return;
10949
11123
  this.recordHistoryEvent(`opencode-${event.type}`, payload, this.historyFile);
10950
11124
  }
11125
+ async respondToOpencodePermission(type, payload) {
11126
+ if (!this.client) return;
11127
+ const requestId = typeof payload.id === "string" ? payload.id : void 0;
11128
+ const sessionId = typeof payload.sessionID === "string" ? payload.sessionID : this.sessionId ?? void 0;
11129
+ if (!requestId || !sessionId || this.handledPermissionRequestIds.has(requestId) || this.pendingPermissionRequestIds.has(requestId)) return;
11130
+ this.pendingPermissionRequestIds.add(requestId);
11131
+ try {
11132
+ const source = isRecord4(payload.source) ? payload.source : null;
11133
+ const callId = typeof source?.callID === "string" ? source.callID : isRecord4(payload.tool) && typeof payload.tool.callID === "string" ? payload.tool.callID : void 0;
11134
+ const toolInput = callId ? this.opencodeToolInputs.get(callId) ?? payload : payload;
11135
+ const command = opencodePermissionCommand(payload, toolInput);
11136
+ const result = ENGINE_ENV.REPLICAS_DISABLE_GH_PR_MERGE ? await evaluateCommandProtection({
11137
+ provider: "opencode",
11138
+ source: "opencode_permission_request",
11139
+ toolName: typeof payload.action === "string" ? payload.action : typeof payload.permission === "string" ? payload.permission : "permission",
11140
+ toolInput,
11141
+ command,
11142
+ cwd: this.workingDirectory,
11143
+ toolUseId: requestId
11144
+ }) : { allowed: true, enabled: false };
11145
+ const reply = result.allowed ? "once" : "reject";
11146
+ const message = result.allowed ? void 0 : reportCommandProtectionBlock({
11147
+ managerName: "OpencodeManager",
11148
+ action: "permission",
11149
+ eventType: "opencode-command-protection.blocked",
11150
+ result,
11151
+ command,
11152
+ context: {
11153
+ requestId
11154
+ },
11155
+ historyFile: this.historyFile,
11156
+ recordHistoryEvent: this.recordHistoryEvent.bind(this)
11157
+ });
11158
+ if (type === "permission.v2.asked") {
11159
+ await this.client.v2.session.permission.reply({
11160
+ sessionID: sessionId,
11161
+ requestID: requestId,
11162
+ reply,
11163
+ ...message ? { message } : {}
11164
+ }, { throwOnError: true });
11165
+ } else {
11166
+ await this.client.permission.reply({
11167
+ requestID: requestId,
11168
+ directory: this.workingDirectory,
11169
+ reply,
11170
+ ...message ? { message } : {}
11171
+ }, { throwOnError: true });
11172
+ }
11173
+ this.handledPermissionRequestIds.add(requestId);
11174
+ } finally {
11175
+ this.pendingPermissionRequestIds.delete(requestId);
11176
+ }
11177
+ }
11178
+ async handleOpencodeToolCallProtection(payload) {
11179
+ const callId = typeof payload.callID === "string" ? payload.callID : void 0;
11180
+ if (!callId || this.handledToolCallIds.has(callId)) return;
11181
+ const toolName = typeof payload.tool === "string" ? payload.tool : "tool";
11182
+ const input = isRecord4(payload.input) ? payload.input : {};
11183
+ this.opencodeToolInputs.set(callId, input);
11184
+ const command = extractToolCommand(input);
11185
+ await this.evaluateOpencodeToolProtection(toolName, input, command, callId);
11186
+ }
11187
+ async handleOpencodePartProtection(part) {
11188
+ if (part.type !== "tool") return;
11189
+ const state = part.state;
11190
+ if (state.status !== "running" && state.status !== "pending") return;
11191
+ await this.evaluateOpencodeToolProtection(part.tool, state.input, extractToolCommand(state.input), part.id);
11192
+ }
11193
+ async evaluateOpencodeToolProtection(toolName, toolInput, command, toolUseId) {
11194
+ if (!ENGINE_ENV.REPLICAS_DISABLE_GH_PR_MERGE || this.handledToolCallIds.has(toolUseId) || this.pendingToolCallIds.has(toolUseId)) return;
11195
+ this.pendingToolCallIds.add(toolUseId);
11196
+ try {
11197
+ const result = await evaluateCommandProtection({
11198
+ provider: "opencode",
11199
+ source: "opencode_tool_call",
11200
+ toolName,
11201
+ toolInput,
11202
+ command,
11203
+ cwd: this.workingDirectory,
11204
+ toolUseId
11205
+ });
11206
+ if (result.allowed) {
11207
+ this.handledToolCallIds.add(toolUseId);
11208
+ return;
11209
+ }
11210
+ const message = reportCommandProtectionBlock({
11211
+ managerName: "OpencodeManager",
11212
+ action: "tool call",
11213
+ eventType: "opencode-command-protection.blocked",
11214
+ result,
11215
+ command,
11216
+ context: {
11217
+ toolName,
11218
+ toolUseId
11219
+ },
11220
+ historyFile: this.historyFile,
11221
+ recordHistoryEvent: this.recordHistoryEvent.bind(this)
11222
+ });
11223
+ this.activeAbortController?.abort();
11224
+ if (this.client && this.sessionId) {
11225
+ await this.client.session.abort(
11226
+ { sessionID: this.sessionId, directory: this.workingDirectory },
11227
+ { throwOnError: true }
11228
+ );
11229
+ }
11230
+ this.handledToolCallIds.add(toolUseId);
11231
+ } finally {
11232
+ this.pendingToolCallIds.delete(toolUseId);
11233
+ }
11234
+ }
10951
11235
  recordOpencodeMessageRole(payload) {
10952
11236
  const info = isRecord4(payload.info) ? payload.info : null;
10953
11237
  const id = typeof info?.id === "string" ? info.id : void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.411",
3
+ "version": "0.1.413",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",