feat(coding-agent): move experimental runtime outside CLI

Keep command parsing under cli/experimental and adapt missing model identities to the current Harness shape.
This commit is contained in:
Christian Klotz 2026-08-16 22:53:32 +03:00
parent e5960aeb31
commit 33dac36236
No known key found for this signature in database
19 changed files with 49 additions and 33 deletions

View file

@ -1,6 +1,6 @@
#!/usr/bin/env node
import { runCoordinatorProcess } from "../cli/experimental/coordinator.ts";
import { consumeInternalProcessRole, getInternalProcessRole } from "../cli/experimental/process.ts";
import { runCoordinatorProcess } from "../experimental/coordinator.ts";
import { consumeInternalProcessRole, getInternalProcessRole } from "../experimental/process.ts";
import { restoreSandboxEnv } from "./restore-sandbox-env.ts";
restoreSandboxEnv();

View file

@ -1,9 +1,9 @@
#!/usr/bin/env node
import { consumeInternalProcessRole } from "./cli/experimental/process.ts";
import { runServerProcess } from "./cli/experimental/server.ts";
import { runSessionWorkerProcess } from "./cli/experimental/session-worker.ts";
import { APP_NAME } from "./config.ts";
import { configureHttpDispatcher } from "./core/http-dispatcher.ts";
import { consumeInternalProcessRole } from "./experimental/process.ts";
import { runServerProcess } from "./experimental/server.ts";
import { runSessionWorkerProcess } from "./experimental/session-worker.ts";
import { main } from "./main.ts";
const internalProcessRole = consumeInternalProcessRole();

View file

@ -2,7 +2,7 @@ import { basename } from "node:path";
import { PiClient } from "@earendil-works/pi-client";
import { createUnixTransportFactory, discoverUnixServers, type UnixServerRoute } from "@earendil-works/pi-client/unix";
import { isServerId } from "@earendil-works/pi-protocol";
import type { ClientCommand } from "./commands/client.ts";
import type { ClientCommand } from "../cli/experimental/commands/client.ts";
import { activateServer, ENV_SERVER_ID, resolveServerDirectory, resolveSessionDirectory } from "./server.ts";
export type ClientResult =

View file

@ -294,7 +294,10 @@ function toWireRunValue(value: Extract<HarnessRunResult, { ok: true }>["value"])
reason: "missing_identities",
runId: value.runId,
leafId: value.leafId,
missing: { tools: [...value.missing.tools], models: [...value.missing.models] },
missing: {
tools: [...value.missing.tools],
models: value.missing.model === undefined ? [] : [value.missing.model],
},
};
default:
return assertNever(value);
@ -316,7 +319,7 @@ function toWireRunError(error: Extract<HarnessRunResult, { ok: false }>["error"]
_tag: "MissingIdentities",
lane: error.lane,
tools: [...error.tools],
models: [...error.models],
models: error.model === undefined ? [] : [error.model],
message: error.message,
};
case "InvalidMessage":

View file

@ -1,6 +1,6 @@
import { type ChildProcess, spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { isBunBinary } from "../../config.ts";
import { isBunBinary } from "../config.ts";
export const INTERNAL_PROCESS_ENV = "__PI_INTERNAL_SPAWN";

View file

@ -11,8 +11,8 @@ import { isServerId, type ServerId } from "@earendil-works/pi-protocol";
import type { PiServer, PiServerHost } from "@earendil-works/pi-server";
import { createUnixServer, getUnixSocketPath } from "@earendil-works/pi-server/unix";
import lockfile from "proper-lockfile";
import { getAgentDir } from "../../config.ts";
import { resolvePath } from "../../utils/paths.ts";
import { getAgentDir } from "../config.ts";
import { resolvePath } from "../utils/paths.ts";
import { CoordinatorConnection, type CoordinatorStartupLease, ensureCoordinator } from "./coordinator.ts";
import { consumeInternalProcessRole, spawnInternalProcess, terminateInternalProcess } from "./process.ts";
import { SessionWorkerManager } from "./session-worker-manager.ts";

View file

@ -22,7 +22,7 @@ import {
import lockfile from "proper-lockfile";
import Type, { type Static } from "typebox";
import { Check } from "typebox/value";
import { ModelRuntime } from "../../core/model-runtime.ts";
import { ModelRuntime } from "../core/model-runtime.ts";
import { toHarnessPromptArguments, toWireRunResult } from "./harness-wire-adapter.ts";
import { consumeInternalProcessRole, encodeControlLine, MAX_CONTROL_LINE_BYTES } from "./process.ts";

View file

@ -27,10 +27,8 @@ import {
} from "./cli/auth-command.ts";
import { resolveCredentialForPrint } from "./cli/credential-print.ts";
import { cli as experimentalCli } from "./cli/experimental/cli.ts";
import { runClient } from "./cli/experimental/client.ts";
import type { ClientCommand } from "./cli/experimental/commands/client.ts";
import type { ServerCommand } from "./cli/experimental/commands/server.ts";
import { startForegroundServer } from "./cli/experimental/server.ts";
import { processFileArguments } from "./cli/file-processor.ts";
import { buildInitialMessage } from "./cli/initial-message.ts";
import { listModels } from "./cli/list-models.ts";
@ -65,6 +63,8 @@ import { assertValidSessionId, SessionManager } from "./core/session-manager.ts"
import { SettingsManager } from "./core/settings-manager.ts";
import { printTimings, resetTimings, time } from "./core/timings.ts";
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "./core/trust-manager.ts";
import { runClient } from "./experimental/client.ts";
import { startForegroundServer } from "./experimental/server.ts";
import { builtInExtensions } from "./extensions/index.ts";
import { runMigrations, showDeprecationWarnings } from "./migrations.ts";
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.ts";

View file

@ -11,7 +11,7 @@ import type { AssistantMessage } from "@earendil-works/pi-ai";
import { type PromptArguments, type PromptMessage, RunResultSchema } from "@earendil-works/pi-protocol";
import { Check } from "typebox/value";
import { describe, expect, test } from "vitest";
import { toHarnessPromptArguments, toWireRunResult } from "../src/cli/experimental/harness-wire-adapter.ts";
import { toHarnessPromptArguments, toWireRunResult } from "../src/experimental/harness-wire-adapter.ts";
const usage = {
input: 1,
@ -165,20 +165,33 @@ describe("Harness wire adapter", () => {
deferred: { provider: "test", modelId: "model", api: "test", id: "deferred-1", data: { row: 1 } },
},
},
{
] satisfies HarnessRunResult[])("projects every Run outcome %#", (result) => {
const wire = toWireRunResult(result);
expect(Check(RunResultSchema, wire)).toBe(true);
expect(wire).toEqual(result);
});
test("projects singular Harness model identities to the wire list", () => {
const wire = toWireRunResult({
ok: true,
value: {
kind: "suspended",
reason: "missing_identities",
runId: "run-1",
leafId: "leaf-1",
missing: { tools: ["read"], models: [] },
missing: { tools: ["read"], model: "missing-model" },
},
},
] satisfies HarnessRunResult[])("projects every Run outcome %#", (result) => {
const wire = toWireRunResult(result);
expect(Check(RunResultSchema, wire)).toBe(true);
expect(wire).toEqual(result);
});
expect(wire).toEqual({
ok: true,
value: {
kind: "suspended",
reason: "missing_identities",
runId: "run-1",
leafId: "leaf-1",
missing: { tools: ["read"], models: ["missing-model"] },
},
});
});
test.each([
@ -188,7 +201,7 @@ describe("Harness wire adapter", () => {
operationKind: "run",
message: "busy",
}),
new MissingIdentities({ lane: "main", tools: ["read"], models: [], message: "missing" }),
new MissingIdentities({ lane: "main", tools: ["read"], model: "missing-model", message: "missing" }),
new InvalidMessage({ lane: "main", reason: "invalid", message: "invalid" }),
new UnknownSkill({ name: "skill", message: "unknown" }),
new UnknownTemplate({ name: "template", message: "unknown" }),

View file

@ -5,7 +5,7 @@ import { createConnection } from "node:net";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { spawnInternalProcess, terminateInternalProcess } from "../src/cli/experimental/process.ts";
import { spawnInternalProcess, terminateInternalProcess } from "../src/experimental/process.ts";
const children = new Set<ChildProcess>();
const directories = new Set<string>();

View file

@ -3,8 +3,8 @@ import { join, resolve } from "node:path";
import { PiClient } from "@earendil-works/pi-client";
import { createUnixTransportFactory } from "@earendil-works/pi-client/unix";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { runClient } from "../src/cli/experimental/client.ts";
import { type RunningServer, startServer } from "../src/cli/experimental/server.ts";
import { runClient } from "../src/experimental/client.ts";
import { type RunningServer, startServer } from "../src/experimental/server.ts";
import { configureExperimentalWorkerModel, createExperimentalSessions } from "./experimental-session-support.ts";
const servers = new Set<RunningServer>();

View file

@ -1,5 +1,5 @@
import { afterEach, describe, expect, test, vi } from "vitest";
import { ServerLifetime } from "../src/cli/experimental/server.ts";
import { ServerLifetime } from "../src/experimental/server.ts";
afterEach(() => {
vi.useRealTimers();

View file

@ -2,7 +2,7 @@ import { mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { acquireServerProfile } from "../src/cli/experimental/server.ts";
import { acquireServerProfile } from "../src/experimental/server.ts";
const directories = new Set<string>();
const FIRST_SERVER_ID = "00000000-0000-4000-8000-000000000001";

View file

@ -4,7 +4,7 @@ import { lstat, mkdir, mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, describe, expect, test } from "vitest";
import { runClient } from "../src/cli/experimental/client.ts";
import { runClient } from "../src/experimental/client.ts";
import { configureExperimentalWorkerModel, createExperimentalSessions } from "./experimental-session-support.ts";
const cliPath = fileURLToPath(new URL("../src/cli.ts", import.meta.url));

View file

@ -1,7 +1,7 @@
import { homedir } from "node:os";
import { resolve } from "node:path";
import { afterEach, describe, expect, test, vi } from "vitest";
import { resolveSessionDirectory } from "../src/cli/experimental/server.ts";
import { resolveSessionDirectory } from "../src/experimental/server.ts";
afterEach(() => vi.unstubAllEnvs());

View file

@ -1,5 +1,5 @@
import { afterEach, describe, expect, test, vi } from "vitest";
import { WorkerLifecycle } from "../src/cli/experimental/session-worker.ts";
import { WorkerLifecycle } from "../src/experimental/session-worker.ts";
const GENERATION = "generation-1";

View file

@ -1,7 +1,7 @@
import type { JsonlSessionMetadata } from "@earendil-works/pi-agent-core";
import { describe, expect, test } from "vitest";
import type { CoordinatorConnectionEvent } from "../src/cli/experimental/coordinator.ts";
import { SessionWorkerManager } from "../src/cli/experimental/session-worker-manager.ts";
import type { CoordinatorConnectionEvent } from "../src/experimental/coordinator.ts";
import { SessionWorkerManager } from "../src/experimental/session-worker-manager.ts";
const metadata: JsonlSessionMetadata = {
id: "session-1",