replicas-cli 0.2.454 → 0.2.456

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/index.mjs +501 -156
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -7730,29 +7730,51 @@ function getClaudePartialMessageStreamId(event) {
7730
7730
  if (event.type !== CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE) return null;
7731
7731
  return typeof event.payload.streamId === "string" && event.payload.streamId ? event.payload.streamId : null;
7732
7732
  }
7733
- function getEventSignature(event) {
7734
- const streamId = getClaudePartialMessageStreamId(event);
7735
- if (streamId) {
7736
- return `${CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE}:${streamId}`;
7737
- }
7738
- const normalize = (input) => {
7739
- if (Array.isArray(input)) return input.map(normalize);
7740
- if (input && typeof input === "object") {
7741
- const entries = Object.entries(input).sort(([a], [b]) => a.localeCompare(b)).map(([key, val]) => [key, normalize(val)]);
7742
- return Object.fromEntries(entries);
7743
- }
7744
- return input;
7745
- };
7746
- try {
7747
- return JSON.stringify(normalize(event));
7748
- } catch {
7749
- return String(event);
7750
- }
7751
- }
7733
+ var ACCEPTED_USER_MESSAGE_SOURCE = "replicas-chat-turn-accepted";
7752
7734
  var USER_MESSAGE_ID_PAYLOAD_KEY = "replicasMessageId";
7753
7735
  var CODEX_ASP_ITEM_ID_PAYLOAD_KEY = "codexAspItemId";
7736
+ var CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE = "codex-asp-transcript-updated";
7754
7737
  var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
7755
7738
  var CHAT_INTERRUPTED_EVENT_TYPE = "replicas-interrupted";
7739
+ var CHAT_GOAL_EVENT_TYPE = "chat-goal";
7740
+ var CHAT_GOAL_STATUSES = [
7741
+ "active",
7742
+ "paused",
7743
+ "blocked",
7744
+ "usageLimited",
7745
+ "budgetLimited",
7746
+ "complete"
7747
+ ];
7748
+ function isChatGoalStatus(value) {
7749
+ return typeof value === "string" && CHAT_GOAL_STATUSES.some((status) => status === value);
7750
+ }
7751
+ function coerceChatGoalPayload(payload) {
7752
+ const goal = payload.goal;
7753
+ if (goal === null || goal === void 0) return null;
7754
+ if (typeof goal !== "object") return null;
7755
+ const candidate = goal;
7756
+ const threadId = candidate.threadId;
7757
+ const objective = candidate.objective;
7758
+ const status = candidate.status;
7759
+ const tokensUsed = candidate.tokensUsed;
7760
+ const timeUsedSeconds = candidate.timeUsedSeconds;
7761
+ const createdAt = candidate.createdAt;
7762
+ const updatedAt = candidate.updatedAt;
7763
+ const tokenBudget = candidate.tokenBudget ?? null;
7764
+ if (typeof threadId !== "string" || typeof objective !== "string" || !isChatGoalStatus(status) || typeof tokensUsed !== "number" || typeof timeUsedSeconds !== "number" || typeof createdAt !== "string" || typeof updatedAt !== "string" || tokenBudget !== null && typeof tokenBudget !== "number") {
7765
+ return null;
7766
+ }
7767
+ return {
7768
+ threadId,
7769
+ objective,
7770
+ status,
7771
+ tokenBudget,
7772
+ tokensUsed,
7773
+ timeUsedSeconds,
7774
+ createdAt,
7775
+ updatedAt
7776
+ };
7777
+ }
7756
7778
 
7757
7779
  // ../shared/src/credentials/types.ts
7758
7780
  var CREDENTIAL_SCOPE = {
@@ -10056,7 +10078,7 @@ function formatTurnElapsed(ms) {
10056
10078
  }
10057
10079
 
10058
10080
  // ../shared/src/cli-version.ts
10059
- var CLI_VERSION = "0.2.454";
10081
+ var CLI_VERSION = "0.2.456";
10060
10082
 
10061
10083
  // ../shared/src/version.ts
10062
10084
  function compareVersions(v1, v2) {
@@ -24651,6 +24673,90 @@ function normalizeCodexAspTranscriptStatus(status, failed = false) {
24651
24673
  if (status === "completed") return "completed";
24652
24674
  return "in_progress";
24653
24675
  }
24676
+ function isCodexAspTranscript(value) {
24677
+ if (!isRecord(value)) return false;
24678
+ return typeof value.threadId === "string" && typeof value.updatedAt === "string" && Array.isArray(value.turns);
24679
+ }
24680
+ function isCodexAspTranscriptDelta(value) {
24681
+ if (!isRecord(value)) return false;
24682
+ if (typeof value.threadId !== "string" || typeof value.updatedAt !== "string" || !Array.isArray(value.turns) || value.reset !== void 0 && value.reset !== null && !isCodexAspTranscript(value.reset)) {
24683
+ return false;
24684
+ }
24685
+ return value.turns.every((turn) => isRecord(turn) && typeof turn.id === "string" && typeof turn.status === "string" && typeof turn.startedAt === "string" && (turn.completedAt === null || typeof turn.completedAt === "string") && Array.isArray(turn.items) && turn.items.every((item) => isRecord(item) && typeof item.id === "string" && (item.item === void 0 || isRecord(item.item) && typeof item.item.type === "string" && typeof item.item.id === "string" && typeof item.item.timestamp === "string") && (item.appendText === void 0 || typeof item.appendText === "string") && (item.appendOutput === void 0 || typeof item.appendOutput === "string")));
24686
+ }
24687
+ function appendCodexAspTranscriptField(current, append) {
24688
+ return append === void 0 ? current : `${current ?? ""}${append}`;
24689
+ }
24690
+ function patchCodexAspTranscriptItem(current, delta) {
24691
+ const base = delta.item ?? current;
24692
+ if (!base) {
24693
+ return null;
24694
+ }
24695
+ if (base.type === "agentMessage" && delta.appendText !== void 0) {
24696
+ return {
24697
+ ...base,
24698
+ text: appendCodexAspTranscriptField(base.text, delta.appendText) ?? ""
24699
+ };
24700
+ }
24701
+ if ((base.type === "commandExecution" || base.type === "fileChange") && delta.appendOutput !== void 0) {
24702
+ return {
24703
+ ...base,
24704
+ output: appendCodexAspTranscriptField(base.output, delta.appendOutput)
24705
+ };
24706
+ }
24707
+ return base;
24708
+ }
24709
+ function applyCodexAspTranscriptDelta(current, delta) {
24710
+ if (!delta) return current ?? null;
24711
+ if (delta.reset === null) {
24712
+ return null;
24713
+ }
24714
+ const base = delta.reset ?? (current?.threadId === delta.threadId ? current : {
24715
+ threadId: delta.threadId,
24716
+ updatedAt: delta.updatedAt,
24717
+ turns: []
24718
+ });
24719
+ const turns = base.turns.map((turn) => ({
24720
+ ...turn,
24721
+ items: [...turn.items]
24722
+ }));
24723
+ for (const turnDelta of delta.turns) {
24724
+ let turn = turns.find((candidate) => candidate.id === turnDelta.id);
24725
+ if (!turn) {
24726
+ turn = {
24727
+ id: turnDelta.id,
24728
+ status: turnDelta.status,
24729
+ startedAt: turnDelta.startedAt,
24730
+ completedAt: turnDelta.completedAt,
24731
+ items: []
24732
+ };
24733
+ turns.push(turn);
24734
+ } else {
24735
+ turn.status = turnDelta.status;
24736
+ turn.startedAt = turnDelta.startedAt;
24737
+ turn.completedAt = turnDelta.completedAt;
24738
+ }
24739
+ for (const itemDelta of turnDelta.items) {
24740
+ const itemIndex = turn.items.findIndex((item) => item.id === itemDelta.id);
24741
+ const patched = patchCodexAspTranscriptItem(turn.items[itemIndex], itemDelta);
24742
+ if (!patched) continue;
24743
+ if (itemIndex === -1) {
24744
+ turn.items.push(patched);
24745
+ } else {
24746
+ turn.items[itemIndex] = patched;
24747
+ }
24748
+ }
24749
+ }
24750
+ turns.sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt));
24751
+ for (const turn of turns) {
24752
+ turn.items.sort((a, b) => (a.sequence ?? Number.MAX_SAFE_INTEGER) - (b.sequence ?? Number.MAX_SAFE_INTEGER) || Date.parse(a.timestamp) - Date.parse(b.timestamp));
24753
+ }
24754
+ return {
24755
+ threadId: delta.threadId,
24756
+ updatedAt: delta.updatedAt,
24757
+ turns
24758
+ };
24759
+ }
24654
24760
 
24655
24761
  // ../shared/src/routes/workspaces.ts
24656
24762
  var WORKSPACE_STATUSES = ["active", "sleeping", "archived", "preparing", "error"];
@@ -24930,6 +25036,15 @@ function normalizeBackgroundTaskStatus(status) {
24930
25036
  // ../shared/src/display-message/constants.ts
24931
25037
  var USER_MESSAGE_MATCH_GRACE_PERIOD_MS = 3e4;
24932
25038
 
25039
+ // ../shared/src/json.ts
25040
+ function safeJsonParse(str, fallback) {
25041
+ try {
25042
+ return JSON.parse(str);
25043
+ } catch {
25044
+ return fallback;
25045
+ }
25046
+ }
25047
+
24933
25048
  // ../shared/src/display-message/parsers/utils.ts
24934
25049
  var INTERRUPTED_MESSAGE_REGEX = /^\[Request interrupted by user.*\]$/;
24935
25050
  function userMessageImages(value) {
@@ -24946,6 +25061,32 @@ function stringifyDisplayValue(value) {
24946
25061
  return String(value);
24947
25062
  }
24948
25063
  }
25064
+ function upsertDisplayMessage(messages, message) {
25065
+ const index = messages.findIndex((candidate) => candidate.id === message.id);
25066
+ if (index === -1) messages.push(message);
25067
+ else messages[index] = message;
25068
+ }
25069
+ function parseSkillName(tool, input) {
25070
+ if (typeof tool !== "string") return null;
25071
+ const normalizedTool = tool.toLowerCase();
25072
+ if (normalizedTool.startsWith("skill.")) return tool.slice("skill.".length) || null;
25073
+ if (normalizedTool !== "skill") return null;
25074
+ const parsedInput = typeof input === "string" ? safeJsonParse(input, null) : input;
25075
+ if (!isRecord(parsedInput)) return null;
25076
+ const skillName = parsedInput.skill ?? parsedInput.name;
25077
+ return typeof skillName === "string" && skillName ? skillName : null;
25078
+ }
25079
+ function createCallDisplayMessage(message) {
25080
+ const skillName = message.server.toLowerCase() === "skills" ? message.tool : parseSkillName(message.tool, message.input);
25081
+ if (skillName === null) return { ...message, type: "tool_call" };
25082
+ return {
25083
+ id: message.id,
25084
+ type: "skill",
25085
+ skillName,
25086
+ status: message.status,
25087
+ timestamp: message.timestamp
25088
+ };
25089
+ }
24949
25090
 
24950
25091
  // ../shared/src/display-message/format.ts
24951
25092
  function formatStoppedAfter(durationMs) {
@@ -24962,13 +25103,47 @@ function areUserMessagesWithinMatchWindow(a, b) {
24962
25103
  return a.content === b.content && Math.abs(parseTimestampMs(a.timestamp) - parseTimestampMs(b.timestamp)) <= USER_MESSAGE_MATCH_GRACE_PERIOD_MS;
24963
25104
  }
24964
25105
 
24965
- // ../shared/src/json.ts
24966
- function safeJsonParse(str, fallback) {
24967
- try {
24968
- return JSON.parse(str);
24969
- } catch {
24970
- return fallback;
25106
+ // ../shared/src/agent-event-utils.ts
25107
+ function getUserMessage(event) {
25108
+ return event.type === "event_msg" && event.payload.type === "user_message" && typeof event.payload.message === "string" ? event.payload.message : null;
25109
+ }
25110
+ function getUserMessageId(event) {
25111
+ const messageId = event.payload[USER_MESSAGE_ID_PAYLOAD_KEY];
25112
+ return typeof messageId === "string" ? messageId : null;
25113
+ }
25114
+ function getUserMessageItemId(event) {
25115
+ const itemId = event.payload[CODEX_ASP_ITEM_ID_PAYLOAD_KEY];
25116
+ return typeof itemId === "string" ? itemId : null;
25117
+ }
25118
+ function areSameUserMessageEvents(a, b) {
25119
+ const aMessage = getUserMessage(a);
25120
+ const bMessage = getUserMessage(b);
25121
+ if (!aMessage || aMessage !== bMessage) return false;
25122
+ const aMessageId = getUserMessageId(a);
25123
+ const bMessageId = getUserMessageId(b);
25124
+ if (aMessageId || bMessageId) return aMessageId === bMessageId;
25125
+ const aItemId = getUserMessageItemId(a);
25126
+ const bItemId = getUserMessageItemId(b);
25127
+ if (aItemId || bItemId) return aItemId === bItemId;
25128
+ return areUserMessagesWithinMatchWindow(
25129
+ { content: aMessage, timestamp: a.timestamp },
25130
+ { content: bMessage, timestamp: b.timestamp }
25131
+ );
25132
+ }
25133
+ function mergeAgentEvents(primary, supplemental, options) {
25134
+ const merged = [...primary];
25135
+ const { areDuplicates, mergeEvent } = options;
25136
+ for (const event of supplemental) {
25137
+ const existingIndex = merged.findIndex((existing) => areDuplicates(existing, event));
25138
+ if (existingIndex === -1) {
25139
+ merged.push(event);
25140
+ continue;
25141
+ }
25142
+ if (mergeEvent) {
25143
+ merged[existingIndex] = mergeEvent(merged[existingIndex], event);
25144
+ }
24971
25145
  }
25146
+ return merged;
24972
25147
  }
24973
25148
 
24974
25149
  // ../shared/src/display-message/parsers/codex-parser.ts
@@ -25137,17 +25312,17 @@ function parseCodexEvents(events) {
25137
25312
  const operations = parsePatch(input);
25138
25313
  pendingPatches.set(callId, { input, status, timestamp: event.timestamp, operations });
25139
25314
  } else {
25140
- const msg = {
25141
- id: `toolcall-${callId || getPayloadString(event, CODEX_ASP_ITEM_ID_PAYLOAD_KEY) || `${event.timestamp}-${eventIndex}`}`,
25142
- type: "tool_call",
25315
+ const id = `toolcall-${callId || getPayloadString(event, CODEX_ASP_ITEM_ID_PAYLOAD_KEY) || `${event.timestamp}-${eventIndex}`}`;
25316
+ const msg = createCallDisplayMessage({
25317
+ id,
25143
25318
  server,
25144
25319
  tool: name,
25145
25320
  input,
25146
25321
  status,
25147
25322
  timestamp: event.timestamp
25148
- };
25323
+ });
25149
25324
  messages.push(msg);
25150
- if (callId) {
25325
+ if (callId && msg.type === "tool_call") {
25151
25326
  pendingToolCalls.set(callId, msg);
25152
25327
  }
25153
25328
  }
@@ -25231,10 +25406,10 @@ function getCursorThinkingText(event) {
25231
25406
  function isTerminalStatus(status) {
25232
25407
  return status === "FINISHED" || status === "ERROR" || status === "CANCELLED" || status === "EXPIRED";
25233
25408
  }
25234
- function finalizeOpenTools(messages, toolIndexes, status) {
25235
- for (const index of toolIndexes.values()) {
25409
+ function finalizeOpenCalls(messages, callIndexes, status) {
25410
+ for (const index of callIndexes.values()) {
25236
25411
  const message = messages[index];
25237
- if (message?.type === "tool_call" && message.status === "in_progress") {
25412
+ if ((message?.type === "tool_call" || message?.type === "skill") && message.status === "in_progress") {
25238
25413
  messages[index] = {
25239
25414
  ...message,
25240
25415
  status
@@ -25244,7 +25419,7 @@ function finalizeOpenTools(messages, toolIndexes, status) {
25244
25419
  }
25245
25420
  function parseCursorEvents(events) {
25246
25421
  const messages = [];
25247
- const toolIndexes = /* @__PURE__ */ new Map();
25422
+ const callIndexes = /* @__PURE__ */ new Map();
25248
25423
  const runAssistantSegments = /* @__PURE__ */ new Map();
25249
25424
  const runThinkingSegments = /* @__PURE__ */ new Map();
25250
25425
  let activeAssistant = null;
@@ -25336,21 +25511,20 @@ function parseCursorEvents(events) {
25336
25511
  const tool = typeof event.payload.name === "string" ? event.payload.name : "tool";
25337
25512
  const status = cursorStatusToDisplayStatus(event.payload.status);
25338
25513
  const input = isRecord(event.payload.args) ? event.payload.args : typeof event.payload.args === "string" ? event.payload.args : void 0;
25339
- const existing = toolIndexes.get(callId);
25340
- const next = {
25514
+ const existing = callIndexes.get(callId);
25515
+ const next = createCallDisplayMessage({
25341
25516
  id: callId,
25342
- type: "tool_call",
25343
25517
  server: "cursor",
25344
25518
  tool,
25345
25519
  ...input !== void 0 ? { input } : {},
25346
25520
  output: stringifyDisplayValue(event.payload.result),
25347
25521
  status,
25348
25522
  timestamp: event.timestamp
25349
- };
25523
+ });
25350
25524
  if (existing === void 0) {
25351
- toolIndexes.set(callId, messages.push(next) - 1);
25525
+ callIndexes.set(callId, messages.push(next) - 1);
25352
25526
  } else {
25353
- messages[existing] = { ...messages[existing], ...next };
25527
+ messages[existing] = next;
25354
25528
  }
25355
25529
  continue;
25356
25530
  }
@@ -25372,7 +25546,7 @@ function parseCursorEvents(events) {
25372
25546
  if (isTerminalStatus(status)) {
25373
25547
  const displayStatus = cursorStatusToDisplayStatus(status);
25374
25548
  finalizeActiveThinking(displayStatus);
25375
- finalizeOpenTools(messages, toolIndexes, displayStatus);
25549
+ finalizeOpenCalls(messages, callIndexes, displayStatus);
25376
25550
  activeThinking = null;
25377
25551
  activeAssistant = null;
25378
25552
  }
@@ -25380,7 +25554,7 @@ function parseCursorEvents(events) {
25380
25554
  }
25381
25555
  if (event.type === "cursor-error") {
25382
25556
  finalizeActiveThinking("failed");
25383
- finalizeOpenTools(messages, toolIndexes, "failed");
25557
+ finalizeOpenCalls(messages, callIndexes, "failed");
25384
25558
  activeAssistant = null;
25385
25559
  activeThinking = null;
25386
25560
  const message = typeof event.payload.message === "string" ? event.payload.message : "Cursor run failed";
@@ -25405,14 +25579,6 @@ function partFromEvent(event) {
25405
25579
  function toolStatus(status) {
25406
25580
  return status === "completed" ? "completed" : status === "error" ? "failed" : "in_progress";
25407
25581
  }
25408
- function setById(messages, id, next) {
25409
- const index = messages.findIndex((message) => message.id === id);
25410
- if (index === -1) {
25411
- messages.push(next);
25412
- } else {
25413
- messages[index] = next;
25414
- }
25415
- }
25416
25582
  function assistantError(info) {
25417
25583
  if (!isRecord(info) || !isRecord(info.error)) return null;
25418
25584
  const data = isRecord(info.error.data) ? info.error.data : null;
@@ -25457,15 +25623,32 @@ function parseOpencodeEvents(events) {
25457
25623
  });
25458
25624
  continue;
25459
25625
  }
25626
+ if (event.type === "opencode-session.next.tool.called") {
25627
+ const tool = typeof event.payload.tool === "string" ? event.payload.tool : "tool";
25628
+ const input = event.payload.input ?? event.payload.arguments;
25629
+ const callId = typeof event.payload.callID === "string" ? event.payload.callID : null;
25630
+ const id2 = callId ? `opencode-${callId}` : `opencode-${event.timestamp}-${messages.length}`;
25631
+ upsertDisplayMessage(messages, createCallDisplayMessage({
25632
+ id: id2,
25633
+ server: "opencode",
25634
+ tool,
25635
+ input: isRecord(input) ? input : stringifyDisplayValue(input),
25636
+ status: "in_progress",
25637
+ timestamp: event.timestamp
25638
+ }));
25639
+ continue;
25640
+ }
25460
25641
  const part = partFromEvent(event);
25461
25642
  if (!part) continue;
25462
- const id = typeof part.id === "string" ? `opencode-${part.id}` : `opencode-${event.timestamp}-${messages.length}`;
25643
+ let id = `opencode-${event.timestamp}-${messages.length}`;
25644
+ if (typeof part.callID === "string") id = `opencode-${part.callID}`;
25645
+ else if (typeof part.id === "string") id = `opencode-${part.id}`;
25463
25646
  const time3 = isRecord(part.time) ? part.time : null;
25464
25647
  const timestamp = timestampFromMs(time3?.start, event.timestamp);
25465
25648
  if (part.type === "text") {
25466
25649
  const text = typeof part.text === "string" ? part.text : "";
25467
25650
  if (text.trim()) {
25468
- setById(messages, id, {
25651
+ upsertDisplayMessage(messages, {
25469
25652
  id,
25470
25653
  type: "agent",
25471
25654
  content: text,
@@ -25477,7 +25660,7 @@ function parseOpencodeEvents(events) {
25477
25660
  if (part.type === "reasoning") {
25478
25661
  const text = typeof part.text === "string" ? part.text : "";
25479
25662
  if (text.trim()) {
25480
- setById(messages, id, {
25663
+ upsertDisplayMessage(messages, {
25481
25664
  id,
25482
25665
  type: "reasoning",
25483
25666
  content: text,
@@ -25490,23 +25673,24 @@ function parseOpencodeEvents(events) {
25490
25673
  if (part.type === "tool" && isRecord(part.state)) {
25491
25674
  const state = part.state;
25492
25675
  const status = toolStatus(state.status);
25676
+ const tool = typeof part.tool === "string" ? part.tool : "tool";
25677
+ const input = state.input;
25493
25678
  const output = state.status === "completed" ? stringifyDisplayValue(state.output) : state.status === "error" ? stringifyDisplayValue(state.error) : void 0;
25494
- setById(messages, id, {
25679
+ upsertDisplayMessage(messages, createCallDisplayMessage({
25495
25680
  id,
25496
- type: "tool_call",
25497
25681
  server: "opencode",
25498
- tool: typeof part.tool === "string" ? part.tool : "tool",
25499
- input: isRecord(state.input) ? state.input : stringifyDisplayValue(state.input),
25682
+ tool,
25683
+ input: isRecord(input) ? input : stringifyDisplayValue(input),
25500
25684
  output,
25501
25685
  status,
25502
25686
  timestamp
25503
- });
25687
+ }));
25504
25688
  continue;
25505
25689
  }
25506
25690
  if (part.type === "patch") {
25507
25691
  const files = Array.isArray(part.files) ? part.files.filter((file2) => typeof file2 === "string") : [];
25508
25692
  if (files.length > 0) {
25509
- setById(messages, id, {
25693
+ upsertDisplayMessage(messages, {
25510
25694
  id,
25511
25695
  type: "file_change",
25512
25696
  changes: files.map((path6) => ({ path: path6, kind: "update" })),
@@ -25523,11 +25707,6 @@ function parseOpencodeEvents(events) {
25523
25707
  function nestedPayload(event) {
25524
25708
  return isRecord(event.payload.assistantMessageEvent) ? event.payload.assistantMessageEvent : event.payload;
25525
25709
  }
25526
- function setById2(messages, id, message) {
25527
- const index = messages.findIndex((candidate) => candidate.id === id);
25528
- if (index === -1) messages.push(message);
25529
- else messages[index] = message;
25530
- }
25531
25710
  function toolStatus2(value) {
25532
25711
  return value === "completed" || value === "success" ? "completed" : value === "error" ? "failed" : "in_progress";
25533
25712
  }
@@ -25563,8 +25742,8 @@ function parsePiEvents(events) {
25563
25742
  if (payload.type === "thinking_delta" && typeof payload.delta === "string") thinking.set(id, `${thinking.get(id) ?? ""}${payload.delta}`);
25564
25743
  const textContent = text.get(id);
25565
25744
  const thinkingContent = thinking.get(id);
25566
- if (textContent !== void 0) setById2(messages, `pi-${id}`, { id: `pi-${id}`, type: "agent", content: textContent, timestamp: event.timestamp });
25567
- if (thinkingContent !== void 0) setById2(messages, `pi-thinking-${id}`, { id: `pi-thinking-${id}`, type: "reasoning", content: thinkingContent, status: "in_progress", timestamp: event.timestamp });
25745
+ if (textContent !== void 0) upsertDisplayMessage(messages, { id: `pi-${id}`, type: "agent", content: textContent, timestamp: event.timestamp });
25746
+ if (thinkingContent !== void 0) upsertDisplayMessage(messages, { id: `pi-thinking-${id}`, type: "reasoning", content: thinkingContent, status: "in_progress", timestamp: event.timestamp });
25568
25747
  continue;
25569
25748
  }
25570
25749
  if (event.type === "pi-message_start") {
@@ -25576,16 +25755,18 @@ function parsePiEvents(events) {
25576
25755
  const payload = event.payload;
25577
25756
  const input = payload.args ?? payload.input;
25578
25757
  const id = typeof payload.toolCallId === "string" ? payload.toolCallId : typeof payload.id === "string" ? payload.id : `tool-${event.timestamp}`;
25579
- setById2(messages, `pi-tool-${id}`, {
25580
- id: `pi-tool-${id}`,
25581
- type: "tool_call",
25758
+ const tool = typeof payload.toolName === "string" ? payload.toolName : "tool";
25759
+ const messageId = `pi-tool-${id}`;
25760
+ const status = event.type === "pi-tool_execution_end" ? toolStatus2(payload.isError ? "error" : "completed") : "in_progress";
25761
+ upsertDisplayMessage(messages, createCallDisplayMessage({
25762
+ id: messageId,
25582
25763
  server: "pi",
25583
- tool: typeof payload.toolName === "string" ? payload.toolName : "tool",
25764
+ tool,
25584
25765
  input: isRecord(input) ? input : stringifyDisplayValue(input),
25585
25766
  output: event.type === "pi-tool_execution_end" ? stringifyDisplayValue(payload.result ?? payload.error) : void 0,
25586
- status: event.type === "pi-tool_execution_end" ? toolStatus2(payload.isError ? "error" : "completed") : "in_progress",
25767
+ status,
25587
25768
  timestamp: event.timestamp
25588
- });
25769
+ }));
25589
25770
  }
25590
25771
  }
25591
25772
  return messages;
@@ -25751,14 +25932,6 @@ function isClaudeResultError(payload) {
25751
25932
  if (payload.errors?.length && stripAgentDiagnosticErrors(payload.errors).length === 0) return false;
25752
25933
  return Boolean(payload.is_error) || payload.subtype !== "success";
25753
25934
  }
25754
- function upsertDisplayMessage(messages, message) {
25755
- const index = messages.findIndex((existing) => existing.id === message.id);
25756
- if (index === -1) {
25757
- messages.push(message);
25758
- } else {
25759
- messages[index] = message;
25760
- }
25761
- }
25762
25935
  var LOCAL_COMMAND_ECHO_REGEX = /^<(?:command-name|command-message|local-command-stdout|local-command-stderr)>/;
25763
25936
  function parseClaudeEvents(events, parentToolUseId) {
25764
25937
  const messages = [];
@@ -26041,7 +26214,7 @@ function parseClaudeEvents(events, parentToolUseId) {
26041
26214
  } else if (toolName === "Skill") {
26042
26215
  const inputObj = typeof toolInput === "string" ? safeJsonParse(toolInput, {}) : toolInput;
26043
26216
  messages.push({
26044
- id: `skill-${event.timestamp}-${messages.length}`,
26217
+ id: `skill-${toolUseId}`,
26045
26218
  type: "skill",
26046
26219
  skillName: inputObj.skill || "unknown",
26047
26220
  args: inputObj.args,
@@ -26316,16 +26489,15 @@ function messageForItem(item) {
26316
26489
  };
26317
26490
  }
26318
26491
  if (item.type === "toolCall") {
26319
- return {
26492
+ return createCallDisplayMessage({
26320
26493
  id: `toolcall-${item.id}`,
26321
- type: "tool_call",
26322
26494
  server: item.server,
26323
26495
  tool: item.tool,
26324
26496
  input: inputForDisplay(item.input),
26325
26497
  output: item.output,
26326
26498
  status: normalizeCodexAspTranscriptStatus(item.status),
26327
26499
  timestamp: item.timestamp
26328
- };
26500
+ });
26329
26501
  }
26330
26502
  if (item.type === "subagent") {
26331
26503
  return {
@@ -26458,7 +26630,8 @@ function parseAgentEvents(events, agentType) {
26458
26630
  }
26459
26631
  function parseDisplayMessages(events, agentType, codexAspTranscript, options = {}) {
26460
26632
  const shouldFilter = options.filter ?? true;
26461
- const legacyMessages = shouldFilter ? filterDisplayMessages(parseAgentEvents(events, agentType), agentType) : parseAgentEvents(events, agentType);
26633
+ const parsedEvents = agentType === "claude" || agentType === "relay" ? parseClaudeEvents(events, options.parentToolUseId) : parseAgentEvents(events, agentType);
26634
+ const legacyMessages = shouldFilter ? filterDisplayMessages(parsedEvents, agentType) : parsedEvents;
26462
26635
  if (agentType !== "codex" || !codexAspTranscript) {
26463
26636
  return shouldFilter ? applyInterruptions(legacyMessages, events) : legacyMessages;
26464
26637
  }
@@ -33742,7 +33915,31 @@ function useGenerateWorkspaceName() {
33742
33915
  // ../shared/src/hooks/useWorkspaceEngine.ts
33743
33916
  import { useCallback as useCallback4 } from "react";
33744
33917
  import { useQuery as useQuery2, useMutation as useMutation2 } from "@tanstack/react-query";
33745
- function upsertChat(chats, chat) {
33918
+
33919
+ // ../shared/src/workspace-chat.ts
33920
+ import { queryOptions } from "@tanstack/react-query";
33921
+ var workspaceChatKeys = {
33922
+ chats: (workspaceId) => ["workspace-chats", workspaceId],
33923
+ allChats: (workspaceId) => ["workspace-all-chats", workspaceId],
33924
+ histories: (workspaceId) => ["chat-history", workspaceId],
33925
+ history: (workspaceId, chatId) => ["chat-history", workspaceId, chatId],
33926
+ queues: (workspaceId) => ["chat-queue", workspaceId],
33927
+ queue: (workspaceId, chatId) => ["chat-queue", workspaceId, chatId]
33928
+ };
33929
+ function workspaceChatsQueryOptions({
33930
+ workspaceId,
33931
+ fetchChats,
33932
+ enabled = true,
33933
+ staleTime = 6e4
33934
+ }) {
33935
+ return queryOptions({
33936
+ queryKey: workspaceChatKeys.chats(workspaceId),
33937
+ queryFn: fetchChats,
33938
+ enabled: Boolean(workspaceId) && enabled,
33939
+ staleTime
33940
+ });
33941
+ }
33942
+ function upsertWorkspaceChat(chats, chat) {
33746
33943
  const existingIndex = chats.findIndex((item) => item.id === chat.id);
33747
33944
  if (existingIndex === -1) {
33748
33945
  return [chat, ...chats].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -33751,7 +33948,7 @@ function upsertChat(chats, chat) {
33751
33948
  next[existingIndex] = chat;
33752
33949
  return next.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
33753
33950
  }
33754
- function patchChat(chats, chatId, patch) {
33951
+ function patchWorkspaceChat(chats, chatId, patch) {
33755
33952
  let changed = false;
33756
33953
  const next = chats.map((chat) => {
33757
33954
  if (chat.id !== chatId) return chat;
@@ -33760,21 +33957,229 @@ function patchChat(chats, chatId, patch) {
33760
33957
  });
33761
33958
  return changed ? next.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)) : chats;
33762
33959
  }
33960
+ function isAcceptedUserMessage(event) {
33961
+ return getUserMessage(event) !== null && event.payload.source === ACCEPTED_USER_MESSAGE_SOURCE;
33962
+ }
33963
+ function areDuplicateUserMessages(a, b) {
33964
+ if (areSameUserMessageEvents(a, b)) return true;
33965
+ const aMessage = getUserMessage(a);
33966
+ const bMessage = getUserMessage(b);
33967
+ if (!aMessage || aMessage !== bMessage) return false;
33968
+ const aAccepted = isAcceptedUserMessage(a);
33969
+ const bAccepted = isAcceptedUserMessage(b);
33970
+ return aAccepted !== bAccepted ? areUserMessagesWithinMatchWindow(
33971
+ { content: aMessage, timestamp: a.timestamp },
33972
+ { content: bMessage, timestamp: b.timestamp }
33973
+ ) : false;
33974
+ }
33975
+ function areDuplicateWorkspaceChatEvents(a, b) {
33976
+ const aPartialStreamId = getClaudePartialMessageStreamId(a);
33977
+ const bPartialStreamId = getClaudePartialMessageStreamId(b);
33978
+ return aPartialStreamId !== null && aPartialStreamId === bPartialStreamId || JSON.stringify(a) === JSON.stringify(b) || areDuplicateUserMessages(a, b);
33979
+ }
33980
+ function mergeWorkspaceChatHistoryEvents(primary, supplemental) {
33981
+ return mergeAgentEvents(primary, supplemental, {
33982
+ areDuplicates: areDuplicateWorkspaceChatEvents,
33983
+ mergeEvent: (current, candidate) => getClaudePartialMessageStreamId(candidate) !== null ? candidate : current
33984
+ });
33985
+ }
33986
+ function isSameChatSender(a, b) {
33987
+ return a.senderUserId === b.senderUserId && a.recordedAt === b.recordedAt;
33988
+ }
33989
+ function codexAspTranscriptUpdateFromEvent(event) {
33990
+ if (event.type !== CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE) return null;
33991
+ const { payload } = event;
33992
+ const updatedAt = payload.updatedAt;
33993
+ const transcript = payload.transcript;
33994
+ const transcriptDelta = payload.transcriptDelta;
33995
+ if (typeof updatedAt !== "string" || transcript !== null && !isCodexAspTranscript(transcript) || transcriptDelta !== void 0 && !isCodexAspTranscriptDelta(transcriptDelta)) {
33996
+ return null;
33997
+ }
33998
+ const threadId = typeof payload.threadId === "string" ? payload.threadId : void 0;
33999
+ return {
34000
+ updatedAt,
34001
+ transcript,
34002
+ ...transcriptDelta ? { transcriptDelta } : {},
34003
+ ...threadId ? { threadId } : {}
34004
+ };
34005
+ }
34006
+ function patchCachedChats(queryClient2, workspaceId, chatId, patch) {
34007
+ for (const queryKey of [
34008
+ workspaceChatKeys.chats(workspaceId),
34009
+ workspaceChatKeys.allChats(workspaceId)
34010
+ ]) {
34011
+ queryClient2.setQueryData(
34012
+ queryKey,
34013
+ (current) => current ? {
34014
+ ...current,
34015
+ chats: patchWorkspaceChat(current.chats, chatId, patch)
34016
+ } : current
34017
+ );
34018
+ }
34019
+ }
34020
+ function applyWorkspaceChatEvent(queryClient2, workspaceId, event, options = {}) {
34021
+ if (event.type === "chat.created" || event.type === "chat.updated") {
34022
+ const { chat } = event.payload;
34023
+ queryClient2.setQueryData(
34024
+ workspaceChatKeys.allChats(workspaceId),
34025
+ (current) => current ? { ...current, chats: upsertWorkspaceChat(current.chats, chat) } : current
34026
+ );
34027
+ if (chat.parentChatId === null && !options.skipRootChatUpsert) {
34028
+ queryClient2.setQueryData(
34029
+ workspaceChatKeys.chats(workspaceId),
34030
+ (current) => ({
34031
+ ...current ?? {},
34032
+ chats: upsertWorkspaceChat(current?.chats ?? [], chat),
34033
+ deletedChats: (current?.deletedChats ?? []).filter((item) => item.id !== chat.id)
34034
+ })
34035
+ );
34036
+ }
34037
+ return "chat-list-changed";
34038
+ }
34039
+ if (event.type === "chat.deleted") {
34040
+ queryClient2.setQueryData(workspaceChatKeys.chats(workspaceId), (current) => {
34041
+ if (!current) return current;
34042
+ const deletedChat = event.payload.chat;
34043
+ return {
34044
+ ...current,
34045
+ chats: current.chats.filter((chat) => chat.id !== event.payload.chatId),
34046
+ deletedChats: deletedChat?.deletedAt ? [
34047
+ deletedChat,
34048
+ ...(current.deletedChats ?? []).filter((chat) => chat.id !== deletedChat.id)
34049
+ ] : (current.deletedChats ?? []).filter((chat) => chat.id !== event.payload.chatId)
34050
+ };
34051
+ });
34052
+ queryClient2.setQueryData(
34053
+ workspaceChatKeys.allChats(workspaceId),
34054
+ (current) => current ? {
34055
+ ...current,
34056
+ chats: current.chats.filter((chat) => chat.id !== event.payload.chatId)
34057
+ } : current
34058
+ );
34059
+ queryClient2.removeQueries({
34060
+ queryKey: workspaceChatKeys.history(workspaceId, event.payload.chatId)
34061
+ });
34062
+ return "chat-list-changed";
34063
+ }
34064
+ if (event.type === "chat.turn.accepted") {
34065
+ if (event.payload.queued) {
34066
+ queryClient2.invalidateQueries({
34067
+ queryKey: workspaceChatKeys.queue(workspaceId, event.payload.chatId)
34068
+ });
34069
+ return "queue-changed";
34070
+ }
34071
+ const sender = event.payload.sender;
34072
+ const acceptedEvent = event.payload.event;
34073
+ if (sender || acceptedEvent) {
34074
+ queryClient2.setQueryData(
34075
+ workspaceChatKeys.history(workspaceId, event.payload.chatId),
34076
+ (current) => ({
34077
+ ...current ?? { thread_id: null },
34078
+ events: acceptedEvent ? mergeWorkspaceChatHistoryEvents(current?.events ?? [], [acceptedEvent]) : current?.events ?? [],
34079
+ senders: sender ? [
34080
+ ...(current?.senders ?? []).filter(
34081
+ (candidate) => !isSameChatSender(candidate, sender)
34082
+ ),
34083
+ sender
34084
+ ] : current?.senders
34085
+ })
34086
+ );
34087
+ }
34088
+ patchCachedChats(queryClient2, workspaceId, event.payload.chatId, {
34089
+ processing: true,
34090
+ awaitingInput: false,
34091
+ updatedAt: event.ts
34092
+ });
34093
+ return "chat-list-changed";
34094
+ }
34095
+ if (event.type === "chat.turn.started") {
34096
+ queryClient2.setQueryData(
34097
+ workspaceChatKeys.queue(workspaceId, event.payload.chatId),
34098
+ (current) => current ? {
34099
+ ...current,
34100
+ queue: current.queue.filter((message) => message.id !== event.payload.messageId)
34101
+ } : current
34102
+ );
34103
+ queryClient2.invalidateQueries({
34104
+ queryKey: workspaceChatKeys.queue(workspaceId, event.payload.chatId)
34105
+ });
34106
+ patchCachedChats(queryClient2, workspaceId, event.payload.chatId, {
34107
+ processing: true,
34108
+ awaitingInput: false,
34109
+ updatedAt: event.ts
34110
+ });
34111
+ const history = queryClient2.getQueryData(
34112
+ workspaceChatKeys.history(workspaceId, event.payload.chatId)
34113
+ );
34114
+ if (!history?.events.some((candidate) => getUserMessageId(candidate) === event.payload.messageId)) {
34115
+ queryClient2.invalidateQueries({
34116
+ queryKey: workspaceChatKeys.history(workspaceId, event.payload.chatId)
34117
+ });
34118
+ }
34119
+ return "chat-list-changed";
34120
+ }
34121
+ if (event.type === "chat.turn.delta") {
34122
+ const historyKey = workspaceChatKeys.history(workspaceId, event.payload.chatId);
34123
+ const transcriptUpdate = codexAspTranscriptUpdateFromEvent(event.payload.event);
34124
+ if (transcriptUpdate) {
34125
+ queryClient2.setQueryData(historyKey, (current) => {
34126
+ const transcript = transcriptUpdate.transcriptDelta ? applyCodexAspTranscriptDelta(
34127
+ current?.codexAspTranscript ?? transcriptUpdate.transcript ?? null,
34128
+ transcriptUpdate.transcriptDelta
34129
+ ) : transcriptUpdate.transcript ?? current?.codexAspTranscript ?? null;
34130
+ return {
34131
+ ...current ?? { events: [] },
34132
+ thread_id: current?.thread_id ?? transcript?.threadId ?? transcriptUpdate.threadId ?? null,
34133
+ codexAspTranscript: transcript
34134
+ };
34135
+ });
34136
+ return "turn-delta";
34137
+ }
34138
+ queryClient2.setQueryData(historyKey, (current) => ({
34139
+ ...current ?? { thread_id: null },
34140
+ events: mergeWorkspaceChatHistoryEvents(current?.events ?? [], [event.payload.event]),
34141
+ goal: event.payload.event.type === CHAT_GOAL_EVENT_TYPE ? coerceChatGoalPayload(event.payload.event.payload) : current?.goal
34142
+ }));
34143
+ return "turn-delta";
34144
+ }
34145
+ if (event.type === "chat.turn.completed" || event.type === "chat.interrupted") {
34146
+ patchCachedChats(queryClient2, workspaceId, event.payload.chatId, {
34147
+ processing: getTerminalChatProcessing(event),
34148
+ awaitingInput: false,
34149
+ isAuthRetrying: false,
34150
+ updatedAt: event.ts
34151
+ });
34152
+ queryClient2.invalidateQueries({
34153
+ queryKey: workspaceChatKeys.history(workspaceId, event.payload.chatId)
34154
+ });
34155
+ queryClient2.invalidateQueries({
34156
+ queryKey: workspaceChatKeys.queue(workspaceId, event.payload.chatId)
34157
+ });
34158
+ queryClient2.invalidateQueries({
34159
+ queryKey: workspaceChatKeys.allChats(workspaceId)
34160
+ });
34161
+ return "turn-completed";
34162
+ }
34163
+ return null;
34164
+ }
34165
+
34166
+ // ../shared/src/hooks/useWorkspaceEngine.ts
33763
34167
  function useWorkspaceChats(workspaceId) {
33764
34168
  const auth = useReplicasAuth();
33765
34169
  const orgFetch = useOrgFetch();
33766
- return useQuery2({
33767
- queryKey: ["workspace-chats", workspaceId],
33768
- queryFn: () => orgFetch(`/v1/workspaces/${workspaceId}/chats`),
33769
- enabled: !!workspaceId,
33770
- staleTime: 6e4
33771
- }, auth.queryClient);
34170
+ return useQuery2(
34171
+ workspaceChatsQueryOptions({
34172
+ workspaceId,
34173
+ fetchChats: () => orgFetch(`/v1/workspaces/${workspaceId}/chats`)
34174
+ }),
34175
+ auth.queryClient
34176
+ );
33772
34177
  }
33773
34178
  function useChatHistory(workspaceId, chatId) {
33774
34179
  const auth = useReplicasAuth();
33775
34180
  const orgFetch = useOrgFetch();
33776
34181
  return useQuery2({
33777
- queryKey: ["chat-history", workspaceId, chatId],
34182
+ queryKey: workspaceChatKeys.history(workspaceId, chatId),
33778
34183
  queryFn: () => orgFetch(`/v1/workspaces/${workspaceId}/chats/${chatId}/history`),
33779
34184
  enabled: !!workspaceId && !!chatId
33780
34185
  }, auth.queryClient);
@@ -33851,67 +34256,7 @@ function useWorkspaceEvents(workspaceId, enabled = true) {
33851
34256
  const qc = auth.queryClient;
33852
34257
  const handleEvent = useCallback4((event) => {
33853
34258
  if (!workspaceId) return;
33854
- const chatsKey = ["workspace-chats", workspaceId];
33855
- if (event.type === "chat.created" || event.type === "chat.updated") {
33856
- if (event.payload.chat.parentChatId === null) {
33857
- qc.setQueryData(chatsKey, (current) => {
33858
- if (!current) return { chats: [event.payload.chat] };
33859
- return { chats: upsertChat(current.chats, event.payload.chat) };
33860
- });
33861
- }
33862
- return;
33863
- }
33864
- if (event.type === "chat.deleted") {
33865
- qc.setQueryData(chatsKey, (current) => {
33866
- if (!current) return current;
33867
- return { chats: current.chats.filter((chat) => chat.id !== event.payload.chatId) };
33868
- });
33869
- qc.removeQueries({ queryKey: ["chat-history", workspaceId, event.payload.chatId] });
33870
- return;
33871
- }
33872
- if (event.type === "chat.turn.accepted") {
33873
- if (!event.payload.queued) {
33874
- qc.setQueryData(chatsKey, (current) => {
33875
- if (!current) return current;
33876
- return { chats: patchChat(current.chats, event.payload.chatId, { processing: true, updatedAt: event.ts }) };
33877
- });
33878
- }
33879
- return;
33880
- }
33881
- if (event.type === "chat.turn.started") {
33882
- qc.setQueryData(chatsKey, (current) => {
33883
- if (!current) return current;
33884
- return { chats: patchChat(current.chats, event.payload.chatId, { processing: true, updatedAt: event.ts }) };
33885
- });
33886
- return;
33887
- }
33888
- if (event.type === "chat.turn.completed" || event.type === "chat.interrupted") {
33889
- qc.setQueryData(chatsKey, (current) => {
33890
- if (!current) return current;
33891
- return { chats: patchChat(current.chats, event.payload.chatId, { processing: getTerminalChatProcessing(event), updatedAt: event.ts }) };
33892
- });
33893
- qc.invalidateQueries({ queryKey: ["chat-history", workspaceId, event.payload.chatId] });
33894
- return;
33895
- }
33896
- if (event.type === "chat.turn.delta") {
33897
- qc.setQueryData(
33898
- ["chat-history", workspaceId, event.payload.chatId],
33899
- (current) => {
33900
- const incomingSignature = getEventSignature(event.payload.event);
33901
- if (!current) {
33902
- return { thread_id: null, events: [event.payload.event] };
33903
- }
33904
- const existingIndex = current.events.findIndex((e) => getEventSignature(e) === incomingSignature);
33905
- if (existingIndex !== -1) {
33906
- const events = current.events.slice();
33907
- events[existingIndex] = event.payload.event;
33908
- return { ...current, events };
33909
- }
33910
- return { ...current, events: [...current.events, event.payload.event] };
33911
- }
33912
- );
33913
- return;
33914
- }
34259
+ if (applyWorkspaceChatEvent(qc, workspaceId, event)) return;
33915
34260
  if (event.type === "repo.status.changed") {
33916
34261
  qc.setQueryData(["workspace-status", workspaceId, false], (current) => {
33917
34262
  if (!current) return current;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.2.454",
3
+ "version": "0.2.456",
4
4
  "description": "CLI for managing Replicas workspaces - SSH into cloud dev environments with automatic port forwarding",
5
5
  "main": "dist/index.mjs",
6
6
  "bin": {