replicas-engine 0.1.703 → 0.1.704

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.
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils/type-guards.ts
4
+ function isRecord(value) {
5
+ return typeof value === "object" && value !== null;
6
+ }
7
+
8
+ export {
9
+ isRecord
10
+ };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  HOOK_EXEC_MAX_BUFFER_BYTES,
4
4
  isVersionBelow
5
- } from "./chunk-LPJ6WYD4.js";
5
+ } from "./chunk-WUJ4IALO.js";
6
6
 
7
7
  // src/utils/presigned-upload.ts
8
8
  import { createReadStream } from "fs";
@@ -267,7 +267,7 @@ var DEFAULT_CODEX_ARGS = [
267
267
  var MIN_CODEX_CLI_VERSION = "0.144.6";
268
268
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
269
269
  var codexCliVersionEnsured = null;
270
- var ENGINE_PACKAGE_VERSION = "0.1.703";
270
+ var ENGINE_PACKAGE_VERSION = "0.1.704";
271
271
  var INITIALIZE_METHOD = "initialize";
272
272
  var INITIALIZED_NOTIFICATION = "initialized";
273
273
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ mayCreatePullRequest
4
+ } from "./chunk-WUJ4IALO.js";
5
+
6
+ // src/services/post-tool-pr-notifier.ts
7
+ async function notifyPostToolUse(toolName, toolInput) {
8
+ if (!mayCreatePullRequest(toolName, toolInput)) return false;
9
+ const port = process.env.REPLICAS_ENGINE_PORT;
10
+ const secret = process.env.REPLICAS_ENGINE_SECRET;
11
+ if (!port || !secret) throw new Error("Replicas engine connection is unavailable.");
12
+ const response = await fetch(`http://127.0.0.1:${port}/pull-requests/check`, {
13
+ method: "POST",
14
+ headers: { "X-Replicas-Engine-Secret": secret },
15
+ signal: AbortSignal.timeout(3e4)
16
+ });
17
+ if (!response.ok) throw new Error(`Replicas pull request check failed (${response.status}).`);
18
+ return true;
19
+ }
20
+
21
+ export {
22
+ notifyPostToolUse
23
+ };
@@ -1128,6 +1128,13 @@ async function raceWithTimeout(promise, ms) {
1128
1128
  });
1129
1129
  }
1130
1130
  }
1131
+ async function withTimeout(promise, ms, message) {
1132
+ const result = await raceWithTimeout(promise, ms);
1133
+ if (result === TIMEOUT) {
1134
+ throw new Error(message);
1135
+ }
1136
+ return result;
1137
+ }
1131
1138
 
1132
1139
  // ../shared/src/agent-quota.ts
1133
1140
  var OUT_OF_CREDITS_PATTERNS = [
@@ -3659,6 +3666,37 @@ function findGitCommitSignals(request) {
3659
3666
  const command = extractCommandProtectionCommandText(request, { stringifyFallback: true }) ?? "";
3660
3667
  return findGitCommitCommands(command).length > 0 ? ["git-commit"] : [];
3661
3668
  }
3669
+ function mayCreatePullRequest(toolName, toolInput) {
3670
+ const input = normalizeCommandProtectionText(stringifyCommandProtectionInput(toolInput));
3671
+ const combined = `${toolName.toLowerCase()} ${input}`;
3672
+ return [
3673
+ /\bgit\s+push\b/,
3674
+ /\bgit\s+(?:branch|checkout|switch|pull|merge|rebase|reset)\b/,
3675
+ /\bgit\s+worktree\s+add\b/,
3676
+ /\bgh\b.{0,120}\bpr\s+(?:checkout|create|new|ready|reopen)\b/,
3677
+ /\bgh\s+api\b[\s\S]*(?:\/repos\/[^/\s]+\/[^/\s]+\/pulls\b|\bcreatepullrequest\b)/,
3678
+ /\b(?:glab|gitlab)\s+mr\s+(?:checkout|create)\b/,
3679
+ /\bhub\s+pull-request\b/,
3680
+ /\b(?:tea\s+pulls|bb\s+pr|az\s+repos\s+pr)\s+create\b/,
3681
+ /\bcreatepullrequest\b/,
3682
+ /\b(?:create|open|submit)[_-]?(?:pr|pull[_-]?request|mr|merge[_-]?request)\b/,
3683
+ /\b(?:pr|pull[_-]?request|mr|merge[_-]?request)[_-]?(?:create|open|submit)\b/,
3684
+ /\b(?:checkout|create|switch)[_-]?branch\b/,
3685
+ /\bbranch[_-]?(?:checkout|create|switch)\b/,
3686
+ /\b(?:switched to|checked out|created and switched to)(?: a new)? branch\b/,
3687
+ /github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+\b/,
3688
+ /\/merge_requests\/\d+\b/,
3689
+ /\b(?:create|open|submit)(?:pull|merge)request\b/,
3690
+ /\b(?:pull|merge)request(?:create|open|submit)\b/,
3691
+ /\bmergerequestcreate\b/,
3692
+ /api\.github\.com\/repos\/[^/\s]+\/[^/\s]+\/pulls\b/,
3693
+ /\/projects\/[^/\s]+\/merge_requests\b/,
3694
+ /\/pullrequests\b/,
3695
+ /\/_apis\/git\/repositories\/[^/\s]+\/pullrequests\b/,
3696
+ /\/pulls\b[\s\S]*\b(?:post|create)\b/,
3697
+ /\/merge_requests\b[\s\S]*\b(?:post|create)\b/
3698
+ ].some((pattern) => pattern.test(combined));
3699
+ }
3662
3700
 
3663
3701
  // ../shared/src/routes/plugin-catalog.ts
3664
3702
  var EXPLICIT_PLUGIN_CATALOG = [
@@ -6579,6 +6617,7 @@ export {
6579
6617
  isNonEmptyString,
6580
6618
  TIMEOUT,
6581
6619
  raceWithTimeout,
6620
+ withTimeout,
6582
6621
  createSuccessResult,
6583
6622
  createErrorResult,
6584
6623
  AGENT,
@@ -6725,6 +6764,7 @@ export {
6725
6764
  extractCommandProtectionCommandText,
6726
6765
  findPrMergeSignals,
6727
6766
  findGitCommitSignals,
6767
+ mayCreatePullRequest,
6728
6768
  coerceBackgroundTaskPayload,
6729
6769
  isTerminalBackgroundTaskStatus,
6730
6770
  areUserMessagesWithinMatchWindow,
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ isRecord as isRecord2
4
+ } from "./chunk-2RB7SIP3.js";
2
5
  import {
3
6
  REPLICAS_RUNTIME_ENV_ALIASES,
4
7
  agentCredentialSnapshotSchema,
@@ -9,12 +12,7 @@ import {
9
12
  isValidAgentProvider,
10
13
  parsePosixEnvFile,
11
14
  readReplicasRuntimeEnv
12
- } from "./chunk-LPJ6WYD4.js";
13
-
14
- // src/utils/type-guards.ts
15
- function isRecord2(value) {
16
- return typeof value === "object" && value !== null;
17
- }
15
+ } from "./chunk-WUJ4IALO.js";
18
16
 
19
17
  // src/engine-env.ts
20
18
  import { readFileSync as readFileSync2 } from "fs";
@@ -275,7 +273,6 @@ function reportCommandProtectionBlock(options) {
275
273
  }
276
274
 
277
275
  export {
278
- isRecord2 as isRecord,
279
276
  IS_WARMING_MODE,
280
277
  ENGINE_ENV,
281
278
  setAgentCredentialSnapshot,
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- evaluateCommandProtection,
3
+ evaluateCommandProtection
4
+ } from "./chunk-ZCBMGGAC.js";
5
+ import {
4
6
  isRecord
5
- } from "./chunk-RVDZXLBY.js";
6
- import "./chunk-LPJ6WYD4.js";
7
+ } from "./chunk-2RB7SIP3.js";
8
+ import "./chunk-WUJ4IALO.js";
7
9
 
8
10
  // src/command-protection-hook.ts
9
11
  var provider = process.argv[2];
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  evaluateCommandProtection
4
- } from "./chunk-RVDZXLBY.js";
5
- import "./chunk-LPJ6WYD4.js";
4
+ } from "./chunk-ZCBMGGAC.js";
5
+ import {
6
+ notifyPostToolUse
7
+ } from "./chunk-GDCMH7YA.js";
8
+ import "./chunk-2RB7SIP3.js";
9
+ import "./chunk-WUJ4IALO.js";
6
10
 
7
11
  // src/deepseek-command-protection-plugin.ts
8
12
  function replicasCommandProtection(context) {
@@ -17,6 +21,11 @@ function replicasCommandProtection(context) {
17
21
  });
18
22
  return result.allowed ? next() : { kind: "deny", reason: result.reason ?? "Blocked by Replicas command protection." };
19
23
  });
24
+ context.on("tools/post-execute", async (execution, _result, next) => {
25
+ await notifyPostToolUse(execution.name, execution.arguments).catch(() => {
26
+ });
27
+ return next();
28
+ });
20
29
  }
21
30
  export {
22
31
  replicasCommandProtection as default
@@ -4,12 +4,12 @@ import {
4
4
  buildCodexAgentEnv,
5
5
  putPresignedFile,
6
6
  recoverCompletedTurn
7
- } from "./chunk-U3XQBTVU.js";
7
+ } from "./chunk-EC4MFY4M.js";
8
8
  import {
9
9
  AGENT,
10
10
  getMemoryOutputSafetyViolation,
11
11
  headlessAgentRequestSchema
12
- } from "./chunk-LPJ6WYD4.js";
12
+ } from "./chunk-WUJ4IALO.js";
13
13
 
14
14
  // src/headless-agent.ts
15
15
  import { createHash } from "crypto";
package/dist/src/index.js CHANGED
@@ -4,12 +4,11 @@ import {
4
4
  IS_WARMING_MODE,
5
5
  evaluateCommandProtection,
6
6
  extractToolCommand,
7
- isRecord as isRecord2,
8
7
  monolithRequest,
9
8
  monolithService,
10
9
  reportCommandProtectionBlock,
11
10
  setAgentCredentialSnapshot
12
- } from "./chunk-RVDZXLBY.js";
11
+ } from "./chunk-ZCBMGGAC.js";
13
12
  import {
14
13
  ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
15
14
  AGENT_MESSAGE_DELTA_METHOD,
@@ -36,7 +35,10 @@ import {
36
35
  execFileAsync,
37
36
  putPresignedFile,
38
37
  recoverCompletedTurn
39
- } from "./chunk-U3XQBTVU.js";
38
+ } from "./chunk-EC4MFY4M.js";
39
+ import {
40
+ isRecord as isRecord2
41
+ } from "./chunk-2RB7SIP3.js";
40
42
  import {
41
43
  ACCEPTED_USER_MESSAGE_SOURCE,
42
44
  AGENT,
@@ -182,6 +184,7 @@ import {
182
184
  isTransientErrorText,
183
185
  isValidAgentProvider,
184
186
  isValidEngineLogSessionId,
187
+ mayCreatePullRequest,
185
188
  mergeSlashCommands,
186
189
  normalizeAutoCompactThreshold,
187
190
  normalizeClaudeModel,
@@ -207,8 +210,9 @@ import {
207
210
  sendChatMessageRequestSchema,
208
211
  serializeCanvasContentResponse,
209
212
  shellQuotePosix,
210
- stripAgentDiagnosticErrors
211
- } from "./chunk-LPJ6WYD4.js";
213
+ stripAgentDiagnosticErrors,
214
+ withTimeout
215
+ } from "./chunk-WUJ4IALO.js";
212
216
 
213
217
  // src/index.ts
214
218
  import { serve } from "@hono/node-server";
@@ -3949,6 +3953,92 @@ var LinearEventForwarder = class {
3949
3953
  }
3950
3954
  };
3951
3955
 
3956
+ // src/services/upload-repo-state.ts
3957
+ var uploadQueue = Promise.resolve();
3958
+ async function sendRepoState(repos) {
3959
+ const form = new FormData();
3960
+ form.append(
3961
+ "file",
3962
+ new Blob([JSON.stringify({ repos })], { type: "application/json" }),
3963
+ "repo-state.json"
3964
+ );
3965
+ const response = await monolithRequest("/v1/engine/repo-state", { body: form });
3966
+ if (!response.ok) {
3967
+ const errorText = await response.text();
3968
+ throw new Error(`upload failed: ${response.status} ${errorText}`);
3969
+ }
3970
+ }
3971
+ function uploadRepoState(repos) {
3972
+ const upload = uploadQueue.then(() => sendRepoState(repos));
3973
+ uploadQueue = upload.catch(() => {
3974
+ });
3975
+ return upload;
3976
+ }
3977
+ async function flushRepoState() {
3978
+ try {
3979
+ const { repos, complete } = await gitService.listReposWithCompleteness({ includeDiffs: true });
3980
+ if (!complete) return { flushed: 0, skipped: 1, failed: 0 };
3981
+ await uploadRepoState(repos);
3982
+ return { flushed: 1, skipped: 0, failed: 0 };
3983
+ } catch (err) {
3984
+ console.error("[RepoStateUploader] upload failed:", err);
3985
+ return { flushed: 0, skipped: 0, failed: 1 };
3986
+ }
3987
+ }
3988
+
3989
+ // src/services/refresh-repo-state.ts
3990
+ async function refreshRepoStatuses(observedBranchesByRepo, includeDiffs = false) {
3991
+ await Promise.all([
3992
+ githubTokenManager.refreshOnce().catch(() => {
3993
+ }),
3994
+ gitlabTokenManager.refreshOnce().catch(() => {
3995
+ })
3996
+ ]);
3997
+ const { repos, complete } = await gitService.refreshReposWithCompleteness(
3998
+ observedBranchesByRepo,
3999
+ { includeDiffs }
4000
+ );
4001
+ if (complete) {
4002
+ uploadRepoState(repos).catch((error) => {
4003
+ console.error("[RefreshRepoState] Failed to upload repository state:", error);
4004
+ });
4005
+ }
4006
+ return repos;
4007
+ }
4008
+
4009
+ // src/services/pull-request-check-service.ts
4010
+ var CHECK_TIMEOUT_MS = 3e4;
4011
+ var PullRequestCheckService = class {
4012
+ running = null;
4013
+ trailing = null;
4014
+ // Plain single-flight would hand a caller a result gathered before its PR
4015
+ // existed, so callers arriving mid-run instead share one trailing run that
4016
+ // starts after the current one settles.
4017
+ check() {
4018
+ const running = this.running;
4019
+ if (!running) return this.start();
4020
+ this.trailing ??= running.catch(() => {
4021
+ }).then(() => {
4022
+ this.trailing = null;
4023
+ return this.start();
4024
+ });
4025
+ return this.trailing;
4026
+ }
4027
+ start() {
4028
+ const run = withTimeout(this.refresh(), CHECK_TIMEOUT_MS, "Pull request check timed out").finally(() => {
4029
+ if (this.running === run) this.running = null;
4030
+ });
4031
+ this.running = run;
4032
+ return run;
4033
+ }
4034
+ async refresh() {
4035
+ const repoStatuses = await refreshRepoStatuses();
4036
+ await monolithService.sendEvent({ type: "pull_request_check", payload: { repoStatuses } });
4037
+ return repoStatuses;
4038
+ }
4039
+ };
4040
+ var pullRequestCheckService = new PullRequestCheckService();
4041
+
3952
4042
  // src/services/skill-registry-service.ts
3953
4043
  import { readFile as readFile8, readdir as readdir3, stat as stat2 } from "fs/promises";
3954
4044
  import { dirname as dirname3, isAbsolute, join as join14, relative, resolve } from "path";
@@ -5152,6 +5242,17 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5152
5242
  matcher: "*",
5153
5243
  hooks: [this.buildCommandProtectionHook()],
5154
5244
  timeout: 30
5245
+ }],
5246
+ PostToolUse: [{
5247
+ matcher: "*",
5248
+ hooks: [async (input) => {
5249
+ if (input.hook_event_name === "PostToolUse" && mayCreatePullRequest(input.tool_name, input.tool_input)) {
5250
+ pullRequestCheckService.check().catch(() => {
5251
+ });
5252
+ }
5253
+ return {};
5254
+ }],
5255
+ timeout: 30
5155
5256
  }]
5156
5257
  }
5157
5258
  }
@@ -7101,6 +7202,11 @@ var CodexAspManager = class extends CodingAgentManager {
7101
7202
  );
7102
7203
  this.emitTranscriptUpdated(notification.params.threadId, { immediate: true });
7103
7204
  linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
7205
+ const completedItem = notification.params.item;
7206
+ if (completedItem.type === "commandExecution" && completedItem.exitCode === 0 && mayCreatePullRequest("commandExecution", { command: completedItem.command }) || completedItem.type === "mcpToolCall" && completedItem.status === "completed" && mayCreatePullRequest(completedItem.tool, completedItem.arguments) || completedItem.type === "dynamicToolCall" && completedItem.success !== false && mayCreatePullRequest(completedItem.tool, completedItem.arguments)) {
7207
+ pullRequestCheckService.check().catch(() => {
7208
+ });
7209
+ }
7104
7210
  },
7105
7211
  [AGENT_MESSAGE_DELTA_METHOD]: (notification) => {
7106
7212
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
@@ -7823,15 +7929,28 @@ var hookPath = [
7823
7929
  join18(moduleDirectory, "command-protection-hook.js"),
7824
7930
  join18(moduleDirectory, "..", "command-protection-hook.ts")
7825
7931
  ].find(existsSync6);
7826
- function shellCommand(provider) {
7827
- if (!hookPath) throw new Error("Replicas command protection hook executable is missing.");
7828
- const args = hookPath.endsWith(".ts") && basename(process.execPath) !== "bun" ? [process.execPath, "--import", "tsx", hookPath, provider] : [process.execPath, hookPath, provider];
7932
+ var postToolHookPath = [
7933
+ join18(moduleDirectory, "post-tool-pr-hook.js"),
7934
+ join18(moduleDirectory, "..", "post-tool-pr-hook.ts")
7935
+ ].find(existsSync6);
7936
+ function shellCommand(path6, provider) {
7937
+ if (!path6) throw new Error("Replicas hook executable is missing.");
7938
+ const args = [
7939
+ process.execPath,
7940
+ ...path6.endsWith(".ts") && basename(process.execPath) !== "bun" ? ["--import", "tsx"] : [],
7941
+ path6,
7942
+ ...provider ? [provider] : []
7943
+ ];
7829
7944
  return args.map((value) => `'${value.replaceAll("'", "'\\''")}'`).join(" ");
7830
7945
  }
7831
7946
  function getCommandProtectionHookPath() {
7832
7947
  if (!hookPath) throw new Error("Replicas command protection hook executable is missing.");
7833
7948
  return hookPath;
7834
7949
  }
7950
+ function getPostToolPrHookPath() {
7951
+ if (!postToolHookPath) throw new Error("Replicas post-tool PR hook executable is missing.");
7952
+ return postToolHookPath;
7953
+ }
7835
7954
  async function ensureCursorCommandProtectionHook() {
7836
7955
  const path6 = join18(ENGINE_ENV.HOME_DIR, ".cursor", "hooks.json");
7837
7956
  let config = {};
@@ -7846,7 +7965,12 @@ async function ensureCursorCommandProtectionHook() {
7846
7965
  const existing = Array.isArray(hooks.preToolUse) ? hooks.preToolUse : [];
7847
7966
  hooks.preToolUse = [
7848
7967
  ...existing.filter((entry) => !isRecord2(entry) || typeof entry.command !== "string" || !entry.command.includes("command-protection-hook")),
7849
- { command: shellCommand("cursor") }
7968
+ { command: shellCommand(hookPath, "cursor") }
7969
+ ];
7970
+ const existingPost = Array.isArray(hooks.postToolUse) ? hooks.postToolUse : [];
7971
+ hooks.postToolUse = [
7972
+ ...existingPost.filter((entry) => !isRecord2(entry) || typeof entry.command !== "string" || !entry.command.includes("post-tool-pr-hook")),
7973
+ { command: shellCommand(postToolHookPath) }
7850
7974
  ];
7851
7975
  await mkdir11(dirname5(path6), { recursive: true });
7852
7976
  await writeFile6(path6, `${JSON.stringify({ ...config, version: 1, hooks }, null, 2)}
@@ -7865,7 +7989,12 @@ async function ensureKimiCommandProtectionHook() {
7865
7989
  const block = `${start}
7866
7990
  [[hooks]]
7867
7991
  event = "PreToolUse"
7868
- command = ${JSON.stringify(shellCommand("kimi"))}
7992
+ command = ${JSON.stringify(shellCommand(hookPath, "kimi"))}
7993
+ timeout = 30
7994
+
7995
+ [[hooks]]
7996
+ event = "PostToolUse"
7997
+ command = ${JSON.stringify(shellCommand(postToolHookPath))}
7869
7998
  timeout = 30
7870
7999
  ${end}`;
7871
8000
  const markerStart = config.indexOf(start);
@@ -9635,6 +9764,7 @@ var OpencodeManager = class extends CodingAgentManager {
9635
9764
  await copyFile(OPENCODE_COMMAND_PROTECTION_PLUGIN_SOURCE, OPENCODE_COMMAND_PROTECTION_PLUGIN_PATH);
9636
9765
  await rm2(OPENCODE_LEGACY_COMMAND_PROTECTION_PLUGIN_PATH, { force: true });
9637
9766
  process.env.REPLICAS_COMMAND_PROTECTION_HOOK_PATH = getCommandProtectionHookPath();
9767
+ process.env.REPLICAS_POST_TOOL_PR_HOOK_PATH = getPostToolPrHookPath();
9638
9768
  process.env.REPLICAS_COMMAND_PROTECTION_RUNTIME_PATH = process.execPath;
9639
9769
  this.eventAbortController?.abort();
9640
9770
  this.server?.close();
@@ -10022,6 +10152,12 @@ function registerCommandProtection(cwd, historyFile, recordHistoryEvent) {
10022
10152
  }
10023
10153
  return void 0;
10024
10154
  });
10155
+ pi.on("tool_result", (event) => {
10156
+ if (!event.isError && mayCreatePullRequest(event.toolName, event.input)) {
10157
+ pullRequestCheckService.check().catch(() => {
10158
+ });
10159
+ }
10160
+ });
10025
10161
  };
10026
10162
  }
10027
10163
  var PiManager = class extends CodingAgentManager {
@@ -12152,39 +12288,6 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
12152
12288
  return artifact;
12153
12289
  }
12154
12290
 
12155
- // src/services/upload-repo-state.ts
12156
- var uploadQueue = Promise.resolve();
12157
- async function sendRepoState(repos) {
12158
- const form = new FormData();
12159
- form.append(
12160
- "file",
12161
- new Blob([JSON.stringify({ repos })], { type: "application/json" }),
12162
- "repo-state.json"
12163
- );
12164
- const response = await monolithRequest("/v1/engine/repo-state", { body: form });
12165
- if (!response.ok) {
12166
- const errorText = await response.text();
12167
- throw new Error(`upload failed: ${response.status} ${errorText}`);
12168
- }
12169
- }
12170
- function uploadRepoState(repos) {
12171
- const upload = uploadQueue.then(() => sendRepoState(repos));
12172
- uploadQueue = upload.catch(() => {
12173
- });
12174
- return upload;
12175
- }
12176
- async function flushRepoState() {
12177
- try {
12178
- const { repos, complete } = await gitService.listReposWithCompleteness({ includeDiffs: true });
12179
- if (!complete) return { flushed: 0, skipped: 1, failed: 0 };
12180
- await uploadRepoState(repos);
12181
- return { flushed: 1, skipped: 0, failed: 0 };
12182
- } catch (err) {
12183
- console.error("[RepoStateUploader] upload failed:", err);
12184
- return { flushed: 0, skipped: 0, failed: 1 };
12185
- }
12186
- }
12187
-
12188
12291
  // src/services/upload-engine-logs.ts
12189
12292
  import { createReadStream as createReadStream2 } from "fs";
12190
12293
  import { readdir as readdir9, stat as stat5 } from "fs/promises";
@@ -13451,12 +13554,6 @@ var ChatService = class {
13451
13554
  }
13452
13555
  }
13453
13556
  async publishAgentTurnCompleteWebhook(chat, completedAt, processing = false) {
13454
- await Promise.all([
13455
- githubTokenManager.refreshOnce().catch(() => {
13456
- }),
13457
- gitlabTokenManager.refreshOnce().catch(() => {
13458
- })
13459
- ]);
13460
13557
  const linearSessionId = ENGINE_ENV.REPLICAS_LINEAR_SESSION_ID;
13461
13558
  const observedBranches = chat.observedBranchesByRepo;
13462
13559
  chat.observedBranchesByRepo = /* @__PURE__ */ new Map();
@@ -13465,20 +13562,12 @@ var ChatService = class {
13465
13562
  const errorNotificationTarget = chat.activeErrorNotificationTarget;
13466
13563
  chat.activeErrorNotificationTarget = null;
13467
13564
  let repoStatuses = [];
13468
- let repoStateComplete = false;
13469
13565
  try {
13470
- const refreshed = await gitService.refreshReposWithCompleteness(observedBranches, { includeDiffs: true });
13471
- repoStatuses = refreshed.repos;
13472
- repoStateComplete = refreshed.complete;
13566
+ repoStatuses = await refreshRepoStatuses(observedBranches, true);
13473
13567
  console.log(`Repository Statuses Refreshed: `, repoStatuses);
13474
13568
  } catch (error) {
13475
13569
  console.error("[ChatService] Failed to refresh repo statuses:", error);
13476
13570
  }
13477
- if (repoStateComplete) {
13478
- uploadRepoState(repoStatuses).catch((err) => {
13479
- console.error("[ChatService] Failed to upload repository state:", err);
13480
- });
13481
- }
13482
13571
  try {
13483
13572
  const payload = {
13484
13573
  ...linearSessionId ? { linearSessionId } : {},
@@ -14319,6 +14408,11 @@ function jsonError(message, details) {
14319
14408
  }
14320
14409
  function createV1Routes(deps) {
14321
14410
  const app2 = new Hono();
14411
+ app2.post("/pull-requests/check", (c) => {
14412
+ pullRequestCheckService.check().catch(() => {
14413
+ });
14414
+ return c.json({ accepted: true }, 202);
14415
+ });
14322
14416
  const loadChatHistory = async (c, chatId, page, failureMessage, unexpectedStatus, legacy = false) => {
14323
14417
  try {
14324
14418
  const history = await deps.chatService.getChatHistory(chatId, legacy && page.limit !== void 0 ? {} : page);
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ notifyPostToolUse
4
+ } from "./chunk-GDCMH7YA.js";
5
+ import {
6
+ isRecord
7
+ } from "./chunk-2RB7SIP3.js";
8
+ import "./chunk-WUJ4IALO.js";
9
+
10
+ // src/post-tool-pr-hook.ts
11
+ async function main() {
12
+ let stdin = "";
13
+ for await (const chunk of process.stdin) stdin += chunk;
14
+ try {
15
+ const input = JSON.parse(stdin);
16
+ const record = isRecord(input) ? input : {};
17
+ const toolName = [record.tool_name, record.tool, record.name].find((value) => typeof value === "string") ?? "";
18
+ const toolInput = record.tool_input ?? record.arguments ?? record.args ?? ("command" in record ? { command: record.command } : record);
19
+ await notifyPostToolUse(toolName, toolInput);
20
+ } catch (error) {
21
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
22
+ `);
23
+ }
24
+ process.stdout.write("{}\n");
25
+ }
26
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.703",
3
+ "version": "0.1.704",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -8,6 +8,7 @@
8
8
  "replicas-engine": "./dist/src/index.js",
9
9
  "replicas-headless-agent": "./dist/src/headless-agent.js",
10
10
  "replicas-command-protection-hook": "./dist/src/command-protection-hook.js",
11
+ "replicas-post-tool-pr-hook": "./dist/src/post-tool-pr-hook.js",
11
12
  "replicas-engine-watchdog": "./scripts/engine-watchdog.sh"
12
13
  },
13
14
  "files": [
@@ -14,6 +14,8 @@ interface ToolExecuteBeforeOutput {
14
14
  args: unknown;
15
15
  }
16
16
 
17
+ const toolInputs = new Map<string, unknown>();
18
+
17
19
  function evaluate(tool: string, args: unknown, callId: string, cwd: string): Promise<PolicyResult> {
18
20
  return new Promise((resolve) => {
19
21
  let settled = false;
@@ -65,5 +67,22 @@ export const ReplicasCommandProtection = async ({ directory }: { directory: stri
65
67
  'tool.execute.before': async (input: ToolExecuteBeforeInput, output: ToolExecuteBeforeOutput) => {
66
68
  const result = await evaluate(input.tool, output.args, input.callID, directory);
67
69
  if (!result.allowed) throw new Error(result.reason ?? 'Blocked by Replicas command protection.');
70
+ toolInputs.set(input.callID, output.args);
71
+ },
72
+ 'tool.execute.after': async (input: ToolExecuteBeforeInput) => {
73
+ const args = toolInputs.get(input.callID);
74
+ toolInputs.delete(input.callID);
75
+ const hookPath = process.env.REPLICAS_POST_TOOL_PR_HOOK_PATH;
76
+ const runtimePath = process.env.REPLICAS_COMMAND_PROTECTION_RUNTIME_PATH;
77
+ if (!hookPath || !runtimePath) return;
78
+ const hookArgs = hookPath.endsWith('.ts') && !runtimePath.endsWith('/bun')
79
+ ? ['--import', 'tsx', hookPath]
80
+ : [hookPath];
81
+ await new Promise<void>((resolve) => {
82
+ const child = spawn(runtimePath, hookArgs, { cwd: directory, stdio: ['pipe', 'ignore', 'ignore'] });
83
+ child.on('error', () => resolve());
84
+ child.on('exit', () => resolve());
85
+ child.stdin.end(JSON.stringify({ tool: input.tool, args }));
86
+ });
68
87
  },
69
88
  });