@zergai/ztc 0.7.0-beta.1 → 0.8.1-dev.202611041708

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.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -5,6 +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
9
  // --- Setter-based transport access ---
9
10
  let busTransportRef = null;
10
11
  export function setBusTransport(transport) {
@@ -26,6 +27,13 @@ function parseBusRecipient(value) {
26
27
  return parseInt(toRaw, 10);
27
28
  return new Error(`Error: "to" must be a numeric pane id from [PEERS] or "all"; got "${toRaw}".`);
28
29
  }
30
+ /** A pane number, "all", or (outside simulations) the name a person gave a pane. */
31
+ async function resolveBusRecipient(value) {
32
+ const parsed = parseBusRecipient(value);
33
+ if (!(parsed instanceof Error) || process.env.ZTC_SIM_FREE_MODE)
34
+ return parsed;
35
+ return resolvePaneIdByName(String(value).trim());
36
+ }
29
37
  async function waitForNewPaneResponse(paneId, baseline, timeoutMs) {
30
38
  const deadline = Date.now() + timeoutMs;
31
39
  while (Date.now() < deadline) {
@@ -81,7 +89,7 @@ When the user asks you to ask/tell another pane and get a reply, answer, respons
81
89
 
82
90
  Do not use send_message for non-roster parties; use contact_external(external_id=...) instead.
83
91
 
84
- Use list_terminal_panes to see available pane IDs.`;
92
+ Panes are named: a ZTC pane renamed with /rename shows its session name. When the person mentions a name ("say hi to ben"), it usually means the pane with that name; pass the name as "to", or check list_terminal_panes first.`;
85
93
  export const sendMessageTool = {
86
94
  definition: {
87
95
  name: 'send_message',
@@ -93,7 +101,7 @@ export const sendMessageTool = {
93
101
  type: 'string',
94
102
  description: process.env.ZTC_SIM_FREE_MODE
95
103
  ? 'Recipient pane number (look it up in [PEERS]) or "all" to broadcast.'
96
- : 'Target member ID (number) or "all" for broadcast. Use pane IDs from list_terminal_panes.',
104
+ : 'Target pane: its ID, its name as list_terminal_panes shows it (a renamed ZTC pane is named after its session, e.g. "ben"), or "all" for broadcast.',
97
105
  },
98
106
  text: {
99
107
  type: 'string',
@@ -142,10 +150,12 @@ export const sendMessageTool = {
142
150
  busTransportRef.sendBusMessage(0, payload, 'ztc_message');
143
151
  return `Message queued (${kind}) to ${externalIdRaw}: "${text.slice(0, 100)}${text.length > 100 ? '...' : ''}"`;
144
152
  }
145
- const to = parseBusRecipient(args.to);
153
+ const to = await resolveBusRecipient(args.to);
146
154
  if (to instanceof Error) {
147
155
  return to.message;
148
156
  }
157
+ const named = typeof to === 'number' && !/^\d+$/.test(String(args.to).trim()) ? String(args.to).trim() : null;
158
+ const recipient = named ? `${named} (ZTC ${to})` : `ZTC ${to}`;
149
159
  // In sim free mode, route messages through the coordinator (pane 0) instead of
150
160
  // sending directly to target panes. Direct delivery triggers runAgent() on the
151
161
  // recipient, creating reactive chains. The coordinator records the message in
@@ -179,16 +189,16 @@ export const sendMessageTool = {
179
189
  if (waitForResponse && baseline) {
180
190
  try {
181
191
  const response = await waitForNewPaneResponse(to, baseline, 90_000);
182
- return `Response from ZTC ${to}:\n${response}`;
192
+ return `Response from ${recipient}:\n${response}`;
183
193
  }
184
194
  catch (err) {
185
- return `Message sent to ZTC ${to}, but no completed response arrived within 90s: ${err.message}`;
195
+ return `Message sent to ${recipient}, but no completed response arrived within 90s: ${err.message}`;
186
196
  }
187
197
  }
188
198
  if (to === 'all') {
189
199
  return `Message broadcast to all ZTCs: "${text.slice(0, 100)}${text.length > 100 ? '...' : ''}"`;
190
200
  }
191
- return `Message sent to ZTC ${to}: "${text.slice(0, 100)}${text.length > 100 ? '...' : ''}"`;
201
+ return `Message sent to ${recipient}: "${text.slice(0, 100)}${text.length > 100 ? '...' : ''}"`;
192
202
  },
193
203
  };
194
204
  export const contactExternalTool = {
@@ -259,8 +269,8 @@ export const askZtcPaneTool = {
259
269
  type: 'object',
260
270
  properties: {
261
271
  pane_id: {
262
- type: 'number',
263
- description: 'Target ZTC pane ID.',
272
+ type: 'string',
273
+ description: 'Target ZTC pane: its ID (e.g. "2") or its name as list_terminal_panes shows it (e.g. "ben").',
264
274
  },
265
275
  text: {
266
276
  type: 'string',
@@ -280,11 +290,16 @@ export const askZtcPaneTool = {
280
290
  }
281
291
  // pane_id 0 (the coordinator) is valid — don't use `|| ''`, which coerces 0
282
292
  // to falsy and then rejects it via the >= 0 guard below.
283
- const paneId = (args.pane_id === undefined || args.pane_id === null || args.pane_id === '')
284
- ? NaN
285
- : Number(args.pane_id);
293
+ const rawTarget = args.pane_id === undefined || args.pane_id === null ? '' : String(args.pane_id).trim();
294
+ let paneId = rawTarget === '' ? NaN : Number(rawTarget);
295
+ if (rawTarget !== '' && Number.isNaN(paneId) && !process.env.ZTC_SIM_FREE_MODE) {
296
+ const resolved = await resolvePaneIdByName(rawTarget);
297
+ if (resolved instanceof Error)
298
+ return resolved.message;
299
+ paneId = resolved;
300
+ }
286
301
  if (Number.isNaN(paneId) || paneId < 0) {
287
- return 'Error: pane_id must be a non-negative integer.';
302
+ return 'Error: pane_id must be a non-negative integer or a pane name.';
288
303
  }
289
304
  const text = String(args.text || '').trim();
290
305
  if (!text) {
@@ -32,6 +32,11 @@ export declare function publishMirrorZdeLiteState(paneId: number, state: Record<
32
32
  export declare function clearPaneToolsTuiProcess(): void;
33
33
  export declare function getMuxTransport(): PaneCommandTransport | null;
34
34
  export declare function getPaneToolsTransport(): PaneCommandTransport | null;
35
+ /**
36
+ * Resolves "ben" to the one other pane named ben. People address panes by the
37
+ * names they gave them with /rename, not by pane number.
38
+ */
39
+ export declare function resolvePaneIdByName(name: string): Promise<number | Error>;
35
40
  export declare function waitForPaneCreatedOrError(tui: PaneCommandTransport, expected: {
36
41
  paneType?: string;
37
42
  title?: string;
@@ -269,6 +269,40 @@ function parseLinesInput(value) {
269
269
  }
270
270
  return text.split(/\r?\n/);
271
271
  }
272
+ /** A pane's name is its displayed title: a renamed ZTC pane shows its session name. */
273
+ function paneName(title) {
274
+ return title.replace(/^\[[^\]]*\]\s*/, '').trim().toLowerCase();
275
+ }
276
+ /**
277
+ * Resolves "ben" to the one other pane named ben. People address panes by the
278
+ * names they gave them with /rename, not by pane number.
279
+ */
280
+ export async function resolvePaneIdByName(name) {
281
+ if (!tuiProcessRef || !tuiProcessRef.isMuxMode) {
282
+ return new Error('Error: Pane names are only available with panes (zmux or the desktop app).');
283
+ }
284
+ const transport = tuiProcessRef;
285
+ const listed = waitForEvent(transport, (event) => event.type === 'pane_list', 5000);
286
+ if (!transport.send({ type: 'list_panes' })) {
287
+ listed.catch(() => { });
288
+ return new Error('Error: Unable to list panes to find that name.');
289
+ }
290
+ const event = await listed.catch((error) => error);
291
+ if (event instanceof Error)
292
+ return new Error(`Error: ${event.message}`);
293
+ if (event.type !== 'pane_list')
294
+ return new Error('Error: Unexpected response while listing panes.');
295
+ const wanted = paneName(name);
296
+ const others = event.panes.filter((pane) => pane.id !== ownPaneId);
297
+ const matches = others.filter((pane) => paneName(pane.title) === wanted);
298
+ if (matches.length === 1)
299
+ return matches[0].id;
300
+ if (matches.length > 1) {
301
+ return new Error(`Error: ${matches.length} panes are named "${name}" (${matches.map((pane) => pane.id).join(', ')}); use a pane ID.`);
302
+ }
303
+ const names = others.map((pane) => `[${pane.id}] ${pane.title}`).join(', ');
304
+ return new Error(`Error: no pane is named "${name}". ${names ? `Panes: ${names}.` : 'There are no other panes.'}`);
305
+ }
272
306
  export function waitForPaneCreatedOrError(tui, expected, timeoutMs = 15000) {
273
307
  return waitForEvent(tui, (event) => event.type === 'error' || (event.type === 'pane_created'
274
308
  && (!expected.requestId || event.request_id === expected.requestId)
@@ -1,6 +1,6 @@
1
1
  import { environmentOperationInProgress } from './agent/commands/environment.js';
2
2
  import { getEnvironment, openEnvironment, releaseEnvironment, describeEnvironmentForAgent, isEnvironmentWorkspacePath } from './environment/store.js';
3
- import { readPaneLabels } from './panes/labels.js';
3
+ import { readPaneLabels, sessionNamePaneTitle } from './panes/labels.js';
4
4
  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';
@@ -2525,7 +2525,7 @@ export class RatatuiApp {
2525
2525
  if (!this.paneActivityTransport().hasCapability('pane_labels_v1'))
2526
2526
  this.syncOwnPaneTitle();
2527
2527
  else if (this.sessionName)
2528
- this.paneActivityTransport().send({ type: 'set_pane_title', pane_id: paneId, title: this.sessionName });
2528
+ this.paneActivityTransport().send(sessionNamePaneTitle(paneId, this.sessionName));
2529
2529
  else
2530
2530
  void readPaneLabels(this.paneActivityTransport(), paneId).then(labels => {
2531
2531
  delete labels.manual_title;
@@ -2533,6 +2533,7 @@ export class RatatuiApp {
2533
2533
  }).catch(error => this.addSystemMessage(`Unable to clear pane name: ${error.message}`));
2534
2534
  }
2535
2535
  this.sessionLabels.manual_title = this.sessionName;
2536
+ this.mirrorClient?.setPaneName(this.sessionName);
2536
2537
  this.render();
2537
2538
  },
2538
2539
  getPaneLabels: async (paneId) => {
@@ -2546,6 +2547,7 @@ export class RatatuiApp {
2546
2547
  if (paneId === undefined) {
2547
2548
  this.sessionLabels = { ...labels };
2548
2549
  this.sessionName = labels.manual_title;
2550
+ this.mirrorClient?.setPaneName(this.sessionName);
2549
2551
  }
2550
2552
  else {
2551
2553
  const pane = this.paneRegistry.get(paneId);
@@ -4263,6 +4265,7 @@ export class RatatuiApp {
4263
4265
  return;
4264
4266
  this.sessionLabels = { ...labels };
4265
4267
  this.sessionName = labels.manual_title;
4268
+ this.mirrorClient?.setPaneName(this.sessionName);
4266
4269
  this.render();
4267
4270
  });
4268
4271
  setPaneToolsTuiProcess(this.paneRelayClient);
@@ -20,6 +20,14 @@ must actually use the relevant pane or bus tools in that turn.
20
20
  - Use \`write_terminal_pane\` only for non-ZTC panes such as shells, commands, or editors.
21
21
  - Use \`list_terminal_panes\` and \`read_terminal_pane\` to confirm what happened before reporting results.
22
22
 
23
+ ### Pane Names
24
+
25
+ Panes have names. A ZTC pane renamed with \`/rename\` shows its session name
26
+ (\`list_terminal_panes\` lists it, e.g. \`[1] ben\`). When the person mentions a
27
+ name ("say hi to ben", "ask matt"), assume they mean the pane with that name:
28
+ pass the name to \`send_message\` (\`to\`) or \`ask_ztc_pane\` (\`pane_id\`), or check
29
+ \`list_terminal_panes\` first. Ask only if no pane has that name.
30
+
23
31
  ### Per-Pane Model Selection
24
32
 
25
33
  When creating ZTC panes with \`create_ztc_pane\`, you can specify different LLM models:
@@ -5,6 +5,13 @@ export declare function readPaneLabels(transport: {
5
5
  on(handler: (event: RustEvent) => void): () => void;
6
6
  hasCapability?(name: string): boolean;
7
7
  }, paneId: number): Promise<PaneLabels>;
8
+ /**
9
+ * Names a pane after its session. Explicitly manual: the native renderers read
10
+ * a title without `source` as inferred, and the next activity title replaces
11
+ * an inferred title, so a /rename vanished from the pane, its pill and
12
+ * list_terminal_panes.
13
+ */
14
+ export declare function sessionNamePaneTitle(paneId: number, name: string): RustCommand;
8
15
  /** Mirror of the native PaneLabels projection; inferred state stays independent. */
9
16
  export declare function displayPaneTitle(inferred: string, labels?: PaneLabels): string;
10
17
  export declare function inferAgentPaneTitle(input: {
@@ -29,6 +29,15 @@ export async function readPaneLabels(transport, paneId) {
29
29
  }
30
30
  });
31
31
  }
32
+ /**
33
+ * Names a pane after its session. Explicitly manual: the native renderers read
34
+ * a title without `source` as inferred, and the next activity title replaces
35
+ * an inferred title, so a /rename vanished from the pane, its pill and
36
+ * list_terminal_panes.
37
+ */
38
+ export function sessionNamePaneTitle(paneId, name) {
39
+ return { type: 'set_pane_title', pane_id: paneId, title: name, source: 'manual' };
40
+ }
32
41
  /** Mirror of the native PaneLabels projection; inferred state stays independent. */
33
42
  export function displayPaneTitle(inferred, labels = {}) {
34
43
  const title = labels.manual_title || [labels.project, labels.task, inferred].filter(Boolean).join(' · ');
@@ -38,6 +38,8 @@ export declare class MirrorClient implements MirrorPublisher {
38
38
  constructor(port: number, paneId: number, options?: MirrorClientOptions | string);
39
39
  getPaneId(): number;
40
40
  getPaneName(): string;
41
+ /** A renamed pane speaks under its session name; no name restores "ZTC <id>". */
42
+ setPaneName(name: string | undefined): void;
41
43
  getBusMember(): BusMember;
42
44
  publish(event: {
43
45
  type: string;
@@ -56,6 +56,11 @@ export class MirrorClient {
56
56
  getPaneName() {
57
57
  return this.paneName;
58
58
  }
59
+ /** A renamed pane speaks under its session name; no name restores "ZTC <id>". */
60
+ setPaneName(name) {
61
+ const trimmed = name?.trim();
62
+ this.paneName = trimmed ? trimmed : this.paneId > 0 ? `ZTC ${this.paneId}` : 'ZTC';
63
+ }
59
64
  getBusMember() {
60
65
  return { id: this.paneId, name: this.paneName };
61
66
  }
@@ -78502,7 +78502,7 @@ var invokeSkillTool = makeInvokeSkillTool(resolveAmbientSkills);
78502
78502
  // src/zlm.ts
78503
78503
  var ZLM_EMBED_API_VERSION = "zerglang.ztc-embed/1";
78504
78504
  var ZLM_STDIO_PROTOCOL = "zerglang.ztc-host/1";
78505
- var ZLM_ZTC_VERSION = true ? "0.7.0-beta.1" : "development";
78505
+ var ZLM_ZTC_VERSION = true ? "0.8.1-dev.202611041708" : "development";
78506
78506
  var ZLM_MAX_HOST_TOOLS = 64;
78507
78507
  var ZLM_MAX_SYSTEM_PROMPT_BYTES = 128 * 1024;
78508
78508
  var ZLM_HOST_APPROVAL_CAPABILITIES = Object.freeze([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zergai/ztc",
3
- "version": "0.7.0-beta.1",
3
+ "version": "0.8.1-dev.202611041708",
4
4
  "description": "Zerg Terminal Client - CLI agent for continual AI systems",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "homepage": "https://zergai.com/docs/ztc",
@@ -275,13 +275,13 @@
275
275
  "node": "^20.19.0 || >=22.12.0"
276
276
  },
277
277
  "zergRelease": {
278
- "branch": "main",
279
- "channel": "next",
280
- "sourceCommit": "fbac9c4b9709a604200a4865467591b4d31dbcf3",
278
+ "branch": "development",
279
+ "channel": "dev",
280
+ "sourceCommit": "eac530d341d6b7057b5fcc242ce3d9c835ceeeb1",
281
281
  "sourceVersion": "0.2.0-canary.2"
282
282
  },
283
283
  "publishConfig": {
284
- "tag": "next",
284
+ "tag": "dev",
285
285
  "access": "public"
286
286
  }
287
287
  }