Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
32641bf
feat(providers): run vim.app cells as a native provider
kupiainen Oct 18, 2026
52e54e7
fix(cell): own runs by id, queue wakes, and recover reconnects
kupiainen Oct 18, 2026
9580b5a
fix(web): providers whose model is decided elsewhere offer no custom …
kupiainen Oct 18, 2026
a523618
docs(cell): a Cell thread runs only in Full access and only the tenan…
kupiainen Oct 18, 2026
68958de
fix(cell): bound T3's run by its first event, judge a reconnect once …
kupiainen Oct 18, 2026
2496c8f
docs(cell): record the run boundary, the judged gap, exact wake match…
kupiainen Oct 18, 2026
80c3b6e
fix(cell): require cell protocol 3, own runs by the 202 alone, open e…
kupiainen Oct 18, 2026
dae70d6
docs(cell): record the level-3 requirement, the connection order, the…
kupiainen Oct 18, 2026
a4a3e17
fix(cell): own a lost prompt by its request id, cancel the run by nam…
kupiainen Oct 18, 2026
f6d3bc2
docs(cell): record ownership by 202 or request id, refusal shapes, th…
kupiainen Oct 18, 2026
49bd147
fix(cell): treat only the cell's own refusal shape as a refusal, take…
kupiainen Oct 18, 2026
7aa9ce0
docs(cell): record the refusal shape, what a restart takes back and w…
kupiainen Oct 18, 2026
35e3614
fix(cell): persist the head at the output applied rather than the num…
kupiainen Oct 18, 2026
9e40232
docs(cell): record the output cursor the head carries, the reset tomb…
kupiainen Oct 18, 2026
94fc9e4
fix(cell): ask the adapter whether work survives when the session pre…
kupiainen Oct 18, 2026
394c2b8
docs(cell): record the identity fields and the misnaming report, the …
kupiainen Oct 18, 2026
6db37f7
fix(cell): attach at the first snapshot to complete, fail a 202 namin…
kupiainen Oct 19, 2026
dda6d98
docs(cell): record the attach at the first snapshot, the stale accept…
kupiainen Oct 19, 2026
ad2fe47
fix(cell): suspend the cancellation hook so a throw cannot abort a de…
kupiainen Oct 19, 2026
b0ba1d8
docs(cell): record the per-task cancel bounds and the hook's overall …
kupiainen Oct 19, 2026
0e2b95e
fix(cell): downgrade only T3's own session release after a run settle…
kupiainen Oct 19, 2026
14b2470
docs(cell): record that a cancel T3 stopped waiting for may still com…
kupiainen Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@t3tools/mobile-source-text": "file:./modules/t3-source-text",
"@t3tools/mobile-terminal-native": "file:./modules/t3-terminal",
"@t3tools/provider-acp-registry": "workspace:*",
"@t3tools/provider-cell": "workspace:*",
"@t3tools/provider-core": "workspace:*",
"@t3tools/provider-cursor": "workspace:*",
"@t3tools/provider-grok": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/src/lib/providerClients.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { isProviderDriverKind } from "@t3tools/contracts";
import { acpRegistryClient } from "@t3tools/provider-acp-registry/client";
import { cellClient } from "@t3tools/provider-cell/client";
import { makeProviderClientRegistry } from "@t3tools/provider-core/client";
import { cursorClient } from "@t3tools/provider-cursor/client";
import { grokClient } from "@t3tools/provider-grok/client";
Expand All @@ -13,6 +14,7 @@ const providerClients = makeProviderClientRegistry([
museClient,
openCodeClient,
piClient,
cellClient,
acpRegistryClient,
]);

Expand Down
4 changes: 3 additions & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"record:cursor-replay": "node --env-file-if-exists=../../.env --experimental-strip-types scripts/record-cursor-agent-sdk-replay-fixture.ts",
"record:pi-replay": "node scripts/record-pi-rpc-replay-fixture.ts",
"record:grok-replay": "node scripts/record-grok-acp-replay-fixture.ts",
"record:muse-replay": "node scripts/record-muse-msp-replay-fixture.ts"
"record:muse-replay": "node scripts/record-muse-msp-replay-fixture.ts",
"record:cell-replay": "node scripts/record-cell-replay-fixture.ts"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.3.276",
Expand All @@ -39,6 +40,7 @@
"@opencode-ai/sdk": "^1.3.15",
"@t3tools/provider-acp": "workspace:*",
"@t3tools/provider-acp-registry": "workspace:*",
"@t3tools/provider-cell": "workspace:*",
"@t3tools/provider-core": "workspace:*",
"@t3tools/provider-cursor": "workspace:*",
"@t3tools/provider-grok": "workspace:*",
Expand Down
198 changes: 198 additions & 0 deletions apps/server/scripts/record-cell-replay-fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// @effect-diagnostics nodeBuiltinImport:off globalTimers:off - a recorder: it drives live I/O under the test clock.
/**
* Records a `cell.http-ws` transcript for one shared fixture by running the
* real orchestrator and adapter against a cell over the network, then
* verifies the recording by replaying it. Pass `--url https://<tenant>` to
* record against a tenant; without it the fake cell from
* `@t3tools/provider-cell/testing` answers on a local port.
*/
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
import * as NodeServices from "@effect/platform-node/NodeServices";
import * as NodeSocket from "@effect/platform-node/NodeSocket";
import type { ProviderReplayEntry, ProviderReplayTranscript } from "@t3tools/contracts";
import { makeHttpCellTransport } from "@t3tools/provider-cell/server";
import { CellSettings } from "@t3tools/provider-cell/settings";
import {
CELL_REPLAY_PROTOCOL,
makeRecordingCellTransport,
startFakeCellServer,
} from "@t3tools/provider-cell/testing";
import * as Console from "effect/Console";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import { TestClock } from "effect/testing";

import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
import { checkpointWorkspace } from "@t3tools/provider-testing/replayWorkspace";
import {
CELL_PROVIDER_KIND,
CellOrchestratorReplayHarness,
layer as cellReplayLayer,
} from "../src/orchestration-v2/Adapters/CellAdapterV2.testkit.ts";
import { provideDeterministicTestRuntime } from "../src/orchestration-v2/testkit/DeterministicRuntime.ts";
import { ORCHESTRATOR_REPLAY_FIXTURES } from "../src/orchestration-v2/testkit/fixtures/index.ts";
import { materializeFixtureInput } from "../src/orchestration-v2/testkit/fixtures/shared.ts";
import { runOrchestratorV2ProviderReplayScenario } from "../src/orchestration-v2/testkit/ProviderReplayHarness.ts";

const CLOCK_TICK = Duration.millis(20);

function readArgValue(name: string): string | undefined {
const index = process.argv.indexOf(name);
return index === -1 ? undefined : process.argv[index + 1];
}

const scenario = readArgValue("--scenario");
const fixture = ORCHESTRATOR_REPLAY_FIXTURES.find((entry) => entry.name === scenario);
const variant = fixture?.providers.find((provider) => provider.driver === CELL_PROVIDER_KIND);
if (fixture === undefined || variant === undefined) {
const names = ORCHESTRATOR_REPLAY_FIXTURES.filter((entry) =>
entry.providers.some((provider) => provider.driver === CELL_PROVIDER_KIND),
).map((entry) => entry.name);
throw new Error(`Pass --scenario with a fixture that registers cell: ${names.join(", ")}`);
}

const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));
const decodeCellSettings = Schema.decodeUnknownEffect(CellSettings);

function encodeTranscriptNdjson(transcript: ProviderReplayTranscript): string {
const { entries, ...metadata } = transcript;
return [
encodeJson({ type: "transcript_start", ...metadata }),
...entries.map((entry) => encodeJson(entry)),
"",
].join("\n");
}

/** Recording follows wall time so the cell's own timeouts still elapse. */
const followWallClock = Effect.gen(function* () {
while (true) {
yield* TestClock.withLive(Effect.sleep(CLOCK_TICK));
yield* TestClock.adjust(CLOCK_TICK);
}
});

const record = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const outputPath = readArgValue("--out") ?? (yield* path.fromFileUrl(variant.transcriptFile));
const requestedUrl = readArgValue("--url");
const fake =
requestedUrl === undefined ? yield* Effect.promise(() => startFakeCellServer()) : undefined;
if (fake) yield* Effect.addFinalizer(() => Effect.promise(() => fake.close()));
const baseUrl = requestedUrl ?? fake!.baseUrl;
const settings = yield* decodeCellSettings({
enabled: true,
baseUrl,
token: readArgValue("--token") ?? "",
});
const liveTransport = yield* makeHttpCellTransport;
const fixtureInput = fixture.buildInput();
const entries: Array<ProviderReplayEntry> = [];
const materializeInput = materializeFixtureInput({
scenario: fixture.name,
fixtureInput,
driver: variant.driver,
modelSelection: variant.modelSelection,
}).pipe(Effect.provide(IdAllocator.layer));
const continuationOptions =
variant.runContinuationWorker === true ? { runContinuationWorker: true } : {};

yield* Effect.gen(function* () {
yield* followWallClock.pipe(Effect.forkScoped);
const workspace = yield* checkpointWorkspace(
`cell-record-${fixture.name}`,
fixtureInput.workspaceFiles,
);
const materialized = yield* materializeInput;
const recorded = yield* runOrchestratorV2ProviderReplayScenario(
{
name: `${fixture.name}/cell:record`,
transcript: {
provider: CELL_PROVIDER_KIND,
protocol: CELL_REPLAY_PROTOCOL,
version: "live",
scenario: fixture.name,
entries: [],
},
commands: materialized.commands,
steps: materialized.steps,
projectionThreadIds: materialized.projectionThreadIds,
runtimePolicyOverride: { ...variant.runtimePolicyOverride, cwd: workspace },
},
{
driver: CellOrchestratorReplayHarness.driver,
decodeTranscript: Effect.succeed,
makeProviderAdapterRegistryLayer: () =>
cellReplayLayer({
transport: makeRecordingCellTransport(liveTransport, entries),
settings,
}),
},
continuationOptions,
);
for (const projection of recorded.projections.values()) {
yield* Console.log(
`recorded runs: ${projection.runs.map((run) => run.status).join(", ")}; failures: ${projection.turnItems
.flatMap((item) => (item.type === "error" ? [item.failure.message] : []))
.join(" | ")}`,
);
}
}).pipe(Effect.scoped, provideDeterministicTestRuntime);

const transcript = {
provider: CELL_PROVIDER_KIND,
protocol: CELL_REPLAY_PROTOCOL,
// The cell reports its version only in `GET state`, which the adapter never calls.
version: fake ? "fake-cell" : (readArgValue("--version") ?? "live"),
scenario: fixture.name,
metadata: {
generatedBy: fake ? "fake-cell-recorder" : "live-cell-recorder",
model: variant.modelSelection.model,
},
entries,
} satisfies ProviderReplayTranscript;

yield* Effect.gen(function* () {
const replayWorkspace = yield* checkpointWorkspace(fixture.name, fixtureInput.workspaceFiles);
const materialized = yield* materializeInput;
const replayResult = yield* runOrchestratorV2ProviderReplayScenario(
{
name: `${fixture.name}/cell:verify`,
transcript: yield* CellOrchestratorReplayHarness.decodeTranscript(transcript),
commands: materialized.commands,
steps: materialized.steps,
projectionThreadIds: materialized.projectionThreadIds,
runtimePolicyOverride: { ...variant.runtimePolicyOverride, cwd: replayWorkspace },
},
CellOrchestratorReplayHarness,
continuationOptions,
);
variant.assertOutput(replayResult, transcript);
}).pipe(
Effect.scoped,
provideDeterministicTestRuntime,
Effect.tapError(() => Console.error(encodeTranscriptNdjson(transcript))),
);

yield* fs.makeDirectory(path.dirname(outputPath), { recursive: true });
yield* fs.writeFileString(outputPath, encodeTranscriptNdjson(transcript));
yield* Console.log(`Wrote ${transcript.entries.length} cell replay entries to ${outputPath}`);
});

// The undici dispatcher lives in its layer's scope, so the layers wrap the whole recording.
await Effect.runPromise(
record.pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
NodeHttpClient.layerUndici,
NodeSocket.layerWebSocketConstructorWS,
),
),
),
);
88 changes: 88 additions & 0 deletions apps/server/src/orchestration-v2/Adapters/CellAdapterV2.testkit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
ProviderDriverKind,
ProviderInstanceId,
ProviderReplayEntry,
type ProviderReplayTranscript,
} from "@t3tools/contracts";
import { makeCellAdapterV2, type CellTransport } from "@t3tools/provider-cell/server";
import { CellSettings } from "@t3tools/provider-cell/settings";
import { CELL_REPLAY_PROTOCOL, makeReplayCellTransport } from "@t3tools/provider-cell/testing";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";

import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts";

export const CELL_PROVIDER_KIND = "cell";

const CellReplayTranscript = Schema.Struct({
provider: Schema.Literal(CELL_PROVIDER_KIND),
protocol: Schema.Literal(CELL_REPLAY_PROTOCOL),
version: Schema.String,
scenario: Schema.String,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
entries: Schema.Array(ProviderReplayEntry),
});
type CellReplayTranscript = typeof CellReplayTranscript.Type;
const decodeCellReplayTranscript = Schema.decodeUnknownEffect(CellReplayTranscript);

class CellReplayTranscriptDecodeError extends Schema.TaggedError<CellReplayTranscriptDecodeError>()(
"CellReplayTranscriptDecodeError",
{ scenario: Schema.optional(Schema.String), cause: Schema.Defect() },
) {
override get message(): string {
return `Failed to decode cell replay transcript for scenario ${this.scenario ?? "<unknown>"}.`;
}
}

/** The base URL only shapes request URLs; replay matches paths, so any origin will do. */
const CELL_REPLAY_SETTINGS = Schema.decodeUnknownSync(CellSettings)({
enabled: true,
baseUrl: "https://cell.replay.test",
});

export function layer(input: {
readonly transport: CellTransport;
readonly settings?: CellSettings;
}) {
return ProviderAdapterRegistry.layerFromAdaptersEffect(
Effect.gen(function* () {
return [
yield* makeCellAdapterV2({
instanceId: ProviderInstanceId.make(CELL_PROVIDER_KIND),
settings: input.settings ?? CELL_REPLAY_SETTINGS,
transport: input.transport,
// Same queue the continuation worker drains when the fixture runs it.
continuationRequests: yield* ProviderContinuationRequests.ProviderContinuationRequests,
}),
];
}),
).pipe(Layer.provide(IdAllocator.layer));
}

export const CellOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness<
CellReplayTranscript,
CellReplayTranscriptDecodeError
> = {
driver: ProviderDriverKind.make(CELL_PROVIDER_KIND),
decodeTranscript: (transcript: ProviderReplayTranscript) =>
decodeCellReplayTranscript(transcript).pipe(
Effect.mapError(
(cause) => new CellReplayTranscriptDecodeError({ scenario: transcript.scenario, cause }),
),
),
makeProviderAdapterRegistryLayer: (transcript) =>
Layer.unwrap(
Effect.gen(function* () {
const controller = makeReplayCellTransport({
scenario: transcript.scenario,
entries: transcript.entries,
});
yield* Effect.addFinalizer(() => Effect.sync(() => controller.assertComplete()));
return layer({ transport: controller.transport });
}),
),
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
AcpRegistryAdapterV2Driver,
type AcpRegistryAdapterV2DriverEnv,
} from "@t3tools/provider-acp-registry/server";
import { CellAdapterV2Driver, type CellAdapterV2DriverEnv } from "@t3tools/provider-cell/server";
import {
ClaudeAdapterV2Driver,
type ClaudeAdapterV2DriverEnv,
Expand All @@ -23,6 +24,7 @@ import type { AnyProviderAdapterDriver } from "@t3tools/provider-core/server/ada

export type BuiltInProviderAdapterDriversV2Env =
| AcpRegistryAdapterV2DriverEnv
| CellAdapterV2DriverEnv
| ClaudeAdapterV2DriverEnv
| CodexAdapterV2DriverEnv
| CursorAdapterV2DriverEnv
Expand All @@ -39,6 +41,7 @@ const BUILT_IN_PROVIDER_ADAPTER_DRIVERS_V2: ReadonlyArray<
OpenCodeAdapterV2Driver,
GrokAdapterV2Driver,
PiAdapterV2Driver,
CellAdapterV2Driver,
AcpRegistryAdapterV2Driver,
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";

import { CellOrchestratorReplayHarness } from "../Adapters/CellAdapterV2.testkit.ts";
import { ClaudeOrchestratorReplayHarness } from "../Adapters/ClaudeAdapterV2.testkit.ts";
import { CodexOrchestratorReplayHarness } from "../Adapters/CodexAdapterV2.testkit.ts";
import { CursorOrchestratorReplayHarness } from "../Adapters/CursorAdapterV2.testkit.ts";
Expand Down Expand Up @@ -224,6 +225,11 @@ function runFixtureProviderWithRegisteredHarness(input: {
...input,
harness: MuseOrchestratorReplayHarness,
}).pipe(Effect.mapError(normalizeTestError), Effect.scoped);
case "cell":
return runFixtureProvider({
...input,
harness: CellOrchestratorReplayHarness,
}).pipe(Effect.mapError(normalizeTestError), Effect.scoped);
default:
return Effect.die(
new Error(`No replay harness registered for provider ${input.driver.driver}.`),
Expand Down
Loading
Loading