@meistrari/remy-cli 1.25.0 → 1.26.0-rc.0

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 (3) hide show
  1. package/README.md +5 -3
  2. package/dist/remy.js +322 -124
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -40,6 +40,8 @@ On first use, Remy opens browser-based device authorization, validates your Codi
40
40
 
41
41
  Run `remy update` to install a newer CLI release using the package manager that owns your installation (mise, Bun, pnpm, or npm). No sign-in is required. Use `remy --version` to print the installed version.
42
42
 
43
+ For mise installations, updates change the existing owning TOML or `.tool-versions` config to the exact new version and keep the previous installation available for running processes. Shared-cache npm installs are supported.
44
+
43
45
  Stable versions check npm’s `latest` channel. Release candidates check both `rc` and `latest` and choose the newer version; Remy never downgrades. To switch from stable to RC, use the explicit `@rc` install command above.
44
46
 
45
47
  Each interactive `remy` or `remy dashboard` startup checks for an update before sign-in or the splash. When one is available, type `y` or `yes`, then Enter, to upgrade. Press Enter or type `n` to continue with the current version. Lookup failures or a two-second timeout let startup continue. After a successful upgrade, Remy exits and asks you to run it again. Installation failures exit with an error so you can resolve the package-manager problem and retry.
@@ -68,7 +70,7 @@ The new-session request and conversation composers share path completion and att
68
70
 
69
71
  Terminal follow-ups default to **Steer**, targeting the work currently in progress. Press `Ctrl+Enter` to queue a message as **Next**, which waits for current work to finish. Plain `Enter` continues to send as Steer. Failed submissions retain their mode, attachments, settings, and idempotency key when retried with `Ctrl+R`. Remy can replay sessions containing queued-message dispatch, withdrawal, and failure events. Messages withdrawn or failed before reaching Remy show an activity notice; an active submission ends as cancelled or failed instead of waiting for a turn that never started.
70
72
 
71
- Use Up/Down to select a session in the dashboard, Left/Right to load the adjacent page, and `Enter` to open it. Remy loads the complete retained conversation before showing the session view, then streams new activity as it arrives. Press `s` to search the loaded page by session ID, title, or number; `Enter` keeps the search active and `Esc` clears it. Press `f` to filter sessions by lifecycle status or `a` to filter by author. Both pickers use checkboxes: Up/Down or `j`/`k` moves, `Space` toggles, Right/`l` checks, Left/`h` clears, `G` and `gg` jump to the last and first option, and `Enter` applies every checked value. An empty picker means all values. Press `s` inside either picker to fuzzy-search its options; `Enter` keeps the matching list and `Esc` cancels the search. Press `Esc` outside search to discard staged checkbox changes. Filters apply to the complete remote result set and reset pagination to its first page. After the dashboard list or repository-review list has focus, Vim keys work too: `j`/`k` move, `h`/`l` go to the previous/next dashboard page (or clear/mark a repository), `G` goes to the last row, and `gg` goes to the first row. Press `Shift+L` in the dashboard to start a logout confirmation. In any message composer, plain `Enter` and `Shift+Enter` submit; `Option+Enter` on macOS (`Alt+Enter` elsewhere) inserts a newline. `Ctrl+J` also inserts a newline for terminals that do not report the Option/Alt modifier. `Cmd+Left`/`Cmd+Right` move to the start or end of the current line, as do `Home`/`End`, which many macOS terminals send for those keys; `Ctrl+D` deletes the character after the cursor. In the session view, `Ctrl+C` first clears a non-empty draft; press it again with an empty composer to exit. The conversation header keeps the numbered session title on its first line and repository, pull request, work state, elapsed time, connection state, and the agent's current `Harness · Model · Effort` on its second line. The header also keeps the last provider-reported context observation across reconnects, such as `Context 50% left · 62,000 used`; when the provider does not report a context window, it shows only used tokens. This is an observation, not a live occupancy guarantee or billing total.
73
+ Use Up/Down to select a session in the dashboard, Left/Right to load the adjacent page, and `Enter` to open it. Remy shows the session header and composer immediately after session details load, while retained conversation history loads in the background. You can start drafting immediately; sending is paused until history finishes, and your draft stays intact. New activity then streams as it arrives. Press `s` to fuzzy-search all accessible sessions through the API by author, repository, branch name, pull request title/number, or session number/ID/title; `Enter` applies immediately and keeps the search active; `Esc` clears it. Typing refreshes results after a short pause. Matching ignores case and query whitespace and finds characters in order within a field (for example, `grch` matches `Grace Hopper`); numbers also accept `#`. Search combines with your other filters and stays active across pages. Session rows and compact cards show the repository and session work branch (`Branch pending` when unassigned). Press `f` to filter sessions by lifecycle status, `a` by author, or `r` by repository. These pickers use checkboxes: Up/Down or `j`/`k` moves, `Space` toggles, Right/`l` checks, Left/`h` clears, `G` and `gg` jump to the last and first option, and `Enter` applies every checked value. An empty picker means all values. Press `s` inside a picker to fuzzy-search its options; `Enter` keeps the matching list and `Esc` cancels the search. Press `Esc` outside search to discard staged checkbox changes. Press `b` to filter by a case-insensitive part of a branch name; Enter applies it, an empty value clears it, and Esc discards edits. Repository and branch filters must match the same repository in a session. Filters apply to the complete remote result set and reset pagination to its first page. After the dashboard list or repository-review list has focus, Vim keys work too: `j`/`k` move, `h`/`l` go to the previous/next dashboard page (or clear/mark a repository), `G` goes to the last row, and `gg` goes to the first row. Press `Shift+L` in the dashboard to start a logout confirmation. In any message composer, plain `Enter` and `Shift+Enter` submit; `Option+Enter` on macOS (`Alt+Enter` elsewhere) inserts a newline. `Ctrl+J` also inserts a newline for terminals that do not report the Option/Alt modifier. `Cmd+Left`/`Cmd+Right` move to the start or end of the current line, as do `Home`/`End`, which many macOS terminals send for those keys; `Ctrl+D` deletes the character after the cursor. In the session view, `Ctrl+C` first clears a non-empty draft; press it again with an empty composer to exit. The conversation header keeps the numbered session title on its first line and repository, pull request, work state, elapsed time, connection state, and the agent's current `Harness · Model · Effort` on its second line. The header also keeps the last provider-reported context observation across reconnects, such as `Context 50% left · 62,000 used`; when the provider does not report a context window, it shows only used tokens. This is an observation, not a live occupancy guarantee or billing total.
72
74
 
73
75
  If a connected session loses its activity stream, Remy retries for up to two minutes and resumes from the last received event. After the live stream stays connected for at least 30 seconds, the next disconnect starts a fresh retry window. Connections that drop sooner share the existing window and backoff. While a submitted message is still awaiting its outcome, retries continue beyond that window.
74
76
 
@@ -98,7 +100,7 @@ Command and attachment feedback share progress, success, failure, and attention
98
100
 
99
101
  Enter `/compact` to admit a durable main-thread context compaction control without sending a human message. Remy reports request admission immediately and shows the eventual completed, no-op, failed, or unknown provider result in retained activity. Compaction waits for a safe point and does not interrupt active work or jump ahead of an earlier Next message. While admission is pending, you can keep editing or open other commands; repeated `/compact` invocations do not send another request.
100
102
 
101
- Drag to select visible text in any Remy view; Remy copies it to the local clipboard and emits OSC 52 for terminal or remote-session clipboard support, then clears the selection highlight. Published file artifacts show their filename and muted, full web preview URL; open the URL in a browser to select that artifact in its session. Tela Page rows retain their canonical Page URL. Downloads are explicit through `/files`; artifact bytes are never printed in the terminal. When the conversation has focus, press `Tab` to return to the composer.
103
+ Drag to select visible text in any Remy view; Remy copies it to the local clipboard and emits OSC 52 for terminal or remote-session clipboard support, then clears the selection highlight. Published file artifacts show their filename and muted, full web preview URL; open the URL in a browser to select that artifact in its session. Tela Page rows retain their canonical Page URL. Downloads are explicit through `/files`; artifact bytes are never printed in the terminal. Clicking the conversation keeps keyboard focus in the composer. Mouse scrolling and drag-to-select still work. Code blocks and expanded code/output use colored text on a distinct background without copyable gutter bars or added line indentation.
102
104
 
103
105
  | Control | Result |
104
106
  | --- | --- |
@@ -129,7 +131,7 @@ After a session becomes terminal, press `Esc` to return to the dashboard or `n`
129
131
 
130
132
  `Shift+L` in the dashboard and `/logout` in a session ask for confirmation; press `y` to sign out or `Esc` to cancel. In the dashboard and repository-review list, Vim navigation is also available: `j`/`k` move, `h`/`l` change dashboard pages (or clear/mark a repository), `G` goes to the last row, and `gg` goes to the first row.
131
133
 
132
- Drag to select visible text in any Remy view; Remy copies it to the local clipboard and emits OSC 52 for terminal or remote-session clipboard support, then clears the selection highlight. When the conversation has focus, press `Tab` to return to the composer.
134
+ Drag to select visible text in any Remy view; Remy copies it to the local clipboard and emits OSC 52 for terminal or remote-session clipboard support, then clears the selection highlight. Clicking the conversation keeps keyboard focus in the composer. Mouse scrolling and drag-to-select still work. Code blocks and expanded code/output use colored text on a distinct background without copyable gutter bars or added line indentation.
133
135
 
134
136
  </details>
135
137
 
package/dist/remy.js CHANGED
@@ -33544,7 +33544,8 @@ var sessionDetailResponseSchema = exports_external2.object({
33544
33544
  }).passthrough()),
33545
33545
  repositories: exports_external2.array(exports_external2.strictObject({
33546
33546
  id: exports_external2.string(),
33547
- full_name: exports_external2.string()
33547
+ full_name: exports_external2.string(),
33548
+ branch_name: exports_external2.string().nullable().optional()
33548
33549
  })),
33549
33550
  connections: exports_external2.array(sessionConnectionSchema).max(1),
33550
33551
  artifacts: exports_external2.array(sessionArtifactSchema),
@@ -33594,7 +33595,8 @@ var sessionSummarySchema = exports_external2.object({
33594
33595
  source: sessionSourceSchema,
33595
33596
  repositories: exports_external2.array(exports_external2.strictObject({
33596
33597
  id: exports_external2.string(),
33597
- full_name: exports_external2.string()
33598
+ full_name: exports_external2.string(),
33599
+ branch_name: exports_external2.string().nullable().default(null)
33598
33600
  })),
33599
33601
  created_by: sessionCreatorSchema,
33600
33602
  created_at: exports_external2.string(),
@@ -33747,6 +33749,9 @@ async function listSessions({
33747
33749
  after,
33748
33750
  statuses,
33749
33751
  creatorUserIds,
33752
+ repositoryIds,
33753
+ branchName,
33754
+ search,
33750
33755
  signal
33751
33756
  }) {
33752
33757
  const searchParams = new URLSearchParams;
@@ -33758,6 +33763,12 @@ async function listSessions({
33758
33763
  searchParams.append("status", status);
33759
33764
  for (const creatorUserId of creatorUserIds ?? [])
33760
33765
  searchParams.append("creator_user_id", creatorUserId);
33766
+ for (const repositoryId of repositoryIds ?? [])
33767
+ searchParams.append("repository_id", repositoryId);
33768
+ if (branchName)
33769
+ searchParams.set("branch_name", branchName);
33770
+ if (search)
33771
+ searchParams.set("search", search);
33761
33772
  const suffix = searchParams.size > 0 ? `?${searchParams.toString()}` : "";
33762
33773
  const response = await client.request(`/v1/sessions${suffix}`, { signal });
33763
33774
  const parsed = sessionListResponseSchema.safeParse(await parseJson2(response, "Session list response was not valid JSON."));
@@ -33777,7 +33788,7 @@ async function listSessions({
33777
33788
  draft: pullRequest.draft
33778
33789
  })),
33779
33790
  source: sessionSourceLabel(session.source),
33780
- repositories: session.repositories.map((repository) => ({ id: repository.id, fullName: repository.full_name })),
33791
+ repositories: session.repositories.map((repository) => ({ id: repository.id, fullName: repository.full_name, branchName: repository.branch_name })),
33781
33792
  creator: sessionCreatorLabel(session.created_by),
33782
33793
  createdAt: session.created_at,
33783
33794
  updatedAt: session.updated_at
@@ -34592,17 +34603,23 @@ function formatSessionJson({
34592
34603
  `;
34593
34604
  }
34594
34605
  // src/version.ts
34595
- var remyCliVersion = "1.25.0";
34606
+ var remyCliVersion = "1.26.0-rc.0";
34596
34607
 
34597
34608
  // src/self-update.ts
34598
34609
  import { spawn } from "child_process";
34599
34610
  import { realpath } from "fs/promises";
34600
- import { join as join3, relative, isAbsolute } from "path";
34611
+ import { join as join3, isAbsolute } from "path";
34601
34612
  import { createInterface } from "readline/promises";
34602
34613
  var {semver } = globalThis.Bun;
34603
34614
  var packageName = "@meistrari/remy-cli";
34604
34615
  var versionSchema = exports_external2.string().regex(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-rc\.(0|[1-9]\d*))?$/);
34605
34616
  var tagsSchema = exports_external2.object({ latest: versionSchema, rc: versionSchema.optional() });
34617
+ var miseInstallationsSchema = exports_external2.array(exports_external2.object({
34618
+ install_path: exports_external2.string(),
34619
+ active: exports_external2.boolean(),
34620
+ installed: exports_external2.boolean(),
34621
+ source: exports_external2.object({ type: exports_external2.string(), path: exports_external2.string() }).optional()
34622
+ }));
34606
34623
  function createCliUpdates({
34607
34624
  currentVersion,
34608
34625
  entryPath,
@@ -34650,11 +34667,26 @@ function createCliUpdates({
34650
34667
  };
34651
34668
  let argv;
34652
34669
  let updatedEntry = runningEntry;
34653
- const miseRoot = await probe(["mise", "where", `npm:${packageName}@${currentVersion}`]);
34654
- const miseRelative = miseRoot ? relative(miseRoot, runningEntry) : "";
34655
- const usesMise = miseRoot !== "" && miseRelative !== "" && !miseRelative.startsWith("..") && !isAbsolute(miseRelative);
34670
+ const miseRoot = await probe(["mise", "where", `npm:${packageName}`]);
34671
+ let miseRelative = "";
34672
+ if (miseRoot) {
34673
+ for (const layout of ["node_modules", "lib/node_modules"]) {
34674
+ const candidate = join3(layout, packageName, "dist", "remy.js");
34675
+ if (await matchesEntry(join3(miseRoot, candidate))) {
34676
+ miseRelative = candidate;
34677
+ break;
34678
+ }
34679
+ }
34680
+ }
34681
+ const usesMise = miseRelative !== "";
34656
34682
  if (usesMise) {
34657
- argv = ["mise", "upgrade", `npm:${packageName}@${version5}`, "--bump", "--no-prune"];
34683
+ const lookup = await probe(["mise", "ls", `npm:${packageName}`, "--current", "--json"]);
34684
+ const installations = lookup ? miseInstallationsSchema.parse(JSON.parse(lookup)) : [];
34685
+ const owners = installations.filter((installation) => installation.active && installation.installed && installation.install_path === miseRoot);
34686
+ const source = owners.length === 1 ? owners[0]?.source : undefined;
34687
+ if (!source || !["mise.toml", ".tool-versions"].includes(source.type) || !isAbsolute(source.path))
34688
+ throw new Error("Cannot identify the owning mise config for this Remy installation. Update it with mise directly, then retry remy --version.");
34689
+ argv = ["mise", "use", "--path", source.path, "--pin", `npm:${packageName}@${version5}`];
34658
34690
  } else {
34659
34691
  const bunBin = await probe(["bun", "pm", "bin", "-g"]);
34660
34692
  if (bunBin && await matchesEntry(join3(bunBin, "remy"))) {
@@ -34679,7 +34711,7 @@ function createCliUpdates({
34679
34711
  if (result.exitCode !== 0)
34680
34712
  throw new Error(`Remy update failed: ${argv[0]} exited with code ${result.exitCode}. Resolve the package manager error and retry remy update.`);
34681
34713
  if (usesMise) {
34682
- const located = await runProcess({ argv: ["mise", "where", `npm:${packageName}@${version5}`], signal, display: false });
34714
+ const located = await runProcess({ argv: ["mise", "where", `npm:${packageName}`], signal, display: false });
34683
34715
  if (located.exitCode !== 0 || !located.stdout.trim())
34684
34716
  throw new Error("Update finished but the new mise installation could not be located. Check remy --version before retrying.");
34685
34717
  updatedEntry = join3(located.stdout.trim(), miseRelative);
@@ -35071,6 +35103,7 @@ function createSessionViewState({ detail, activeMessageId }) {
35071
35103
  title: detail.title ?? null,
35072
35104
  aggregateStatus: detail.status,
35073
35105
  ...detail.agentStatus === undefined ? {} : { agentStatus: detail.agentStatus },
35106
+ historyStatus: "ready",
35074
35107
  connectionStatus: "connected",
35075
35108
  pullRequest: toSessionPullRequest(detail),
35076
35109
  agent: toSessionAgentSettings(detail.agent),
@@ -36390,19 +36423,22 @@ function createRemoteSessionController(dependencies) {
36390
36423
  ready = new Promise((resolve) => {
36391
36424
  resolveReady = resolve;
36392
36425
  });
36426
+ state = { ...createSessionViewState({ detail: input.detail, activeMessageId: dependencies.activeMessageId }), historyStatus: "loading" };
36393
36427
  const cache = await readSessionCache(cachePath).catch((error93) => {
36394
36428
  handleCacheError(error93);
36395
36429
  return null;
36396
36430
  });
36397
36431
  cachedLastRetainedEventId = input.mode === "live" ? input.lastRetainedEventId ?? cache?.lastRetainedEventId : undefined;
36398
- state = createSessionViewState({
36399
- detail: input.detail,
36400
- activeMessageId: dependencies.activeMessageId ?? cache?.activeMessageId
36401
- });
36432
+ if (stopped)
36433
+ return;
36434
+ state = { ...getState(), activeMessageId: dependencies.activeMessageId ?? cache?.activeMessageId };
36402
36435
  if (input.mode === "cold-resume") {
36403
36436
  const hydrationGeneration = beginReasoningAttempt();
36404
36437
  await hydrateRetainedHistoryFromBeginning(hydrationGeneration);
36405
36438
  }
36439
+ if (stopped)
36440
+ return;
36441
+ state = { ...getState(), historyStatus: "ready" };
36406
36442
  resolveReady();
36407
36443
  publishState();
36408
36444
  try {
@@ -37746,7 +37782,7 @@ function createWorkspaceController({ client, sessionId, signal, operations = {},
37746
37782
  }
37747
37783
  return;
37748
37784
  }
37749
- if (selection.kind !== "changes" && selection.repositoryId !== event.repositoryId)
37785
+ if (event.kind !== "turn-ended" && selection.kind !== "changes" && selection.repositoryId !== event.repositoryId)
37750
37786
  return;
37751
37787
  if (event.kind === "initialized") {
37752
37788
  if (initializedRepositories.has(event.repositoryId))
@@ -37784,10 +37820,10 @@ import { BoxRenderable as BoxRenderable2, TextRenderable, TextareaRenderable } f
37784
37820
  import { dim as dim2, fg as fg3, StyledText as StyledText4 } from "@opentui/core";
37785
37821
 
37786
37822
  // src/tui/theme.ts
37787
- import { bold, dim, fg as fg2, stringToStyledText, StyledText as StyledText3 } from "@opentui/core";
37823
+ import { bg as bg2, bold, dim, fg as fg2, stringToStyledText, StyledText as StyledText3 } from "@opentui/core";
37788
37824
 
37789
37825
  // src/tui/markdown.ts
37790
- import { fg, StyledText as StyledText2, TextAttributes as TextAttributes2 } from "@opentui/core";
37826
+ import { bg, fg, StyledText as StyledText2, TextAttributes as TextAttributes2 } from "@opentui/core";
37791
37827
 
37792
37828
  // ../../node_modules/.bun/marked@17.0.1/node_modules/marked/lib/marked.esm.js
37793
37829
  function L() {
@@ -39254,7 +39290,7 @@ function renderCodeBlock(token, palette) {
39254
39290
  `)) {
39255
39291
  chunks.push(plain(`
39256
39292
  `, { color: palette.body, attributes: 0 }));
39257
- chunks.push(plain("\u2502 ", { color: palette.codeBar, attributes: 0 }), plain(line, { color: palette.code, attributes: 0 }));
39293
+ chunks.push(plain(line, { color: palette.code, background: palette.codeBackground, attributes: 0 }));
39258
39294
  }
39259
39295
  return chunks;
39260
39296
  }
@@ -39347,6 +39383,8 @@ function trimTrailingNewlines(chunks) {
39347
39383
  }
39348
39384
  function plain(text, style) {
39349
39385
  const chunk = fg(style.color)(text);
39386
+ if (style.background)
39387
+ chunk.bg = bg(style.background)(text).bg;
39350
39388
  if (style.attributes)
39351
39389
  chunk.attributes = style.attributes;
39352
39390
  if (style.link)
@@ -39364,6 +39402,7 @@ var PALETTE = {
39364
39402
  codeLabel: "#64748B",
39365
39403
  codeText: "#93C5FD",
39366
39404
  codeBar: "#475569",
39405
+ codeBackground: "#20252E",
39367
39406
  reasoning: "#C084FC",
39368
39407
  progress: "#60A5FA",
39369
39408
  tool: "#22D3EE",
@@ -39519,7 +39558,7 @@ function renderFencedCodeBody(text) {
39519
39558
  `)) {
39520
39559
  chunks.push(...stringToStyledText(`
39521
39560
  `).chunks);
39522
- chunks.push(fg2(PALETTE.codeBar)("\u2502 "), fg2(PALETTE.codeText)(line));
39561
+ chunks.push(bg2(PALETTE.codeBackground)(fg2(PALETTE.codeText)(line)));
39523
39562
  }
39524
39563
  cursor = start + fence.length;
39525
39564
  }
@@ -39539,7 +39578,8 @@ function renderFormattedMessageBody(text, role2) {
39539
39578
  muted: PALETTE.dimText,
39540
39579
  code: PALETTE.codeText,
39541
39580
  codeLabel: PALETTE.codeLabel,
39542
- codeBar: PALETTE.codeBar
39581
+ codeBar: PALETTE.codeBar,
39582
+ codeBackground: PALETTE.codeBackground
39543
39583
  });
39544
39584
  if (formattedMessages.size >= FORMATTED_MESSAGE_CACHE_LIMIT)
39545
39585
  formattedMessages.delete(formattedMessages.keys().next().value);
@@ -39547,14 +39587,12 @@ function renderFormattedMessageBody(text, role2) {
39547
39587
  return formatted;
39548
39588
  }
39549
39589
  function renderCodeSnippet({ text, language = "code", indent = "" }) {
39550
- const gutter = `${indent}\u2502 `;
39551
39590
  return new StyledText3([
39552
39591
  ...indent ? [dim(fg2(PALETTE.dimText)(indent))] : [],
39553
39592
  dim(fg2(PALETTE.codeLabel)(language)),
39554
39593
  fg2(PALETTE.codeText)(`
39555
- ${gutter}${text.replaceAll(`
39556
- `, `
39557
- ${gutter}`)}`)
39594
+ `),
39595
+ bg2(PALETTE.codeBackground)(fg2(PALETTE.codeText)(text))
39558
39596
  ]);
39559
39597
  }
39560
39598
  function formatTime(occurredAt) {
@@ -39740,12 +39778,12 @@ function renderKeyHints({ hints, width }) {
39740
39778
  }
39741
39779
 
39742
39780
  // src/tui/selection.ts
39743
- import { bg, bold as bold2, fg as fg5 } from "@opentui/core";
39781
+ import { bg as bg3, bold as bold2, fg as fg5 } from "@opentui/core";
39744
39782
  function selectionLabel({ text, checked }) {
39745
39783
  return checked ? bold2(fg5(PALETTE.humanAccent)(text)) : fg5(PALETTE.dimText)(text);
39746
39784
  }
39747
39785
  function highlightSelection({ chunks, cursor }) {
39748
- return cursor ? chunks.map((chunk) => bg(PALETTE.selectionBg)(chunk)) : chunks;
39786
+ return cursor ? chunks.map((chunk) => bg3(PALETTE.selectionBg)(chunk)) : chunks;
39749
39787
  }
39750
39788
 
39751
39789
  // src/tui/renderer.ts
@@ -40638,7 +40676,9 @@ var lifecycleStatusOptions = [
40638
40676
  ];
40639
40677
  async function createDashboardTui({
40640
40678
  initialPage,
40641
- initialFilters = { statuses: [], creators: [] },
40679
+ initialFilters = { statuses: [], creators: [], repositories: [], branchName: "" },
40680
+ repositories: initialRepositories = [],
40681
+ loadRepositories,
40642
40682
  authors: initialAuthors = [],
40643
40683
  loadAuthors,
40644
40684
  loadPage,
@@ -40649,23 +40689,27 @@ async function createDashboardTui({
40649
40689
  let keyHandler;
40650
40690
  let resizeHandler;
40651
40691
  let refreshTimer;
40692
+ let searchTimer;
40652
40693
  let contentScrollHandler;
40653
40694
  let rendererDestroyed2 = false;
40654
40695
  const loadPageAbortController = new AbortController;
40655
40696
  try {
40656
40697
  let stopPageRefresh = function() {
40698
+ clearTimeout(searchTimer);
40657
40699
  if (refreshTimer !== undefined) {
40658
40700
  clearInterval(refreshTimer);
40659
40701
  refreshTimer = undefined;
40660
40702
  }
40661
40703
  loadPageAbortController.abort();
40704
+ }, currentOptionState = function() {
40705
+ return filterMode?.kind === "creator" || filterMode?.kind === "repository" ? optionStates[filterMode.kind] : undefined;
40662
40706
  }, render = function() {
40663
40707
  const contentWidth = Math.max(1, renderer.width - TUI_LAYOUT.dashboardGutter * 2);
40664
- const sessions = visibleDashboardSessions({ sessions: page.sessions, query: sessionSearchQuery });
40708
+ const sessions = page.sessions.map((session) => prioritizeSessionMatches(session, filters, sessionSearchQuery));
40665
40709
  const rowsBelow = rowsBelowVisibleWindow({ sessionCount: sessions.length, selectedIndex, height: renderer.height });
40666
40710
  const isNarrow = renderer.width < TUI_LAYOUT.dashboardCardBreakpoint;
40667
40711
  const selectedSession = filterMode ? undefined : sessions[selectedIndex];
40668
- const sessionRows = filterMode ? formatFilterMenu({ mode: filterMode, authors, width: contentWidth }) : sessions.length === 0 ? stringToStyledText2(sessionSearchQuery ? "No sessions match the search." : hasActiveFilters(filters) ? "No sessions match the active filters." : "No remote sessions yet.") : isNarrow ? formatSessionCard({
40712
+ const sessionRows = filterMode ? formatFilterMenu({ mode: filterMode, authors, repositories, width: contentWidth }) : sessions.length === 0 ? stringToStyledText2(sessionSearchQuery ? "No sessions match the search." : hasActiveFilters(filters) ? "No sessions match the active filters." : "No remote sessions yet.") : isNarrow ? formatSessionCard({
40669
40713
  session: selectedSession,
40670
40714
  selectedIndex,
40671
40715
  sessionCount: sessions.length,
@@ -40715,7 +40759,7 @@ async function createDashboardTui({
40715
40759
  else if (activeSearchComposer === "session")
40716
40760
  sessionSearchComposer.render();
40717
40761
  separator.content = activeSearchComposer ? "" : renderComposerDivider({ width: contentWidth });
40718
- footer.content = filterMode ? dashboardFilterFooter({ mode: filterMode, loadingPage, loadingAuthors, pageError, authorError, width: contentWidth }) : dashboardFooter({ loadingPage, pageError, rowsBelow, logoutConfirmationOpen, sessionSearchOpen, width: contentWidth });
40762
+ footer.content = filterMode ? dashboardFilterFooter({ mode: filterMode, loadingPage, loadingOptions: currentOptionState()?.loading ?? false, pageError, optionError: currentOptionState()?.error, width: contentWidth }) : dashboardFooter({ loadingPage, pageError, rowsBelow, logoutConfirmationOpen, sessionSearchOpen, width: contentWidth });
40719
40763
  renderer.requestRender();
40720
40764
  }, finish = function(value) {
40721
40765
  if (settled)
@@ -40730,7 +40774,7 @@ async function createDashboardTui({
40730
40774
  stopPageRefresh();
40731
40775
  rejectDraft(error93);
40732
40776
  }, moveSelectionTo = function(index) {
40733
- const sessions = visibleDashboardSessions({ sessions: page.sessions, query: sessionSearchQuery });
40777
+ const sessions = page.sessions;
40734
40778
  if (sessions.length === 0)
40735
40779
  return;
40736
40780
  selectedIndex = Math.max(0, Math.min(index, sessions.length - 1));
@@ -40738,6 +40782,10 @@ async function createDashboardTui({
40738
40782
  }, applyFilterSearch = function() {
40739
40783
  if (!filterMode?.searching)
40740
40784
  return;
40785
+ if (filterMode.kind === "branch") {
40786
+ applyFilters({ ...filters, statuses: filterMode.statuses, creators: filterMode.creators, repositories: filterMode.repositories, branchName: filterMode.query.trim() });
40787
+ return;
40788
+ }
40741
40789
  filterMode = { ...filterMode, searching: false, selectedIndex: 0 };
40742
40790
  filterSearchComposer.textarea.setText("");
40743
40791
  render();
@@ -40756,6 +40804,7 @@ async function createDashboardTui({
40756
40804
  return;
40757
40805
  sessionSearchOpen = false;
40758
40806
  selectedIndex = 0;
40807
+ loadSessionSearch();
40759
40808
  render();
40760
40809
  }, cancelSessionSearch = function() {
40761
40810
  if (!sessionSearchOpen)
@@ -40764,6 +40813,7 @@ async function createDashboardTui({
40764
40813
  sessionSearchQuery = "";
40765
40814
  selectedIndex = 0;
40766
40815
  sessionSearchComposer.textarea.setText("");
40816
+ loadSessionSearch();
40767
40817
  render();
40768
40818
  }, handleSessionKey = function(key) {
40769
40819
  if (logoutConfirmationOpen) {
@@ -40784,22 +40834,21 @@ async function createDashboardTui({
40784
40834
  return;
40785
40835
  }
40786
40836
  if (filterMode) {
40787
- const options = visibleDashboardFilterOptions({ mode: filterMode, authors });
40837
+ const options = visibleDashboardFilterOptions({ mode: filterMode, authors, repositories });
40788
40838
  if (key.name === "escape") {
40789
40839
  key.preventDefault();
40790
- if (filterMode.searching) {
40840
+ if (filterMode.searching && filterMode.kind !== "branch") {
40791
40841
  cancelFilterSearch();
40792
40842
  return;
40793
40843
  }
40794
40844
  filterMode = undefined;
40795
40845
  pageError = undefined;
40796
- authorError = undefined;
40797
40846
  render();
40798
40847
  return;
40799
40848
  }
40800
40849
  if (filterMode.searching)
40801
40850
  return;
40802
- if (loadingAuthors) {
40851
+ if (currentOptionState()?.loading) {
40803
40852
  key.preventDefault();
40804
40853
  return;
40805
40854
  }
@@ -40836,7 +40885,7 @@ async function createDashboardTui({
40836
40885
  if (!option)
40837
40886
  return;
40838
40887
  const selected = key.name === "space" ? !isDashboardFilterOptionSelected({ mode: filterMode, option }) : key.name === "right" || key.name === "l";
40839
- filterMode = updateDashboardFilterSelection({ mode: filterMode, option, selected, authors });
40888
+ filterMode = updateDashboardFilterSelection({ mode: filterMode, option, selected, authors, repositories });
40840
40889
  render();
40841
40890
  return;
40842
40891
  }
@@ -40849,7 +40898,7 @@ async function createDashboardTui({
40849
40898
  }
40850
40899
  if (key.name === "return" || key.name === "enter") {
40851
40900
  key.preventDefault();
40852
- const nextFilters = { statuses: [...filterMode.statuses], creators: [...filterMode.creators] };
40901
+ const nextFilters = { ...filters, statuses: [...filterMode.statuses], creators: [...filterMode.creators], repositories: [...filterMode.repositories], branchName: filterMode.branchName };
40853
40902
  applyFilters(nextFilters);
40854
40903
  }
40855
40904
  return;
@@ -40882,7 +40931,7 @@ async function createDashboardTui({
40882
40931
  if (listFocused && (key.name === "G" || key.name === "g" && key.shift)) {
40883
40932
  key.preventDefault();
40884
40933
  awaitingVimGoToTop = false;
40885
- moveSelectionTo(visibleDashboardSessions({ sessions: page.sessions, query: sessionSearchQuery }).length - 1);
40934
+ moveSelectionTo(page.sessions.length - 1);
40886
40935
  return;
40887
40936
  }
40888
40937
  if (listFocused && key.name === "g") {
@@ -40907,7 +40956,9 @@ async function createDashboardTui({
40907
40956
  awaitingVimGoToTop = false;
40908
40957
  if (key.name === "return" || key.name === "enter") {
40909
40958
  key.preventDefault();
40910
- const session = visibleDashboardSessions({ sessions: page.sessions, query: sessionSearchQuery })[selectedIndex];
40959
+ if (loadingPage || sessionSearchQuery.trim() !== (filters.search ?? ""))
40960
+ return;
40961
+ const session = page.sessions[selectedIndex];
40911
40962
  if (session)
40912
40963
  finish({ kind: "session", sessionId: session.id });
40913
40964
  return;
@@ -40922,13 +40973,27 @@ async function createDashboardTui({
40922
40973
  openSessionSearch();
40923
40974
  return;
40924
40975
  }
40976
+ if (["f", "a", "r", "b"].includes(key.name) && (loadingPage || sessionSearchQuery.trim() !== (filters.search ?? "")))
40977
+ return;
40925
40978
  if (key.name === "f") {
40926
40979
  key.preventDefault();
40927
- authorError = undefined;
40928
40980
  filterMode = createFilterMode({ kind: "status", filters });
40929
40981
  render();
40930
40982
  return;
40931
40983
  }
40984
+ if (key.name === "r" || key.name === "b") {
40985
+ key.preventDefault();
40986
+ filterMode = createFilterMode({ kind: key.name === "r" ? "repository" : "branch", filters });
40987
+ if (filterMode.kind === "branch") {
40988
+ filterMode.searching = true;
40989
+ filterMode.query = filters.branchName;
40990
+ filterSearchComposer.textarea.setText(filters.branchName);
40991
+ }
40992
+ render();
40993
+ if (key.name === "r")
40994
+ loadRepositoryOptions();
40995
+ return;
40996
+ }
40932
40997
  if (key.name === "a") {
40933
40998
  key.preventDefault();
40934
40999
  openAuthorFilter();
@@ -40971,17 +41036,22 @@ async function createDashboardTui({
40971
41036
  let selectedIndex = 0;
40972
41037
  let loadingPage = false;
40973
41038
  let pageRequestInFlight = false;
41039
+ let pageRequestVersion = 0;
40974
41040
  let pageError;
40975
- let authorError;
41041
+ const optionStates = {
41042
+ creator: { loading: false, error: undefined },
41043
+ repository: { loading: false, error: undefined }
41044
+ };
41045
+ let repositories = initialRepositories;
41046
+ let repositoriesLoaded = loadRepositories === undefined || initialRepositories.length > 0;
40976
41047
  let authors = initialAuthors;
40977
41048
  let authorsLoaded = loadAuthors === undefined || initialAuthors.length > 0;
40978
- let loadingAuthors = false;
40979
41049
  let awaitingVimGoToTop = false;
40980
41050
  let logoutConfirmationOpen = false;
40981
41051
  let filters = initialFilters;
40982
41052
  let filterMode;
40983
41053
  let sessionSearchOpen = false;
40984
- let sessionSearchQuery = "";
41054
+ let sessionSearchQuery = initialFilters.search ?? "";
40985
41055
  root.onMouseDown = (event) => {
40986
41056
  if (filterMode?.searching || sessionSearchOpen)
40987
41057
  return;
@@ -41021,7 +41091,7 @@ async function createDashboardTui({
41021
41091
  renderer,
41022
41092
  parent: filterSearchRegion,
41023
41093
  tag: "Search",
41024
- placeholder: "Search by ID, title, or number\u2026",
41094
+ placeholder: "Search sessions, authors, repositories, branches, or PRs\u2026",
41025
41095
  screenPaddingX: 2,
41026
41096
  onSubmit: () => applySessionSearch(),
41027
41097
  onContentChange: () => {
@@ -41029,104 +41099,151 @@ async function createDashboardTui({
41029
41099
  return;
41030
41100
  sessionSearchQuery = sessionSearchComposer.textarea.plainText;
41031
41101
  selectedIndex = 0;
41102
+ pageRequestVersion += 1;
41103
+ loadingPage = true;
41104
+ clearTimeout(searchTimer);
41105
+ searchTimer = setTimeout(() => {
41106
+ searchTimer = undefined;
41107
+ loadSessionSearch();
41108
+ }, 200);
41032
41109
  render();
41033
41110
  }
41034
41111
  });
41035
41112
  async function loadAdjacentPage(direction) {
41036
- if (pageRequestInFlight)
41113
+ if (pageRequestInFlight || loadingPage || sessionSearchQuery.trim() !== (filters.search ?? ""))
41037
41114
  return;
41038
41115
  const canLoad = direction === "next" ? page.hasMore : page.canGoPrevious;
41039
41116
  if (!canLoad)
41040
41117
  return;
41118
+ const requestVersion = ++pageRequestVersion;
41041
41119
  pageRequestInFlight = true;
41042
41120
  loadingPage = true;
41043
41121
  pageError = undefined;
41044
41122
  render();
41045
41123
  try {
41046
41124
  const loadedPage = await loadPage({ target: direction, signal: loadPageAbortController.signal });
41047
- if (destroyed || settled)
41125
+ if (destroyed || settled || requestVersion !== pageRequestVersion)
41048
41126
  return;
41049
41127
  page = loadedPage;
41050
41128
  pageIndex = Math.max(1, pageIndex + (direction === "next" ? 1 : -1));
41051
- const sessions = visibleDashboardSessions({ sessions: page.sessions, query: sessionSearchQuery });
41129
+ const sessions = page.sessions;
41052
41130
  selectedIndex = direction === "next" ? 0 : Math.max(0, sessions.length - 1);
41053
41131
  } catch (error93) {
41054
- if (!destroyed && !settled)
41132
+ if (!destroyed && !settled && requestVersion === pageRequestVersion)
41055
41133
  pageError = error93 instanceof Error ? error93.message : String(error93);
41056
41134
  } finally {
41057
- pageRequestInFlight = false;
41058
- loadingPage = false;
41059
- if (!destroyed && !settled)
41060
- render();
41135
+ if (requestVersion === pageRequestVersion) {
41136
+ pageRequestInFlight = false;
41137
+ loadingPage = false;
41138
+ if (!destroyed && !settled)
41139
+ render();
41140
+ }
41061
41141
  }
41062
41142
  }
41063
41143
  async function refreshCurrentPage() {
41064
- if (pageRequestInFlight || destroyed || settled)
41144
+ if (pageRequestInFlight || loadingPage || destroyed || settled || sessionSearchQuery.trim() !== (filters.search ?? ""))
41065
41145
  return;
41146
+ const requestVersion = ++pageRequestVersion;
41066
41147
  pageRequestInFlight = true;
41067
41148
  try {
41068
41149
  const refreshedPage = await loadPage({ target: "current", signal: loadPageAbortController.signal });
41069
- if (destroyed || settled)
41150
+ if (destroyed || settled || requestVersion !== pageRequestVersion)
41070
41151
  return;
41071
- const selectedSessionId = visibleDashboardSessions({ sessions: page.sessions, query: sessionSearchQuery })[selectedIndex]?.id;
41152
+ const selectedSessionId = page.sessions[selectedIndex]?.id;
41072
41153
  page = refreshedPage;
41073
- const refreshedSelectedIndex = selectedSessionId ? visibleDashboardSessions({ sessions: page.sessions, query: sessionSearchQuery }).findIndex((session) => session.id === selectedSessionId) : -1;
41154
+ const refreshedSelectedIndex = selectedSessionId ? page.sessions.findIndex((session) => session.id === selectedSessionId) : -1;
41074
41155
  selectedIndex = refreshedSelectedIndex >= 0 ? refreshedSelectedIndex : Math.max(0, Math.min(selectedIndex, page.sessions.length - 1));
41075
41156
  pageError = undefined;
41076
41157
  render();
41077
41158
  } catch (error93) {
41078
- if (!destroyed && !settled) {
41159
+ if (!destroyed && !settled && requestVersion === pageRequestVersion) {
41079
41160
  pageError = error93 instanceof Error ? error93.message : String(error93);
41080
41161
  render();
41081
41162
  }
41082
41163
  } finally {
41083
- pageRequestInFlight = false;
41164
+ if (requestVersion === pageRequestVersion)
41165
+ pageRequestInFlight = false;
41084
41166
  }
41085
41167
  }
41086
41168
  async function applyFilters(nextFilters) {
41087
- if (loadingPage)
41088
- return;
41169
+ const appliedFilterMode = filterMode;
41170
+ const requestVersion = ++pageRequestVersion;
41171
+ pageRequestInFlight = true;
41089
41172
  loadingPage = true;
41090
41173
  pageError = undefined;
41091
- authorError = undefined;
41092
41174
  render();
41093
41175
  try {
41094
- page = await loadPage({ filters: nextFilters, signal: loadPageAbortController.signal });
41176
+ const loadedPage = await loadPage({ filters: nextFilters, signal: loadPageAbortController.signal });
41177
+ if (destroyed || settled || requestVersion !== pageRequestVersion)
41178
+ return;
41179
+ page = loadedPage;
41095
41180
  filters = nextFilters;
41096
41181
  pageIndex = 1;
41097
41182
  selectedIndex = 0;
41098
- filterMode = undefined;
41183
+ if (filterMode === appliedFilterMode)
41184
+ filterMode = undefined;
41099
41185
  } catch (error93) {
41100
- pageError = error93 instanceof Error ? error93.message : String(error93);
41186
+ if (!destroyed && !settled && requestVersion === pageRequestVersion)
41187
+ pageError = error93 instanceof Error ? error93.message : String(error93);
41101
41188
  } finally {
41102
- loadingPage = false;
41103
- render();
41189
+ if (requestVersion === pageRequestVersion) {
41190
+ pageRequestInFlight = false;
41191
+ loadingPage = false;
41192
+ if (!destroyed && !settled)
41193
+ render();
41194
+ }
41195
+ }
41196
+ }
41197
+ async function loadRepositoryOptions() {
41198
+ if (repositoriesLoaded || optionStates.repository.loading || !loadRepositories)
41199
+ return;
41200
+ optionStates.repository.loading = true;
41201
+ optionStates.repository.error = undefined;
41202
+ render();
41203
+ try {
41204
+ const loaded = await loadRepositories();
41205
+ if (destroyed || settled)
41206
+ return;
41207
+ repositories = loaded;
41208
+ repositoriesLoaded = true;
41209
+ } catch (error93) {
41210
+ if (!destroyed && !settled)
41211
+ optionStates.repository.error = error93 instanceof Error ? error93.message : String(error93);
41212
+ } finally {
41213
+ optionStates.repository.loading = false;
41214
+ if (!destroyed && !settled)
41215
+ render();
41104
41216
  }
41105
41217
  }
41106
41218
  async function openAuthorFilter() {
41107
41219
  filterMode = createFilterMode({ kind: "creator", filters });
41108
- authorError = undefined;
41220
+ optionStates.creator.error = undefined;
41109
41221
  render();
41110
- if (authorsLoaded || loadingAuthors || !loadAuthors)
41222
+ if (authorsLoaded || optionStates.creator.loading || !loadAuthors)
41111
41223
  return;
41112
- loadingAuthors = true;
41224
+ optionStates.creator.loading = true;
41113
41225
  render();
41114
41226
  try {
41115
- authors = await loadAuthors();
41227
+ const loaded = await loadAuthors();
41228
+ if (destroyed || settled)
41229
+ return;
41230
+ authors = loaded;
41116
41231
  authorsLoaded = true;
41117
41232
  if (filterMode?.kind === "creator") {
41118
41233
  const firstSelectedIndex = authors.findIndex((author) => filterMode?.creators.some((creator) => creator.id === author.id));
41119
41234
  filterMode = { ...filterMode, selectedIndex: Math.max(0, firstSelectedIndex) };
41120
41235
  }
41121
41236
  } catch (error93) {
41122
- authorError = error93 instanceof Error ? error93.message : String(error93);
41237
+ if (!destroyed && !settled)
41238
+ optionStates.creator.error = error93 instanceof Error ? error93.message : String(error93);
41123
41239
  } finally {
41124
- loadingAuthors = false;
41125
- render();
41240
+ optionStates.creator.loading = false;
41241
+ if (!destroyed && !settled)
41242
+ render();
41126
41243
  }
41127
41244
  }
41128
41245
  async function moveSelection(direction) {
41129
- const sessions = visibleDashboardSessions({ sessions: page.sessions, query: sessionSearchQuery });
41246
+ const sessions = page.sessions;
41130
41247
  if (loadingPage || sessions.length === 0)
41131
41248
  return;
41132
41249
  const isBoundary = direction === "next" ? selectedIndex === sessions.length - 1 : selectedIndex === 0;
@@ -41137,6 +41254,12 @@ async function createDashboardTui({
41137
41254
  }
41138
41255
  await loadAdjacentPage(direction);
41139
41256
  }
41257
+ async function loadSessionSearch() {
41258
+ clearTimeout(searchTimer);
41259
+ searchTimer = undefined;
41260
+ const search = sessionSearchQuery.trim();
41261
+ await applyFilters({ ...filters, search: search || undefined });
41262
+ }
41140
41263
  inputHandler = (sequence) => {
41141
41264
  if (sequence === "\x03") {
41142
41265
  finish({ kind: "interrupt" });
@@ -41215,7 +41338,8 @@ function formatSessionRow({ session, selected, width }) {
41215
41338
  const title = truncateText(session.title ?? "Untitled session", Math.max(8, Math.min(40, Math.floor(width * 0.24))));
41216
41339
  const attribution = `${session.creator} (${session.source})`;
41217
41340
  const metadataWidth = Math.max(0, width - Bun.stringWidth(prefix + title + activity.label) - 4);
41218
- const metadata = truncateText(`${attribution} \xB7 ${outcome} \xB7 ${updated}`, metadataWidth);
41341
+ const work = session.repositories.length > 0 ? `${sessionRepositoryBranches(session, Math.max(0, Math.floor(metadataWidth * 0.6)))} \xB7 ` : "";
41342
+ const metadata = truncateText(`${work}${attribution} \xB7 ${outcome} \xB7 ${updated}`, metadataWidth);
41219
41343
  const line = [
41220
41344
  fg7(PALETTE.dimText)(prefix),
41221
41345
  fg7(PALETTE.bodyText)(title),
@@ -41243,14 +41367,18 @@ function createFilterMode({ kind, filters }) {
41243
41367
  selectedIndex: 0,
41244
41368
  statuses: [...filters.statuses],
41245
41369
  creators: [...filters.creators],
41370
+ repositories: [...filters.repositories],
41371
+ branchName: filters.branchName,
41246
41372
  query: "",
41247
41373
  searching: false
41248
41374
  };
41249
41375
  }
41250
- function formatFilterMenu({ mode, authors, width }) {
41251
- const options = visibleDashboardFilterOptions({ mode, authors });
41252
- const selectedCount = mode.kind === "status" ? mode.statuses.length : mode.creators.length;
41253
- const heading = `${mode.kind === "status" ? "Filter by status" : "Filter by author"} \xB7 ${selectedCount} selected \xB7 empty shows all`;
41376
+ function formatFilterMenu({ mode, authors, repositories, width }) {
41377
+ if (mode.kind === "branch")
41378
+ return stringToStyledText2("Filter by branch \xB7 contains text \xB7 empty shows all");
41379
+ const options = visibleDashboardFilterOptions({ mode, authors, repositories });
41380
+ const selectedCount = mode.kind === "status" ? mode.statuses.length : mode.kind === "repository" ? mode.repositories.length : mode.creators.length;
41381
+ const heading = `${mode.kind === "status" ? "Filter by status" : mode.kind === "repository" ? "Filter by repository" : "Filter by author"} \xB7 ${selectedCount} selected \xB7 empty shows all`;
41254
41382
  const rows = options.map((option, index) => {
41255
41383
  const active = isDashboardFilterOptionSelected({ mode, option });
41256
41384
  const label = `${active ? "[x]" : "[ ]"} ${truncateText(option.label, Math.max(1, width - 4))}`;
@@ -41265,16 +41393,16 @@ function formatFilterMenu({ mode, authors, width }) {
41265
41393
  ], `
41266
41394
  `);
41267
41395
  }
41268
- function dashboardFilterOptions({ kind, authors }) {
41269
- return kind === "status" ? lifecycleStatusOptions.map((option) => ({ kind: "status", id: option.value, label: option.label })) : authors.map((author) => ({ kind: "creator", id: author.id, label: author.name }));
41396
+ function dashboardFilterOptions({ kind, authors, repositories }) {
41397
+ return kind === "status" ? lifecycleStatusOptions.map((option) => ({ kind: "status", id: option.value, label: option.label })) : kind === "repository" ? repositories.map((repository) => ({ kind: "repository", id: repository.id, label: repository.fullName })) : authors.map((author) => ({ kind: "creator", id: author.id, label: author.name }));
41270
41398
  }
41271
- function visibleDashboardFilterOptions({ mode, authors }) {
41272
- return dashboardFilterOptions({ kind: mode.kind, authors }).filter((option) => fuzzyMatches(option.label, mode.query));
41399
+ function visibleDashboardFilterOptions({ mode, authors, repositories }) {
41400
+ return dashboardFilterOptions({ kind: mode.kind, authors, repositories }).filter((option) => fuzzyMatches(option.label, mode.query));
41273
41401
  }
41274
41402
  function isDashboardFilterOptionSelected({ mode, option }) {
41275
- return option.kind === "status" ? mode.statuses.includes(option.id) : mode.creators.some((creator) => creator.id === option.id);
41403
+ return option.kind === "status" ? mode.statuses.includes(option.id) : option.kind === "repository" ? mode.repositories.some((repository) => repository.id === option.id) : mode.creators.some((creator) => creator.id === option.id);
41276
41404
  }
41277
- function updateDashboardFilterSelection({ mode, option, selected, authors }) {
41405
+ function updateDashboardFilterSelection({ mode, option, selected, authors, repositories }) {
41278
41406
  if (option.kind === "status") {
41279
41407
  const selectedStatuses = new Set(mode.statuses);
41280
41408
  if (selected)
@@ -41286,6 +41414,14 @@ function updateDashboardFilterSelection({ mode, option, selected, authors }) {
41286
41414
  statuses: lifecycleStatusOptions.map((entry2) => entry2.value).filter((status) => selectedStatuses.has(status))
41287
41415
  };
41288
41416
  }
41417
+ if (option.kind === "repository") {
41418
+ const ids = new Set(mode.repositories.map((repository) => repository.id));
41419
+ if (selected)
41420
+ ids.add(option.id);
41421
+ else
41422
+ ids.delete(option.id);
41423
+ return { ...mode, repositories: repositories.filter((repository) => ids.has(repository.id)).map(({ id, fullName }) => ({ id, fullName })) };
41424
+ }
41289
41425
  const selectedCreatorIds = new Set(mode.creators.map((creator) => creator.id));
41290
41426
  if (selected)
41291
41427
  selectedCreatorIds.add(option.id);
@@ -41313,7 +41449,7 @@ function formatSessionPreview({ session, width }) {
41313
41449
  fg7(PALETTE.dimText)(" \xB7 "),
41314
41450
  bold3(fg7(activity.color)(activity.label))
41315
41451
  ],
41316
- [fg7(PALETTE.dimText)(truncateText(`Work: ${sessionWork(session)}`, width))],
41452
+ [fg7(PALETTE.dimText)(truncateText(`Work: ${sessionWork(session, width)}`, width))],
41317
41453
  [fg7(PALETTE.bodyText)(truncateText(`Outcome: ${sessionOutcome(session)}`, width))],
41318
41454
  [fg7(PALETTE.dimText)(truncateText(`Created by: ${session.creator} \xB7 Updated ${formatUpdatedAt(session.updatedAt ?? session.createdAt)}`, width))]
41319
41455
  ], `
@@ -41324,7 +41460,7 @@ function formatSessionCard({ session, selectedIndex, sessionCount, width }) {
41324
41460
  const lines = [
41325
41461
  `Session #${session.sessionNumber} \xB7 ${selectedIndex + 1} of ${sessionCount}`,
41326
41462
  truncateText(session.title ?? "Untitled session", width),
41327
- truncateText(`Work: ${sessionWork(session)}`, width),
41463
+ truncateText(`Work: ${sessionWork(session, width)}`, width),
41328
41464
  `Now: ${activity.label}`,
41329
41465
  truncateText(`Outcome: ${sessionOutcome(session)}`, width),
41330
41466
  truncateText(`Created by: ${session.creator} \xB7 Updated ${formatUpdatedAt(session.updatedAt ?? session.createdAt)}`, width)
@@ -41357,9 +41493,33 @@ function sessionActivityPresentation(session) {
41357
41493
  return { ...agent, label: "Ready" };
41358
41494
  return agent;
41359
41495
  }
41360
- function sessionWork(session) {
41361
- const repositories = session.repositories.map((repository) => repository.fullName).join(", ") || "No repository selected";
41362
- return `${repositories} \xB7 ${session.source}`;
41496
+ function prioritizeSessionMatches(session, filters, query) {
41497
+ const filteredRepositories = session.repositories.filter((repository2) => (filters.repositories.length === 0 || filters.repositories.some((selected) => selected.id === repository2.id)) && (!filters.branchName || repository2.branchName?.toLowerCase().includes(filters.branchName.toLowerCase())));
41498
+ const repository = filteredRepositories.find((repository2) => fuzzyMatches(repository2.fullName, query) || fuzzyMatches(repository2.branchName ?? "", query)) ?? filteredRepositories[0];
41499
+ const pullRequest = session.pullRequests.find((pullRequest2) => matchesPullRequestSearch(pullRequest2, query));
41500
+ return {
41501
+ ...session,
41502
+ repositories: repository ? [repository, ...session.repositories.filter((item) => item !== repository)] : session.repositories,
41503
+ pullRequests: pullRequest ? [pullRequest, ...session.pullRequests.filter((item) => item !== pullRequest)] : session.pullRequests
41504
+ };
41505
+ }
41506
+ function matchesPullRequestSearch(pullRequest, query) {
41507
+ return fuzzyMatches(`#${pullRequest.number}`, query) || fuzzyMatches(pullRequest.title, query);
41508
+ }
41509
+ function sessionRepositoryBranches(session, width) {
41510
+ const repository = session.repositories[0];
41511
+ if (!repository)
41512
+ return truncateText("No repository selected", width);
41513
+ const more = session.repositories.length > 1 ? ` +${session.repositories.length - 1}` : "";
41514
+ const available = Math.max(0, width - Bun.stringWidth(more) - 3);
41515
+ const name = truncateText(repository.fullName, Math.floor(available / 2));
41516
+ const branch = truncateText(repository.branchName ?? "Branch pending", Math.max(0, available - Bun.stringWidth(name)));
41517
+ return `${name} \xB7 ${branch}${more}`;
41518
+ }
41519
+ function sessionWork(session, width) {
41520
+ const source = truncateText(session.source, Math.floor(width / 4));
41521
+ const repositories = sessionRepositoryBranches(session, Math.max(0, width - Bun.stringWidth(source) - 9));
41522
+ return `${repositories} \xB7 ${source}`;
41363
41523
  }
41364
41524
  function sessionOutcome(session) {
41365
41525
  const first = session.pullRequests[0];
@@ -41382,14 +41542,8 @@ function formatUpdatedAt(value) {
41382
41542
  return `${Math.floor(seconds / (60 * 60))}h ago`;
41383
41543
  return new Date(milliseconds).toLocaleDateString(undefined, { month: "short", day: "numeric" });
41384
41544
  }
41385
- function matchesSessionSearch({ session, query }) {
41386
- return fuzzyMatches(session.id, query) || fuzzyMatches(session.title ?? "", query) || fuzzyMatches(String(session.sessionNumber), query);
41387
- }
41388
- function visibleDashboardSessions({ sessions, query }) {
41389
- return sessions.filter((session) => matchesSessionSearch({ session, query }));
41390
- }
41391
41545
  function dashboardStatusBand({ page, pageIndex, filters, sessionSearchQuery }) {
41392
- const openCount = visibleDashboardSessions({ sessions: page.sessions, query: sessionSearchQuery }).filter((session) => session.status === "open").length;
41546
+ const openCount = page.sessions.filter((session) => session.status === "open").length;
41393
41547
  const paging = [`page ${pageIndex}`];
41394
41548
  if (page.canGoPrevious)
41395
41549
  paging.push("\u2190 prev");
@@ -41398,12 +41552,14 @@ function dashboardStatusBand({ page, pageIndex, filters, sessionSearchQuery }) {
41398
41552
  const activeFilters = [
41399
41553
  filters.statuses.length > 0 ? `status: ${filters.statuses.map(sessionStatusLabel).join(", ")}` : undefined,
41400
41554
  filters.creators.length > 0 ? `author: ${filters.creators.map((creator) => creator.name).join(", ")}` : undefined,
41555
+ filters.repositories.length > 0 ? `repo: ${filters.repositories.map((repository) => repository.fullName).join(", ")}` : undefined,
41556
+ filters.branchName ? `branch: ${filters.branchName}` : undefined,
41401
41557
  sessionSearchQuery ? `search: ${sessionSearchQuery}` : undefined
41402
41558
  ].filter((value) => value !== undefined);
41403
41559
  return `remy \xB7 sessions \xB7 ${openCount} open${activeFilters.length > 0 ? ` \xB7 ${activeFilters.join(" \xB7 ")}` : ""} \xB7 ${paging.join(" \xB7 ")}`;
41404
41560
  }
41405
41561
  function hasActiveFilters(filters) {
41406
- return filters.statuses.length > 0 || filters.creators.length > 0;
41562
+ return filters.statuses.length > 0 || filters.creators.length > 0 || filters.repositories.length > 0 || filters.branchName.length > 0;
41407
41563
  }
41408
41564
  function dashboardFooter({ loadingPage, pageError, rowsBelow, logoutConfirmationOpen, sessionSearchOpen, width }) {
41409
41565
  if (logoutConfirmationOpen)
@@ -41419,19 +41575,24 @@ function dashboardFooter({ loadingPage, pageError, rowsBelow, logoutConfirmation
41419
41575
  parts.push(`\u2193 ${rowsBelow} more below`);
41420
41576
  return joinStyled([
41421
41577
  ...parts.length > 0 ? [[fg7(pageError ? PALETTE.failure : PALETTE.dimText)(parts.join(" \xB7 "))]] : [],
41422
- renderKeyHints({ hints: [{ key: "\u2191\u2193", action: "move" }, { key: "\u23CE", action: "open" }, { key: "s", action: "search" }, { key: "f", action: "status" }, { key: "a", action: "author" }, { key: "n", action: "new session" }, { key: "\u21E7l", action: "log out" }, { key: "q", action: "quit" }], width })
41578
+ renderKeyHints({ hints: [{ key: "\u2191\u2193", action: "move" }, { key: "\u23CE", action: "open" }, { key: "s", action: "search" }, { key: "f", action: "status" }, { key: "a", action: "author" }, { key: "r", action: "repository" }, { key: "b", action: "branch" }, { key: "n", action: "new session" }, { key: "\u21E7l", action: "log out" }, { key: "q", action: "quit" }], width })
41423
41579
  ], `
41424
41580
  `);
41425
41581
  }
41426
- function dashboardFilterFooter({ mode, loadingPage, loadingAuthors, pageError, authorError, width }) {
41427
- if (loadingAuthors)
41428
- return joinStyled([[fg7(PALETTE.progress)("Loading authors\u2026")], renderKeyHints({ hints: [{ key: "Esc", action: "cancel" }], width })], " \xB7 ");
41582
+ function dashboardFilterFooter({ mode, loadingPage, loadingOptions, pageError, optionError, width }) {
41583
+ if (loadingOptions)
41584
+ return joinStyled([[fg7(PALETTE.progress)("Loading filter options\u2026")], renderKeyHints({ hints: [{ key: "Esc", action: "cancel" }], width })], " \xB7 ");
41429
41585
  if (loadingPage)
41430
41586
  return stringToStyledText2("Applying filter\u2026");
41431
- if (mode.searching)
41432
- return renderKeyHints({ hints: [{ key: "type", action: "to filter" }, { key: "\u23CE", action: "apply search" }, { key: "Esc", action: "cancel search" }], width });
41587
+ if (mode.searching) {
41588
+ return joinStyled([
41589
+ ...pageError ? [[fg7(PALETTE.failure)(`Could not apply filter: ${pageError}`)]] : [],
41590
+ renderKeyHints({ hints: [{ key: "type", action: "to filter" }, { key: "\u23CE", action: mode.kind === "branch" ? "apply branch" : "apply search" }, { key: "Esc", action: "cancel" }], width })
41591
+ ], `
41592
+ `);
41593
+ }
41433
41594
  return joinStyled([
41434
- ...authorError ? [[fg7(PALETTE.failure)(`Could not load authors: ${authorError}`)]] : [],
41595
+ ...optionError ? [[fg7(PALETTE.failure)(`Could not load filter options: ${optionError}`)]] : [],
41435
41596
  ...pageError ? [[fg7(PALETTE.failure)(`Could not apply filter: ${pageError}`)]] : [],
41436
41597
  renderKeyHints({ hints: [{ key: "\u2191\u2193/jk", action: "move" }, { key: "space", action: "toggle" }, { key: "\u2190\u2192/hl", action: "mark" }, { key: "s", action: "search" }, { key: "\u23CE", action: "apply" }, { key: "Esc", action: "cancel" }], width })
41437
41598
  ], `
@@ -41442,7 +41603,7 @@ async function createDefaultRenderer() {
41442
41603
  }
41443
41604
 
41444
41605
  // src/tui/new-session-wizard.ts
41445
- import { bg as bg2, bold as bold4, CliRenderEvents as CliRenderEvents4, dim as dim6, fg as fg11, StyledText as StyledText10, stringToStyledText as stringToStyledText3, TextRenderable as TextRenderable4 } from "@opentui/core";
41606
+ import { bg as bg4, bold as bold4, CliRenderEvents as CliRenderEvents4, dim as dim6, fg as fg11, StyledText as StyledText10, stringToStyledText as stringToStyledText3, TextRenderable as TextRenderable4 } from "@opentui/core";
41446
41607
 
41447
41608
  // src/tui/attachment-presentation.ts
41448
41609
  import { dim as dim4, fg as fg9, StyledText as StyledText8 } from "@opentui/core";
@@ -41492,7 +41653,7 @@ var SPINNER_INTERVAL_MS = 80;
41492
41653
  // src/tui/path-completion.ts
41493
41654
  import { readdir } from "fs/promises";
41494
41655
  import { homedir } from "os";
41495
- import { basename as basename5, dirname as dirname5, isAbsolute as isAbsolute2, join as join8, relative as relative2, resolve as resolve2, sep } from "path";
41656
+ import { basename as basename5, dirname as dirname5, isAbsolute as isAbsolute2, join as join8, relative, resolve as resolve2, sep } from "path";
41496
41657
  var maximumCandidates = 50;
41497
41658
  async function completePathToken({ text, cwd, homeDirectory = homedir() }) {
41498
41659
  const token = activePathToken(text);
@@ -41512,7 +41673,7 @@ async function completePathToken({ text, cwd, homeDirectory = homedir() }) {
41512
41673
  }
41513
41674
  return entries.filter((entry2) => entry2.name.toLowerCase().startsWith(prefix.toLowerCase())).sort((left, right) => left.name.localeCompare(right.name)).slice(0, maximumCandidates).map((entry2) => {
41514
41675
  const completedPath = join8(directory, entry2.name);
41515
- const replacementPath = pathUsesTilde ? `~/${relative2(homeDirectory, completedPath)}` : isAbsolute2(pathText) ? completedPath : relative2(cwd, completedPath) || entry2.name;
41676
+ const replacementPath = pathUsesTilde ? `~/${relative(homeDirectory, completedPath)}` : isAbsolute2(pathText) ? completedPath : relative(cwd, completedPath) || entry2.name;
41516
41677
  const normalized = replacementPath.split(sep).join("/");
41517
41678
  const suffix = entry2.isDirectory() ? "/" : "";
41518
41679
  const completed = `${normalized}${suffix}`;
@@ -42920,7 +43081,7 @@ function renderStepIndicator({ step, hasInstallationChoice, hasBranchChoice = fa
42920
43081
  function renderSelectableRow({ label, isCursor }) {
42921
43082
  const line = `${isCursor ? "> " : " "}${label}`;
42922
43083
  const styled = fg11(PALETTE.bodyText)(line);
42923
- return [isCursor ? bg2(PALETTE.selectionBg)(styled) : styled];
43084
+ return [isCursor ? bg4(PALETTE.selectionBg)(styled) : styled];
42924
43085
  }
42925
43086
  function renderRepositoryRow({ repository, isCursor, isChecked, source }) {
42926
43087
  const marker = isChecked ? "[x]" : "[ ]";
@@ -42944,7 +43105,7 @@ async function createDefaultRenderer2() {
42944
43105
  }
42945
43106
 
42946
43107
  // src/tui/session-view.ts
42947
- import { CliRenderEvents as CliRenderEvents5, bg as bg3, bold as bold5, dim as dim7, fg as fg12, link as link2, StyledText as StyledText11, stringToStyledText as stringToStyledText5, TextRenderable as TextRenderable5 } from "@opentui/core";
43108
+ import { CliRenderEvents as CliRenderEvents5, bg as bg5, bold as bold5, dim as dim7, fg as fg12, link as link2, StyledText as StyledText11, stringToStyledText as stringToStyledText5, TextRenderable as TextRenderable5 } from "@opentui/core";
42948
43109
 
42949
43110
  // src/tui/session-mcps-view.ts
42950
43111
  import { stringToStyledText as stringToStyledText4 } from "@opentui/core";
@@ -43323,7 +43484,7 @@ async function createSessionTui({
43323
43484
  `), joinStyled(liveContent, `
43324
43485
 
43325
43486
  `)], ""));
43326
- composer.placeholder = lifecycleRequestState.kind === "pending" ? `${lifecycleOperationPresentParticiple(lifecycleRequestState.operation)} the session \u2014 messages are paused.` : renderer.height <= 10 ? "Message Remy\u2026 \xB7 Enter sends" : idleComposerPlaceholder;
43487
+ composer.placeholder = latestState.historyStatus === "loading" ? "Draft a message\u2026 \xB7 sending waits for history" : lifecycleRequestState.kind === "pending" ? `${lifecycleOperationPresentParticiple(lifecycleRequestState.operation)} the session \u2014 messages are paused.` : renderer.height <= 10 ? "Message Remy\u2026 \xB7 Enter sends" : idleComposerPlaceholder;
43327
43488
  resizeComposer(composer);
43328
43489
  if (latestState.aggregateStatus === "open")
43329
43490
  composerLayout.render();
@@ -43332,6 +43493,7 @@ async function createSessionTui({
43332
43493
  const commandMenu = renderSlashCommandMenu({ commands: slashCompletions, selectedIndex: slashCompletionIndex, width: renderer.width - 2, maxRows: completionMenuRows(renderer.height) });
43333
43494
  const agentSettingsMenu = agentSettingsPicker && latestState.aggregateStatus === "open" ? renderAgentSettingsPicker({ picker: agentSettingsPicker, width: renderer.width - 2, maxRows: completionMenuRows(renderer.height) }) : undefined;
43334
43495
  const composerStatus = [
43496
+ latestState.historyStatus === "loading" ? renderFeedback({ kind: "progress", message: "Loading conversation\u2026 You can draft now; sending is paused." }) : undefined,
43335
43497
  composerDraft.workspaceContexts?.length ? renderFeedback({ kind: "info", message: `${composerDraft.workspaceContexts.length} workspace context(s) \xB7 /context to review/remove` }) : undefined,
43336
43498
  attachmentFeedback ? renderAttachmentFeedback(attachmentFeedback) : undefined,
43337
43499
  composerFeedback ? renderFeedback(composerFeedback) : undefined,
@@ -43995,6 +44157,12 @@ ${segment.excerpt}`).join(`
43995
44157
  render();
43996
44158
  }
43997
44159
  }
44160
+ transcriptScrollBox.onMouseDown = (event) => {
44161
+ if (latestState.aggregateStatus === "open" && event.button === 0) {
44162
+ event.preventDefault();
44163
+ composer.focus();
44164
+ }
44165
+ };
43998
44166
  for (const text of [transcript, liveTranscript]) {
43999
44167
  let pressedLink;
44000
44168
  text.onMouseDown = (event) => {
@@ -44048,6 +44216,10 @@ ${segment.excerpt}`).join(`
44048
44216
  }
44049
44217
  if (!ownsAttachmentPreparation(preparationOwner))
44050
44218
  return;
44219
+ if (latestState.historyStatus === "loading") {
44220
+ invalidateAttachmentPreparation();
44221
+ return;
44222
+ }
44051
44223
  if (lifecycleRequestState.kind === "pending") {
44052
44224
  composerFeedback = { kind: "progress", message: `${lifecycleOperationPresentParticiple(lifecycleRequestState.operation)} the session \u2014 messages are paused.` };
44053
44225
  render();
@@ -44514,8 +44686,8 @@ function renderTerminalSessionBand({ aggregateStatus, width }) {
44514
44686
  const status = sessionStatusLabel(aggregateStatus).toLowerCase();
44515
44687
  return joinStyled([
44516
44688
  new StyledText11([
44517
- bg3(PALETTE.selectionBg)(bold5(fg12(PALETTE.systemAccent)(` Session ${status} `))),
44518
- bg3(PALETTE.selectionBg)(fg12(PALETTE.bodyText)("\u2014 this conversation is closed. "))
44689
+ bg5(PALETTE.selectionBg)(bold5(fg12(PALETTE.systemAccent)(` Session ${status} `))),
44690
+ bg5(PALETTE.selectionBg)(fg12(PALETTE.bodyText)("\u2014 this conversation is closed. "))
44519
44691
  ]),
44520
44692
  renderKeyHints({ hints: [{ key: "esc", action: "return to dashboard" }, { key: "n", action: "new session" }, { key: "m", action: "MCPs" }, { key: "ctrl+f", action: "files" }, { key: "ctrl+l", action: "links" }], width })
44521
44693
  ], `
@@ -45224,7 +45396,7 @@ function isPng(bytes) {
45224
45396
  }
45225
45397
 
45226
45398
  // src/tui/remy-splash.ts
45227
- import { bold as bold6, BoxRenderable as BoxRenderable5, fg as fg13, NativeImage, StyledText as StyledText12, TextRenderable as TextRenderable6, bg as bg4 } from "@opentui/core";
45399
+ import { bold as bold6, BoxRenderable as BoxRenderable5, fg as fg13, NativeImage, StyledText as StyledText12, TextRenderable as TextRenderable6, bg as bg6 } from "@opentui/core";
45228
45400
 
45229
45401
  // src/tui/remy-mark.ts
45230
45402
  var remyPixelFieldSource = new URL("../assets/remy-pixel-field.png", import.meta.url);
@@ -45498,7 +45670,7 @@ function renderMark(field, frame, rows = markRows) {
45498
45670
  const flush = () => {
45499
45671
  if (runLength === 0)
45500
45672
  return;
45501
- chunks.push(fg13(grey(runTop))(bg4(grey(runBottom))(upperHalfBlock.repeat(runLength))));
45673
+ chunks.push(fg13(grey(runTop))(bg6(grey(runBottom))(upperHalfBlock.repeat(runLength))));
45502
45674
  runLength = 0;
45503
45675
  };
45504
45676
  for (let column = 0;column < columns; column++) {
@@ -46070,7 +46242,7 @@ async function dashboard({
46070
46242
  const openDashboardTui = dependencies.openDashboardTui ?? createDashboardTui;
46071
46243
  const pages = [initialSessions];
46072
46244
  let pageIndex = 0;
46073
- let filters = { statuses: [], creators: [] };
46245
+ let filters = { statuses: [], creators: [], repositories: [], branchName: "" };
46074
46246
  let authors;
46075
46247
  let activeDashboardGeneration = 0;
46076
46248
  function holdTransitionScreen() {
@@ -46079,8 +46251,9 @@ async function dashboard({
46079
46251
  function createDashboardPageLoader({ generation }) {
46080
46252
  const loadingPages = new Map;
46081
46253
  let pageLoadTail = Promise.resolve();
46254
+ let filterRequestNumber = 0;
46082
46255
  return async (input) => {
46083
- const requestKey = "filters" in input ? `filters:${JSON.stringify(input.filters)}` : input.target;
46256
+ const requestKey = "filters" in input ? `filters:${++filterRequestNumber}` : input.target;
46084
46257
  const matchingPageLoad = loadingPages.get(requestKey);
46085
46258
  if (matchingPageLoad)
46086
46259
  return await matchingPageLoad;
@@ -46095,6 +46268,9 @@ async function dashboard({
46095
46268
  limit: 20,
46096
46269
  ...nextFilters.statuses.length > 0 ? { statuses: nextFilters.statuses } : {},
46097
46270
  ...nextFilters.creators.length > 0 ? { creatorUserIds: nextFilters.creators.map((creator) => creator.id) } : {},
46271
+ ...nextFilters.repositories.length > 0 ? { repositoryIds: nextFilters.repositories.map((repository) => repository.id) } : {},
46272
+ ...nextFilters.branchName ? { branchName: nextFilters.branchName } : {},
46273
+ ...nextFilters.search ? { search: nextFilters.search } : {},
46098
46274
  signal: input.signal
46099
46275
  }));
46100
46276
  if (generation !== activeDashboardGeneration)
@@ -46113,6 +46289,9 @@ async function dashboard({
46113
46289
  after,
46114
46290
  ...filters.statuses.length > 0 ? { statuses: filters.statuses } : {},
46115
46291
  ...filters.creators.length > 0 ? { creatorUserIds: filters.creators.map((creator) => creator.id) } : {},
46292
+ ...filters.repositories.length > 0 ? { repositoryIds: filters.repositories.map((repository) => repository.id) } : {},
46293
+ ...filters.branchName ? { branchName: filters.branchName } : {},
46294
+ ...filters.search ? { search: filters.search } : {},
46116
46295
  signal
46117
46296
  }));
46118
46297
  if (generation !== activeDashboardGeneration)
@@ -46133,6 +46312,9 @@ async function dashboard({
46133
46312
  after: current.nextCursor,
46134
46313
  ...filters.statuses.length > 0 ? { statuses: filters.statuses } : {},
46135
46314
  ...filters.creators.length > 0 ? { creatorUserIds: filters.creators.map((creator) => creator.id) } : {},
46315
+ ...filters.repositories.length > 0 ? { repositoryIds: filters.repositories.map((repository) => repository.id) } : {},
46316
+ ...filters.branchName ? { branchName: filters.branchName } : {},
46317
+ ...filters.search ? { search: filters.search } : {},
46136
46318
  signal
46137
46319
  }));
46138
46320
  if (generation !== activeDashboardGeneration)
@@ -46172,6 +46354,9 @@ async function dashboard({
46172
46354
  limit: 20,
46173
46355
  ...filters.statuses.length > 0 ? { statuses: filters.statuses } : {},
46174
46356
  ...filters.creators.length > 0 ? { creatorUserIds: filters.creators.map((creator) => creator.id) } : {},
46357
+ ...filters.repositories.length > 0 ? { repositoryIds: filters.repositories.map((repository) => repository.id) } : {},
46358
+ ...filters.branchName ? { branchName: filters.branchName } : {},
46359
+ ...filters.search ? { search: filters.search } : {},
46175
46360
  signal: dependencies.abortSignal
46176
46361
  })));
46177
46362
  pageIndex = 0;
@@ -46182,6 +46367,7 @@ async function dashboard({
46182
46367
  const action = await renderRemyView({
46183
46368
  open: async () => await openDashboardTui({
46184
46369
  initialPage: pages[0],
46370
+ loadRepositories: async () => await repositories,
46185
46371
  initialFilters: filters,
46186
46372
  loadAuthors: loadDashboardAuthors,
46187
46373
  loadPage: loadDashboardPage
@@ -46660,6 +46846,10 @@ var workspaceInvalidationEventSchema = exports_external2.object({
46660
46846
  type: exports_external2.enum(["session.workspace.git.initialized", "session.workspace.git.committed", "session.workspace.git.pushed"]),
46661
46847
  payload: exports_external2.object({ repository: exports_external2.object({ id: exports_external2.string().min(1) }) })
46662
46848
  });
46849
+ var mainAgentTurnEndedEventSchema = exports_external2.object({
46850
+ type: exports_external2.literal("agent.turn.ended"),
46851
+ actor: exports_external2.object({ type: exports_external2.literal("main") })
46852
+ });
46663
46853
  async function runAttachedSession({
46664
46854
  dependencies,
46665
46855
  operations,
@@ -46706,6 +46896,9 @@ async function runAttachedSession({
46706
46896
  const aborted3 = interactive ? new Promise((_resolve, reject) => {
46707
46897
  rejectAborted = reject;
46708
46898
  }) : undefined;
46899
+ aborted3?.catch(() => {
46900
+ return;
46901
+ });
46709
46902
  const abortHandler = () => {
46710
46903
  stop();
46711
46904
  rejectAborted(dependencies.abortSignal?.reason instanceof Error ? dependencies.abortSignal.reason : new Error("interrupted"));
@@ -46722,11 +46915,12 @@ async function runAttachedSession({
46722
46915
  });
46723
46916
  }
46724
46917
  const startPromise = controller.start(start);
46725
- await Promise.race([
46726
- controller.waitUntilReady(),
46727
- startPromise,
46728
- ...aborted3 ? [aborted3] : []
46729
- ]);
46918
+ const controllerFailed = startPromise.then(async () => await new Promise(() => {}));
46919
+ controllerFailed.catch(() => {
46920
+ return;
46921
+ });
46922
+ if (!interactive)
46923
+ await Promise.race([controller.waitUntilReady(), startPromise, ...aborted3 ? [aborted3] : []]);
46730
46924
  if (interactive) {
46731
46925
  if (dependencies.abortSignal?.aborted)
46732
46926
  throw dependencies.abortSignal.reason ?? new Error("interrupted");
@@ -46799,7 +46993,7 @@ async function runAttachedSession({
46799
46993
  savePastedImage: async (input) => await writePastedImage({ ...input, temporaryDirectory: tmpdir2() }),
46800
46994
  attachPromptPaths: async (input) => await uploadPromptPaths({ operations, ...input })
46801
46995
  }),
46802
- waitForAction: async (tui) => await Promise.race([tui.waitForAction(), aborted3])
46996
+ waitForAction: async (tui) => await Promise.race([tui.waitForAction(), aborted3, controllerFailed])
46803
46997
  });
46804
46998
  if (!action)
46805
46999
  throw dependencies.abortSignal?.reason ?? new Error("interrupted");
@@ -46852,6 +47046,10 @@ async function runAttachedSession({
46852
47046
  connection = state.connectionStatus;
46853
47047
  if (frame?.kind !== "retained")
46854
47048
  return;
47049
+ if (mainAgentTurnEndedEventSchema.safeParse(frame.data.event).success) {
47050
+ invalidateWorkspace({ kind: "turn-ended" });
47051
+ return;
47052
+ }
46855
47053
  const event = workspaceInvalidationEventSchema.safeParse(frame.data.event);
46856
47054
  if (!event.success)
46857
47055
  return;
@@ -46882,7 +47080,7 @@ async function runAttachedSession({
46882
47080
  view.invalidate(event);
46883
47081
  }
46884
47082
  mountedView = view;
46885
- return await Promise.race([view.waitForAction(), aborted3]);
47083
+ return await Promise.race([view.waitForAction(), aborted3, controllerFailed]);
46886
47084
  }
46887
47085
  });
46888
47086
  if (!result2 || result2.kind === "interrupt")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/remy-cli",
3
- "version": "1.25.0",
3
+ "version": "1.26.0-rc.0",
4
4
  "description": "Remy, the Coding Agent terminal client.",
5
5
  "type": "module",
6
6
  "bin": {