Skip to content

Sync with upstream v0.1.3-nightly.20261024.3617 (review only, do not merge) - #39

Merged
martinothamar merged 0 commit into
mainfrom
sync/2026-10-24
Oct 24, 2026
Merged

martinothamar merged 0 commit into
mainfrom
sync/2026-10-24

Conversation

@martinothamar

@martinothamar martinothamar commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Review only — do not merge. This shows the rewrite of main for the upstream sync. Once it is approved, main is force-pushed to this branch, the old tip is archived as archive/main-2026-10-24, and this PR is closed.

Base

Upstream nightly v0.1.3-nightly.20261024.3617 (27e0c68), the latest, moving from v0.1.2-nightly.20261022.3526 (f054d1b).

The stack was first rebuilt on v0.1.3-nightly.20261023.3579 as sync/2026-10-23. That branch was never reviewed, and a newer nightly arrived before it was, so this branch replaces it. Moving from .3579 to .3617 was conflict-free and left every patch identical (git range-diff shows all five commits as =).

Mapping

Old main New Commit
9584908 + e3db001 2b44d95806 chore(fork): set up development, releases and maintenance (… #29, #38)
b3e0f4d 3aec799060 fix(server): wake watched pull requests for users sharing the agent account (#5)
06f1619 272c27e7f0 feat: agentctl Sandboxes as desktop environments (#2, #13, #20, #30, #32, #34, #35)
f446965 5ecddad15b feat: pull requests for repositories in a workspace folder (#19, #22, #23)
17b20ef ee02861 fix(desktop): brand preview builds Beta at runtime, as they are packaged (#33)

#38 (the dev Agent on the agentctl desktop base image) is folded into the setup commit. Its message's dev Agent paragraph now describes the image on the base.

Fixes for upstream changes

A review of the sync against the current release (v0.1.2-preview.20261022.22) found no regressions, but found two places where upstream's new code doesn't account for the fork's features. Each is fixed in the commit that owns the fork side, with a test that fails without the fix.

  • SSH version-mismatch diagnosis (setup commit). Upstream (upstream#23647) now tells you to restart a reused remote server that answers 500 on pairing and runs a different version, but it compares against the desktop's own APP_VERSION. With T3CODE_REMOTE_SERVER_VERSION, as when testing a sync build, the desktop runs another release on the host. In that case every 500 was diagnosed as a mismatch, and you were told to install the wrong version. The SSH bootstrap now reports the release the desktop runs on the host (remoteServerVersion), and the diagnosis compares against that, falling back to APP_VERSION.
  • Label and author filter search (workspace-folder commit). Upstream (upstream#23924) made those filters search the host within the listing's scope, but the scope dropped excludeRepositories. That's how the page lists a repository that several Sandboxes clone only once, so each search queried that repository again on every Sandbox that holds it. PullRequestFilterOptionsInput now takes excludeRepositories, and the page forwards it. Mobile doesn't use exclusions, so its filters already match its list.

Conflicts

  • docs/operations/development.md (setup): upstream rewrote "First checkout" to say vp provides the pinned Node version, so the separate Node 24 requirement is gone. The fork's link to Vite+'s getting started guide replaces upstream's link to the root README's #install-vp section, because the fork's README has no such section.
  • apps/server/src/orchestration-v2/pullRequestWatch.ts and pullRequestWatch.test.ts (fix(server): wake watched pull requests for a user who shares the agent's account #5): the fork's remarks is combined with upstream's no-checks, now, owedChecks and checksAwaitedSince, so watch evaluation keeps both, and the tests cover both.
  • None on the move to .3617. The fork's PR code overlaps 12 files upstream changed in that nightly, including the PR list filters and PullRequestService, but they merged cleanly and the checks below cover them.

Everything else in the commit 3 range-diff is context drift.

Requested change

  • New Sandbox's agent picker (Sandboxes commit). The trigger separated the repository from the agent's folder with a spaced, muted /, but the folder path kept plain slashes, so it read agents / self-dev/full. Every level now uses the same separator: agents / self-dev / full in the trigger, and self-dev / full in the list.
Before After (trigger) After (list)
Before After: trigger After: list

Range-diff

The full git range-diff f054d1b801..e3db001403 27e0c684c5..ee02861281 is about 100 KB, mostly #38's own diff appearing in commit 1. The fork's own files (scripts/fork, .github/workflows/fork-*.yml, Makefile, README.md) are identical to main's, agents differs only by the dev Agent change above, and apart from the two fixes above, git diff origin/main sync/2026-10-24 touches only files upstream changed.

Commit 1: message, development.md and the SSH version fix
    @@ Commit message
         scripts/fork/seed-sandbox-server.ts installs that build's server in a
         Sandbox for testing, and a run on main with from_run publishes the tested
         build once main points at its commit. T3CODE_REMOTE_SERVER_VERSION and
    -    T3CODE_REMOTE_SERVER_RELEASE_BASE_URL choose another release.
    +    T3CODE_REMOTE_SERVER_RELEASE_BASE_URL choose another release, and the SSH
    +    bootstrap reports that version, so a reused server on another build is
    +    told to run it rather than the desktop's own.
         scripts/fork/install-desktop.ts installs a release, or builds one with
         --from-source, beside an upstream install.
     
         The dev Agent. agents/dev is an agentctl Agent (t3code-dev) for developing
    -    the fork: an Ubuntu 24.04 image with Node, vp, Rust, ImageMagick, Claude
    -    Code, Codex, gh, Podman and agentctl; a boot-time clone of the fork; and a
    -    nested variant for testing the image from inside a Session. Its VNC desktop
    -    shows the Electron client, with the `desktop` helper and the computer-use
    -    skill to drive it, gnome-keyring for Electron's safeStorage, and SSH aliases
    -    for nested Agents. instructions.md describes how the fork is maintained. The
    -    image pins the Claude Code and Codex versions that agentctl and agentd
    -    release tests, and carries Inter and JetBrains Mono for its demo-video
    -    skill, which makes captioned demo videos of T3 Code from real screen
    -    recordings of the desktop with fframes: a storyboard, scripts that record,
    -    steer the pointer, cut takes and make contact sheets, and a fframes project
    -    template whose kit holds the look and scenes so a new video rewrites only
    -    its lib.rs.
    +    the fork, built on digdir-agents' ubuntu-26.04-desktop agentctl base image,
    +    which provides agentd's image contract, Claude Code and Codex at the versions
    +    agentd is tested with, gh, the VNC desktop with the `desktop` helper and
    +    computer-use skill, gnome-keyring for Electron's safeStorage and the
    +    boot-time workspace clone. The image adds what developing T3 Code needs:
    +    Node 26, Playwright's headless shell for the web browser tests, Vite+, Rust,
    +    ImageMagick, Podman with nested networking, agentctl and agentd built from a
    +    pinned digdir-agents commit, the kvm grant for nested Sandboxes, SSH aliases
    +    for nested Agents, a Norwegian keyboard and a 1600x900 display, and a
    +    drop-in that keeps one OOM kill over SSH from ending the other sessions. Its
    +    nested variant, for testing the image from inside a Session, builds from
    +    source without nested Sandboxes. instructions.md describes how the fork is
    +    maintained. The demo-video skill, with Inter beside the base's JetBrains
    +    Mono, makes captioned demo videos of T3 Code from real screen recordings of
    +    the desktop with fframes: a storyboard, scripts that record, steer the
    +    pointer, cut takes and make contact sheets, and a fframes project template
    +    whose kit holds the look and scenes so a new video rewrites only its
    +    lib.rs.
     
         AGENTS.md says how to release and install the fork, and that nothing
         published on the fork may reference an upstream issue, pull request or
    @@ apps/desktop/src/ssh/remoteServerOverrides.ts (new)
     +  return runner.nodeScriptPath ? runner : { ...runner, ...overrides };
     +}
     
    + ## apps/web/src/connection/platform.test.ts ##
    +@@ apps/web/src/connection/platform.test.ts: describe("desktop SSH pairing", () => {
    +     }),
    +   );
    + 
    +-  const failPairing = (serverVersion: string, message: string) => {
    ++  const failPairing = (serverVersion: string, message: string, remoteServerVersion?: string) => {
    +     const bridge = makeBridge([]);
    ++    if (remoteServerVersion !== undefined) {
    ++      vi.spyOn(bridge, "ensureSshEnvironment").mockResolvedValue({
    ++        ...SSH_BOOTSTRAP,
    ++        remoteServerVersion,
    ++      });
    ++    }
    +     const fetchDescriptor = bridge.fetchSshEnvironmentDescriptor;
    +     vi.spyOn(bridge, "fetchSshEnvironmentDescriptor").mockImplementation(async (url) => ({
    +       ...(await fetchDescriptor(url)),
    +@@ apps/web/src/connection/platform.test.ts: describe("desktop SSH pairing", () => {
    +     }),
    +   );
    + 
    ++  it.effect("names the server release the desktop runs on the host, not its own version", () =>
    ++    Effect.gen(function* () {
    ++      const preview = "0.1.3-preview.20261024.25";
    ++
    ++      const error = yield* failPairing(APP_VERSION, PAIRING_500, preview);
    ++      expect(error).toBeInstanceOf(ConnectionBlockedError);
    ++      expect(error.detail).toContain(`so it runs ${preview}`);
    ++
    ++      expect(yield* failPairing(preview, PAIRING_500, preview)).toBeInstanceOf(
    ++        ConnectionTransientError,
    ++      );
    ++    }),
    ++  );
    ++
    +   it.effect("keeps retrying pairing failures that are not a version mismatch", () =>
    +     Effect.gen(function* () {
    +       expect(yield* failPairing(APP_VERSION, PAIRING_500)).toBeInstanceOf(ConnectionTransientError);
    +
    + ## apps/web/src/connection/platform.ts ##
    +@@ apps/web/src/connection/platform.ts: export const provisionDesktopSshEnvironment = Effect.fn(
    +       detail: `That host reaches ${descriptor.label}, a different machine. Add it as its own environment instead.`,
    +     });
    +   }
    ++  // A build can choose another server release for SSH hosts than its own version.
    ++  const expectedVersion = bootstrap.remoteServerVersion ?? APP_VERSION;
    +   const access = yield* Effect.tryPromise({
    +     try: () => bridge.bootstrapSshBearerSession(bootstrap.httpBaseUrl, pairingToken),
    +     catch: (cause) => {
    +@@ apps/web/src/connection/platform.ts: export const provisionDesktopSshEnvironment = Effect.fn(
    +       // the desktop's runner just minted, and answers 500 on every retry.
    +       if (
    +         error._tag === "ConnectionBlockedError" ||
    +-        descriptor.serverVersion === APP_VERSION ||
    ++        descriptor.serverVersion === expectedVersion ||
    +         !error.detail.includes("[ssh_http:500]")
    +       ) {
    +         return error;
    +       }
    +       return new ConnectionBlockedError({
    +         reason: "configuration",
    +-        detail: `${descriptor.label} runs T3 Code ${descriptor.serverVersion}, but this desktop is ${APP_VERSION}, and that server could not accept its pairing. Restart the T3 server on ${descriptor.label} so it runs ${APP_VERSION}, then retry.`,
    ++        detail: `${descriptor.label} runs T3 Code ${descriptor.serverVersion}, but this desktop pairs with ${expectedVersion}, and that server could not accept its pairing. Restart the T3 server on ${descriptor.label} so it runs ${expectedVersion}, then retry.`,
    +       });
    +     },
    +   });
    +
      ## docs/README.md ##
     @@ docs/README.md: source alone does not explain. Most code changes do not need an internal documen
      ### Runbooks
    @@ docs/operations/development.md
      
      ## First checkout
      
    --Install `vp` using the [root README](../../README.md#install-vp). The checkout requires Node 24;
    -+Install `vp` with its [getting started guide](https://viteplus.dev/guide/). The checkout requires Node 26;
    - Bun is optional. From the repository root:
    +-Install `vp` using the [root README](../../README.md#install-vp). It provides the Node version
    ++Install `vp` with its [getting started guide](https://viteplus.dev/guide/). It provides the Node version
    + pinned in `package.json` (Node 26), so no separate Node install is needed. From the repository root:
      
      ```sh
     
    @@ docs/operations/fork-desktop-build.md (new)
     +- `T3CODE_REMOTE_SERVER_RELEASE_BASE_URL`: where release archives are downloaded from, such as
     +  `https://github.com/pingdotgg/t3code/releases/download` for upstream's.
     
    + ## packages/contracts/src/ipc.ts ##
    +@@ packages/contracts/src/ipc.ts: export interface DesktopSshEnvironmentBootstrap {
    +   pairingToken: string | null;
    +   remotePort?: number;
    +   remoteServerKind?: "external" | "managed";
    ++  /** The T3 release the desktop runs on the host, which mints its pairing credentials. */
    ++  remoteServerVersion?: string;
    + }
    + 
    + export const DesktopSshEnvironmentBootstrapSchema = Schema.Struct({
    +@@ packages/contracts/src/ipc.ts: export const DesktopSshEnvironmentBootstrapSchema = Schema.Struct({
    +   pairingToken: Schema.NullOr(Schema.String),
    +   remotePort: Schema.optionalKey(Schema.Number),
    +   remoteServerKind: Schema.optionalKey(Schema.Literals(["external", "managed"])),
    ++  remoteServerVersion: Schema.optionalKey(Schema.String),
    + });
    + 
    + export interface DesktopSshPasswordPromptRequest {
    +
    + ## packages/ssh/src/tunnel.test.ts ##
    +@@ packages/ssh/src/tunnel.test.ts: describe("ssh tunnel scripts", () => {
    +           assert.equal(tunnels, 2);
    +           assert.isTrue(remoteRunning);
    +           assert.equal(first.httpBaseUrl, second.httpBaseUrl);
    ++          assert.equal(first.remoteServerVersion, ARCHIVE.archiveVersion);
    +         }).pipe(
    +           Effect.ensuring(Deferred.succeed(finishShutdown, undefined)),
    +           Effect.provide(layer),
    +
    + ## packages/ssh/src/tunnel.ts ##
    +@@ packages/ssh/src/tunnel.ts: const makeSshEnvironmentManager = Effect.fn("ssh/tunnel.SshEnvironmentManager.ma
    +           pairingToken,
    +           remotePort: entry.remotePort,
    +           ...(entry.remoteServerKind ? { remoteServerKind: entry.remoteServerKind } : {}),
    ++          ...(runner?.archiveVersion?.trim()
    ++            ? { remoteServerVersion: runner.archiveVersion.trim() }
    ++            : {}),
    +         };
    +       }),
    +     );
    +
      ## scripts/fork/install-desktop.test.ts (new) ##
     @@
     +// @effect-diagnostics nodeBuiltinImport:off globalDate:off - tests the script, which only uses Node built-ins.
Commits 2 to 4 (3 holds the picker change, 4 the filter search fix)
2:  b3e0f4d980 ! 2:  3aec799060 fix(server): wake watched pull requests for users sharing the agent account (#5)
    @@ apps/server/src/orchestration-v2/pullRequestWatch.forgejoComments.test.ts: descr
          );
     
      ## apps/server/src/orchestration-v2/pullRequestWatch.test.ts ##
    -@@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: import type {
    - } from "@t3tools/contracts";
    - import { assert, describe, it } from "@effect/vitest";
    - 
    --import { PULL_REQUEST_WATCH_WAKE_LIMIT, evaluatePullRequestWatch } from "./pullRequestWatch.ts";
    -+import {
    -+  PULL_REQUEST_WATCH_WAKE_LIMIT,
    -+  evaluatePullRequestWatch,
    +@@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: import { assert, describe, it } from "@effect/vitest";
    + import {
    +   PULL_REQUEST_WATCH_WAKE_LIMIT,
    +   evaluatePullRequestWatch as evaluate,
     +  pullRequestWatchMessage,
    -+} from "./pullRequestWatch.ts";
    - 
    - const STARTED = "2026-10-02T12:00:00.000Z";
    + } from "./pullRequestWatch.ts";
      
    + // These cases read the head once or at one moment, so the no-checks notice never comes due.
     @@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: describe("evaluatePullRequestWatch", () => {
          const late = { ...remark("bot", "2026-10-02T12:06:00Z"), id: "late" };
          const reported = evaluatePullRequestWatch(watch(), detail(), [first]);
    @@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: describe("evaluatePul
          const conflicting = detail({ mergeability: "conflicting" });
          const first = evaluatePullRequestWatch(watch(), conflicting, noRemarks);
     @@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: describe("evaluatePullRequestWatch", () => {
    -     assert.equal(pushed.next.wakes, 1);
    +     assert.isTrue(anHourLater(watch(), azure).every((kinds) => kinds.length === 0));
        });
      });
     +
    @@ apps/server/src/orchestration-v2/pullRequestWatch.test.ts: describe("evaluatePul
     +});
     
      ## apps/server/src/orchestration-v2/pullRequestWatch.ts ##
    -@@ apps/server/src/orchestration-v2/pullRequestWatch.ts: import type {
    +@@ apps/server/src/orchestration-v2/pullRequestWatch.ts: import * as DateTime from "effect/DateTime";
      
      /**
       * Wakes in a row that bring only comments. Check, conflict, or push news resets the count, so
    @@ apps/server/src/orchestration-v2/pullRequestWatch.ts: import type {
     + * this stops a chatty bot, or the agent's own unmarked replies, from looping it.
       */
      export const PULL_REQUEST_WATCH_WAKE_LIMIT = 10;
    + /** How long a GitHub head may go without any check before the agent is told none reported. */
    + const PULL_REQUEST_WATCH_NO_CHECKS_MS = 15 * 60_000;
      const LISTED_ITEMS = 10;
      const SNIPPET_LENGTH = 200;
     +/**
    @@ apps/server/src/orchestration-v2/pullRequestWatch.ts: import type {
     +      /** The agent's own login, to point out its unmarked remarks, which are likely the user's. */
     +      readonly own: string | undefined;
     +    }
    +   | { readonly kind: "no-checks"; readonly minutes: number }
        | { readonly kind: "conflicting" };
      
    - export interface PullRequestWatchReport {
     @@ apps/server/src/orchestration-v2/pullRequestWatch.ts: export function evaluatePullRequestWatch(
        const own = (detail.viewer ?? detail.author?.login)?.toLowerCase();
        const through = Date.parse(watch.remarksThrough);
    @@ apps/server/src/orchestration-v2/pullRequestWatch.ts: function changeLines(
     +          : []),
            ];
     +    }
    -     case "conflicting":
    -       return [`- The branch now conflicts with ${context.baseBranch}.`];
    -   }
    +     case "no-checks":
    +       return [
    +         `- No checks have reported${context.commit} after ${change.minutes} minutes. None may be configured to run on this change.`,
     
      ## apps/server/src/orchestration-v2/runtimeLayer.test.ts ##
     @@ apps/server/src/orchestration-v2/runtimeLayer.test.ts: it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => {
3:  06f16197b6 ! 3:  272c27e7f0 feat: agentctl Sandboxes as desktop environments (#2, #13, #20, #30, #32, #34, #35)
    @@ apps/web/src/components/Sidebar.tsx: import { buildThreadRouteParams, resolveAct
      import { ProjectEnvironmentBadge } from "./ProjectEnvironmentBadge";
      import {
        buildDraftActionMenuItems,
    -@@ apps/web/src/components/Sidebar.tsx: export default function Sidebar() {
    +@@ apps/web/src/components/Sidebar.tsx: export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
              }
            >
              <SidebarGroup className="flex-1" role="presentation">
    @@ apps/web/src/components/Sidebar.tsx: export default function Sidebar() {
                {isSearchingThreads ? (
                  threadSearchResults.length > 0 ? (
                    <TooltipProvider
    -@@ apps/web/src/components/Sidebar.tsx: export default function Sidebar() {
    +@@ apps/web/src/components/Sidebar.tsx: export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
                          <PlusIcon className="-mx-0.5 size-3" />
                          Add project
                        </button>
    @@ apps/web/src/routes/__root.tsx: import { RelayClientInstallDialog } from "../com
     +import { NewSandboxDialogHost } from "../sandboxes/NewSandboxDialog";
      import { DesktopWebLinkCoordinator } from "../components/desktop/DesktopWebLinkCoordinator";
      import { RunningThreadKeepAlive } from "../components/desktop/RunningThreadKeepAlive";
    - import { ProviderUpdateLaunchNotification } from "../components/ProviderUpdateLaunchNotification";
    + import { DesktopUpdateLaunchNotice } from "../components/sidebar/updateEverythingRun";
     @@ apps/web/src/routes/__root.tsx: function RootRouteView() {
              >
                {primaryEnvironmentAuthenticated ? <AuthenticatedTracingBootstrap /> : null}
    @@ apps/web/src/sandboxes/ManifestPicker.tsx (new)
     @@
     +import type { AgentctlSandboxManifest } from "@t3tools/contracts";
     +import { ChevronsUpDownIcon, FolderGit2Icon } from "lucide-react";
    -+import { useRef, useState } from "react";
    ++import { Fragment, useRef, useState } from "react";
     +
     +import { Button } from "../components/ui/button";
     +import {
    @@ apps/web/src/sandboxes/ManifestPicker.tsx (new)
     +          >
     +            {repository && agent ? (
     +              <span className="truncate">
    -+                {repository.name}
    -+                <span className="text-muted-foreground"> / </span>
    -+                {agent.label}
    ++                <PathSegments segments={[repository.name, ...agent.label.split("/")]} />
     +              </span>
     +            ) : (
     +              <span className="text-muted-foreground">Choose an agent</span>
    @@ apps/web/src/sandboxes/ManifestPicker.tsx (new)
     +                        value={candidate.directory}
     +                        className="ms-6"
     +                      >
    -+                        <span className="truncate">{candidate.label}</span>
    ++                        <span className="truncate">
    ++                          <PathSegments segments={candidate.label.split("/")} />
    ++                        </span>
     +                        {candidate.agentName !== null && candidate.agentName !== candidate.label ? (
     +                          <span className="truncate text-muted-foreground text-xs">
     +                            {candidate.agentName}
    @@ apps/web/src/sandboxes/ManifestPicker.tsx (new)
     +  );
     +}
     +
    ++/** Separates a repository and its folders alike, so the levels read as one path. */
    ++function PathSegments({ segments }: { segments: ReadonlyArray<string> }) {
    ++  return segments.map((segment, index) => (
    ++    <Fragment key={segments.slice(0, index + 1).join("/")}>
    ++      {index > 0 ? <span className="text-muted-foreground"> / </span> : null}
    ++      {segment}
    ++    </Fragment>
    ++  ));
    ++}
    ++
     +function ManifestPath({ manifest }: { manifest: AgentctlSandboxManifest }) {
     +  return (
     +    <span className="min-w-0 font-mono text-muted-foreground text-xs">
4:  f446965627 ! 4:  5ecddad15b feat: pull requests for repositories in a workspace folder (#19, #22, #23)
    @@ Commit message
     
         Numbered Sandboxes that each clone the same repository listed each of its
         pull requests once per Sandbox. A pull request listing can now leave out
    -    repositories by canonical key, without reading them from the host. The page
    +    repositories by canonical key, without reading them from the host, and so can
    +    the host search behind its label and author filters. The page
         assigns each repository to one connected server, folders' checkouts
         included, in a fork-owned helper beside upstream's project assignment, asks
         the others to skip it, and the picker offers each Sandbox's checkout of it.
    @@ apps/server/src/pullRequest/PullRequestService.test.ts: function makeService(inp
     +  }),
     +);
     +
    ++it.effect("searches filter options only in the repositories its listing reads", () =>
    ++  Effect.gen(function* () {
    ++    const searched: Array<string> = [];
    ++    const repositories = ["acme/app", "acme/infra"].map(
    ++      (repository) =>
    ++        project({ id: "member", title: "", workspaceRoot: "", repository }).repositoryIdentity!,
    ++    );
    ++    const service = yield* makeService({
    ++      projects: [project({ id: "sandbox", title: "sandbox", workspaceRoot: "/workspace" })],
    ++      resolveWorkspaceRepositories: () =>
    ++        Effect.succeed(
    ++          repositories.map((identity) => ({
    ++            ...identity,
    ++            rootPath: `/workspace/${identity.displayName!.split("/")[1]}`,
    ++          })),
    ++        ),
    ++      providers: [
    ++        fakeProvider(SourceControlProviderKind.make("github"), {
    ++          listRepositoryLabels: ({ repository }) =>
    ++            Effect.sync(() => {
    ++              searched.push(repository);
    ++              return { items: [], truncated: false };
    ++            }),
    ++        }),
    ++      ],
    ++    });
    ++
    ++    yield* service.filterOptions({
    ++      kind: "labels",
    ++      excludeRepositories: ["GitHub.com/Acme/Infra"],
    ++    });
    ++
    ++    assert.deepStrictEqual(searched, ["acme/app"]);
    ++  }),
    ++);
    ++
     +it.effect(
     +  "lists a repository through the project rooted in it, not a workspace that holds it",
     +  () =>
    @@ apps/web/src/components/pullRequest/PullRequestsPage.tsx: export function PullRe
              ...(search.host ? { host: search.host } : {}),
            } satisfies PullRequestListInput,
          }));
    +   })();
    +   const facetQuery = usePullRequestList(facetTargets);
    +-  const filterOptionScope = environmentQueries.map(({ environmentId, projectIds }) => ({
    +-    environmentId,
    +-    input: {
    +-      ...(scopedProjectId ? { projectId: scopedProjectId } : {}),
    +-      ...(projectIds ? { projectIds } : {}),
    +-      ...(search.host ? { host: search.host } : {}),
    +-    },
    +-  }));
    ++  const filterOptionScope = environmentQueries.map(
    ++    ({ environmentId, projectIds, excludeRepositories }) => ({
    ++      environmentId,
    ++      input: {
    ++        ...(scopedProjectId ? { projectId: scopedProjectId } : {}),
    ++        ...(projectIds ? { projectIds } : {}),
    ++        ...(excludeRepositories ? { excludeRepositories } : {}),
    ++        ...(search.host ? { host: search.host } : {}),
    ++      },
    ++    }),
    ++  );
    +   const partitionsWanted = search.involvement === "all" && typedQuery.length === 0;
    +   const partitionTargets = (() => {
    +     // The main list goes first. Besides putting the visible rows on screen sooner, it proves
     @@ apps/web/src/components/pullRequest/PullRequestsPage.tsx: export function PullRequestsPage() {
            return { authored: NO_LIST_TARGETS, reviewing: NO_LIST_TARGETS };
          }
    @@ packages/contracts/src/pullRequest.ts: export const PullRequestListInput = Schem
        host: Schema.optional(TrimmedNonEmptyString),
        limit: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 500 }))),
        cursors: Schema.optional(PullRequestListCursors),
    +@@ packages/contracts/src/pullRequest.ts: export const PullRequestFilterOptionsInput = Schema.Struct({
    +   kind: PullRequestFilterOptionKind,
    +   projectId: Schema.optional(ProjectId),
    +   projectIds: Schema.optional(Schema.Array(ProjectId).check(Schema.isMaxLength(100))),
    ++  excludeRepositories: PullRequestListInput.fields.excludeRepositories,
    +   host: Schema.optional(TrimmedNonEmptyString),
    +   query: Schema.optional(TrimmedNonEmptyString.check(Schema.isMaxLength(100))),
    + });
     
      ## packages/shared/src/composerPullRequestMatches.test.ts ##
     @@ packages/shared/src/composerPullRequestMatches.test.ts: import {

Checks

  • vp run check:changed v0.1.3-nightly.20261024.3617, which covers every file the stack touches: format, lint, knip, graphql, typecheck (32 packages), and 1063 tests passed, 1 skipped.
  • MuseReplayFixtures.integration.test.ts: 8 of 8 passed.
  • ci.yml, release.yml and release-desktop.yml are unchanged upstream, so fork-ci.yml and fork-release.yml need no updates. Upstream added relay-preview.yml, relay-preview-cleanup.yml and stale-needs-more-info.yml. They aren't on fork-guard.yml's allowlist, so it disables them.
  • Fork CI and the Fork release build passed on 6c52d30, and the nested Agent test of that build is in the comments. The dev Agent change made a new head, so Fork CI and a new Fork release build passed on ee02861. That build passed the nested Agent test again; the results are in the latest comment.

@martinothamar
martinothamar force-pushed the sync/2026-10-24 branch 2 times, most recently from 5f151ce to 6c52d30 Compare October 24, 2026 15:29
@martinothamar

Copy link
Copy Markdown
Owner Author

Build and nested Agent test

Tested the Fork release build of 6c52d30, run 40107236833, version 0.1.3-preview.20261024.27. Publishing is skipped on a sync branch, as expected. All six desktop builds and both Windows verify jobs passed, and so did Fork CI.

Against a nested Agent from this branch's agents/dev, using vp run dev:desktop with T3CODE_REMOTE_SERVER_VERSION=0.1.3-preview.20261024.27:

  • Added the copy as a manifest folder, and created t3code-dev-nested-1 from the nested variant with New Sandbox. Until the build's server was installed it showed Cannot connect (a 404 on the unpublished release), which is expected. After seed-sandbox-server.ts --run 40107236833 --agent t3code-dev-nested-1, it connected and got its project.
  • A Claude thread (Haiku 5.5, Low) ran hostname and answered t3code-dev-nested-1.
  • Stop, then Start: it reconnected and kept its thread.
  • A Codex thread (GPT-6-Luna, Low), started after the restart, ran hostname and answered t3code-dev-nested-1. The nested agentd first needed agentctl codex login --from-stdin from the outer Agent's placeholder auth file. The test instructions only cover the Claude login, so they should get this step too.
  • Delete removed the Sandbox, its project and its threads.

Spot checks of the features behind the conflicts and fixes:

@martinothamar

Copy link
Copy Markdown
Owner Author

Build and nested Agent test, after the dev Agent change

Tested the Fork release build of ee02861, run 40125733503, version 0.1.3-preview.20261024.28. All six desktop builds and both Windows verify jobs passed, and publishing was skipped as expected. Fork CI passed on the same commit. This replaces the test of 6c52d30 above.

This Sandbox's agentctl and agentd were replaced with the build the new image ships (v0.1.0-preview.12.main.g07b3ae5). The nested agentd was logged in with agentctl claude login and agentctl codex login, with no flags. Both stored this Agent's mediated login. Then, against a nested Agent from this branch's agents/dev, using vp run dev:desktop with T3CODE_REMOTE_SERVER_VERSION=0.1.3-preview.20261024.28:

  • Create: I added the copy as a manifest folder and created t3code-dev-nested-1 from the nested variant with New Sandbox. It showed Cannot connect (a 404 on the unpublished release) until seed-sandbox-server.ts --run 40125733503 --agent t3code-dev-nested-1 installed the server. Then it connected and got its project.
  • The dev Agent change, checked inside the nested Agent:
    • ~/.claude/CLAUDE.md and ~/.codex/AGENTS.md start with the # Agent sandbox section written by agentd g07b3ae5. The trimmed instructions.md follows it.
    • ~/.claude/skills has agentctl beside computer-use, demo-video and playwright-cli.
    • ~/.claude/settings.json has the home overlay's attribution block.
  • Codex: a thread on GPT-6-Luna at Low ran hostname and answered t3code-dev-nested-1. Codex was offered as soon as the Sandbox connected. Last time it first needed a manual agentctl codex login --from-stdin, so the flagless login fixes the step the previous test reported missing.
  • Claude: a thread on Haiku 5.5 at Low ran hostname and answered t3code-dev-nested-1.
  • Stop, then Start: the Sandbox kept both threads. The first reconnect attempt failed with "did not respond during connection setup", and the retry connected. A follow-up in the Codex thread then answered t3code-dev-nested-1.
  • Delete: removed the Sandbox, its project and its threads.

Only agents/dev changed since the last test, so I didn't repeat the earlier spot checks of the Pull Requests page and the New Sandbox picker.

@martinothamar
martinothamar merged commit ee02861 into main Oct 24, 2026
32 checks passed
@martinothamar
martinothamar deleted the sync/2026-10-24 branch October 24, 2026 21:36
@martinothamar

Copy link
Copy Markdown
Owner Author

Approved and applied: main now points at ee02861, and the old tip e3db001 is archived as archive/main-2026-10-24. Publishing the tested build from run 40125733503.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant