replicas-engine 0.1.580 → 0.1.582

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 +180 -58
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -1434,6 +1434,7 @@ GitHub does NOT have a public API for uploading images to PRs/issues. When you n
1434
1434
  - Do NOT use placeholder image URLs
1435
1435
  - Do NOT commit screenshots as files to the repository
1436
1436
  - Run \`replicas media upload <image> --name "<display name>" --share\` and put the printed \`Forge embed\` Markdown in the PR body or comment
1437
+ - For a self-contained HTML report, run \`replicas media upload <page.html> --name "<display name>" --access organization\` and put the printed \`Forge link\` Markdown in the PR body or comment. Organization members who are already signed in open it directly; others sign in and return to the page
1437
1438
  - For video, embed a shared poster image linked to the recording's **View in Replicas** URL; GitHub does not reliably render externally hosted video inline
1438
1439
  - Treat the public URL as an opt-in bearer capability; revoke it with \`replicas media revoke <media-id>\` only when the user asks or the media should stop rendering \u2014 revoking breaks embeds already posted on the PR
1439
1440
  - If you were triggered from Slack, also upload the image to the Slack thread so the user can see it directly
@@ -2035,17 +2036,18 @@ var LINEAR_ABILITY = {
2035
2036
 
2036
2037
  // ../shared/src/default-skills/replicas-agent/abilities/media.ts
2037
2038
  var SECTION8 = `### Media
2038
- Share screenshots, screen recordings, generated diagrams, and audio clips inline in the Replicas chat and natively embedded in external messages.
2039
+ Share screenshots, screen recordings, generated diagrams, audio clips, and self-contained HTML pages through Replicas and external messages.
2039
2040
 
2040
2041
  **Reference:** \`references/MEDIA.md\`
2041
2042
 
2042
2043
  Use this when:
2043
2044
  - You produce a screenshot, recording, generated image, or audio clip the user should see
2045
+ - You produce a self-contained HTML report that should be linked from a pull request
2044
2046
  - You record video output (browser automation, screen capture) \u2014 including the recommended aspect ratio and FPS
2045
2047
  - You need to embed media in a Slack/Linear/GitHub message AND keep a referenceable copy in the Replicas dashboard`;
2046
- var REFERENCE8 = `# Media (Screenshots, Recordings, Audio)
2048
+ var REFERENCE8 = `# Media (Screenshots, Recordings, Audio, HTML)
2047
2049
 
2048
- This guide covers how to share screenshots, screen recordings, generated diagrams, and audio clips \u2014 both inline in the Replicas chat and natively embedded in external surfaces (Slack, Linear, GitHub).
2050
+ This guide covers how to share screenshots, screen recordings, generated diagrams, audio clips, and self-contained HTML pages through Replicas and external surfaces (Slack, Linear, GitHub).
2049
2051
 
2050
2052
  ## Prerequisites
2051
2053
 
@@ -2056,7 +2058,7 @@ The \`replicas\` CLI is pre-installed and authenticated in your workspace. No ad
2056
2058
  Upload to Replicas in these cases \u2014 and **only** these cases:
2057
2059
 
2058
2060
  1. **Media you produce.** Any screenshot, screen recording, generated diagram, or audio clip you create that the user might want to see. Upload before doing anything else with the file (analyzing, deleting, sending elsewhere). This applies even when you're also sending the file to Slack, Linear, GitHub, etc.
2059
- 2. **Files the user explicitly asks you to upload.** If the user sends or points at a file (image, video, audio) and asks you to upload it, run \`replicas media upload\`. Otherwise leave it alone \u2014 files in the workspace the user did not ask about should not be auto-uploaded as media.
2061
+ 2. **Files the user explicitly asks you to upload.** If the user sends or points at a file (image, video, audio, HTML) and asks you to upload it, run \`replicas media upload\`. Otherwise leave it alone \u2014 files in the workspace the user did not ask about should not be auto-uploaded as media.
2060
2062
  3. **Anything you plan to share externally (Slack, Linear, GitHub, etc.).** Upload to Replicas *in addition to* the platform's native upload. Never as a replacement.
2061
2063
 
2062
2064
  If none of these apply, don't upload.
@@ -2085,28 +2087,39 @@ replicas media upload /tmp/before.png /tmp/after.png --name "Before fix" --name
2085
2087
 
2086
2088
  Names are normalized to hyphen-case (spaces become hyphens, e.g. \`Login page\` is stored as \`login-page\`). Give every asset a **distinct** name \u2014 duplicates are not rejected, but Replicas keeps the first as-is and adds a \`-1\`, \`-2\` suffix to later collisions, which produces confusing lists, so pick unique names yourself.
2087
2089
 
2088
- For each file, the CLI prints two lines:
2090
+ For each media file, the CLI prints two lines:
2089
2091
 
2090
2092
  1. \`![filename](<api-url>)\` \u2014 the chat embed (renders inline in Replicas chat **only** \u2014 see below).
2091
2093
  2. \`View in Replicas: <deep-link>\` \u2014 a per-file dashboard URL of the form \`https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>\` that opens directly to that specific file.
2092
2094
 
2093
2095
  Match each "View in Replicas" line to the embed line directly above it \u2014 that's the deep link for that file.
2094
2096
 
2095
- Pass \`--share\` only when the media must render on an external forge:
2097
+ HTML uploads print an \`HTML page\` Markdown link instead of an image embed.
2098
+
2099
+ For an HTML page that should stay inside the Replicas organization, create an authenticated link:
2096
2100
 
2097
2101
  \`\`\`bash
2098
- replicas media upload screenshot.png --name "Login page" --share
2102
+ replicas media upload recap.html --name "Visual recap" --access organization
2099
2103
  \`\`\`
2100
2104
 
2101
- This prints an additional \`Forge embed\` Markdown line backed by an opt-in public bearer URL. Anyone with that unguessable URL can view the asset until it is rotated or revoked. Use \`replicas media share <media-id>\` to rotate it and \`replicas media revoke <media-id>\` to revoke it.
2105
+ The printed \`Forge link (organization only)\` opens a dedicated page. Signed-in organization members go straight to it; signed-out members sign in and return to the page.
2106
+
2107
+ Use public access only when the media must render without Replicas authentication:
2102
2108
 
2103
- ## The two URLs the CLI gives you \u2014 and where each one is allowed
2109
+ \`\`\`bash
2110
+ replicas media upload screenshot.png --name "Login page" --access public
2111
+ \`\`\`
2112
+
2113
+ \`--share\` remains an alias for \`--access public\`. Public access prints an additional \`Forge embed\` for media or \`Forge link\` for HTML, backed by an opt-in bearer URL. Anyone with that unguessable URL can view the asset until it is rotated or revoked. Use \`replicas media share <media-id>\` to rotate it and \`replicas media revoke <media-id>\` to revoke it.
2114
+
2115
+ ## The URLs the CLI gives you \u2014 and where each one is allowed
2104
2116
 
2105
2117
  | URL | What it is | Where it's allowed |
2106
2118
  |---|---|---|
2107
2119
  | \`https://api.tryreplicas.com/v1/media/<id>\` (inside \`![\u2026](\u2026)\`) | Authenticated API redirect to a presigned blob \u2014 requires the Replicas chat session | **Only** in the Replicas chat reply. Nowhere else. |
2108
- | \`https://api.tryreplicas.com/v1/media/public/<id>/<token>\` | Opt-in public bearer URL printed by \`--share\` | GitHub image embeds only. |
2109
- | \`https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>\` | Public dashboard deep link that opens the media file in the workspace | Everywhere external \u2014 Slack, GitHub, Linear, PR bodies, comments, emails. |
2120
+ | \`https://tryreplicas.com/organizations/<organization-id>/media/<media-id>\` | Organization-only page printed by \`--access organization\` | Pull requests and other places where every viewer belongs to the Replicas organization. |
2121
+ | \`https://api.tryreplicas.com/v1/media/public/<id>/<token>\` | Opt-in public bearer URL printed by \`--access public\` or \`--share\` | GitHub image embeds and intentionally public HTML page links. |
2122
+ | \`https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>\` | Authenticated dashboard deep link that opens the media file in the workspace | Everywhere external \u2014 Slack, GitHub, Linear, PR bodies, comments, emails. |
2110
2123
 
2111
2124
  ### NEVER link the API redirect URL outside the Replicas chat reply
2112
2125
 
@@ -2119,9 +2132,9 @@ This rule has no exceptions:
2119
2132
  - \u274C Never link to it from any external doc, email, or external chat.
2120
2133
  - \u274C Never include it as a "backup link" in case the dashboard link breaks \u2014 it will always break externally.
2121
2134
 
2122
- ### ALWAYS link the dashboard URL \u2014 never the API URL \u2014 when referencing media externally
2135
+ ### ALWAYS link a Replicas web URL \u2014 never the authenticated API URL \u2014 when referencing media externally
2123
2136
 
2124
- When you reference an uploaded file from anywhere outside the Replicas chat reply, the link must be the dashboard form:
2137
+ For an organization-only HTML page, use the \`Forge link\` printed by \`--access organization\`. For other authenticated media references, use the dashboard form:
2125
2138
 
2126
2139
  \`\`\`
2127
2140
  https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>
@@ -2135,7 +2148,7 @@ Always render as a markdown / mrkdwn hyperlink labeled **View in Replicas** \u20
2135
2148
 
2136
2149
  ## In your Replicas chat reply
2137
2150
 
2138
- Include each markdown embed line **verbatim** where you want that file to render inline. The chat substitutes each one with an embedded image, video, or audio player. Multiple embeds can appear in a single reply.
2151
+ Include each markdown embed line **verbatim** where you want that file to render inline. The chat substitutes each one with an embedded image, video, or audio player. For HTML, use the \`HTML page\` or \`View in Replicas\` link instead. Multiple embeds can appear in a single reply.
2139
2152
 
2140
2153
  After (or alongside) the embeds, include a \`[View in Replicas](<deep-link>)\` hyperlink for each file using the per-file dashboard URL from the CLI. This lets the user open the specific item in the workspace.
2141
2154
 
@@ -2281,6 +2294,8 @@ GitHub does not reliably render arbitrary external videos as inline players. For
2281
2294
 
2282
2295
  Public forge shares are bearer capabilities. For private repositories, create one only when the user asked for inline PR media. Revoke with \`replicas media revoke <media-id>\` **only** when the user asks or the media should intentionally stop rendering \u2014 never as routine post-task cleanup, since revoking breaks every embed already posted on the PR.
2283
2296
 
2297
+ For a self-contained HTML report, upload it with \`--access organization\` and put the printed \`Forge link\` Markdown in the PR body or comment. GitHub cannot render the page inline, so use the normal link rather than image syntax. Use \`--access public\` only when the user explicitly wants unauthenticated access.
2298
+
2284
2299
  Do **not** commit screenshots to the repo, use placeholder URLs, expose the authenticated \`/v1/media/<id>\` URL, or automate GitHub's undocumented browser upload flow.
2285
2300
 
2286
2301
  ### GitLab: native inline screenshots and videos
@@ -2315,8 +2330,9 @@ Auto-detected from the filename extension:
2315
2330
  | \`png\`, \`jpg\`, \`jpeg\`, \`webp\` | image |
2316
2331
  | \`mp4\`, \`webm\` | video |
2317
2332
  | \`mp3\`, \`wav\` | audio |
2333
+ | \`html\`, \`htm\` | html |
2318
2334
 
2319
- For other extensions, pass \`--kind image|video|audio\` explicitly. The kind applies to every file in that invocation, so group files of the same kind together:
2335
+ For other extensions, pass \`--kind image|video|audio|html\` explicitly. The kind applies to every file in that invocation, so group files of the same kind together:
2320
2336
 
2321
2337
  \`\`\`bash
2322
2338
  replicas media upload diagram.svg --name "Architecture diagram" --kind image
@@ -2325,15 +2341,16 @@ replicas media upload chart-a.svg chart-b.svg --name "Latency chart" --name "Err
2325
2341
 
2326
2342
  ## Options
2327
2343
 
2328
- - \`--kind <image|video|audio>\` \u2014 override auto-detection
2344
+ - \`--kind <image|video|audio|html>\` \u2014 override auto-detection
2329
2345
  - \`--name <name>\`: required display name shown in the dashboard, one per file in order
2330
2346
  - \`--session-id <id>\` \u2014 associate the upload with a specific session
2331
- - \`--share\`: create a revocable public bearer URL for a forge image embed
2347
+ - \`--access organization|public\`: create an organization-only page link or a public bearer URL
2348
+ - \`--share\`: alias for \`--access public\`
2332
2349
  `;
2333
2350
  var MEDIA_ABILITY = {
2334
2351
  label: "Media",
2335
- description: "Share screenshots, recordings, generated images, and audio clips.",
2336
- bullet: "- Producing or showing the user any media \u2014 screenshots, screen recordings, generated images or diagrams, audio clips \u2014 including in your Replicas chat reply, PR descriptions/comments, and other external platforms",
2352
+ description: "Share screenshots, recordings, generated images, audio clips, and HTML pages.",
2353
+ bullet: "- Producing or showing the user any media \u2014 screenshots, screen recordings, generated images or diagrams, audio clips, or HTML recap pages \u2014 including in your Replicas chat reply, PR descriptions/comments, and other external platforms",
2337
2354
  section: SECTION8,
2338
2355
  referenceFile: { name: "MEDIA.md", content: REFERENCE8 }
2339
2356
  };
@@ -3311,14 +3328,23 @@ function chatHistoryPageParamsFromQuery(query2) {
3311
3328
  beforeTurn: parse(query2.beforeTurn)
3312
3329
  };
3313
3330
  }
3331
+ function getChatHistoryPageWindow(totalItems, limit, before) {
3332
+ const endIndex = Math.min(before ?? totalItems, totalItems);
3333
+ return { startIndex: Math.max(0, endIndex - Math.max(1, limit)), endIndex };
3334
+ }
3314
3335
  function paginateChatHistory(full, params) {
3315
3336
  if (params.limit === void 0) return { ...full, totalEvents: full.events.length };
3316
- const limit = Math.max(1, params.limit);
3317
- const eventsEnd = Math.min(params.beforeEvent ?? full.events.length, full.events.length);
3318
- const eventsStartIndex = Math.max(0, eventsEnd - limit);
3337
+ const { startIndex: eventsStartIndex, endIndex: eventsEnd } = getChatHistoryPageWindow(
3338
+ full.events.length,
3339
+ params.limit,
3340
+ params.beforeEvent
3341
+ );
3319
3342
  const turns = full.codexAspTranscript?.turns;
3320
- const turnsEnd = turns ? Math.min(params.beforeTurn ?? turns.length, turns.length) : 0;
3321
- const turnsStartIndex = Math.max(0, turnsEnd - limit);
3343
+ const { startIndex: turnsStartIndex, endIndex: turnsEnd } = getChatHistoryPageWindow(
3344
+ turns?.length ?? 0,
3345
+ params.limit,
3346
+ params.beforeTurn
3347
+ );
3322
3348
  const codexAspTranscript = full.codexAspTranscript && turns && (turnsStartIndex > 0 || turnsEnd < turns.length) ? { ...full.codexAspTranscript, turns: turns.slice(turnsStartIndex, turnsEnd) } : full.codexAspTranscript;
3323
3349
  return {
3324
3350
  ...full,
@@ -5558,9 +5584,10 @@ var EMAIL_TEMPLATE_KEYS = [...EMAIL_KEYS, ...MANUAL_EMAIL_TEMPLATE_KEYS];
5558
5584
  var MEDIA_KIND = {
5559
5585
  IMAGE: "image",
5560
5586
  VIDEO: "video",
5561
- AUDIO: "audio"
5587
+ AUDIO: "audio",
5588
+ HTML: "html"
5562
5589
  };
5563
- var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
5590
+ var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO, MEDIA_KIND.HTML];
5564
5591
  var MAX_ENGINE_LOG_BYTES = 50 * 1024 * 1024;
5565
5592
 
5566
5593
  // ../shared/src/skill-registry.ts
@@ -9406,13 +9433,14 @@ function isNotFoundError(error) {
9406
9433
  }
9407
9434
 
9408
9435
  // src/managers/codex-asp/codex-history-file.ts
9409
- import { appendFile as appendFile2, readFile as readFile9 } from "fs/promises";
9436
+ import { appendFile as appendFile2, open, readFile as readFile9 } from "fs/promises";
9410
9437
  var CodexHistoryFile = class {
9411
9438
  constructor(filePath) {
9412
9439
  this.filePath = filePath;
9413
9440
  }
9414
9441
  filePath;
9415
9442
  writeLock = new AsyncLock();
9443
+ eventLineIndex = null;
9416
9444
  /** Best-effort ordered append; failures must not disrupt the turn. */
9417
9445
  append(event) {
9418
9446
  void this.writeLock.run(
@@ -9435,6 +9463,88 @@ var CodexHistoryFile = class {
9435
9463
  return { events: [], transcript: null, transcriptsByThreadId: /* @__PURE__ */ new Map() };
9436
9464
  }
9437
9465
  }
9466
+ async loadEventsPage(page) {
9467
+ if (page.limit === void 0) {
9468
+ const history2 = await this.load();
9469
+ return { events: history2.events, eventsStartIndex: 0, totalEvents: history2.events.length };
9470
+ }
9471
+ let file;
9472
+ try {
9473
+ file = await open(this.filePath, "r");
9474
+ } catch (error) {
9475
+ if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) {
9476
+ console.error("[CodexHistoryFile] Failed to open history file:", error);
9477
+ }
9478
+ return { events: [], eventsStartIndex: 0, totalEvents: 0 };
9479
+ }
9480
+ try {
9481
+ const stats = await file.stat();
9482
+ let index = this.eventLineIndex;
9483
+ if (!index || index.size !== stats.size || index.mtimeMs !== stats.mtimeMs) {
9484
+ const starts = stats.size > 0 ? [0] : [];
9485
+ const buffer = Buffer.allocUnsafe(64 * 1024);
9486
+ let position = 0;
9487
+ while (position < stats.size) {
9488
+ const { bytesRead: bytesRead2 } = await file.read(
9489
+ buffer,
9490
+ 0,
9491
+ Math.min(buffer.length, stats.size - position),
9492
+ position
9493
+ );
9494
+ if (bytesRead2 === 0) break;
9495
+ for (let offset = 0; offset < bytesRead2; offset += 1) {
9496
+ const next = position + offset + 1;
9497
+ if (buffer[offset] === 10 && next < stats.size) starts.push(next);
9498
+ }
9499
+ position += bytesRead2;
9500
+ }
9501
+ index = { size: stats.size, mtimeMs: stats.mtimeMs, starts };
9502
+ this.eventLineIndex = index;
9503
+ }
9504
+ const totalEvents2 = index.starts.length;
9505
+ const { startIndex: eventsStartIndex2, endIndex: eventsEnd2 } = getChatHistoryPageWindow(
9506
+ totalEvents2,
9507
+ page.limit,
9508
+ page.beforeEvent
9509
+ );
9510
+ const startByte = index.starts[eventsStartIndex2] ?? index.size;
9511
+ const endByte = index.starts[eventsEnd2] ?? index.size;
9512
+ const content = Buffer.allocUnsafe(endByte - startByte);
9513
+ let bytesRead = 0;
9514
+ while (bytesRead < content.length) {
9515
+ const result = await file.read(
9516
+ content,
9517
+ bytesRead,
9518
+ content.length - bytesRead,
9519
+ startByte + bytesRead
9520
+ );
9521
+ if (result.bytesRead === 0) break;
9522
+ bytesRead += result.bytesRead;
9523
+ }
9524
+ if (bytesRead === content.length) {
9525
+ const parsed = parseAgentEventJsonlWithCodexAspTranscript(content.toString("utf-8"));
9526
+ if (!parsed.transcript && parsed.events.length === eventsEnd2 - eventsStartIndex2) {
9527
+ return { events: parsed.events, eventsStartIndex: eventsStartIndex2, totalEvents: totalEvents2 };
9528
+ }
9529
+ }
9530
+ } catch (error) {
9531
+ console.error("[CodexHistoryFile] Failed to load history page:", error);
9532
+ } finally {
9533
+ await file.close();
9534
+ }
9535
+ const history = await this.load();
9536
+ const totalEvents = history.events.length;
9537
+ const { startIndex: eventsStartIndex, endIndex: eventsEnd } = getChatHistoryPageWindow(
9538
+ totalEvents,
9539
+ page.limit,
9540
+ page.beforeEvent
9541
+ );
9542
+ return {
9543
+ events: history.events.slice(eventsStartIndex, eventsEnd),
9544
+ eventsStartIndex,
9545
+ totalEvents
9546
+ };
9547
+ }
9438
9548
  };
9439
9549
 
9440
9550
  // src/managers/claude-activity.ts
@@ -10563,13 +10673,13 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
10563
10673
  }
10564
10674
  return null;
10565
10675
  }
10566
- async getHistory() {
10676
+ async getHistory(page = {}) {
10567
10677
  await this.initialized;
10568
10678
  await this.historyFile.flush();
10569
- const history = await this.historyFile.load();
10679
+ const history = await this.historyFile.loadEventsPage(page);
10570
10680
  return {
10571
10681
  thread_id: this.sessionId,
10572
- events: history.events
10682
+ ...history
10573
10683
  };
10574
10684
  }
10575
10685
  partialMessageStreamKey(message) {
@@ -10896,7 +11006,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
10896
11006
  var MIN_CODEX_CLI_VERSION = "0.144.6";
10897
11007
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
10898
11008
  var codexCliVersionEnsured = null;
10899
- var ENGINE_PACKAGE_VERSION = "0.1.580";
11009
+ var ENGINE_PACKAGE_VERSION = "0.1.582";
10900
11010
  var INITIALIZE_METHOD = "initialize";
10901
11011
  var INITIALIZED_NOTIFICATION = "initialized";
10902
11012
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -12009,22 +12119,24 @@ var CodexAspManager = class extends CodingAgentManager {
12009
12119
  this.recordUserMessageEvent(request);
12010
12120
  return true;
12011
12121
  }
12012
- async getHistory() {
12122
+ async getHistory(page = {}) {
12013
12123
  if (!this.currentThreadId) {
12014
- return { thread_id: null, events: [], goal: null };
12124
+ return paginateChatHistory({ thread_id: null, events: [], goal: null }, page);
12015
12125
  }
12016
- if (this.activeTurnId && this.codexAspTranscript?.threadId === this.currentThreadId) {
12017
- try {
12018
- const host = await getCodexAspHost();
12019
- await this.refreshThreadGoal(host, this.currentThreadId);
12020
- } catch {
12126
+ if (this.codexAspTranscript?.threadId === this.currentThreadId) {
12127
+ if (page.beforeEvent === void 0 && page.beforeTurn === void 0) {
12128
+ try {
12129
+ const host = await getCodexAspHost();
12130
+ await this.refreshThreadGoal(host, this.currentThreadId);
12131
+ } catch {
12132
+ }
12021
12133
  }
12022
- return {
12134
+ return paginateChatHistory({
12023
12135
  thread_id: this.currentThreadId,
12024
12136
  events: [...this.historyEvents],
12025
12137
  codexAspTranscript: this.codexAspTranscript,
12026
12138
  goal: this.currentGoal
12027
- };
12139
+ }, page);
12028
12140
  }
12029
12141
  try {
12030
12142
  const host = await getCodexAspHost();
@@ -12039,20 +12151,20 @@ var CodexAspManager = class extends CodingAgentManager {
12039
12151
  if (transcript) {
12040
12152
  transcript.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
12041
12153
  }
12042
- return {
12154
+ return paginateChatHistory({
12043
12155
  thread_id: this.currentThreadId,
12044
12156
  events: [...this.historyEvents],
12045
12157
  codexAspTranscript: transcript,
12046
12158
  goal: this.currentGoal
12047
- };
12159
+ }, page);
12048
12160
  } catch {
12049
12161
  }
12050
- return {
12162
+ return paginateChatHistory({
12051
12163
  thread_id: this.currentThreadId,
12052
12164
  events: [...this.historyEvents],
12053
12165
  codexAspTranscript: this.codexAspTranscript,
12054
12166
  goal: this.currentGoal
12055
- };
12167
+ }, page);
12056
12168
  }
12057
12169
  getGoal() {
12058
12170
  return this.currentGoal;
@@ -13260,12 +13372,12 @@ var CursorManager = class extends CodingAgentManager {
13260
13372
  }
13261
13373
  this.agent = null;
13262
13374
  }
13263
- async getHistory() {
13375
+ async getHistory(page = {}) {
13264
13376
  await this.historyFile.flush();
13265
- const history = await this.historyFile.load();
13377
+ const history = await this.historyFile.loadEventsPage(page);
13266
13378
  return {
13267
13379
  thread_id: this.agent?.agentId ?? this.initialSessionId,
13268
- events: history.events,
13380
+ ...history,
13269
13381
  goal: null
13270
13382
  };
13271
13383
  }
@@ -13859,12 +13971,12 @@ var OpencodeManager = class extends CodingAgentManager {
13859
13971
  { throwOnError: true }
13860
13972
  );
13861
13973
  }
13862
- async getHistory() {
13974
+ async getHistory(page = {}) {
13863
13975
  await this.historyFile.flush();
13864
- const history = await this.historyFile.load();
13976
+ const history = await this.historyFile.loadEventsPage(page);
13865
13977
  return {
13866
13978
  thread_id: this.sessionId ?? this.initialSessionId,
13867
- events: history.events,
13979
+ ...history,
13868
13980
  goal: null
13869
13981
  };
13870
13982
  }
@@ -14435,10 +14547,10 @@ var PiManager = class extends CodingAgentManager {
14435
14547
  this.recordUserMessage(session, request.message);
14436
14548
  return true;
14437
14549
  }
14438
- async getHistory() {
14550
+ async getHistory(page = {}) {
14439
14551
  await this.historyFile.flush();
14440
- const history = await this.historyFile.load();
14441
- return { thread_id: this.activeSessionFile ?? this.initialSessionId, events: history.events, goal: null };
14552
+ const history = await this.historyFile.loadEventsPage(page);
14553
+ return { thread_id: this.activeSessionFile ?? this.initialSessionId, ...history, goal: null };
14442
14554
  }
14443
14555
  async listSlashCommands() {
14444
14556
  await this.initialized;
@@ -15155,8 +15267,8 @@ var RelayManager = class {
15155
15267
  async interrupt() {
15156
15268
  return this.inner.interrupt();
15157
15269
  }
15158
- async getHistory() {
15159
- return this.inner.getHistory();
15270
+ async getHistory(page = {}) {
15271
+ return this.inner.getHistory(page);
15160
15272
  }
15161
15273
  async listSlashCommands() {
15162
15274
  return this.inner.listSlashCommands?.() ?? [];
@@ -16419,17 +16531,27 @@ var ChatService = class {
16419
16531
  async getChatHistory(chatId, page = {}) {
16420
16532
  const chat = this.requireChat(chatId);
16421
16533
  const [history, senders] = await Promise.all([
16422
- chat.provider.getHistory(),
16534
+ chat.provider.getHistory(page),
16423
16535
  this.readSenders(chatId)
16424
16536
  ]);
16425
16537
  for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
16426
- if (acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript)) {
16538
+ if (acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) || history.events.some((event) => isSameAcceptedUserEvent(event, acceptedEvent))) {
16427
16539
  chat.acceptedUserEvents.delete(messageId);
16428
16540
  }
16429
16541
  }
16542
+ const isLatestPage = page.beforeEvent === void 0 && page.beforeTurn === void 0;
16430
16543
  const queuedMessageIds = new Set(chat.provider.getQueue().map((message) => message.id));
16431
- const acceptedEvents = [...chat.acceptedUserEvents.entries()].filter(([messageId]) => messageId === chat.activeMessageId || !chat.hasActiveTurn && chat.pendingMessageIds[0] === messageId && !queuedMessageIds.has(messageId)).map(([, acceptedEvent]) => acceptedEvent).filter((acceptedEvent) => !acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) && !history.events.some((event) => isSameAcceptedUserEvent(event, acceptedEvent)));
16544
+ const acceptedEvents = (isLatestPage ? [...chat.acceptedUserEvents.entries()] : []).filter(([messageId]) => messageId === chat.activeMessageId || !chat.hasActiveTurn && chat.pendingMessageIds[0] === messageId && !queuedMessageIds.has(messageId)).map(([, acceptedEvent]) => acceptedEvent).filter((acceptedEvent) => !acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) && !history.events.some((event) => isSameAcceptedUserEvent(event, acceptedEvent)));
16432
16545
  const events = [...history.events, ...acceptedEvents].sort((a, b) => getEventTimestampMs(a) - getEventTimestampMs(b));
16546
+ if (history.eventsStartIndex !== void 0) {
16547
+ return {
16548
+ ...history,
16549
+ events,
16550
+ totalEvents: (history.totalEvents ?? history.eventsStartIndex + history.events.length) + acceptedEvents.length,
16551
+ goal: history.goal ?? chat.provider.getGoal?.() ?? null,
16552
+ senders
16553
+ };
16554
+ }
16433
16555
  return paginateChatHistory({
16434
16556
  thread_id: history.thread_id,
16435
16557
  events,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.580",
3
+ "version": "0.1.582",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",