Skip to content

feat(server): add ZCode provider for Z.AI coding-plan models - #8116

Open
aintoniodev wants to merge 1 commit into
pingdotgg:mainfrom
aintoniodev:feat/zcode-provider
Open

feat(server): add ZCode provider for Z.AI coding-plan models#8116
aintoniodev wants to merge 1 commit into
pingdotgg:mainfrom
aintoniodev:feat/zcode-provider

Conversation

@aintoniodev

@aintoniodev aintoniodev commented Aug 24, 2026

Copy link
Copy Markdown

Problem

T3 Code can control Codex, Claude, Cursor, Grok, and OpenCode, but not ZCode. People with a Z.AI coding-plan subscription have to leave T3, and calling GLM through a third-party client (Pi, raw API keys) uses a smaller quota than the official ZCode app.

Change

Add a zcode driver that:

  • finds the CLI bundled in the ZCode desktop app (zcode.cjs), not the Electron wrapper
  • starts app-server --surface desktop with the existing coding-plan login
  • maps ZCode Protocol sessions, streaming, tools, and permissions into T3
  • exposes GLM-5.3 / GLM-5.2 / GLM-5-Turbo in the model picker (off by default, same as Grok)

Focused tests cover CLI discovery, protocol parsing, settings defaults, and a mock session/turn.

Notes

ZCode is off until you enable it in Settings. Install the ZCode app and sign in there, or run zcode login.

Made with Grok Build.


Note

Medium Risk
Large new provider surface (child processes, local credential reads, permission bridging) with broad contract and UI touchpoints; behavior is mostly isolated behind the adapter pattern but subprocess and auth edge cases can affect session reliability.

Overview
Adds ZCode as a sixth built-in provider so T3 can drive the Z.ai desktop agent from web, mobile, and settings like the other CLIs.

On the server, a new zcode driver spawns the bundled zcode.cjs entry (not the Electron zcode launcher) as app-server --surface desktop, speaks line-delimited ZCode Protocol JSON, and maps sessions, streaming, tools, permissions, and user-input/plan prompts into T3 runtime events. Desktop auth is read from ~/.zcode so sessions use the same Z.AI coding-plan OAuth as the official app. Provider health checks probe CLI install/version and login state; ZCodeTextGeneration reuses the protocol for commit/PR/title helpers.

Contracts add ZCodeSettings (off by default), GLM model defaults/aliases, and zcode.protocol.event as a raw runtime source. Web and mobile wire ZCode into the provider picker, settings cards, and icons. User docs cover install, binary path, and quota behavior; a mock app-server supports adapter tests.

Reviewed by Cursor Bugbot for commit 6adf406. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add ZCode provider for Z.AI coding-plan models

  • Introduces a full ZCode provider: a stdio JSON-RPC protocol client in zcodeRuntime.ts, an adapter translating ZCode events to runtime events in ZCodeAdapter.ts, and a provider driver in ZCodeDriver.ts.
  • Adds ZCodeSettings schema (enabled false by default, binaryPath, customModels) and contracts constants mapping ZCODE_DRIVER_KIND to default models GLM-5.3 and GLM-5-Turbo.
  • Registers ZCodeDriver in BUILT_IN_DRIVERS and adds ZCode to the web and mobile UI provider pickers with dedicated icons.
  • Implements makeZcodeTextGeneration in ZCodeTextGeneration.ts to fulfill text generation requests using a local ZCode runtime with a 180s timeout.
  • Risk: ZcodeProtocolClient spawns a detached ZCode app-server child process; verify close() terminates the process group cleanly on non-Windows platforms.
📊 Macroscope summarized 6adf406. 26 files reviewed, 28 issues evaluated, 8 issues filtered, 15 comments posted

🗂️ Filtered Issues

apps/server/src/provider/Drivers/ZCodeExecutable.ts — 1 comment posted, 2 evaluated, 1 filtered
  • line 84: whichOnPath treats any regular file as a usable PATH command and returns it without checking executable permission. On Unix, a non-executable file named zcode in an earlier PATH directory therefore shadows a valid executable in a later directory; the returned path is then spawned directly and fails with EACCES instead of discovering the working CLI. [ Out of scope (post-validation triage) ]
apps/server/src/provider/Layers/ZCodeAdapter.ts — 6 comments posted, 7 evaluated, 1 filtered
  • line 755: Every interrupted turn is added to ctx.interruptedTurnIds, but no path ever removes IDs from this set. A long-lived session that repeatedly interrupts turns accumulates one permanent entry per interruption, causing unbounded memory growth until the whole session is stopped. [ Out of scope (post-validation triage) ]
apps/server/src/provider/zcodeRuntime.ts — 2 comments posted, 13 evaluated, 6 filtered
  • line 144: ZcodeProtocolClient never registers an error listener on proc. spawn() reports failures such as a missing configured binary or nonexistent cwd asynchronously via the child process error event, so the surrounding Effect.try cannot catch them; an unhandled EventEmitter error then terminates the server instead of returning a provider startup error. [ Out of scope ]
  • line 148: The readline close callback calls failAll, which sets closed = true. If the child closes stdout without exiting, subsequent scope cleanup calls close() and returns immediately at its closed guard, so the detached ZCode process is never sent SIGTERM/SIGKILL and can leak indefinitely. [ Already posted ]
  • line 149: ZcodeProtocolClient never registers an error listener on proc. Node emits this event when spawn cannot start the command (for example, the configured ZCode binary or cwd no longer exists), and spawn() returns before that asynchronous failure, so the surrounding Effect.try cannot catch it. The unhandled error event can terminate the entire server instead of returning a provider startup error. [ Already posted ]
  • line 241: onLine trusts any successfully parsed JSON as a ZcodeInboundMessage and immediately reads message.id. A valid JSON line such as null causes a TypeError in the readline event callback, which is uncaught and can crash the server. The parsed value should be validated as a non-null object before property access. [ Exceeded comment limit ]
  • line 264: Inbound messages containing both id and method are server requests, but this branch treats one as a response whenever its ID matches an outstanding client request. Client and server request ID namespaces can overlap (for example, both sides may begin at 1), so a runtime-preferences/permission request can incorrectly resolve session/create with undefined and is never delivered or replied to, breaking the session. [ Out of scope ]
  • line 264: Inbound messages containing both id and method are server requests, but this branch reclassifies them as responses whenever the server-chosen ID happens to match a client request in pending. Bidirectional RPC peers have independent request-ID namespaces, so a normal collision (for example both sides using ID 1) resolves the unrelated client request with undefined and silently drops the server request instead of invoking serverRequestHandler. [ Exceeded comment limit ]

T3 Code can drive Codex, Claude, Cursor, Grok, and OpenCode, but not the
ZCode CLI that ships with the Z.AI desktop app. Third-party clients that
call the Z.AI API with a key also get a smaller quota than the official
app.

This adds a first-party zcode driver that launches the bundled zcode.cjs
app-server with the desktop surface and the user's existing coding-plan
login, then maps that protocol into T3 sessions.

Made with Grok Build.
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 99e313b4-8544-4f66-b867-1408f0bc24e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 24, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect Service Conventions: two findings in the new ZCode provider runtime (apps/server/src/provider/zcodeRuntime.ts). Both concern dependency acquisition and typed failures; the rest of the ZCode modules (driver shape, Services/ZCodeAdapter.ts, snapshot/enrich helpers, contracts changes) follow the existing provider conventions.

Posted via Macroscope — Effect Service Conventions

Comment on lines +296 to +306
}): Effect.Effect<ZcodeProtocolClient, Error, Scope.Scope> =>
Effect.acquireRelease(
Effect.try({
try: () => {
const argv = buildZcodeAppServerArgv({
binaryPath: input.binaryPath,
env: input.environment,
});
const plan = readZcodeDesktopPlan(input.environment);
const env = buildZcodeDesktopSpawnEnv(input.environment ?? process.env, plan);
const proc = spawn(argv.command, [...argv.args], {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ZCode app-server process is spawned with node:child_process.spawn instead of being acquired from the Effect environment, so a runtime-backed dependency of two Effect services (makeZcodeAdapter, makeZcodeTextGeneration) is hidden from the layer graph and cannot be substituted in tests. Every other provider runtime in this repo does yield* ChildProcessSpawner.ChildProcessSpawner (opencodeRuntime.ts, Layers/CodexSessionRuntime.ts, acp/AcpSessionRuntime.ts), and ZCodeDriver already lists ChildProcessSpawner in ZCodeDriverEnv — but only the version probe uses it.

Suggest acquiring the spawner here and spawning through it (ChildProcess.make(command, args, { env, cwd })), letting ChildProcessSpawner.ChildProcessSpawner surface in this effect's requirements; that also removes the direct process.kill / setTimeout handling in ZcodeProtocolClient.close() in favour of the handle's lifecycle.

Posted via Macroscope — Effect Service Conventions

Comment on lines +292 to +296
export const startZcodeProtocolClient = (input: {
readonly binaryPath: string | null | undefined;
readonly cwd: string;
readonly environment?: NodeJS.ProcessEnv;
}): Effect.Effect<ZcodeProtocolClient, Error, Scope.Scope> =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The failure channel here (and in zcodeCreateSession / zcodeResumeSession below) is the global Error, constructed with new Error(...). Service failures should be declared with Schema.TaggedErrorClass and structured attributes, with message derived from those attributes — compare OpenCodeRuntimeError in opencodeRuntime.ts. This is also flagged by the repo's globalErrorInEffectFailure: "error" diagnostic in tsconfig.base.json.

The untyped Error then propagates into wrappers that copy cause.message into detail (Layers/ZCodeAdapter.ts startSession, textGeneration/ZCodeTextGeneration.ts), which the conventions call out explicitly. Suggest a ZcodeRuntimeError carrying stable context (e.g. operation/method plus cause) and mapping it to ProviderAdapterProcessError/TextGenerationError from those structural fields rather than from cause.message.

Posted via Macroscope — Effect Service Conventions

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6adf406. Configure here.

detail: cause instanceof Error ? cause.message : String(cause),
cause,
}),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stuck turn after send failure

High Severity

sendTurn publishes turn.started and marks the session running before session/send. If that request fails, the error propagates with no turn.aborted/turn.completed and without clearing activeTurnId, so the thread stays stuck running. OpenCode resets session state and emits turn.aborted on the same failure path.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6adf406. Configure here.

payload: { state: "cancelled" },
});
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interrupt leaves approvals open

High Severity

interruptTurn cancels the turn via session/stop but never settles pendingApprovals / pendingUserInputs or emits request.resolved. stopSessionInternal succeeds the Deferreds without client.reply or UI resolve events. Unlike Grok/Claude, open permission prompts can leave the thread unsettled and the ZCode process waiting.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6adf406. Configure here.

proc.stdin.on("error", () => {
this.closed = true;
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unread stderr can deadlock

Medium Severity

The ZCode app-server is spawned with piped stderr, but ZcodeProtocolClient only reads stdout. Sibling providers drain stderr. If the CLI writes enough diagnostics to fill the pipe buffer, the child blocks and sessions/text-generation hang with no protocol progress.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6adf406. Configure here.


client.setServerRequestHandler((request) => {
handleBuiltinZcodeServerRequest(client, request);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Text gen drops server replies

Medium Severity

The text-generation server-request handler only runs handleBuiltinZcodeServerRequest and never replies when that returns false. Any non-builtin interaction/* request during commit/title generation leaves the app-server waiting until the 180s timeout instead of failing fast or auto-resolving.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6adf406. Configure here.

Comment on lines +211 to +218
probe: {
installed: false,
version: null,
status: "error",
auth: { status: "unknown" },
message:
"ZCode CLI is not installed. Install the ZCode desktop app from https://zcode.z.ai.",
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Layers/ZCodeProvider.ts:211

A non-.cjs CLI that is found and executes --version but exits nonzero is reported as installed: false with “ZCode CLI is not installed,” so a transient version-probe failure is misdiagnosed as a missing installation. Keep installed true and report the version health-check failure instead.

       probe: {
-        installed: false,
+        installed: true,
         version: null,
         status: "error",
         auth: { status: "unknown" },
-        message:
-          "ZCode CLI is not installed. Install the ZCode desktop app from https://zcode.z.ai.",
+        message: "Failed to execute ZCode CLI health check.",
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/ZCodeProvider.ts around lines 211-218:

A non-`.cjs` CLI that is found and executes `--version` but exits nonzero is reported as `installed: false` with “ZCode CLI is not installed,” so a transient version-probe failure is misdiagnosed as a missing installation. Keep `installed` true and report the version health-check failure instead.

Comment on lines +158 to +160
proc.stdin.on("error", () => {
this.closed = true;
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium provider/zcodeRuntime.ts:158

A broken proc.stdin pipe leaves in-flight request() promises pending until their 30-second timeouts and can leave the child process running. The handler only sets closed, so close() immediately returns and never rejects pending requests or performs cleanup; invoke close() from the error handler instead.

-    proc.stdin.on("error", () => {
-      this.closed = true;
-    });
+    proc.stdin.on("error", () => {
+      void this.close();
+    });
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/zcodeRuntime.ts around lines 158-160:

A broken `proc.stdin` pipe leaves in-flight `request()` promises pending until their 30-second timeouts and can leave the child process running. The handler only sets `closed`, so `close()` immediately returns and never rejects pending requests or performs cleanup; invoke `close()` from the error handler instead.


constructor(proc: ChildProcessWithoutNullStreams) {
this.proc = proc;
this.reader = createInterface({ input: proc.stdout });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High provider/zcodeRuntime.ts:146

The app-server eventually hangs and protocol requests time out when ZCode's piped stderr buffer fills, because ZcodeProtocolClient never consumes proc.stderr. Drain, log, or inherit stderr so diagnostics cannot block the child.

Suggested change
this.reader = createInterface({ input: proc.stdout });
this.reader = createInterface({ input: proc.stdout });
proc.stderr.on("data", () => {});
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/zcodeRuntime.ts around line 146:

The app-server eventually hangs and protocol requests time out when ZCode's piped `stderr` buffer fills, because `ZcodeProtocolClient` never consumes `proc.stderr`. Drain, log, or inherit `stderr` so diagnostics cannot block the child.

const versionOutput = versionResult.success.value;
const version = parseGenericCliVersion(`${versionOutput.stdout}\n${versionOutput.stderr}`);

if (versionOutput.code !== 0 && !cliPath.endsWith(".cjs")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Layers/ZCodeProvider.ts:205

An authenticated desktop plan is reported as ready even when the resolved .cjs bundle exits nonzero, so a corrupt installation or runtime exception is advertised as usable. The versionOutput.code !== 0 && !cliPath.endsWith(".cjs") guard deliberately skips the failure path for every .cjs bundle; handle every nonzero exit as a failed health check instead.

Suggested change
if (versionOutput.code !== 0 && !cliPath.endsWith(".cjs")) {
if (versionOutput.code !== 0) {
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/ZCodeProvider.ts around line 205:

An authenticated desktop plan is reported as `ready` even when the resolved `.cjs` bundle exits nonzero, so a corrupt installation or runtime exception is advertised as usable. The `versionOutput.code !== 0 && !cliPath.endsWith(".cjs")` guard deliberately skips the failure path for every `.cjs` bundle; handle every nonzero exit as a failed health check instead.

Comment on lines +26 to +35
function looksLikeElectronLauncher(path: string): boolean {
try {
const head = NodeFs.readFileSync(path, { encoding: "utf8" }).slice(0, 8_192);
return (
head.includes("electron") || head.includes("ELECTRON_IS_DEV") || head.includes("app.asar")
);
} catch {
return false;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Drivers/ZCodeExecutable.ts:26

looksLikeElectronLauncher reads and UTF-8-decodes the entire launcher on every resolution or status probe, so a large Electron executable blocks the server synchronously and allocates memory proportional to its full size. Open the file and read only a bounded 8 KiB prefix before checking the markers.

-  try {
-    const head = NodeFs.readFileSync(path, { encoding: "utf8" }).slice(0, 8_192);
+  let fd: number | undefined;
+  try {
+    fd = NodeFs.openSync(path, "r");
+    const prefix = Buffer.alloc(8_192);
+    const bytesRead = NodeFs.readSync(fd, prefix, 0, prefix.length, 0);
+    const head = prefix.toString("utf8", 0, bytesRead);
     return (
       head.includes("electron") || head.includes("ELECTRON_IS_DEV") || head.includes("app.asar")
     );
   } catch {
     return false;
+  } finally {
+    if (fd !== undefined) NodeFs.closeSync(fd);
   }
 }
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Drivers/ZCodeExecutable.ts around lines 26-35:

`looksLikeElectronLauncher` reads and UTF-8-decodes the entire launcher on every resolution or status probe, so a large Electron executable blocks the server synchronously and allocates memory proportional to its full size. Open the file and read only a bounded 8 KiB prefix before checking the markers.

env: environment,
});
const versionArgs =
argv.args[0] && isZcodeNodeBundle(argv.args[0]) ? [argv.args[0], "--version"] : ["--version"];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Layers/ZCodeProvider.ts:111

A .js or .mjs bundle that executes successfully but exits non-zero for --version is reported as not installed, just like a failed CLI. runZcodeVersionCommand recognizes all three Node bundle extensions, but the status check only exempts .cjs via cliPath.endsWith(".cjs"); use isZcodeNodeBundle(cliPath) there so .js and .mjs receive the same handling.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/ZCodeProvider.ts around line 111:

A `.js` or `.mjs` bundle that executes successfully but exits non-zero for `--version` is reported as not installed, just like a failed CLI. `runZcodeVersionCommand` recognizes all three Node bundle extensions, but the status check only exempts `.cjs` via `cliPath.endsWith(".cjs")`; use `isZcodeNodeBundle(cliPath)` there so `.js` and `.mjs` receive the same handling.

Comment on lines +721 to +734
yield* Effect.tryPromise({
try: () =>
ctx.client.request("session/send", {
sessionId: ctx.zcodeSessionId,
content: input.input ?? "",
}),
catch: (cause) =>
new ProviderAdapterRequestError({
provider: PROVIDER,
method: "session/send",
detail: cause instanceof Error ? cause.message : String(cause),
cause,
}),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Layers/ZCodeAdapter.ts:721

When session/send rejects or times out, sendTurn returns an error but leaves ctx.activeTurnId set and ctx.session.status as "running"; listSessions then exposes a stuck turn and the next sendTurn reuses its stale turnId. Restore the session to ready and clear the active turn in the request's error path.

-          yield* Effect.tryPromise({
+          yield* Effect.tryPromise({
             try: () =>
               ctx.client.request("session/send", {
                 sessionId: ctx.zcodeSessionId,
                 content: input.input ?? "",
@@
                 }),
           });
+          }).pipe(
+            Effect.tapError(() =>
+              Effect.sync(() => {
+                const { activeTurnId: _cleared, ...readySession } = ctx.session;
+                ctx.activeTurnId = undefined;
+                ctx.assistantItemStarted = false;
+                ctx.session = { ...readySession, status: "ready" };
+              }),
+            ),
+          );
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/ZCodeAdapter.ts around lines 721-734:

When `session/send` rejects or times out, `sendTurn` returns an error but leaves `ctx.activeTurnId` set and `ctx.session.status` as `"running"`; `listSessions` then exposes a stuck turn and the next `sendTurn` reuses its stale `turnId`. Restore the session to ready and clear the active turn in the request's error path.

detail: cause instanceof Error ? cause.message : String(cause),
cause,
}),
}).pipe(Effect.ignore);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Layers/ZCodeAdapter.ts:707

A rejected or timed-out session/setModel request is ignored, but ctx.currentModelId and ctx.session.model are still set to the requested model. Subsequent turns therefore suppress retries for that model while ZCode continues generating with the old one. Let the request failure propagate before updating the adapter state.

Suggested change
}).pipe(Effect.ignore);
});
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/ZCodeAdapter.ts around line 707:

A rejected or timed-out `session/setModel` request is ignored, but `ctx.currentModelId` and `ctx.session.model` are still set to the requested model. Subsequent turns therefore suppress retries for that model while ZCode continues generating with the old one. Let the request failure propagate before updating the adapter state.

Comment on lines +671 to +686
if (input.interactionMode === "plan") {
yield* Effect.tryPromise({
try: () =>
ctx.client.request("session/setMode", {
sessionId: ctx.zcodeSessionId,
mode: "plan",
}),
catch: (cause) =>
new ProviderAdapterRequestError({
provider: PROVIDER,
method: "session/setMode",
detail: cause instanceof Error ? cause.message : String(cause),
cause,
}),
}).pipe(Effect.ignore);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Layers/ZCodeAdapter.ts:671

After a plan turn, later non-plan turns leave the persistent ZCode session in plan mode, so ordinary prompts continue running without the expected build/edit actions. sendTurn only calls session/setMode for interactionMode === "plan"; set the mode on every turn using mapRuntimeModeToZcodeMode(input.runtimeMode, input.interactionMode).

Suggested change
if (input.interactionMode === "plan") {
yield* Effect.tryPromise({
try: () =>
ctx.client.request("session/setMode", {
sessionId: ctx.zcodeSessionId,
mode: "plan",
}),
catch: (cause) =>
new ProviderAdapterRequestError({
provider: PROVIDER,
method: "session/setMode",
detail: cause instanceof Error ? cause.message : String(cause),
cause,
}),
}).pipe(Effect.ignore);
}
const mode = mapRuntimeModeToZcodeMode(input.runtimeMode, input.interactionMode);
yield* Effect.tryPromise({
try: () =>
ctx.client.request("session/setMode", {
sessionId: ctx.zcodeSessionId,
mode,
}),
catch: (cause) =>
new ProviderAdapterRequestError({
provider: PROVIDER,
method: "session/setMode",
detail: cause instanceof Error ? cause.message : String(cause),
cause,
}),
}).pipe(Effect.ignore);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/ZCodeAdapter.ts around lines 671-686:

After a plan turn, later non-plan turns leave the persistent ZCode session in `plan` mode, so ordinary prompts continue running without the expected build/edit actions. `sendTurn` only calls `session/setMode` for `interactionMode === "plan"`; set the mode on every turn using `mapRuntimeModeToZcodeMode(input.runtimeMode, input.interactionMode)`.

const modelIds = modelIdsFromProvider(selected.provider);
const credentials = readJsonFile(NodePath.join(home, "v2", "credentials.json"));
const authenticated = isRecord(credentials)
? Object.keys(credentials).some(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High provider/zcodeDesktopAuth.ts:92

desktopPlan.authenticated becomes true when credentials.json contains an unrelated provider token, so checkZcodeProviderStatus reports ZCode as ready even though Z.AI session creation fails without credentials for the selected provider. The broad oauth: and token key checks must be restricted to credential keys associated with selected.id.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/zcodeDesktopAuth.ts around line 92:

`desktopPlan.authenticated` becomes `true` when `credentials.json` contains an unrelated provider token, so `checkZcodeProviderStatus` reports ZCode as ready even though Z.AI session creation fails without credentials for the selected provider. The broad `oauth:` and `token` key checks must be restricted to credential keys associated with `selected.id`.

@macroscopeapp

macroscopeapp Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial new ZCode provider, including a subprocess protocol, persistent sessions, approvals, model switching, desktop credential/quota integration, and text-generation workflows across the server and clients. The scope and unresolved lifecycle, authorization, process-cleanup, and authentication-related risks warrant human review.

Not approved because:

  • 15 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

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

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant