replicas-engine 0.1.681 → 0.1.683

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.
@@ -747,6 +747,44 @@ function coerceAuthFallbackPayload(payload) {
747
747
  ...typeof detail === "string" && detail ? { detail } : {}
748
748
  };
749
749
  }
750
+ var CHAT_GOAL_STATUSES = [
751
+ "active",
752
+ "paused",
753
+ "blocked",
754
+ "usageLimited",
755
+ "budgetLimited",
756
+ "complete"
757
+ ];
758
+ function isChatGoalStatus(value) {
759
+ return typeof value === "string" && CHAT_GOAL_STATUSES.some((status) => status === value);
760
+ }
761
+ function coerceChatGoalPayload(payload) {
762
+ const goal = payload.goal;
763
+ if (goal === null || goal === void 0) return null;
764
+ if (typeof goal !== "object") return null;
765
+ const candidate = goal;
766
+ const threadId = candidate.threadId;
767
+ const objective = candidate.objective;
768
+ const status = candidate.status;
769
+ const tokensUsed = candidate.tokensUsed;
770
+ const timeUsedSeconds = candidate.timeUsedSeconds;
771
+ const createdAt = candidate.createdAt;
772
+ const updatedAt = candidate.updatedAt;
773
+ const tokenBudget = candidate.tokenBudget ?? null;
774
+ 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") {
775
+ return null;
776
+ }
777
+ return {
778
+ threadId,
779
+ objective,
780
+ status,
781
+ tokenBudget,
782
+ tokensUsed,
783
+ timeUsedSeconds,
784
+ createdAt,
785
+ updatedAt
786
+ };
787
+ }
750
788
  var CONTEXT_USAGE_EVENT_TYPE = "context-usage";
751
789
 
752
790
  // ../shared/src/engine/v1.ts
@@ -1632,7 +1670,7 @@ For any external destination, continue with the required delivery flow in \`MEDI
1632
1670
  ## Command reference
1633
1671
 
1634
1672
  ### \`replicas computer info\`
1635
- Prints the live desktop viewer URL (\`https://6080-<hash>.tryreplicas.com/\`). The engine auto-registers this authenticated preview on startup; \`info\` just looks it up. Use it when you want to share the live stream URL with the user out-of-band (e.g. in a Slack reply) - the dashboard \`Desktop\` tab already points at the same URL automatically.
1673
+ Prints the live desktop viewer URL (\`https://6080-<hash>.replicas.dev/\`). The engine auto-registers this authenticated preview on startup; \`info\` just looks it up. Use it when you want to share the live stream URL with the user out-of-band (e.g. in a Slack reply) - the dashboard \`Desktop\` tab already points at the same URL automatically.
1636
1674
 
1637
1675
  If invoked very early in the workspace lifecycle, \`info\` will poll briefly while the engine finishes registering the preview, then error if it's still not available.
1638
1676
 
@@ -2180,7 +2218,7 @@ Names are normalized to hyphen-case (spaces become hyphens, e.g. \`Login page\`
2180
2218
  For each media file, the CLI prints two lines:
2181
2219
 
2182
2220
  1. \`![filename](<api-url>)\` \u2014 the chat embed (renders inline in Replicas chat **only** \u2014 see below).
2183
- 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.
2221
+ 2. \`View in Replicas: <deep-link>\` \u2014 a per-file dashboard URL of the form \`https://replicas.dev/workspaces/<workspace-id>?media=<media-id>\` that opens directly to that specific file.
2184
2222
 
2185
2223
  Match each "View in Replicas" line to the embed line directly above it \u2014 that's the deep link for that file.
2186
2224
 
@@ -2206,14 +2244,14 @@ replicas media upload screenshot.png --name "Login page" --access public
2206
2244
 
2207
2245
  | URL | What it is | Where it's allowed |
2208
2246
  |---|---|---|
2209
- | \`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. |
2210
- | \`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. |
2211
- | \`https://api.tryreplicas.com/v1/media/public/<id>/<token>\` | Opt-in public bearer URL printed by \`--access public\` or \`--share\` | GitHub embeds for image, video, or audio media. |
2212
- | \`https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>\` | Authenticated dashboard deep link that opens the media file in the workspace | Everywhere external: Slack, GitHub, GitLab, Linear, PR bodies, comments, emails. |
2247
+ | \`https://api.replicas.dev/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. |
2248
+ | \`https://replicas.dev/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. |
2249
+ | \`https://api.replicas.dev/v1/media/public/<id>/<token>\` | Opt-in public bearer URL printed by \`--access public\` or \`--share\` | GitHub embeds for image, video, or audio media. |
2250
+ | \`https://replicas.dev/workspaces/<workspace-id>?media=<media-id>\` | Authenticated dashboard deep link that opens the media file in the workspace | Everywhere external: Slack, GitHub, GitLab, Linear, PR bodies, comments, emails. |
2213
2251
 
2214
2252
  ### NEVER link the API redirect URL outside the Replicas chat reply
2215
2253
 
2216
- The \`api.tryreplicas.com/v1/media/<id>\` URL is **not public**. It's an authenticated redirect that only resolves to a presigned download when the Replicas chat session token is present. Anywhere else (Slack, Linear, GitHub, a browser tab the user opens directly, a customer copy-pasting from chat) it returns \`{"error":"Missing authorization token"}\` and renders as a broken image.
2254
+ The \`api.replicas.dev/v1/media/<id>\` URL is **not public**. It's an authenticated redirect that only resolves to a presigned download when the Replicas chat session token is present. Anywhere else (Slack, Linear, GitHub, a browser tab the user opens directly, a customer copy-pasting from chat) it returns \`{"error":"Missing authorization token"}\` and renders as a broken image.
2217
2255
 
2218
2256
  This rule has no exceptions:
2219
2257
  - \u274C Never paste it in a Slack message \u2014 upload the actual bytes via the 3-step \`files.getUploadURLExternal\` \u2192 POST bytes \u2192 \`files.completeUploadExternal\` flow instead (see below).
@@ -2228,7 +2266,7 @@ This rule has no exceptions:
2228
2266
  For an organization-only HTML page, use the \`Forge link\` printed by \`--access organization\`. For other authenticated media references, use the dashboard form:
2229
2267
 
2230
2268
  \`\`\`
2231
- https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>
2269
+ https://replicas.dev/workspaces/<workspace-id>?media=<media-id>
2232
2270
  \`\`\`
2233
2271
 
2234
2272
  The \`media=<media-id>\` query param opens that specific file in the workspace.
@@ -2256,7 +2294,7 @@ Slack, Linear, and GitLab receive the original bytes through their native upload
2256
2294
 
2257
2295
  ### Slack \u2014 embed natively via the 3-step external file upload flow
2258
2296
 
2259
- A Slack message that just contains a \`tryreplicas.com\` link is not acceptable when the message is supposed to show an image. The recipient should see the image inline in the thread.
2297
+ A Slack message that just contains a \`replicas.dev\` link is not acceptable when the message is supposed to show an image. The recipient should see the image inline in the thread.
2260
2298
 
2261
2299
  Slack deprecated the single-shot \`files.upload\` endpoint. The raw HTTP replacement is a 3-step flow: reserve an upload URL with \`files.getUploadURLExternal\`, POST the bytes to it, then share into the channel/thread with \`files.completeUploadExternal\`. (\`files.uploadV2\` is **only** an SDK convenience wrapper \u2014 it is NOT a real Web API HTTP endpoint and \`POST https://slack.com/api/files.uploadV2\` will return \`unknown_method\`.)
2262
2300
 
@@ -2288,7 +2326,7 @@ curl -s -X POST "https://slack.com/api/files.completeUploadExternal" \\
2288
2326
  \\"files\\": [{\\"id\\": \\"$FILE_ID\\", \\"title\\": \\"Screenshot\\"}],
2289
2327
  \\"channel_id\\": \\"CHANNEL_ID\\",
2290
2328
  \\"thread_ts\\": \\"THREAD_TS\\",
2291
- \\"initial_comment\\": \\"<https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>|View in Replicas>\\"
2329
+ \\"initial_comment\\": \\"<https://replicas.dev/workspaces/<workspace-id>?media=<media-id>|View in Replicas>\\"
2292
2330
  }"
2293
2331
  \`\`\`
2294
2332
 
@@ -2299,7 +2337,7 @@ Notes:
2299
2337
  - For multiple files in the same message, run steps 1\u20132 per file and pass all \`{id, title}\` objects in the \`files\` array of a single \`files.completeUploadExternal\` call \u2014 they share into the thread together.
2300
2338
  - If you also need to post a separate text reply in the thread, send it with \`chat.postMessage\` after the upload \u2014 but the upload itself must carry the image.
2301
2339
 
2302
- Never paste the \`api.tryreplicas.com/v1/media/<id>\` URL into a Slack message body or \`initial_comment\` \u2014 it will render as a broken image attempt.
2340
+ Never paste the \`api.replicas.dev/v1/media/<id>\` URL into a Slack message body or \`initial_comment\` \u2014 it will render as a broken image attempt.
2303
2341
 
2304
2342
  ### Linear \u2014 embed natively via the GraphQL \`fileUpload\` mutation
2305
2343
 
@@ -2348,7 +2386,7 @@ curl -s -X PUT "$UPLOAD_URL" \\
2348
2386
  --data-binary @"$FILE"
2349
2387
 
2350
2388
  # 3. Reference assetUrl inline in the comment body, alongside the dashboard link.
2351
- BODY=$(printf '![screenshot](%s)\\n\\n[View in Replicas](https://tryreplicas.com/workspaces/%s?media=%s)' \\
2389
+ BODY=$(printf '![screenshot](%s)\\n\\n[View in Replicas](https://replicas.dev/workspaces/%s?media=%s)' \\
2352
2390
  "$ASSET_URL" "$REPLICAS_WORKSPACE_ID" "$MEDIA_ID")
2353
2391
 
2354
2392
  jq -n \\
@@ -2362,7 +2400,7 @@ jq -n \\
2362
2400
 
2363
2401
  The same flow works for issue bodies (use \`issueCreate\` / \`issueUpdate\` with the same markdown body).
2364
2402
 
2365
- Never paste the \`api.tryreplicas.com/v1/media/<id>\` URL into a Linear body or comment \u2014 it will render as a broken image to anyone outside your Replicas chat.
2403
+ Never paste the \`api.replicas.dev/v1/media/<id>\` URL into a Linear body or comment \u2014 it will render as a broken image to anyone outside your Replicas chat.
2366
2404
 
2367
2405
  ### GitHub: inline screenshots and recording previews
2368
2406
 
@@ -6704,7 +6742,7 @@ var DEFAULT_CODEX_ARGS = [
6704
6742
  var MIN_CODEX_CLI_VERSION = "0.144.6";
6705
6743
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
6706
6744
  var codexCliVersionEnsured = null;
6707
- var ENGINE_PACKAGE_VERSION = "0.1.681";
6745
+ var ENGINE_PACKAGE_VERSION = "0.1.683";
6708
6746
  var INITIALIZE_METHOD = "initialize";
6709
6747
  var INITIALIZED_NOTIFICATION = "initialized";
6710
6748
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -6985,6 +7023,7 @@ export {
6985
7023
  AUTH_RETRY_STATUS_EVENT_TYPE,
6986
7024
  CURSOR_CONNECTION_STATUS_EVENT_TYPE,
6987
7025
  AUTH_FALLBACK_EVENT_TYPE,
7026
+ coerceChatGoalPayload,
6988
7027
  CONTEXT_USAGE_EVENT_TYPE,
6989
7028
  detectLanguageByPath,
6990
7029
  ASTER_PROVIDER,
@@ -7085,6 +7124,7 @@ export {
7085
7124
  parseChatHistoryCursor,
7086
7125
  createChatHistoryCursor,
7087
7126
  chatHistoryPageParamsFromQuery,
7127
+ getChatHistoryPageWindow,
7088
7128
  paginateChatHistory,
7089
7129
  CODEX_AUTH_ENV_KEYS,
7090
7130
  codexAuthEnvFromResponse,
@@ -7,7 +7,7 @@ import {
7
7
  headlessAgentRequestSchema,
8
8
  putPresignedFile,
9
9
  recoverCompletedTurn
10
- } from "./chunk-HWSR36NN.js";
10
+ } from "./chunk-TQJ3KKVC.js";
11
11
 
12
12
  // src/headless-agent.ts
13
13
  import { createHash } from "crypto";
package/dist/src/index.js CHANGED
@@ -106,6 +106,7 @@ import {
106
106
  codexAuthEnvFromResponse,
107
107
  codexReasoningEffortForThinkingLevel,
108
108
  coerceBackgroundTaskPayload,
109
+ coerceChatGoalPayload,
109
110
  coerceClaudeResultPayload,
110
111
  createAcceptedUserMessageEvent,
111
112
  createChatHistoryCursor,
@@ -127,6 +128,7 @@ import {
127
128
  findGitCommitSignals,
128
129
  findPrMergeSignals,
129
130
  getChatHistoryPageSenders,
131
+ getChatHistoryPageWindow,
130
132
  getChatTranscriptHistorySize,
131
133
  getClaudeModelContextWindow,
132
134
  getCodexAspTurnResponse,
@@ -190,7 +192,7 @@ import {
190
192
  serializeCanvasContentResponse,
191
193
  shellQuotePosix,
192
194
  stripAgentDiagnosticErrors
193
- } from "./chunk-HWSR36NN.js";
195
+ } from "./chunk-TQJ3KKVC.js";
194
196
 
195
197
  // src/index.ts
196
198
  import { serve } from "@hono/node-server";
@@ -5772,7 +5774,6 @@ var DEFAULT_MODEL = DEFAULT_CODEX_MODEL;
5772
5774
  var THREAD_START_METHOD = "thread/start";
5773
5775
  var THREAD_RESUME_METHOD = "thread/resume";
5774
5776
  var THREAD_READ_METHOD = "thread/read";
5775
- var THREAD_TURNS_LIST_METHOD = "thread/turns/list";
5776
5777
  var THREAD_GOAL_SET_METHOD = "thread/goal/set";
5777
5778
  var THREAD_GOAL_GET_METHOD = "thread/goal/get";
5778
5779
  var THREAD_GOAL_CLEAR_METHOD = "thread/goal/clear";
@@ -6510,6 +6511,7 @@ var CodexAspManager = class extends CodingAgentManager {
6510
6511
  codexAspSequence = 0;
6511
6512
  quotaStatus = new CodexQuotaStatusTracker();
6512
6513
  currentGoal = null;
6514
+ goalHistoryKnown = false;
6513
6515
  transcriptUpdateCoalescer = new TranscriptUpdateCoalescer((threadId) => {
6514
6516
  this.flushTranscriptUpdated(threadId);
6515
6517
  });
@@ -6530,6 +6532,11 @@ var CodexAspManager = class extends CodingAgentManager {
6530
6532
  if (replayed?.transcript && (!this.initialSessionId || replayed.transcript.threadId === this.initialSessionId)) {
6531
6533
  this.mergeTranscriptSnapshot(replayed.transcript);
6532
6534
  }
6535
+ const goalEvent = replayed?.events.findLast((event) => event.type === CHAT_GOAL_EVENT_TYPE);
6536
+ if (goalEvent) {
6537
+ this.currentGoal = coerceChatGoalPayload(goalEvent.payload);
6538
+ this.goalHistoryKnown = true;
6539
+ }
6533
6540
  this.currentThreadId = this.initialSessionId ?? replayed?.transcript?.threadId ?? null;
6534
6541
  }
6535
6542
  getHistorySink() {
@@ -6614,38 +6621,45 @@ var CodexAspManager = class extends CodingAgentManager {
6614
6621
  return true;
6615
6622
  }
6616
6623
  async getHistory(page = {}) {
6624
+ await this.initialized;
6617
6625
  await this.historyFile?.flush();
6626
+ const transcript = await this.ensureTranscriptHistory();
6627
+ const events = await this.historyFile?.loadEventsPage(page) ?? { events: [], beforeCursor: null };
6618
6628
  if (page.limit === void 0) {
6619
- const local = async () => {
6620
- const history = await this.historyFile?.load();
6621
- return {
6622
- thread_id: this.currentThreadId,
6623
- events: history?.events ?? [],
6624
- codexAspTranscript: this.currentThreadId ? history?.transcriptsByThreadId.get(this.currentThreadId) ?? null : null,
6625
- goal: this.currentGoal
6626
- };
6629
+ return {
6630
+ thread_id: this.currentThreadId,
6631
+ events: events.events,
6632
+ codexAspTranscript: transcript,
6633
+ goal: this.currentGoal
6627
6634
  };
6628
- if (!this.currentThreadId) return local();
6629
- return this.readFullNativeHistory().catch(local);
6630
6635
  }
6631
- const events = await this.historyFile?.loadEventsPage(page) ?? { events: [], beforeCursor: null };
6632
6636
  if (!this.currentThreadId) return { thread_id: null, ...events, goal: this.currentGoal };
6633
6637
  const cursor = parseChatHistoryCursor(page.beforeCursor);
6634
6638
  if (page.beforeCursor !== void 0 && cursor?.mode !== "range") throw new RangeError("Invalid chat history cursor");
6635
- let transcript = null;
6639
+ if (page.beforeCursor === void 0 && !this.goalHistoryKnown) {
6640
+ try {
6641
+ await this.refreshThreadGoal(await getCodexAspHost(), this.currentThreadId);
6642
+ } catch (error) {
6643
+ console.warn("[CodexAspManager] Failed to connect while refreshing thread goal:", error);
6644
+ }
6645
+ }
6646
+ let transcriptPage = null;
6636
6647
  let turnsBefore = null;
6637
- if (cursor?.turns !== null) {
6638
- const host = await getCodexAspHost();
6639
- if (page.beforeCursor === void 0) await this.refreshThreadGoal(host, this.currentThreadId);
6640
- const turns = await this.readNativeTurnsPage(page.limit, cursor?.turns, host);
6641
- transcript = turns.transcript;
6642
- turnsBefore = turns.before;
6648
+ if (cursor?.turns !== null && transcript) {
6649
+ const before = cursor?.turns === void 0 ? transcript.turns.length : /^\d+$/.test(cursor.turns) ? Number(cursor.turns) : Number.NaN;
6650
+ if (!Number.isSafeInteger(before) || before < 0) throw new RangeError("Invalid chat history cursor");
6651
+ const { startIndex, endIndex } = getChatHistoryPageWindow(transcript.turns.length, page.limit, before);
6652
+ transcriptPage = {
6653
+ ...transcript,
6654
+ turns: transcript.turns.slice(startIndex, endIndex)
6655
+ };
6656
+ turnsBefore = startIndex > 0 ? String(startIndex) : null;
6643
6657
  }
6644
6658
  const eventCursor = parseChatHistoryCursor(events.beforeCursor ?? void 0);
6645
6659
  return {
6646
6660
  thread_id: this.currentThreadId,
6647
6661
  ...events,
6648
- codexAspTranscript: transcript,
6662
+ codexAspTranscript: transcriptPage,
6649
6663
  goal: this.currentGoal,
6650
6664
  beforeCursor: createChatHistoryCursor({
6651
6665
  events: eventCursor?.events ?? null,
@@ -6653,54 +6667,14 @@ var CodexAspManager = class extends CodingAgentManager {
6653
6667
  })
6654
6668
  };
6655
6669
  }
6656
- async readFullNativeHistory() {
6657
- if (!this.currentThreadId) {
6658
- return { thread_id: null, events: [], goal: this.currentGoal };
6659
- }
6670
+ async ensureTranscriptHistory() {
6671
+ if (this.codexAspTranscript || !this.currentThreadId) return this.codexAspTranscript;
6660
6672
  const host = await getCodexAspHost();
6661
- const [response, history] = await Promise.all([
6662
- host.client.request(
6663
- THREAD_READ_METHOD,
6664
- { threadId: this.currentThreadId, includeTurns: true }
6665
- ),
6666
- this.historyFile?.load()
6667
- ]);
6668
- const transcript = this.mergeTranscriptSnapshot(threadToAspTranscript(response.thread));
6669
- return {
6670
- thread_id: this.currentThreadId,
6671
- events: history?.events ?? [],
6672
- codexAspTranscript: transcript,
6673
- goal: this.currentGoal
6674
- };
6675
- }
6676
- async readNativeTurnsPage(limit, cursor, host) {
6677
- if (!this.currentThreadId) throw new Error("A thread is required for native turn pagination");
6678
6673
  const response = await host.client.request(
6679
- THREAD_TURNS_LIST_METHOD,
6680
- {
6681
- threadId: this.currentThreadId,
6682
- cursor: cursor ?? null,
6683
- limit,
6684
- sortDirection: "desc",
6685
- itemsView: "full"
6686
- }
6687
- );
6688
- const nativeTranscript = turnsToAspTranscript(
6689
- this.currentThreadId,
6690
- (/* @__PURE__ */ new Date()).toISOString(),
6691
- response.data
6674
+ THREAD_READ_METHOD,
6675
+ { threadId: this.currentThreadId, includeTurns: true }
6692
6676
  );
6693
- const mergedTranscript = mergeCodexAspTranscripts(this.codexAspTranscript, nativeTranscript) ?? nativeTranscript;
6694
- this.codexAspTranscript = mergedTranscript;
6695
- this.syncTranscriptSequence(mergedTranscript);
6696
- const turnIds = new Set(response.data.map((turn) => turn.id));
6697
- return {
6698
- transcript: {
6699
- ...mergedTranscript,
6700
- turns: mergedTranscript.turns.filter((turn) => turnIds.has(turn.id))
6701
- },
6702
- before: response.nextCursor
6703
- };
6677
+ return this.mergeTranscriptSnapshot(threadToAspTranscript(response.thread));
6704
6678
  }
6705
6679
  getGoal() {
6706
6680
  return this.currentGoal;
@@ -7619,6 +7593,7 @@ var CodexAspManager = class extends CodingAgentManager {
7619
7593
  { threadId }
7620
7594
  );
7621
7595
  this.currentGoal = response.goal ? aspGoalToChatGoal(response.goal) : null;
7596
+ this.goalHistoryKnown = true;
7622
7597
  } catch (error) {
7623
7598
  console.warn("[CodexAspManager] Failed to read ASP thread goal:", error);
7624
7599
  }
@@ -7629,6 +7604,7 @@ var CodexAspManager = class extends CodingAgentManager {
7629
7604
  return;
7630
7605
  }
7631
7606
  this.currentGoal = nextGoal;
7607
+ this.goalHistoryKnown = true;
7632
7608
  const event = this.recordCodexHistoryEvent(
7633
7609
  CHAT_GOAL_EVENT_TYPE,
7634
7610
  { goal: nextGoal }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.681",
3
+ "version": "0.1.683",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",