replicas-engine 0.1.451 → 0.1.452

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 +260 -68
  2. package/package.json +3 -1
package/dist/src/index.js CHANGED
@@ -43,10 +43,13 @@ function createErrorResult(error) {
43
43
  }
44
44
 
45
45
  // ../shared/src/agent.ts
46
- var VALID_AGENT_PROVIDERS = ["claude", "codex", "cursor", "opencode", "relay"];
46
+ var VALID_AGENT_PROVIDERS = ["claude", "codex", "cursor", "opencode", "pi", "relay"];
47
47
  var VALID_CODING_AGENT_PROVIDERS = VALID_AGENT_PROVIDERS.filter(
48
48
  (provider) => provider !== "relay"
49
49
  );
50
+ function isValidAgentProvider(value) {
51
+ return VALID_AGENT_PROVIDERS.some((p) => p === value);
52
+ }
50
53
  var VALID_THINKING_LEVELS = ["low", "medium", "high", "xhigh", "max"];
51
54
  var CODEX_REASONING_EFFORT_BY_THINKING_LEVEL = {
52
55
  low: "low",
@@ -166,6 +169,7 @@ var DEFAULT_CHAT_TITLES = {
166
169
  codex: "Codex",
167
170
  cursor: "Cursor",
168
171
  opencode: "Opencode",
172
+ pi: "Pi",
169
173
  relay: "Relay"
170
174
  };
171
175
  function isDefaultChat(chat) {
@@ -188,6 +192,13 @@ var GPT_5_6_LUNA_MODEL = "gpt-5.6-luna";
188
192
  var DEFAULT_CODEX_MODEL = GPT_5_6_SOL_MODEL;
189
193
  var DEFAULT_CURSOR_MODEL = "composer-2.5";
190
194
  var DEFAULT_OPENCODE_MODEL = "z-ai/glm-5.2";
195
+ var DEFAULT_PI_MODEL = DEFAULT_OPENCODE_MODEL;
196
+ var OPENROUTER_MODELS = [
197
+ DEFAULT_OPENCODE_MODEL,
198
+ "minimax/minimax-m3",
199
+ "xiaomi/mimo-v2.5-pro",
200
+ "moonshotai/kimi-k2.6"
201
+ ];
191
202
  function normalizeClaudeModel(model) {
192
203
  if (model === "opus" || model === CLAUDE_OPUS_1M_MODEL || model === LEGACY_CLAUDE_OPUS_1M_MODEL) {
193
204
  return DEFAULT_CLAUDE_MODEL;
@@ -256,7 +267,8 @@ var AGENT_MODELS = {
256
267
  CLAUDE_HAIKU_4_5_MODEL,
257
268
  "kimi-k2.5"
258
269
  ],
259
- opencode: [DEFAULT_OPENCODE_MODEL, "minimax/minimax-m3", "xiaomi/mimo-v2.5-pro", "moonshotai/kimi-k2.6"],
270
+ opencode: OPENROUTER_MODELS,
271
+ pi: OPENROUTER_MODELS,
260
272
  relay: [CLAUDE_FABLE_5_MODEL, DEFAULT_CLAUDE_MODEL, CLAUDE_SONNET_5_MODEL]
261
273
  };
262
274
  var MODEL_LABELS = {
@@ -516,7 +528,7 @@ var WORKSPACE_SIZES = ["small", "large"];
516
528
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
517
529
 
518
530
  // ../shared/src/e2b.ts
519
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-19-v3";
531
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-19-v4";
520
532
 
521
533
  // ../shared/src/runtime-env.ts
522
534
  function shellQuotePosix(value) {
@@ -672,7 +684,7 @@ var SLASH_COMMANDS = [
672
684
  command: "/plan",
673
685
  description: "Switch to plan mode and optionally send a prompt.",
674
686
  argumentHint: "[prompt]",
675
- providers: ["claude", "codex", "cursor", "opencode", "relay"]
687
+ providers: ["claude", "codex", "cursor", "opencode", "pi", "relay"]
676
688
  },
677
689
  {
678
690
  command: "/fast",
@@ -5012,6 +5024,9 @@ function detectCursorAuthMethod() {
5012
5024
  function detectOpencodeAuthMethod() {
5013
5025
  return existsSync3(OPENCODE_AUTH_PATH) || ENGINE_ENV.OPENROUTER_API_KEY ? "api_key" : "none";
5014
5026
  }
5027
+ function detectPiAuthMethod() {
5028
+ return ENGINE_ENV.OPENROUTER_API_KEY ? "api_key" : "none";
5029
+ }
5015
5030
  async function detectGitIdentityConfigured() {
5016
5031
  try {
5017
5032
  const { stdout } = await execFileAsync("git", ["config", "--global", "user.name"]);
@@ -5069,6 +5084,7 @@ function createDefaultDetails() {
5069
5084
  codexAuthMethod: "none",
5070
5085
  cursorAuthMethod: "none",
5071
5086
  opencodeAuthMethod: "none",
5087
+ piAuthMethod: "none",
5072
5088
  lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
5073
5089
  };
5074
5090
  }
@@ -5104,6 +5120,7 @@ var EnvironmentDetailsService = class {
5104
5120
  details.codexAuthMethod = detectCodexAuthMethod();
5105
5121
  details.cursorAuthMethod = detectCursorAuthMethod();
5106
5122
  details.opencodeAuthMethod = detectOpencodeAuthMethod();
5123
+ details.piAuthMethod = detectPiAuthMethod();
5107
5124
  details.gitIdentityConfigured = gitIdentityConfigured;
5108
5125
  const ghConfigured = existsSync3(GH_HOSTS_PATH);
5109
5126
  details.githubAccessConfigured = ghConfigured;
@@ -5854,9 +5871,9 @@ async function registerDesktopPreview() {
5854
5871
 
5855
5872
  // src/services/chat/chat-service.ts
5856
5873
  import { existsSync as existsSync7 } from "fs";
5857
- import { appendFile as appendFile3, copyFile, mkdir as mkdir13, readFile as readFile14, rename as rename2, rm as rm2 } from "fs/promises";
5874
+ import { appendFile as appendFile3, copyFile, mkdir as mkdir14, readFile as readFile14, rename as rename2, rm as rm2 } from "fs/promises";
5858
5875
  import { homedir as homedir15 } from "os";
5859
- import { join as join21 } from "path";
5876
+ import { join as join22 } from "path";
5860
5877
  import { randomUUID as randomUUID5 } from "crypto";
5861
5878
 
5862
5879
  // src/managers/claude-manager.ts
@@ -6576,16 +6593,16 @@ var CodingAgentManager = class {
6576
6593
  });
6577
6594
  }
6578
6595
  recordHistoryEvent(type, payload, historyFile) {
6579
- const eventPayload = {};
6596
+ const eventPayload2 = {};
6580
6597
  if (payload && typeof payload === "object") {
6581
- Object.assign(eventPayload, payload);
6598
+ Object.assign(eventPayload2, payload);
6582
6599
  } else {
6583
- eventPayload.value = payload;
6600
+ eventPayload2.value = payload;
6584
6601
  }
6585
6602
  const event = {
6586
6603
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6587
6604
  type,
6588
- payload: eventPayload
6605
+ payload: eventPayload2
6589
6606
  };
6590
6607
  this.onEvent(event);
6591
6608
  historyFile.append(event);
@@ -8569,7 +8586,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
8569
8586
  var MIN_CODEX_CLI_VERSION = "0.144.0";
8570
8587
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
8571
8588
  var codexCliVersionEnsured = null;
8572
- var ENGINE_PACKAGE_VERSION = "0.1.451";
8589
+ var ENGINE_PACKAGE_VERSION = "0.1.452";
8573
8590
  var INITIALIZE_METHOD = "initialize";
8574
8591
  var INITIALIZED_NOTIFICATION = "initialized";
8575
8592
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -9447,12 +9464,14 @@ var RELAY_HISTORY_DIR = join15(ENGINE_DIR2, "relay-histories");
9447
9464
  var CODEX_HISTORY_DIR = join15(ENGINE_DIR2, "codex-histories");
9448
9465
  var CURSOR_HISTORY_DIR = join15(ENGINE_DIR2, "cursor-histories");
9449
9466
  var OPENCODE_HISTORY_DIR = join15(ENGINE_DIR2, "opencode-histories");
9467
+ var PI_HISTORY_DIR = join15(ENGINE_DIR2, "pi-histories");
9450
9468
  var HISTORY_DIR_BY_PROVIDER = {
9451
9469
  claude: CLAUDE_HISTORY_DIR,
9452
9470
  relay: RELAY_HISTORY_DIR,
9453
9471
  codex: CODEX_HISTORY_DIR,
9454
9472
  cursor: CURSOR_HISTORY_DIR,
9455
- opencode: OPENCODE_HISTORY_DIR
9473
+ opencode: OPENCODE_HISTORY_DIR,
9474
+ pi: PI_HISTORY_DIR
9456
9475
  };
9457
9476
 
9458
9477
  // src/services/chat/errors.ts
@@ -11744,6 +11763,162 @@ var OpencodeManager = class extends CodingAgentManager {
11744
11763
  }
11745
11764
  };
11746
11765
 
11766
+ // src/managers/pi-manager.ts
11767
+ import { mkdir as mkdir13 } from "fs/promises";
11768
+ import { dirname as dirname7, join as join19 } from "path";
11769
+ import {
11770
+ AuthStorage,
11771
+ createAgentSession,
11772
+ ModelRegistry,
11773
+ SessionManager,
11774
+ DefaultResourceLoader
11775
+ } from "@mariozechner/pi-coding-agent";
11776
+ function eventPayload(event) {
11777
+ return isRecord4(event) ? { ...event } : { value: event };
11778
+ }
11779
+ function registerCommandProtection(cwd) {
11780
+ return (pi) => {
11781
+ pi.on("tool_call", async (event) => {
11782
+ const command = extractToolCommand(event.input);
11783
+ const result = await evaluateCommandProtection({
11784
+ provider: "pi",
11785
+ source: "pi_tool_call",
11786
+ toolName: event.toolName,
11787
+ toolInput: event.input,
11788
+ command,
11789
+ cwd,
11790
+ toolUseId: event.toolCallId
11791
+ });
11792
+ if (!result.allowed) {
11793
+ reportCommandProtectionBlock({ provider: "pi", toolName: event.toolName, command, reason: result.reason });
11794
+ return { block: true, reason: result.reason ?? "Command blocked by Replicas protection." };
11795
+ }
11796
+ return void 0;
11797
+ });
11798
+ };
11799
+ }
11800
+ var PiManager = class extends CodingAgentManager {
11801
+ session = null;
11802
+ unsubscribe = null;
11803
+ activeSessionFile = null;
11804
+ historyFilePath;
11805
+ historyFile;
11806
+ constructor(options) {
11807
+ super(options);
11808
+ this.historyFilePath = options.historyFilePath ?? join19(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
11809
+ this.historyFile = new CodexHistoryFile(this.historyFilePath);
11810
+ this.initializeManager(this.processMessageInternal.bind(this));
11811
+ }
11812
+ async initialize() {
11813
+ await mkdir13(dirname7(this.historyFilePath), { recursive: true });
11814
+ }
11815
+ async interruptActiveTurn() {
11816
+ await this.session?.abort();
11817
+ }
11818
+ async steerRequest(request) {
11819
+ if (!this.session?.isStreaming) return false;
11820
+ await this.session.steer(request.message);
11821
+ return true;
11822
+ }
11823
+ async getHistory() {
11824
+ await this.historyFile.flush();
11825
+ const history = await this.historyFile.load();
11826
+ return { thread_id: this.activeSessionFile ?? this.initialSessionId, events: history.events, goal: null };
11827
+ }
11828
+ async listSlashCommands() {
11829
+ await this.initialized;
11830
+ return mergeSlashCommands(...(this.session?.promptTemplates ?? []).map((template) => createProviderSlashCommand("pi", template.name, template.description)).filter((command) => Boolean(command)));
11831
+ }
11832
+ dispose() {
11833
+ this.unsubscribe?.();
11834
+ this.unsubscribe = null;
11835
+ this.session?.dispose();
11836
+ this.session = null;
11837
+ }
11838
+ async ensureSession(request) {
11839
+ if (this.session) {
11840
+ if (request.model && request.model !== this.session.model?.id) {
11841
+ const model2 = this.session.modelRegistry.find("openrouter", request.model);
11842
+ if (model2) await this.session.setModel(model2);
11843
+ }
11844
+ if (request.thinkingLevel) this.session.setThinkingLevel(request.thinkingLevel);
11845
+ return this.session;
11846
+ }
11847
+ const apiKey = ENGINE_ENV.OPENROUTER_API_KEY;
11848
+ if (!apiKey) throw new Error("OpenRouter API key is not configured for Pi.");
11849
+ const authStorage = AuthStorage.inMemory({ openrouter: { type: "api_key", key: apiKey } });
11850
+ const modelRegistry = ModelRegistry.create(authStorage);
11851
+ const openRouterModels = modelRegistry.getAll().filter((candidate) => candidate.provider === "openrouter");
11852
+ const modelTemplate = openRouterModels[0];
11853
+ if (!modelTemplate) throw new Error("Pi OpenRouter model catalog is empty.");
11854
+ modelRegistry.registerProvider("openrouter", {
11855
+ api: modelTemplate.api,
11856
+ apiKey,
11857
+ baseUrl: modelTemplate.baseUrl,
11858
+ models: [
11859
+ ...openRouterModels,
11860
+ ...OPENROUTER_MODELS.filter((id) => !openRouterModels.some((candidate) => candidate.id === id)).map((id) => ({
11861
+ id,
11862
+ name: id,
11863
+ api: modelTemplate.api,
11864
+ baseUrl: modelTemplate.baseUrl,
11865
+ reasoning: modelTemplate.reasoning,
11866
+ input: modelTemplate.input,
11867
+ cost: modelTemplate.cost,
11868
+ contextWindow: modelTemplate.contextWindow,
11869
+ maxTokens: modelTemplate.maxTokens
11870
+ }))
11871
+ ]
11872
+ });
11873
+ const modelId = request.model ?? DEFAULT_PI_MODEL;
11874
+ const model = modelRegistry.find("openrouter", modelId) ?? modelRegistry.find("openrouter", DEFAULT_PI_MODEL);
11875
+ if (!model) throw new Error(`Pi model is not available through OpenRouter: ${modelId}`);
11876
+ const sessionManager = this.initialSessionId ? SessionManager.open(this.initialSessionId, PI_HISTORY_DIR, this.workingDirectory) : SessionManager.create(this.workingDirectory, PI_HISTORY_DIR);
11877
+ const resourceLoader = new DefaultResourceLoader({
11878
+ cwd: this.workingDirectory,
11879
+ agentDir: join19(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
11880
+ extensionFactories: [registerCommandProtection(this.workingDirectory)],
11881
+ appendSystemPrompt: [this.buildCombinedInstructions() ?? ""]
11882
+ });
11883
+ await resourceLoader.reload();
11884
+ const result = await createAgentSession({
11885
+ cwd: this.workingDirectory,
11886
+ authStorage,
11887
+ modelRegistry,
11888
+ model,
11889
+ sessionManager,
11890
+ resourceLoader
11891
+ });
11892
+ this.session = result.session;
11893
+ if (request.thinkingLevel) this.session.setThinkingLevel(request.thinkingLevel);
11894
+ this.activeSessionFile = sessionManager.getSessionFile() ?? null;
11895
+ await this.onSaveSessionId(this.activeSessionFile);
11896
+ this.unsubscribe = this.session.subscribe((event) => this.handleEvent(event));
11897
+ return this.session;
11898
+ }
11899
+ handleEvent(event) {
11900
+ const payload = eventPayload(event);
11901
+ this.recordHistoryEvent(`pi-${event.type}`, payload, this.historyFile);
11902
+ }
11903
+ async processMessageInternal(request) {
11904
+ const session = await this.ensureSession(request);
11905
+ this.recordHistoryEvent("event_msg", { type: "user_message", message: request.message }, this.historyFile);
11906
+ try {
11907
+ const images = request.images && request.images.length > 0 ? (await normalizeImages(request.images)).map((image) => ({
11908
+ type: "image",
11909
+ data: image.source.data,
11910
+ mimeType: image.source.media_type
11911
+ })) : void 0;
11912
+ await session.prompt(request.message, images ? { images } : void 0);
11913
+ } catch (error) {
11914
+ this.recordHistoryEvent("pi-error", { message: error instanceof Error ? error.message : String(error) }, this.historyFile);
11915
+ throw error;
11916
+ } finally {
11917
+ await this.onTurnComplete();
11918
+ }
11919
+ }
11920
+ };
11921
+
11747
11922
  // src/managers/relay-tools.ts
11748
11923
  import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
11749
11924
  import { z } from "zod";
@@ -11753,15 +11928,17 @@ function getAvailableRelayProviders(availability) {
11753
11928
  const codexAvailable = availability.codexAvailable ?? false;
11754
11929
  const cursorAvailable = availability.cursorAvailable ?? false;
11755
11930
  const opencodeAvailable = availability.opencodeAvailable ?? false;
11931
+ const piAvailable = availability.piAvailable ?? false;
11756
11932
  const providers = ["claude"];
11757
11933
  if (codexAvailable) providers.push("codex");
11758
11934
  if (cursorAvailable) providers.push("cursor");
11759
11935
  if (opencodeAvailable) providers.push("opencode");
11936
+ if (piAvailable) providers.push("pi");
11760
11937
  providers.push("relay");
11761
11938
  return providers;
11762
11939
  }
11763
11940
  function getAvailableCodeProviders(availability) {
11764
- return getAvailableRelayProviders(availability).filter((provider) => provider === "codex" || provider === "cursor" || provider === "opencode");
11941
+ return getAvailableRelayProviders(availability).filter((provider) => provider === "codex" || provider === "cursor" || provider === "opencode" || provider === "pi");
11765
11942
  }
11766
11943
 
11767
11944
  // src/managers/relay-tools.ts
@@ -12115,13 +12292,14 @@ function getUsingToolsSection() {
12115
12292
  ];
12116
12293
  return [`# Using your tools`, ...prependBullets(items)].join("\n");
12117
12294
  }
12118
- function getDelegationSection(codexAvailable, cursorAvailable, opencodeAvailable) {
12119
- const providerList = getAvailableRelayProviders({ codexAvailable, cursorAvailable, opencodeAvailable }).join(", ");
12295
+ function getDelegationSection(codexAvailable, cursorAvailable, opencodeAvailable, piAvailable) {
12296
+ const providerList = getAvailableRelayProviders({ codexAvailable, cursorAvailable, opencodeAvailable, piAvailable }).join(", ");
12120
12297
  const spawnDesc = `Create a new subagent with a specific provider (${providerList}), send it a prompt, and wait for its response. Returns the chatId and the agent's final response. You can set a custom timeout via the timeout_minutes parameter (default: 10 minutes).`;
12121
12298
  const claudeModelList = AGENT_MODELS.claude.join(", ");
12122
12299
  const extraAgentLines = [
12123
12300
  codexAvailable ? `Use provider 'codex' for heavy code writing, implementation, and large refactors. Suggested models: ${AGENT_MODELS.codex.join(", ")} (${AGENT_MODELS.codex[0]} is the default).` : null,
12124
12301
  opencodeAvailable ? `Use provider 'opencode' for cheaper routine implementation tasks through OpenRouter-backed open source models. Suggested models: ${AGENT_MODELS.opencode.join(", ")}.` : null,
12302
+ piAvailable ? `Use provider 'pi' for coding tasks through Pi's OpenRouter-backed coding agent. Suggested models: ${AGENT_MODELS.pi.join(", ")}.` : null,
12125
12303
  cursorAvailable ? `Use provider 'cursor' for fast iteration on code changes. Suggested models: ${AGENT_MODELS.cursor.join(", ")}.` : null
12126
12304
  ].filter(Boolean);
12127
12305
  const agentSelectionLines = extraAgentLines.length > 0 ? `${extraAgentLines.join("\n\n")}
@@ -12244,14 +12422,14 @@ function getEnvironmentSection() {
12244
12422
  ].join("\n");
12245
12423
  }
12246
12424
  function buildRelaySystemPrompt(options) {
12247
- const { customInstructions, codexAvailable, cursorAvailable, opencodeAvailable } = options ?? {};
12425
+ const { customInstructions, codexAvailable, cursorAvailable, opencodeAvailable, piAvailable } = options ?? {};
12248
12426
  const sections = [
12249
12427
  getIntroSection(),
12250
12428
  getSystemSection(),
12251
12429
  getDoingTasksSection(),
12252
12430
  getActionsSection(),
12253
12431
  getUsingToolsSection(),
12254
- getDelegationSection(codexAvailable ?? false, cursorAvailable ?? false, opencodeAvailable ?? false),
12432
+ getDelegationSection(codexAvailable ?? false, cursorAvailable ?? false, opencodeAvailable ?? false, piAvailable ?? false),
12255
12433
  getToneAndStyleSection(),
12256
12434
  getOutputEfficiencySection(),
12257
12435
  getEnvironmentSection(),
@@ -12284,12 +12462,13 @@ var RelayManager = class {
12284
12462
  const codexAvailable = options.codexAvailable ?? false;
12285
12463
  const cursorAvailable = options.cursorAvailable ?? false;
12286
12464
  const opencodeAvailable = options.opencodeAvailable ?? false;
12465
+ const piAvailable = options.piAvailable ?? false;
12287
12466
  this.inner = new ClaudeManager({
12288
12467
  ...options,
12289
- systemPromptOverride: (customInstructions) => buildRelaySystemPrompt({ customInstructions, codexAvailable, cursorAvailable, opencodeAvailable }),
12468
+ systemPromptOverride: (customInstructions) => buildRelaySystemPrompt({ customInstructions, codexAvailable, cursorAvailable, opencodeAvailable, piAvailable }),
12290
12469
  tools: RELAY_TOOLS,
12291
12470
  mcpServers: {
12292
- "relay-subagent-tools": createRelayMcpServer(options.chatId, { codexAvailable, cursorAvailable, opencodeAvailable })
12471
+ "relay-subagent-tools": createRelayMcpServer(options.chatId, { codexAvailable, cursorAvailable, opencodeAvailable, piAvailable })
12293
12472
  },
12294
12473
  envOverrides: {
12295
12474
  CLAUDE_CODE_STREAM_CLOSE_TIMEOUT: "900000"
@@ -12381,17 +12560,17 @@ var keepAliveService = new KeepAliveService();
12381
12560
  // src/services/canvas-service.ts
12382
12561
  import { readdir as readdir6, readFile as readFile12, stat as stat3 } from "fs/promises";
12383
12562
  import { homedir as homedir13 } from "os";
12384
- import { join as join19 } from "path";
12563
+ import { join as join20 } from "path";
12385
12564
  var GLOBAL_CANVAS_DIRECTORIES = [
12386
- join19(homedir13(), ".claude", "plans"),
12387
- join19(process.env.XDG_DATA_HOME ?? join19(homedir13(), ".local", "share"), "opencode", "plans"),
12388
- join19(homedir13(), ".replicas", "canvas")
12565
+ join20(homedir13(), ".claude", "plans"),
12566
+ join20(process.env.XDG_DATA_HOME ?? join20(homedir13(), ".local", "share"), "opencode", "plans"),
12567
+ join20(homedir13(), ".replicas", "canvas")
12389
12568
  ];
12390
12569
  async function canvasDirectories() {
12391
12570
  const repositories = await gitService.listRepositories().catch(() => []);
12392
12571
  return [
12393
12572
  ...GLOBAL_CANVAS_DIRECTORIES,
12394
- ...repositories.map((repository) => join19(repository.path, ".opencode", "plans"))
12573
+ ...repositories.map((repository) => join20(repository.path, ".opencode", "plans"))
12395
12574
  ];
12396
12575
  }
12397
12576
  var CanvasService = class {
@@ -12411,7 +12590,7 @@ var CanvasService = class {
12411
12590
  const { kind } = classifyCanvasFilename(entry.name);
12412
12591
  let sizeBytes = 0;
12413
12592
  try {
12414
- const s = await stat3(join19(directory, entry.name));
12593
+ const s = await stat3(join20(directory, entry.name));
12415
12594
  sizeBytes = s.size;
12416
12595
  } catch {
12417
12596
  continue;
@@ -12426,7 +12605,7 @@ var CanvasService = class {
12426
12605
  if (!safe) return null;
12427
12606
  const { kind, mimeType } = classifyCanvasFilename(safe);
12428
12607
  for (const directory of await canvasDirectories()) {
12429
- const filePath = join19(directory, safe);
12608
+ const filePath = join20(directory, safe);
12430
12609
  let sizeBytes = 0;
12431
12610
  let updatedAt = "";
12432
12611
  try {
@@ -12563,13 +12742,13 @@ async function reconcileCanvasItems(filenames) {
12563
12742
 
12564
12743
  // src/services/upload-chat-transcripts.ts
12565
12744
  import { readdir as readdir7, readFile as readFile13 } from "fs/promises";
12566
- import { basename as basename2, join as join20 } from "path";
12745
+ import { basename as basename2, join as join21 } from "path";
12567
12746
  import { homedir as homedir14 } from "os";
12568
- var ENGINE_DIR3 = join20(homedir14(), ".replicas", "engine");
12747
+ var ENGINE_DIR3 = join21(homedir14(), ".replicas", "engine");
12569
12748
  var HISTORY_DIRS = [
12570
- join20(ENGINE_DIR3, "claude-histories"),
12571
- join20(ENGINE_DIR3, "relay-histories"),
12572
- join20(ENGINE_DIR3, "codex-histories")
12749
+ join21(ENGINE_DIR3, "claude-histories"),
12750
+ join21(ENGINE_DIR3, "relay-histories"),
12751
+ join21(ENGINE_DIR3, "codex-histories")
12573
12752
  ];
12574
12753
  async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
12575
12754
  let flushed = 0;
@@ -12586,7 +12765,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
12586
12765
  if (!entry.endsWith(".jsonl")) continue;
12587
12766
  const chatId = basename2(entry, ".jsonl");
12588
12767
  tasks.push(
12589
- uploadChatTranscript(chatId, join20(dir, entry), chatsById.get(chatId)).then(() => {
12768
+ uploadChatTranscript(chatId, join21(dir, entry), chatsById.get(chatId)).then(() => {
12590
12769
  flushed++;
12591
12770
  }).catch((err) => {
12592
12771
  failed++;
@@ -12665,9 +12844,9 @@ async function flushRepoState() {
12665
12844
  }
12666
12845
 
12667
12846
  // src/services/chat/chat-service.ts
12668
- var CHAT_SENDERS_DIR = join21(ENGINE_DIR2, "chat-senders");
12669
- var CODEX_AUTH_PATH2 = join21(homedir15(), ".codex", "auth.json");
12670
- var OPENCODE_AUTH_PATH2 = join21(homedir15(), ".local", "share", "opencode", "auth.json");
12847
+ var CHAT_SENDERS_DIR = join22(ENGINE_DIR2, "chat-senders");
12848
+ var CODEX_AUTH_PATH2 = join22(homedir15(), ".codex", "auth.json");
12849
+ var OPENCODE_AUTH_PATH2 = join22(homedir15(), ".local", "share", "opencode", "auth.json");
12671
12850
  var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
12672
12851
  function isChatMessageSender(value) {
12673
12852
  if (!isRecord4(value)) return false;
@@ -12679,6 +12858,9 @@ function isCodexAvailable() {
12679
12858
  function isOpencodeAvailable() {
12680
12859
  return existsSync7(OPENCODE_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENROUTER_API_KEY);
12681
12860
  }
12861
+ function isPiAvailable() {
12862
+ return Boolean(ENGINE_ENV.OPENROUTER_API_KEY);
12863
+ }
12682
12864
  function isCursorAvailable() {
12683
12865
  return Boolean(ENGINE_ENV.CURSOR_API_KEY);
12684
12866
  }
@@ -12727,7 +12909,7 @@ function isPersistedChat(value) {
12727
12909
  return false;
12728
12910
  }
12729
12911
  const candidate = value;
12730
- return typeof candidate.id === "string" && (candidate.provider === "claude" || candidate.provider === "codex" || candidate.provider === "cursor" || candidate.provider === "opencode" || candidate.provider === "relay") && typeof candidate.title === "string" && typeof candidate.createdAt === "string" && typeof candidate.updatedAt === "string" && (candidate.providerSessionId === null || typeof candidate.providerSessionId === "string") && (candidate.parentChatId === void 0 || candidate.parentChatId === null || typeof candidate.parentChatId === "string") && (candidate.deletedAt === void 0 || candidate.deletedAt === null || typeof candidate.deletedAt === "string");
12912
+ return typeof candidate.id === "string" && typeof candidate.provider === "string" && isValidAgentProvider(candidate.provider) && typeof candidate.title === "string" && typeof candidate.createdAt === "string" && typeof candidate.updatedAt === "string" && (candidate.providerSessionId === null || typeof candidate.providerSessionId === "string") && (candidate.parentChatId === void 0 || candidate.parentChatId === null || typeof candidate.parentChatId === "string") && (candidate.deletedAt === void 0 || candidate.deletedAt === null || typeof candidate.deletedAt === "string");
12731
12913
  }
12732
12914
  function normalizePersistedChat(chat) {
12733
12915
  const isLegacyCodexSdkChat = chat.provider === "codex" && (chat.codexBackend === "sdk" || chat.codexBackend === void 0 && chat.providerSessionId !== null);
@@ -12779,13 +12961,13 @@ var ChatService = class {
12779
12961
  persistInFlight = false;
12780
12962
  persistQueued = false;
12781
12963
  async initialize() {
12782
- await mkdir13(ENGINE_DIR2, { recursive: true });
12783
- await mkdir13(CLAUDE_HISTORY_DIR, { recursive: true });
12784
- await mkdir13(RELAY_HISTORY_DIR, { recursive: true });
12785
- await mkdir13(CODEX_HISTORY_DIR, { recursive: true });
12786
- await mkdir13(CURSOR_HISTORY_DIR, { recursive: true });
12787
- await mkdir13(OPENCODE_HISTORY_DIR, { recursive: true });
12788
- await mkdir13(CHAT_SENDERS_DIR, { recursive: true });
12964
+ await mkdir14(ENGINE_DIR2, { recursive: true });
12965
+ await mkdir14(CLAUDE_HISTORY_DIR, { recursive: true });
12966
+ await mkdir14(RELAY_HISTORY_DIR, { recursive: true });
12967
+ await mkdir14(CODEX_HISTORY_DIR, { recursive: true });
12968
+ await mkdir14(CURSOR_HISTORY_DIR, { recursive: true });
12969
+ await mkdir14(OPENCODE_HISTORY_DIR, { recursive: true });
12970
+ await mkdir14(CHAT_SENDERS_DIR, { recursive: true });
12789
12971
  const persisted = await this.loadChats();
12790
12972
  for (const chat of persisted) {
12791
12973
  const runtime = this.createRuntimeChat(chat);
@@ -12909,7 +13091,7 @@ var ChatService = class {
12909
13091
  };
12910
13092
  }
12911
13093
  senderFilePath(chatId) {
12912
- return join21(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
13094
+ return join22(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
12913
13095
  }
12914
13096
  async appendSender(chatId, sender) {
12915
13097
  try {
@@ -13103,7 +13285,7 @@ var ChatService = class {
13103
13285
  return descendants;
13104
13286
  }
13105
13287
  async deleteHistoryFile(persisted) {
13106
- await rm2(join21(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
13288
+ await rm2(join22(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
13107
13289
  await rm2(this.senderFilePath(persisted.id), { force: true });
13108
13290
  }
13109
13291
  async getChatHistory(chatId, page = {}) {
@@ -13177,7 +13359,7 @@ var ChatService = class {
13177
13359
  if (persisted.provider === "claude") {
13178
13360
  provider = new ClaudeManager({
13179
13361
  workingDirectory: this.workingDirectory,
13180
- historyFilePath: join21(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
13362
+ historyFilePath: join22(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
13181
13363
  initialSessionId: persisted.providerSessionId,
13182
13364
  onSaveSessionId: saveSession,
13183
13365
  onTurnComplete: onProviderTurnComplete,
@@ -13186,7 +13368,7 @@ var ChatService = class {
13186
13368
  } else if (persisted.provider === "relay") {
13187
13369
  provider = new RelayManager({
13188
13370
  workingDirectory: this.workingDirectory,
13189
- historyFilePath: join21(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
13371
+ historyFilePath: join22(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
13190
13372
  initialSessionId: persisted.providerSessionId,
13191
13373
  onSaveSessionId: saveSession,
13192
13374
  onTurnComplete: onProviderTurnComplete,
@@ -13194,12 +13376,13 @@ var ChatService = class {
13194
13376
  chatId: persisted.id,
13195
13377
  codexAvailable: isCodexAvailable(),
13196
13378
  opencodeAvailable: isOpencodeAvailable(),
13379
+ piAvailable: isPiAvailable(),
13197
13380
  cursorAvailable: isCursorAvailable()
13198
13381
  });
13199
13382
  } else if (persisted.provider === "cursor") {
13200
13383
  provider = new CursorManager({
13201
13384
  workingDirectory: this.workingDirectory,
13202
- historyFilePath: join21(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
13385
+ historyFilePath: join22(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
13203
13386
  initialSessionId: persisted.providerSessionId,
13204
13387
  onSaveSessionId: saveSession,
13205
13388
  onTurnComplete: onProviderTurnComplete,
@@ -13208,7 +13391,16 @@ var ChatService = class {
13208
13391
  } else if (persisted.provider === "opencode") {
13209
13392
  provider = new OpencodeManager({
13210
13393
  workingDirectory: this.workingDirectory,
13211
- historyFilePath: join21(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
13394
+ historyFilePath: join22(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
13395
+ initialSessionId: persisted.providerSessionId,
13396
+ onSaveSessionId: saveSession,
13397
+ onTurnComplete: onProviderTurnComplete,
13398
+ onEvent: onProviderEvent
13399
+ });
13400
+ } else if (persisted.provider === "pi") {
13401
+ provider = new PiManager({
13402
+ workingDirectory: this.workingDirectory,
13403
+ historyFilePath: join22(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
13212
13404
  initialSessionId: persisted.providerSessionId,
13213
13405
  onSaveSessionId: saveSession,
13214
13406
  onTurnComplete: onProviderTurnComplete,
@@ -13217,7 +13409,7 @@ var ChatService = class {
13217
13409
  } else {
13218
13410
  provider = new CodexAspManager({
13219
13411
  workingDirectory: this.workingDirectory,
13220
- historyFilePath: join21(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
13412
+ historyFilePath: join22(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
13221
13413
  initialSessionId: persisted.providerSessionId,
13222
13414
  onSaveSessionId: saveSession,
13223
13415
  onTurnComplete: onProviderTurnComplete,
@@ -13357,7 +13549,7 @@ var ChatService = class {
13357
13549
  });
13358
13550
  uploadChatTranscript(
13359
13551
  chatId,
13360
- join21(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
13552
+ join22(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
13361
13553
  this.toSummary(chat)
13362
13554
  ).catch((err) => {
13363
13555
  console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
@@ -13484,7 +13676,7 @@ var ChatService = class {
13484
13676
  // src/services/repo-file-service.ts
13485
13677
  import { execFile as execFile2 } from "child_process";
13486
13678
  import { readFile as readFile15, realpath, stat as stat4 } from "fs/promises";
13487
- import { join as join22, resolve as resolve2, extname as extname2 } from "path";
13679
+ import { join as join23, resolve as resolve2, extname as extname2 } from "path";
13488
13680
  var CACHE_TTL_MS = 3e4;
13489
13681
  var SEARCH_TIMEOUT_MS = 15e3;
13490
13682
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -13644,7 +13836,7 @@ var RepoFileService = class {
13644
13836
  const repo = repos.find((r) => r.name === repoName);
13645
13837
  if (!repo) return null;
13646
13838
  try {
13647
- const fullPath = await realpath(resolve2(join22(repo.path, filePath)));
13839
+ const fullPath = await realpath(resolve2(join23(repo.path, filePath)));
13648
13840
  const repoRoot = await realpath(repo.path);
13649
13841
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
13650
13842
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
@@ -13752,20 +13944,20 @@ var RepoFileService = class {
13752
13944
  import { Hono } from "hono";
13753
13945
  import { z as z2 } from "zod";
13754
13946
  import { readdir as readdir9, stat as stat5, readFile as readFile18 } from "fs/promises";
13755
- import { join as join25, resolve as resolve3 } from "path";
13947
+ import { join as join26, resolve as resolve3 } from "path";
13756
13948
 
13757
13949
  // src/services/warm-hooks-service.ts
13758
13950
  import { spawn as spawn4 } from "child_process";
13759
13951
  import { readFile as readFile17 } from "fs/promises";
13760
13952
  import { existsSync as existsSync8 } from "fs";
13761
- import { join as join24 } from "path";
13953
+ import { join as join25 } from "path";
13762
13954
 
13763
13955
  // src/services/warm-hook-logs-service.ts
13764
- import { mkdir as mkdir14, readFile as readFile16, writeFile as writeFile6, readdir as readdir8, appendFile as appendFile4, unlink as unlink3 } from "fs/promises";
13956
+ import { mkdir as mkdir15, readFile as readFile16, writeFile as writeFile6, readdir as readdir8, appendFile as appendFile4, unlink as unlink3 } from "fs/promises";
13765
13957
  import { homedir as homedir16 } from "os";
13766
- import { join as join23 } from "path";
13767
- var LOGS_DIR2 = join23(homedir16(), ".replicas", "warm-hook-logs");
13768
- var CURRENT_RUN_LOG = join23(LOGS_DIR2, "current-run.log");
13958
+ import { join as join24 } from "path";
13959
+ var LOGS_DIR2 = join24(homedir16(), ".replicas", "warm-hook-logs");
13960
+ var CURRENT_RUN_LOG = join24(LOGS_DIR2, "current-run.log");
13769
13961
  var GLOBAL_FILENAME = "global.json";
13770
13962
  function withPreview2(stored) {
13771
13963
  const preview = buildHookOutputPreview(stored.output);
@@ -13773,7 +13965,7 @@ function withPreview2(stored) {
13773
13965
  }
13774
13966
  var WarmHookLogsService = class {
13775
13967
  async ensureDir() {
13776
- await mkdir14(LOGS_DIR2, { recursive: true });
13968
+ await mkdir15(LOGS_DIR2, { recursive: true });
13777
13969
  }
13778
13970
  async saveGlobalHookLog(entry) {
13779
13971
  await this.ensureDir();
@@ -13782,7 +13974,7 @@ var WarmHookLogsService = class {
13782
13974
  hookName: "organization",
13783
13975
  ...entry
13784
13976
  };
13785
- await writeFile6(join23(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
13977
+ await writeFile6(join24(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
13786
13978
  `, "utf-8");
13787
13979
  }
13788
13980
  async saveEnvironmentHookLog(entry) {
@@ -13792,7 +13984,7 @@ var WarmHookLogsService = class {
13792
13984
  hookName: "environment",
13793
13985
  ...entry
13794
13986
  };
13795
- await writeFile6(join23(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
13987
+ await writeFile6(join24(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
13796
13988
  `, "utf-8");
13797
13989
  }
13798
13990
  async saveRepoHookLog(repoName, entry) {
@@ -13802,7 +13994,7 @@ var WarmHookLogsService = class {
13802
13994
  hookName: repoName,
13803
13995
  ...entry
13804
13996
  };
13805
- await writeFile6(join23(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
13997
+ await writeFile6(join24(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
13806
13998
  `, "utf-8");
13807
13999
  }
13808
14000
  async getAllLogs() {
@@ -13821,7 +14013,7 @@ var WarmHookLogsService = class {
13821
14013
  continue;
13822
14014
  }
13823
14015
  try {
13824
- const raw = await readFile16(join23(LOGS_DIR2, file), "utf-8");
14016
+ const raw = await readFile16(join24(LOGS_DIR2, file), "utf-8");
13825
14017
  const stored = JSON.parse(raw);
13826
14018
  logs.push(withPreview2(stored));
13827
14019
  } catch {
@@ -13859,7 +14051,7 @@ var WarmHookLogsService = class {
13859
14051
  async getFullOutput(hookType, hookName) {
13860
14052
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
13861
14053
  try {
13862
- const raw = await readFile16(join23(LOGS_DIR2, filename), "utf-8");
14054
+ const raw = await readFile16(join24(LOGS_DIR2, filename), "utf-8");
13863
14055
  const stored = JSON.parse(raw);
13864
14056
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
13865
14057
  return null;
@@ -13878,7 +14070,7 @@ var warmHookLogsService = new WarmHookLogsService();
13878
14070
  // src/services/warm-hooks-service.ts
13879
14071
  async function readRepoWarmHook(repoPath) {
13880
14072
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
13881
- const configPath = join24(repoPath, filename);
14073
+ const configPath = join25(repoPath, filename);
13882
14074
  if (!existsSync8(configPath)) {
13883
14075
  continue;
13884
14076
  }
@@ -14262,7 +14454,7 @@ var setWorkspaceNameSchema = z2.object({
14262
14454
  name: z2.string().min(1).max(48)
14263
14455
  });
14264
14456
  var createChatSchema = z2.object({
14265
- provider: z2.enum(["claude", "codex", "cursor", "opencode", "relay"]),
14457
+ provider: z2.enum(["claude", "codex", "cursor", "opencode", "pi", "relay"]),
14266
14458
  title: z2.string().min(1).optional(),
14267
14459
  parentChatId: z2.string().uuid().optional(),
14268
14460
  clientRequestId: z2.string().min(1).max(128).optional()
@@ -15100,7 +15292,7 @@ data: ${JSON.stringify("Terminal session not found")}
15100
15292
  const logFiles = files.filter((f) => f.endsWith(".log"));
15101
15293
  const sessions = await Promise.all(
15102
15294
  logFiles.map(async (filename) => {
15103
- const filePath = join25(LOG_DIR, filename);
15295
+ const filePath = join26(LOG_DIR, filename);
15104
15296
  const fileStat = await stat5(filePath);
15105
15297
  const sessionId = filename.replace(/\.log$/, "");
15106
15298
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.451",
3
+ "version": "0.1.452",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -35,6 +35,8 @@
35
35
  "@connectrpc/connect-node": "1.7.0",
36
36
  "@cursor/sdk": "1.0.19",
37
37
  "@hono/node-server": "^1.19.5",
38
+ "@mariozechner/pi-ai": "0.73.1",
39
+ "@mariozechner/pi-coding-agent": "0.73.1",
38
40
  "@opencode-ai/sdk": "1.17.9",
39
41
  "hono": "^4.10.3",
40
42
  "opencode-ai": "1.17.9",