@zergai/ztc 0.9.0-beta.1 → 0.10.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/bin/zmux-darwin-arm64 +0 -0
- package/bin/ztc-audio-darwin-arm64 +0 -0
- package/bin/ztc-capture-darwin-arm64 +0 -0
- package/bin/ztc-tui-darwin-arm64 +0 -0
- package/bin/zterm-darwin-arm64 +0 -0
- package/dist/agent/commands/agent_cli.d.ts +21 -0
- package/dist/agent/commands/agent_cli.js +66 -0
- package/dist/agent/commands/index.js +2 -1
- package/dist/agent/tools/bus_message.js +27 -4
- package/dist/agent/tools/pane.d.ts +5 -1
- package/dist/agent/tools/pane.js +19 -8
- package/dist/app/command_context.js +5 -3
- package/dist/cli_main.js +13 -0
- package/dist/cli_ratatui.d.ts +10 -0
- package/dist/cli_ratatui.js +62 -17
- package/dist/mcp/workspace_server.d.ts +44 -0
- package/dist/mcp/workspace_server.js +403 -0
- package/dist/panes/agent_cli.d.ts +105 -0
- package/dist/panes/agent_cli.js +295 -0
- package/dist/panes/agent_panes.d.ts +61 -0
- package/dist/panes/agent_panes.js +119 -0
- package/dist/panes/new_pane_choices.d.ts +14 -0
- package/dist/panes/new_pane_choices.js +36 -0
- package/dist/ui/ratatui/protocol.d.ts +3 -0
- package/dist/web/mirror_stream.d.ts +7 -0
- package/dist/web/mirror_stream.js +17 -0
- package/dist/zlm-embed.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1322,6 +1322,49 @@ npm run web # Web UI at http://localhost:3000
|
|
|
1322
1322
|
npm start # Production entry point
|
|
1323
1323
|
```
|
|
1324
1324
|
|
|
1325
|
+
### Claude Code and Codex in panes
|
|
1326
|
+
|
|
1327
|
+
ZTC hosts your own Claude Code and Codex next to its agents. Each pane runs
|
|
1328
|
+
your installed, unmodified `claude` or `codex`, signed in with your own account
|
|
1329
|
+
through that CLI's own sign-in; ZTC never reads, stores or forwards those
|
|
1330
|
+
credentials. Panes need zmux (plain `ztc`) or the desktop app.
|
|
1331
|
+
|
|
1332
|
+
- `/claude [prompt | flags]` and `/codex [prompt | flags]` open one in this
|
|
1333
|
+
pane's directory. Plain text is the opening prompt (`/claude fix the failing
|
|
1334
|
+
test`); text that starts with a flag goes to the CLI as its arguments
|
|
1335
|
+
(`/claude --continue`, `/codex -m gpt-5 "review the diff"`).
|
|
1336
|
+
- Ctrl+B c offers Claude Code and Codex next to bash and ZTC when they are
|
|
1337
|
+
installed. In the desktop app, the tab strip's + opens a menu: New ZTC Pane,
|
|
1338
|
+
New Claude Code Pane, New Codex Pane, New Shell Pane. Cmd+T still opens a ZTC
|
|
1339
|
+
tab directly.
|
|
1340
|
+
|
|
1341
|
+
Each of these panes starts with ZTC's workspace MCP server (`ztc
|
|
1342
|
+
workspace-mcp`) attached for that session only, through Claude Code's
|
|
1343
|
+
`--mcp-config` or Codex's `-c mcp_servers.ztc.*`, so your own CLI configuration
|
|
1344
|
+
is never edited. Its tools give the agent the workspace:
|
|
1345
|
+
|
|
1346
|
+
| Tool | What it does |
|
|
1347
|
+
|------|--------------|
|
|
1348
|
+
| `list_panes` | Each pane's id, name and kind, and which pane is the agent's own |
|
|
1349
|
+
| `open_pane` | Open a ZTC agent, Claude Code, Codex or a shell, optionally with a first task |
|
|
1350
|
+
| `read_pane` | Read a pane's scrollback, or what is on screen |
|
|
1351
|
+
| `type_into_pane` | Type into a pane, then press Enter |
|
|
1352
|
+
| `send_message` | Message another agent over ZTC's bus |
|
|
1353
|
+
| `close_pane` | Close another pane |
|
|
1354
|
+
|
|
1355
|
+
A ZTC agent reaches these panes by name as well: a message for a Claude Code or
|
|
1356
|
+
Codex pane is typed into it as `[Message from ben (pane 2)] …` (a question also
|
|
1357
|
+
says how to answer), and the answer comes back as a message from that pane.
|
|
1358
|
+
Broadcasts to `all` reach ZTC agents only.
|
|
1359
|
+
|
|
1360
|
+
ZTC's own agent loop uses an Anthropic API key for Claude models (`/config key`
|
|
1361
|
+
or `ANTHROPIC_API_KEY`). Anthropic's [legal and compliance
|
|
1362
|
+
terms](https://code.claude.com/docs/en/legal-and-compliance) reserve Claude
|
|
1363
|
+
subscription sign-in for Claude Code and Anthropic's own apps: third-party tools
|
|
1364
|
+
may not route requests through Free, Pro, Max or Ultra plan credentials or
|
|
1365
|
+
handle Claude.ai credentials. Running your own Claude Code in a pane is how to
|
|
1366
|
+
use a Claude subscription alongside ZTC.
|
|
1367
|
+
|
|
1325
1368
|
### Claude Code CLI OAuth
|
|
1326
1369
|
|
|
1327
1370
|
ZTC can use Claude Code subscription/OAuth auth through the official Claude Code CLI instead of direct Anthropic API-key billing:
|
package/bin/zmux-darwin-arm64
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin/ztc-tui-darwin-arm64
CHANGED
|
Binary file
|
package/bin/zterm-darwin-arm64
CHANGED
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/claude` and `/codex` — open the user's own Claude Code or Codex in a new
|
|
3
|
+
* pane, in this pane's directory.
|
|
4
|
+
*
|
|
5
|
+
* The CLI signs in with the user's own account through its own flow; ZTC only
|
|
6
|
+
* runs it, unmodified, and attaches its workspace MCP server so the agent can
|
|
7
|
+
* open, read, type into and message other panes. Plain text after the command
|
|
8
|
+
* is the opening prompt; text starting with a flag is the CLI's arguments.
|
|
9
|
+
*/
|
|
10
|
+
import { type AgentCliId, type PaneProgram } from '../../panes/agent_cli.js';
|
|
11
|
+
import type { PaneCommandTransport } from '../../ui/ratatui/pane_relay.js';
|
|
12
|
+
import type { Command } from './types.js';
|
|
13
|
+
export interface AgentCliCommandDeps {
|
|
14
|
+
transport?: () => PaneCommandTransport | null;
|
|
15
|
+
findBinary?: (id: AgentCliId) => string | null;
|
|
16
|
+
server?: () => PaneProgram | Error;
|
|
17
|
+
env?: () => NodeJS.ProcessEnv;
|
|
18
|
+
}
|
|
19
|
+
export declare function createAgentCliCommand(id: AgentCliId, deps?: AgentCliCommandDeps): Command;
|
|
20
|
+
export declare const claudeCommand: Command;
|
|
21
|
+
export declare const codexCommand: Command;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/claude` and `/codex` — open the user's own Claude Code or Codex in a new
|
|
3
|
+
* pane, in this pane's directory.
|
|
4
|
+
*
|
|
5
|
+
* The CLI signs in with the user's own account through its own flow; ZTC only
|
|
6
|
+
* runs it, unmodified, and attaches its workspace MCP server so the agent can
|
|
7
|
+
* open, read, type into and message other panes. Plain text after the command
|
|
8
|
+
* is the opening prompt; text starting with a flag is the CLI's arguments.
|
|
9
|
+
*/
|
|
10
|
+
import { getMuxTransport } from '../tools/pane.js';
|
|
11
|
+
import { AGENT_CLIS, findAgentCli, formatCliArguments, parseAgentCommandBody, workspaceMcpProgram, } from '../../panes/agent_cli.js';
|
|
12
|
+
import { openAgentPane } from '../../panes/agent_panes.js';
|
|
13
|
+
function shownCommand(binary, request) {
|
|
14
|
+
const words = [...request.words, ...(request.prompt ? ['--', request.prompt] : [])];
|
|
15
|
+
return words.length > 0 ? `${binary} ${formatCliArguments(words)}` : binary;
|
|
16
|
+
}
|
|
17
|
+
export function createAgentCliCommand(id, deps = {}) {
|
|
18
|
+
const spec = AGENT_CLIS[id];
|
|
19
|
+
const account = id === 'claude' ? 'Claude' : 'ChatGPT';
|
|
20
|
+
return {
|
|
21
|
+
name: id,
|
|
22
|
+
description: `Open ${spec.label} in a new pane, signed in with your own ${account} account`,
|
|
23
|
+
usage: '[prompt | flags]',
|
|
24
|
+
agentRunnable: true,
|
|
25
|
+
category: 'panes',
|
|
26
|
+
handler: async (args, ctx) => {
|
|
27
|
+
let request;
|
|
28
|
+
try {
|
|
29
|
+
request = parseAgentCommandBody(args.join(' '));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
ctx.addMessage({ role: 'system', content: `/${id}: ${error.message}` });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const transport = (deps.transport ?? getMuxTransport)();
|
|
36
|
+
if (!transport) {
|
|
37
|
+
ctx.addMessage({
|
|
38
|
+
role: 'system',
|
|
39
|
+
content: `/${id} opens ${spec.label} in a pane, which needs ZTC's panes: start ZTC with zmux (plain \`ztc\`) or use the desktop app. You can also run \`${spec.binary}\` in another terminal.`,
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const cwd = ctx.shell.getCwd();
|
|
44
|
+
const result = await openAgentPane(transport, {
|
|
45
|
+
cli: id,
|
|
46
|
+
request,
|
|
47
|
+
cwd,
|
|
48
|
+
findBinary: deps.findBinary ?? findAgentCli,
|
|
49
|
+
server: (deps.server ?? workspaceMcpProgram)(),
|
|
50
|
+
env: (deps.env ?? (() => process.env))(),
|
|
51
|
+
});
|
|
52
|
+
if (result.error) {
|
|
53
|
+
ctx.addMessage({ role: 'system', content: result.error });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
ctx.addMessage({
|
|
57
|
+
role: 'system',
|
|
58
|
+
content: `Opened ${result.title} in pane ${result.paneId} (${cwd}): ${shownCommand(spec.binary, request)}`,
|
|
59
|
+
});
|
|
60
|
+
if (result.notice)
|
|
61
|
+
ctx.addMessage({ role: 'system', content: result.notice });
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export const claudeCommand = createAgentCliCommand('claude');
|
|
66
|
+
export const codexCommand = createAgentCliCommand('codex');
|
|
@@ -30,6 +30,7 @@ import { connectCommand } from './connect.js';
|
|
|
30
30
|
import { reloadCommand, restartCommand } from './restart.js';
|
|
31
31
|
import { initCommand } from './init.js';
|
|
32
32
|
import { forkCommand, sideCommand } from './fork.js';
|
|
33
|
+
import { claudeCommand, codexCommand } from './agent_cli.js';
|
|
33
34
|
import { diffCommand } from './diff.js';
|
|
34
35
|
import { rawCommand } from './raw.js';
|
|
35
36
|
import { themeCommand } from './theme.js';
|
|
@@ -82,7 +83,7 @@ import { effortCommand, thinkingCommand, reasoningCommand, fastCommand } from '.
|
|
|
82
83
|
import { runtimePluginRegistry } from '../../plugin/runtime_registry.js';
|
|
83
84
|
const commandList = [];
|
|
84
85
|
const helpCommand = makeHelpCommand(() => commandList);
|
|
85
|
-
commandList.push(helpCommand, authCommand, loginCommand, logoutCommand, accountsCommand, zergCommand, zergsCommand, configCommand, debugCommand, clearCommand, handoffCommand, statusCommand, runtimeCommand, environmentCommand, jobCommand, shareCommand, hooksCommand, emulationCommand, shellCommand, cdCommand, clipboardCommand, modelsCommand, modelCommand, permissionsCommand, resourcesCommand, initCommand, forkCommand, sideCommand, diffCommand, rawCommand, themeCommand, doctorCommand, intentCommand, profileCommand, skillsCommand, runbooksCommand, keybindingsCommand, keymapCommand, shortcutsCommand, updateCommand, desktopCommand, layoutCommand, setupCommand, connectCommand, keysCommand, restartCommand, reloadCommand, inputModeCommand, retryCommand, dictationStatusCommand, embeddedCommand, benchCommand, voiceCommand, sfxCommand, compactCommand, automodeCommand, sessionsCommand, resumeCommand, rewindCommand, renameCommand, labelCommand, inboxCommand, attentionCommand, contextCommand, evidenceCommand, boardCommand, usageCommand, shannonRunnerCommand, planCommand, executeCommand, modeCommand, modeListCommand, exitModeCommand, validateCommand, previewCommand, saveCommand, dcaigCommand, manifestCommand, swarmCommand, swarmStopCommand, swarmStatusCommand, superviseCommand, superviseStopCommand, superviseStatusCommand, zlickyCommand, zlickyStopCommand, zlickyStatusCommand, zlickyActCommand, zlickyPresenceCommand, zlickyPttCommand, simCommand, simStopCommand, simStatusCommand, simRunCommand, simCheckpointCommand, simResumeCommand, evolveCommand, evolveStopCommand, evolveStatusCommand, evolveBestCommand, optimizeCommand, batchCommand, batchStatusCommand, batchStopCommand, workflowsCommand, mirrorCommand, settingsCommand, mcpCommand, loopCommand, goalCommand, effortCommand, thinkingCommand, reasoningCommand, fastCommand, exitCommand);
|
|
86
|
+
commandList.push(helpCommand, authCommand, loginCommand, logoutCommand, accountsCommand, zergCommand, zergsCommand, configCommand, debugCommand, clearCommand, handoffCommand, statusCommand, runtimeCommand, environmentCommand, jobCommand, shareCommand, hooksCommand, emulationCommand, shellCommand, cdCommand, clipboardCommand, modelsCommand, modelCommand, permissionsCommand, resourcesCommand, initCommand, forkCommand, sideCommand, claudeCommand, codexCommand, diffCommand, rawCommand, themeCommand, doctorCommand, intentCommand, profileCommand, skillsCommand, runbooksCommand, keybindingsCommand, keymapCommand, shortcutsCommand, updateCommand, desktopCommand, layoutCommand, setupCommand, connectCommand, keysCommand, restartCommand, reloadCommand, inputModeCommand, retryCommand, dictationStatusCommand, embeddedCommand, benchCommand, voiceCommand, sfxCommand, compactCommand, automodeCommand, sessionsCommand, resumeCommand, rewindCommand, renameCommand, labelCommand, inboxCommand, attentionCommand, contextCommand, evidenceCommand, boardCommand, usageCommand, shannonRunnerCommand, planCommand, executeCommand, modeCommand, modeListCommand, exitModeCommand, validateCommand, previewCommand, saveCommand, dcaigCommand, manifestCommand, swarmCommand, swarmStopCommand, swarmStatusCommand, superviseCommand, superviseStopCommand, superviseStatusCommand, zlickyCommand, zlickyStopCommand, zlickyStatusCommand, zlickyActCommand, zlickyPresenceCommand, zlickyPttCommand, simCommand, simStopCommand, simStatusCommand, simRunCommand, simCheckpointCommand, simResumeCommand, evolveCommand, evolveStopCommand, evolveStatusCommand, evolveBestCommand, optimizeCommand, batchCommand, batchStatusCommand, batchStopCommand, workflowsCommand, mirrorCommand, settingsCommand, mcpCommand, loopCommand, goalCommand, effortCommand, thinkingCommand, reasoningCommand, fastCommand, exitCommand);
|
|
86
87
|
export const commands = commandList;
|
|
87
88
|
export function refreshPluginCommands() {
|
|
88
89
|
const existing = new Set(commandList.map(command => command.name));
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* from cli_ratatui.ts at startup.
|
|
6
6
|
*/
|
|
7
7
|
import { readPaneResponseSnapshot } from '../../utils/pane_response.js';
|
|
8
|
-
import { resolvePaneIdByName } from './pane.js';
|
|
8
|
+
import { listWorkspacePanes, resolvePaneIdByName } from './pane.js';
|
|
9
9
|
// --- Setter-based transport access ---
|
|
10
10
|
let busTransportRef = null;
|
|
11
11
|
export function setBusTransport(transport) {
|
|
@@ -34,6 +34,18 @@ async function resolveBusRecipient(value) {
|
|
|
34
34
|
return parsed;
|
|
35
35
|
return resolvePaneIdByName(String(value).trim());
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* The name of a pane that is not a ZTC agent (Claude Code, Codex, a shell), or
|
|
39
|
+
* null. Such a pane writes no ZTC response to wait for: its agent answers later
|
|
40
|
+
* with a message of its own.
|
|
41
|
+
*/
|
|
42
|
+
async function nonZtcPaneName(paneId) {
|
|
43
|
+
const panes = await listWorkspacePanes(undefined, 3000);
|
|
44
|
+
if (panes instanceof Error)
|
|
45
|
+
return null;
|
|
46
|
+
const pane = panes.find(candidate => candidate.id === paneId);
|
|
47
|
+
return pane && pane.pane_type !== 'ztc' ? (pane.labels?.manual_title || pane.title) : null;
|
|
48
|
+
}
|
|
37
49
|
async function waitForNewPaneResponse(paneId, baseline, timeoutMs) {
|
|
38
50
|
const deadline = Date.now() + timeoutMs;
|
|
39
51
|
while (Date.now() < deadline) {
|
|
@@ -182,10 +194,17 @@ export const sendMessageTool = {
|
|
|
182
194
|
}
|
|
183
195
|
const rawKind = String(args.kind || 'statement').toLowerCase();
|
|
184
196
|
const kind = rawKind === 'question' || rawKind === 'request' || rawKind === 'reply' ? rawKind : 'statement';
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
197
|
+
const asks = typeof to === 'number' && (kind === 'question' || kind === 'request');
|
|
198
|
+
// The baseline is read and the message sent before anything awaits, so a
|
|
199
|
+
// reply written right after delivery is never mistaken for the baseline.
|
|
200
|
+
const baseline = asks ? readPaneResponseSnapshot(to) : null;
|
|
201
|
+
const outgoingText = asks ? buildZtcRequestPayload(text, kind) : text;
|
|
188
202
|
busTransportRef.sendBusMessage(to, outgoingText, 'ztc_message');
|
|
203
|
+
const external = asks ? await nonZtcPaneName(to) : null;
|
|
204
|
+
if (external !== null) {
|
|
205
|
+
return `${kind === 'question' ? 'Question' : 'Request'} sent to ${external} (pane ${to}). It is not a ZTC agent, so its answer arrives later as a message from that pane.`;
|
|
206
|
+
}
|
|
207
|
+
const waitForResponse = asks;
|
|
189
208
|
if (waitForResponse && baseline) {
|
|
190
209
|
try {
|
|
191
210
|
const response = await waitForNewPaneResponse(to, baseline, 90_000);
|
|
@@ -311,6 +330,10 @@ export const askZtcPaneTool = {
|
|
|
311
330
|
: 90;
|
|
312
331
|
const baseline = readPaneResponseSnapshot(paneId);
|
|
313
332
|
busTransportRef.sendBusMessage(paneId, buildZtcRequestPayload(text, 'request'), 'ztc_message');
|
|
333
|
+
const external = await nonZtcPaneName(paneId);
|
|
334
|
+
if (external !== null) {
|
|
335
|
+
return `Sent to ${external} (pane ${paneId}). It is not a ZTC agent, so its answer arrives later as a message from that pane.`;
|
|
336
|
+
}
|
|
314
337
|
try {
|
|
315
338
|
const response = await waitForNewPaneResponse(paneId, baseline, timeoutSeconds * 1000);
|
|
316
339
|
return `Response from ZTC ${paneId}:\n${response}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Tool } from './types.js';
|
|
2
2
|
import type { PaneCommandTransport } from '../../ui/ratatui/pane_relay.js';
|
|
3
|
-
import type { RustEvent } from '../../ui/ratatui/protocol.js';
|
|
3
|
+
import type { PaneInfo, RustEvent } from '../../ui/ratatui/protocol.js';
|
|
4
4
|
import { resolveModelAndProvider } from '../../panes/model_catalog.js';
|
|
5
5
|
import type { PaneLaunchSpec } from '../../panes/types.js';
|
|
6
6
|
export { resolveModelAndProvider };
|
|
@@ -36,6 +36,8 @@ export declare function getPaneToolsTransport(): PaneCommandTransport | null;
|
|
|
36
36
|
* Resolves "ben" to the one other pane named ben. People address panes by the
|
|
37
37
|
* names they gave them with /rename, not by pane number.
|
|
38
38
|
*/
|
|
39
|
+
/** The renderer's current pane list, through the given transport (default: the pane tools'). */
|
|
40
|
+
export declare function listWorkspacePanes(transport?: PaneCommandTransport | null, timeoutMs?: number): Promise<PaneInfo[] | Error>;
|
|
39
41
|
export declare function resolvePaneIdByName(name: string): Promise<number | Error>;
|
|
40
42
|
export declare function waitForPaneCreatedOrError(tui: PaneCommandTransport, expected: {
|
|
41
43
|
paneType?: string;
|
|
@@ -43,6 +45,8 @@ export declare function waitForPaneCreatedOrError(tui: PaneCommandTransport, exp
|
|
|
43
45
|
requestId?: string;
|
|
44
46
|
}, timeoutMs?: number): Promise<RustEvent>;
|
|
45
47
|
export declare function paneLimitReached(): boolean;
|
|
48
|
+
/** Environment a ZTC child pane needs to join this workspace (relay, mirror bus, PATH). */
|
|
49
|
+
export declare function childZtcPaneEnv(): Record<string, string>;
|
|
46
50
|
export declare function buildChildZtcPaneLaunch(extraArgs?: string[], childSessionId?: string, options?: {
|
|
47
51
|
mirrorPort?: number;
|
|
48
52
|
entrypoint?: string;
|
package/dist/agent/tools/pane.js
CHANGED
|
@@ -277,23 +277,33 @@ function paneName(title) {
|
|
|
277
277
|
* Resolves "ben" to the one other pane named ben. People address panes by the
|
|
278
278
|
* names they gave them with /rename, not by pane number.
|
|
279
279
|
*/
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
/** The renderer's current pane list, through the given transport (default: the pane tools'). */
|
|
281
|
+
export async function listWorkspacePanes(transport = tuiProcessRef, timeoutMs = 5000) {
|
|
282
|
+
if (!transport || !transport.isMuxMode) {
|
|
283
|
+
return new Error('Error: Panes are only available with zmux or the desktop app.');
|
|
283
284
|
}
|
|
284
|
-
const
|
|
285
|
-
const listed = waitForEvent(transport, (event) => event.type === 'pane_list', 5000);
|
|
285
|
+
const listed = waitForEvent(transport, (event) => event.type === 'pane_list', timeoutMs);
|
|
286
286
|
if (!transport.send({ type: 'list_panes' })) {
|
|
287
287
|
listed.catch(() => { });
|
|
288
|
-
return new Error('Error: Unable to list panes
|
|
288
|
+
return new Error('Error: Unable to list panes.');
|
|
289
289
|
}
|
|
290
290
|
const event = await listed.catch((error) => error);
|
|
291
291
|
if (event instanceof Error)
|
|
292
292
|
return new Error(`Error: ${event.message}`);
|
|
293
293
|
if (event.type !== 'pane_list')
|
|
294
294
|
return new Error('Error: Unexpected response while listing panes.');
|
|
295
|
+
return event.panes;
|
|
296
|
+
}
|
|
297
|
+
export async function resolvePaneIdByName(name) {
|
|
298
|
+
if (!tuiProcessRef || !tuiProcessRef.isMuxMode) {
|
|
299
|
+
return new Error('Error: Pane names are only available with panes (zmux or the desktop app).');
|
|
300
|
+
}
|
|
301
|
+
const panes = await listWorkspacePanes(tuiProcessRef);
|
|
302
|
+
if (panes instanceof Error) {
|
|
303
|
+
return new Error(panes.message === 'Error: Unable to list panes.' ? 'Error: Unable to list panes to find that name.' : panes.message);
|
|
304
|
+
}
|
|
295
305
|
const wanted = paneName(name);
|
|
296
|
-
const others =
|
|
306
|
+
const others = panes.filter((pane) => pane.id !== ownPaneId);
|
|
297
307
|
const matches = others.filter((pane) => paneName(pane.title) === wanted);
|
|
298
308
|
if (matches.length === 1)
|
|
299
309
|
return matches[0].id;
|
|
@@ -417,7 +427,8 @@ function filterChildZtcArgs(args) {
|
|
|
417
427
|
}
|
|
418
428
|
return filtered;
|
|
419
429
|
}
|
|
420
|
-
|
|
430
|
+
/** Environment a ZTC child pane needs to join this workspace (relay, mirror bus, PATH). */
|
|
431
|
+
export function childZtcPaneEnv() {
|
|
421
432
|
const env = {
|
|
422
433
|
ZTC_PARENT_PID: String(process.pid),
|
|
423
434
|
};
|
|
@@ -76,9 +76,11 @@ export function routeUserInput(text) {
|
|
|
76
76
|
tokens.push(token);
|
|
77
77
|
return { type: 'command', cmd, args: tokens.slice(1) };
|
|
78
78
|
}
|
|
79
|
-
if (
|
|
80
|
-
// A fork's body is authored task data, not tokenized command options
|
|
81
|
-
//
|
|
79
|
+
if (['fork', 'side', 'claude', 'codex'].includes(cmd.toLowerCase())) {
|
|
80
|
+
// A fork's body is authored task data, not tokenized command options,
|
|
81
|
+
// and /claude and /codex read theirs as a prompt or a shell-quoted
|
|
82
|
+
// argument list. Remove exactly the command separator and retain the
|
|
83
|
+
// remaining bytes.
|
|
82
84
|
const task = line.slice(cmd.length + 2);
|
|
83
85
|
return { type: 'command', cmd, args: task.length ? [task] : [] };
|
|
84
86
|
}
|
package/dist/cli_main.js
CHANGED
|
@@ -76,6 +76,19 @@ if (commandArgs[0] === 'attach') {
|
|
|
76
76
|
const { runTerminalAttachCli } = await import('./terminal_attach.js');
|
|
77
77
|
process.exit(await runTerminalAttachCli(commandArgs.slice(1)));
|
|
78
78
|
}
|
|
79
|
+
// The MCP server Claude Code and Codex panes start: stdio only, no agent, no TTY,
|
|
80
|
+
// and nothing on stdout but the protocol.
|
|
81
|
+
if (commandArgs[0] === 'workspace-mcp') {
|
|
82
|
+
try {
|
|
83
|
+
const { runWorkspaceMcpServer } = await import('./mcp/workspace_server.js');
|
|
84
|
+
await runWorkspaceMcpServer();
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.error(`[ztc workspace-mcp] ${error.message}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
79
92
|
// Adapters must be able to deliver receipts without starting an agent or a TTY.
|
|
80
93
|
if (commandArgs[0] === 'inbox') {
|
|
81
94
|
try {
|
package/dist/cli_ratatui.d.ts
CHANGED
|
@@ -420,6 +420,8 @@ export declare class RatatuiApp {
|
|
|
420
420
|
private desktopMode;
|
|
421
421
|
private desktopRuntime;
|
|
422
422
|
private childPaneIds;
|
|
423
|
+
/** Panes running the user's own Claude Code or Codex, by pane id. */
|
|
424
|
+
private readonly agentCliPanes;
|
|
423
425
|
private colonyPaneIds;
|
|
424
426
|
/** Cache of latest pane list from pane_state events (including colony panes, filtered in switcher) */
|
|
425
427
|
private knownPanes;
|
|
@@ -1290,6 +1292,14 @@ export declare class RatatuiApp {
|
|
|
1290
1292
|
* working. Ctrl+B c does not come through here — that raises the chooser.
|
|
1291
1293
|
*/
|
|
1292
1294
|
private handleCreatePaneRequest;
|
|
1295
|
+
/**
|
|
1296
|
+
* Open the user's own Claude Code or Codex in a pane: the chooser's and the
|
|
1297
|
+
* desktop menu's entry point. Returns what to tell the user (an error or a
|
|
1298
|
+
* notice), or null.
|
|
1299
|
+
*/
|
|
1300
|
+
private spawnAgentCliPane;
|
|
1301
|
+
/** Where the user's own `claude` and `codex` are installed, if they are. */
|
|
1302
|
+
private installedAgentClis;
|
|
1293
1303
|
/**
|
|
1294
1304
|
* Create a ZTC agent pane on the user's behalf: no agent turn, no tool call.
|
|
1295
1305
|
*
|
package/dist/cli_ratatui.js
CHANGED
|
@@ -5,6 +5,9 @@ import { reconcileOrderedTranscript } from './app/ordered_transcript.js';
|
|
|
5
5
|
import { listCommandJobs, shutdownCommandJobs, subscribeCommandJobs } from './agent/runtime/command_jobs.js';
|
|
6
6
|
import { CommandPaneManager, commandPaneText } from './agent/runtime/command_panes.js';
|
|
7
7
|
import { waitForPaneCreatedOrError } from './agent/tools/pane.js';
|
|
8
|
+
import { findAgentCli } from './panes/agent_cli.js';
|
|
9
|
+
import { agentCliFromRequestId, formatAgentPaneMessage, openAgentPane, typeLineIntoPane } from './panes/agent_panes.js';
|
|
10
|
+
import { agentCliFromChoice, newPaneChoices } from './panes/new_pane_choices.js';
|
|
8
11
|
import { IntakeNotifications } from './intake/notifications.js';
|
|
9
12
|
import { projectGoal } from './goal_contract.js';
|
|
10
13
|
import { isToolResultMessage } from './utils/tool_result_message.js';
|
|
@@ -772,6 +775,8 @@ export class RatatuiApp {
|
|
|
772
775
|
desktopMode = false;
|
|
773
776
|
desktopRuntime;
|
|
774
777
|
childPaneIds = new Set();
|
|
778
|
+
/** Panes running the user's own Claude Code or Codex, by pane id. */
|
|
779
|
+
agentCliPanes = new Map();
|
|
775
780
|
colonyPaneIds = new Set();
|
|
776
781
|
/** Cache of latest pane list from pane_state events (including colony panes, filtered in switcher) */
|
|
777
782
|
knownPanes = [];
|
|
@@ -1468,6 +1473,13 @@ export class RatatuiApp {
|
|
|
1468
1473
|
debugLog(`[Mirror] Pane control via stdin (bare): pane=${paneId} cmd=${controlLine.slice(0, 60)}`);
|
|
1469
1474
|
this.tui.send({ type: 'write_pane', pane_id: paneId, input: controlLine, control: true });
|
|
1470
1475
|
});
|
|
1476
|
+
// An agent's message for a Claude Code or Codex pane is typed in, one
|
|
1477
|
+
// line saying who sent it, with Enter as its own keystroke.
|
|
1478
|
+
this.mirrorServer.onAgentPaneMessage((paneId, msg) => {
|
|
1479
|
+
if (!this.agentCliPanes.has(paneId))
|
|
1480
|
+
return false;
|
|
1481
|
+
return typeLineIntoPane(this.tui, paneId, formatAgentPaneMessage(msg));
|
|
1482
|
+
});
|
|
1471
1483
|
}
|
|
1472
1484
|
// Handle input from browser mirror (single-pane mode fallback)
|
|
1473
1485
|
this.mirrorServer.onInput((input) => {
|
|
@@ -6793,6 +6805,9 @@ export class RatatuiApp {
|
|
|
6793
6805
|
case 'pane_created': {
|
|
6794
6806
|
this.childPaneIds.add(event.pane_id);
|
|
6795
6807
|
this.notePaneCreated(event.pane_id, event.request_id);
|
|
6808
|
+
const agentCli = agentCliFromRequestId(event.request_id);
|
|
6809
|
+
if (agentCli)
|
|
6810
|
+
this.agentCliPanes.set(event.pane_id, agentCli);
|
|
6796
6811
|
const launchSpec = this.paneRegistry.get(event.pane_id)?.launchSpec
|
|
6797
6812
|
|| this.paneRegistry.consumePendingCreate(event.request_id)
|
|
6798
6813
|
|| this.resumedPaneSpecs.find((pane) => pane.paneId === event.pane_id)?.launchSpec;
|
|
@@ -6910,6 +6925,7 @@ export class RatatuiApp {
|
|
|
6910
6925
|
this.childPaneModels.delete(event.pane_id);
|
|
6911
6926
|
this.childPaneSessionIds.delete(event.pane_id);
|
|
6912
6927
|
this.colonyPaneIds.delete(event.pane_id);
|
|
6928
|
+
this.agentCliPanes.delete(event.pane_id);
|
|
6913
6929
|
this.paneRegistry.remove(event.pane_id);
|
|
6914
6930
|
if (this.mirrorServer) {
|
|
6915
6931
|
this.mirrorServer.unregisterPane(event.pane_id);
|
|
@@ -12988,18 +13004,48 @@ export class RatatuiApp {
|
|
|
12988
13004
|
* working. Ctrl+B c does not come through here — that raises the chooser.
|
|
12989
13005
|
*/
|
|
12990
13006
|
handleCreatePaneRequest(event) {
|
|
12991
|
-
|
|
12992
|
-
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
|
|
12996
|
-
|
|
12997
|
-
|
|
13007
|
+
const placement = { direction: event.direction, ratio: event.ratio, target: event.target, newWindow: event.new_window };
|
|
13008
|
+
// The desktop app's new-pane menu names what it wants; no kind is a ZTC pane.
|
|
13009
|
+
if (event.kind === 'shell') {
|
|
13010
|
+
const error = this.spawnShellPane({ cwd: event.cwd, ...placement });
|
|
13011
|
+
if (error)
|
|
13012
|
+
this.addSystemMessage(`Could not open a shell pane: ${error}`);
|
|
13013
|
+
return;
|
|
13014
|
+
}
|
|
13015
|
+
if (event.kind === 'claude_code' || event.kind === 'codex') {
|
|
13016
|
+
void this.spawnAgentCliPane(event.kind === 'codex' ? 'codex' : 'claude', { cwd: event.cwd, ...placement })
|
|
13017
|
+
.then((message) => { if (message)
|
|
13018
|
+
this.addSystemMessage(message); });
|
|
13019
|
+
return;
|
|
13020
|
+
}
|
|
13021
|
+
void this.spawnZtcPane({ cwd: event.cwd, ...placement }).then((result) => {
|
|
12998
13022
|
if (result.error) {
|
|
12999
13023
|
debugLog(`[handleCreatePaneRequest] ${result.error}`);
|
|
13000
13024
|
}
|
|
13001
13025
|
});
|
|
13002
13026
|
}
|
|
13027
|
+
/**
|
|
13028
|
+
* Open the user's own Claude Code or Codex in a pane: the chooser's and the
|
|
13029
|
+
* desktop menu's entry point. Returns what to tell the user (an error or a
|
|
13030
|
+
* notice), or null.
|
|
13031
|
+
*/
|
|
13032
|
+
async spawnAgentCliPane(cli, options) {
|
|
13033
|
+
if (!this.muxMode || this.paneMode)
|
|
13034
|
+
return 'Opening Claude Code or Codex needs a zmux session or the desktop app.';
|
|
13035
|
+
const result = await openAgentPane(this.tui, {
|
|
13036
|
+
cli,
|
|
13037
|
+
...options,
|
|
13038
|
+
cwd: options.cwd || this.shellCwd || process.cwd(),
|
|
13039
|
+
findBinary: id => this.installedAgentClis()[id] ?? null,
|
|
13040
|
+
});
|
|
13041
|
+
if (result.notice)
|
|
13042
|
+
this.addSystemMessage(result.notice);
|
|
13043
|
+
return result.error ?? null;
|
|
13044
|
+
}
|
|
13045
|
+
/** Where the user's own `claude` and `codex` are installed, if they are. */
|
|
13046
|
+
installedAgentClis() {
|
|
13047
|
+
return { claude: findAgentCli('claude'), codex: findAgentCli('codex') };
|
|
13048
|
+
}
|
|
13003
13049
|
/**
|
|
13004
13050
|
* Create a ZTC agent pane on the user's behalf: no agent turn, no tool call.
|
|
13005
13051
|
*
|
|
@@ -13108,20 +13154,12 @@ export class RatatuiApp {
|
|
|
13108
13154
|
return;
|
|
13109
13155
|
}
|
|
13110
13156
|
const ctx = this.buildCommandContext((msg) => this.controller.addMessage(msg));
|
|
13111
|
-
const
|
|
13157
|
+
const choices = newPaneChoices(customPaneOptions(configStore.get()), this.installedAgentClis());
|
|
13112
13158
|
ctx.select.showSelect({
|
|
13113
13159
|
title: 'New pane',
|
|
13114
13160
|
question: 'What should the new pane be?',
|
|
13115
13161
|
type: 'select',
|
|
13116
|
-
options:
|
|
13117
|
-
{ value: 'bash', label: 'bash', description: 'A shell pane' },
|
|
13118
|
-
{ value: 'ztc', label: 'ZTC', description: 'A ZTC agent pane' },
|
|
13119
|
-
...custom.map((option, index) => ({
|
|
13120
|
-
value: `custom:${index}`,
|
|
13121
|
-
label: option.label,
|
|
13122
|
-
description: option.command,
|
|
13123
|
-
})),
|
|
13124
|
-
],
|
|
13162
|
+
options: choices,
|
|
13125
13163
|
}, async (value) => {
|
|
13126
13164
|
if (value === null || value === undefined)
|
|
13127
13165
|
return;
|
|
@@ -13145,6 +13183,9 @@ export class RatatuiApp {
|
|
|
13145
13183
|
const result = await this.spawnZtcPane({ cwd });
|
|
13146
13184
|
return result.error ?? null;
|
|
13147
13185
|
}
|
|
13186
|
+
const agentCli = agentCliFromChoice(choice);
|
|
13187
|
+
if (agentCli)
|
|
13188
|
+
return this.spawnAgentCliPane(agentCli, { cwd });
|
|
13148
13189
|
if (choice.startsWith('custom:')) {
|
|
13149
13190
|
const option = customPaneOptions(configStore.get())[Number(choice.slice('custom:'.length))];
|
|
13150
13191
|
if (!option)
|
|
@@ -13179,6 +13220,10 @@ export class RatatuiApp {
|
|
|
13179
13220
|
cwd,
|
|
13180
13221
|
title,
|
|
13181
13222
|
pane_type: 'process',
|
|
13223
|
+
...(options?.direction ? { direction: options.direction } : {}),
|
|
13224
|
+
...(options?.ratio !== undefined ? { ratio: options.ratio } : {}),
|
|
13225
|
+
...(options?.target !== undefined ? { target: options.target } : {}),
|
|
13226
|
+
...(options?.newWindow ? { new_window: true } : {}),
|
|
13182
13227
|
})
|
|
13183
13228
|
? null
|
|
13184
13229
|
: 'failed to create shell pane';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ztc workspace-mcp` — the MCP server attached to Claude Code and Codex panes.
|
|
3
|
+
*
|
|
4
|
+
* The CLI starts it from its session-scoped MCP config (see panes/agent_cli.ts).
|
|
5
|
+
* It reaches this workspace through the pane relay ($ZTC_PANE_RELAY) as pane
|
|
6
|
+
* $ZTC_PANE_ID, and gives the agent the workspace: list panes; open a ZTC
|
|
7
|
+
* agent, Claude Code, Codex or a shell; read a pane; type into one; message
|
|
8
|
+
* another agent; close a pane. Messages travel over ZTC's bus when the mirror
|
|
9
|
+
* is reachable, so ZTC panes receive them as messages from this pane;
|
|
10
|
+
* otherwise they are typed in with the sender's name.
|
|
11
|
+
*/
|
|
12
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
13
|
+
import { type AgentCliId, type PaneProgram } from '../panes/agent_cli.js';
|
|
14
|
+
import { type PaneCommandTransport } from '../ui/ratatui/pane_relay.js';
|
|
15
|
+
export interface WorkspaceBusMessage {
|
|
16
|
+
from: {
|
|
17
|
+
id: number;
|
|
18
|
+
name: string;
|
|
19
|
+
};
|
|
20
|
+
to: number | 'all';
|
|
21
|
+
type: 'ztc_message';
|
|
22
|
+
text: string;
|
|
23
|
+
}
|
|
24
|
+
export interface WorkspaceBusSender {
|
|
25
|
+
send(message: WorkspaceBusMessage): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export interface WorkspaceMcpOptions {
|
|
28
|
+
/** The pane relay; null outside a ZTC workspace (every tool then says so). */
|
|
29
|
+
transport: PaneCommandTransport | null;
|
|
30
|
+
ownPaneId: number | null;
|
|
31
|
+
bus?: WorkspaceBusSender | null;
|
|
32
|
+
findBinary?: (id: AgentCliId) => string | null;
|
|
33
|
+
/** The server to attach to agent panes this one opens. */
|
|
34
|
+
server?: PaneProgram | Error | null;
|
|
35
|
+
env?: NodeJS.ProcessEnv;
|
|
36
|
+
/** Working directory for panes opened without one. */
|
|
37
|
+
cwd?: string;
|
|
38
|
+
}
|
|
39
|
+
/** The MCP server, connected to a workspace through `transport`. */
|
|
40
|
+
export declare function createWorkspaceMcpServer(options: WorkspaceMcpOptions): Server;
|
|
41
|
+
/** Posts bus messages to the overseer's mirror, as MirrorClient does, without joining the bus. */
|
|
42
|
+
export declare function mirrorBusSender(env?: NodeJS.ProcessEnv): WorkspaceBusSender | null;
|
|
43
|
+
/** Entry point for `ztc workspace-mcp`: serve on stdio until the CLI closes it. */
|
|
44
|
+
export declare function runWorkspaceMcpServer(env?: NodeJS.ProcessEnv): Promise<void>;
|