@zergai/ztc 0.3.0-beta.1 → 0.4.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.
Files changed (69) hide show
  1. package/bin/zmux-darwin-arm64 +0 -0
  2. package/bin/ztc-tui-darwin-arm64 +0 -0
  3. package/bin/zterm-darwin-arm64 +0 -0
  4. package/dist/App.js +16 -2
  5. package/dist/agent/agent.d.ts +20 -8
  6. package/dist/agent/agent.js +92 -40
  7. package/dist/agent/backends/gemini.js +1 -1
  8. package/dist/agent/backends/openai_format.js +2 -2
  9. package/dist/agent/backends/openai_responses.js +1 -1
  10. package/dist/agent/backends/types.d.ts +1 -0
  11. package/dist/agent/commands/config.js +15 -1
  12. package/dist/agent/commands/settings.js +23 -0
  13. package/dist/agent/commands/status.js +3 -0
  14. package/dist/agent/commands/types.d.ts +2 -0
  15. package/dist/agent/completion_state.js +2 -2
  16. package/dist/agent/factory.js +7 -1
  17. package/dist/agent/request_encoder.js +3 -0
  18. package/dist/agent/runtime/command_jobs.d.ts +47 -0
  19. package/dist/agent/runtime/command_jobs.js +268 -0
  20. package/dist/agent/runtime/execution_context.d.ts +16 -0
  21. package/dist/agent/runtime/execution_context.js +20 -0
  22. package/dist/agent/runtime/recovery.d.ts +9 -0
  23. package/dist/agent/runtime/recovery.js +71 -0
  24. package/dist/agent/runtime/tool_runtime.js +3 -1
  25. package/dist/agent/runtime/tool_scheduler.d.ts +6 -0
  26. package/dist/agent/runtime/tool_scheduler.js +57 -0
  27. package/dist/agent/subagent.js +30 -12
  28. package/dist/agent/tool_response_store.d.ts +2 -0
  29. package/dist/agent/tool_response_store.js +1 -0
  30. package/dist/agent/tools/deferred.js +2 -0
  31. package/dist/agent/tools/file.js +2 -0
  32. package/dist/agent/tools/pane.js +139 -42
  33. package/dist/agent/tools/recall_tool_response.js +1 -0
  34. package/dist/agent/tools/registry.js +3 -1
  35. package/dist/agent/tools/search.js +1 -0
  36. package/dist/agent/tools/shell.d.ts +2 -0
  37. package/dist/agent/tools/shell.js +57 -2
  38. package/dist/agent/tools/types.d.ts +4 -0
  39. package/dist/app/agent_runner.js +1 -1
  40. package/dist/app/ordered_transcript.d.ts +13 -0
  41. package/dist/app/ordered_transcript.js +55 -0
  42. package/dist/cli_ratatui.d.ts +2 -7
  43. package/dist/cli_ratatui.js +53 -34
  44. package/dist/config/execution.d.ts +12 -0
  45. package/dist/config/execution.js +13 -0
  46. package/dist/config/types.d.ts +1 -0
  47. package/dist/config.d.ts +1 -0
  48. package/dist/config.js +5 -0
  49. package/dist/emulation/tool_adapters.js +5 -2
  50. package/dist/headless.d.ts +1 -18
  51. package/dist/headless.js +15 -0
  52. package/dist/plugin/runtime.d.ts +1 -1
  53. package/dist/types.d.ts +3 -0
  54. package/dist/ui/ratatui/headless_pane_manager.d.ts +2 -19
  55. package/dist/ui/ratatui/headless_pane_manager.js +41 -34
  56. package/dist/ui/ratatui/pane_input.d.ts +4 -0
  57. package/dist/ui/ratatui/pane_input.js +15 -0
  58. package/dist/ui/ratatui/pane_relay.d.ts +3 -0
  59. package/dist/ui/ratatui/pane_relay.js +20 -2
  60. package/dist/ui/ratatui/protocol.d.ts +17 -0
  61. package/dist/ui/ratatui/protocol.js +1 -0
  62. package/dist/utils/shell.d.ts +2 -0
  63. package/dist/utils/shell.js +54 -11
  64. package/dist/web/mirror_stream.d.ts +2 -1
  65. package/dist/web/mirror_stream.js +7 -0
  66. package/dist/web/pane_registry.js +8 -2
  67. package/dist/web/view_plugin.d.ts +1 -0
  68. package/dist/zlm-embed.mjs +384 -118
  69. package/package.json +2 -2
@@ -28,6 +28,7 @@ export interface TranscriptAnchor {
28
28
  line_offset: number;
29
29
  }
30
30
  /** Capability strings advertised by zmux/ztc-tui on the `Ready` event. */
31
+ export declare const CAPABILITY_PANE_DELIVERY_ACK = "pane_delivery_ack_v1";
31
32
  export declare const CAPABILITY_SCROLLBACK_ANCHOR = "scrollback_anchor";
32
33
  export declare const CAPABILITY_DESKTOP_UPDATE_RESTART = "desktop_update_restart_v1";
33
34
  export declare const CAPABILITY_CLIPBOARD_IMAGE = "clipboard_image";
@@ -158,12 +159,14 @@ export type RustCommand = {
158
159
  target?: number;
159
160
  } | {
160
161
  type: 'read_pane';
162
+ request_id?: string;
161
163
  pane_id: number;
162
164
  lines?: number;
163
165
  viewport?: boolean;
164
166
  ansi?: boolean;
165
167
  } | {
166
168
  type: 'write_pane';
169
+ request_id?: string;
167
170
  pane_id: number;
168
171
  input: string;
169
172
  submit?: boolean;
@@ -356,8 +359,22 @@ export type RustEvent = {
356
359
  type: 'session_restored';
357
360
  restored_panes: number;
358
361
  skipped_panes: number;
362
+ } | {
363
+ type: 'pane_write_result';
364
+ request_id?: string;
365
+ pane_id: number;
366
+ delivered: boolean;
367
+ queued?: boolean;
368
+ encoding_pending?: boolean;
369
+ delivered_bytes?: number;
370
+ total_bytes?: number;
371
+ partial?: boolean;
372
+ cancelled?: boolean;
373
+ error?: string;
359
374
  } | {
360
375
  type: 'pane_read';
376
+ request_id?: string;
377
+ error?: string;
361
378
  pane_id: number;
362
379
  content: string;
363
380
  viewport?: boolean;
@@ -5,6 +5,7 @@
5
5
  * Shared by both ztc-tui (inline viewport) and zmux (fullscreen multiplexer).
6
6
  */
7
7
  /** Capability strings advertised by zmux/ztc-tui on the `Ready` event. */
8
+ export const CAPABILITY_PANE_DELIVERY_ACK = 'pane_delivery_ack_v1';
8
9
  export const CAPABILITY_SCROLLBACK_ANCHOR = 'scrollback_anchor';
9
10
  export const CAPABILITY_DESKTOP_UPDATE_RESTART = 'desktop_update_restart_v1';
10
11
  export const CAPABILITY_CLIPBOARD_IMAGE = 'clipboard_image';
@@ -18,6 +18,8 @@ export type RunShellCommandOptions = SandboxCommandOptions & {
18
18
  temporaryWriteRoot?: string;
19
19
  /** Text written verbatim to the command's stdin (avoids shell quoting). */
20
20
  stdin?: string;
21
+ noTimeout?: boolean;
22
+ onOutput?: (stream: 'stdout' | 'stderr', chunk: string) => void;
21
23
  };
22
24
  /**
23
25
  * Terminate every live shell-tool child group with `signal` and return the pids
@@ -157,7 +157,7 @@ export async function runShellCommand(command, cwd, signal, timeoutMs, sandboxOp
157
157
  throw new Error(`Command rejected for safety reasons: ${describeSafetyRule(verdict.rule)} (target: ${verdict.token}). `
158
158
  + `[safety rule=${verdict.rule} token=${verdict.token}]`);
159
159
  }
160
- const { env, temporaryWriteRoot, stdin, ...sandboxCommandOptions } = sandboxOptions ?? {};
160
+ const { env, temporaryWriteRoot, stdin, noTimeout, onOutput, ...sandboxCommandOptions } = sandboxOptions ?? {};
161
161
  const subprocessEnv = env
162
162
  ?? (temporaryWriteRoot === undefined
163
163
  ? buildUserSubprocessEnv()
@@ -201,7 +201,15 @@ export async function runShellCommand(command, cwd, signal, timeoutMs, sandboxOp
201
201
  let stderrOverflow = false;
202
202
  let timedOut = false;
203
203
  let cancelled = false;
204
+ let outputError;
204
205
  child.stdout?.on('data', (chunk) => {
206
+ try {
207
+ onOutput?.('stdout', chunk);
208
+ }
209
+ catch (error) {
210
+ outputError = error;
211
+ onAbort();
212
+ }
205
213
  if (stdout.length >= MAX_OUTPUT) {
206
214
  stdoutOverflow = true;
207
215
  return;
@@ -213,6 +221,13 @@ export async function runShellCommand(command, cwd, signal, timeoutMs, sandboxOp
213
221
  }
214
222
  });
215
223
  child.stderr?.on('data', (chunk) => {
224
+ try {
225
+ onOutput?.('stderr', chunk);
226
+ }
227
+ catch (error) {
228
+ outputError = error;
229
+ onAbort();
230
+ }
216
231
  if (stderr.length >= MAX_OUTPUT) {
217
232
  stderrOverflow = true;
218
233
  return;
@@ -224,16 +239,32 @@ export async function runShellCommand(command, cwd, signal, timeoutMs, sandboxOp
224
239
  }
225
240
  });
226
241
  let killTimer = null;
242
+ let termination;
227
243
  const escalateKill = () => {
228
- if (child.pid)
229
- killGroup(child.pid, 'SIGTERM');
230
- killTimer = setTimeout(() => {
231
- if (child.pid && child.exitCode === null && child.signalCode === null) {
232
- killGroup(child.pid, 'SIGKILL');
233
- }
234
- }, 250);
244
+ if (termination)
245
+ return;
246
+ termination = new Promise(resolve => {
247
+ if (child.pid)
248
+ killGroup(child.pid, 'SIGTERM');
249
+ killTimer = setTimeout(() => {
250
+ if (child.pid) {
251
+ // The leader may already have exited while descendants still own
252
+ // the group. Never fall back to a potentially recycled leader PID.
253
+ if (process.platform !== 'win32') {
254
+ try {
255
+ process.kill(-child.pid, 'SIGKILL');
256
+ }
257
+ catch { /* group already exited */ }
258
+ }
259
+ else if (child.exitCode === null && child.signalCode === null) {
260
+ killGroup(child.pid, 'SIGKILL');
261
+ }
262
+ }
263
+ resolve();
264
+ }, 250);
265
+ });
235
266
  };
236
- const timeoutHandle = setTimeout(() => { timedOut = true; escalateKill(); }, timeout);
267
+ const timeoutHandle = noTimeout ? undefined : setTimeout(() => { timedOut = true; escalateKill(); }, timeout);
237
268
  const onAbort = () => { cancelled = true; escalateKill(); };
238
269
  if (signal)
239
270
  signal.addEventListener('abort', onAbort, { once: true });
@@ -261,7 +292,13 @@ export async function runShellCommand(command, cwd, signal, timeoutMs, sandboxOp
261
292
  cleanup();
262
293
  rejectResult(e);
263
294
  };
264
- child.on('error', (err) => {
295
+ child.on('error', async (err) => {
296
+ if (termination)
297
+ await termination;
298
+ if (outputError) {
299
+ settleReject(outputError);
300
+ return;
301
+ }
265
302
  if (cancelled) {
266
303
  settleReject(new Error('Command cancelled'));
267
304
  return;
@@ -275,7 +312,13 @@ export async function runShellCommand(command, cwd, signal, timeoutMs, sandboxOp
275
312
  truncated: stdoutOverflow || stderrOverflow,
276
313
  });
277
314
  });
278
- child.on('close', (code, sig) => {
315
+ child.on('close', async (code, sig) => {
316
+ if (termination)
317
+ await termination;
318
+ if (outputError) {
319
+ settleReject(outputError);
320
+ return;
321
+ }
279
322
  if (cancelled) {
280
323
  settleReject(new Error('Command cancelled'));
281
324
  return;
@@ -18,7 +18,7 @@ import type { SlashCommandUsage } from '../agent/commands/index.js';
18
18
  import type { MessageAttachment, MessageInput } from '../types.js';
19
19
  import type { HostedSessionSettings } from '../hosted/session_settings.js';
20
20
  import { type PaneStateUpdate } from './pane_registry.js';
21
- export type MirrorEventType = 'message' | 'stream_start' | 'reasoning_delta' | 'stream_delta' | 'stream_end' | 'tool_call' | 'tool_result' | 'status' | 'token_usage' | 'error' | 'cancel' | 'mode_change' | 'pane_info' | 'pane_output' | 'zde_lite_state' | 'session_end' | 'reload' | 'clear' | 'todo_list' | 'prompt_entry' | 'ask_user' | 'queue_state' | 'accounts' | 'open_accounts' | 'hook_approval_request' | 'widget_prompt' | 'widget_select' | 'widget_questionnaire' | 'widget_dismiss' | 'widget_settings' | 'context_progress_bar_config' | 'zlicky_cursor' | 'zlicky_highlight' | 'zlicky_clear' | 'zlicky_status' | 'zlicky_capture_request' | 'cv_run_start' | 'cv_run_tick' | 'cv_run_end' | 'cv_diagnose_start' | 'cv_diagnose_classification' | 'cv_diagnose_proposal' | 'cv_diagnose_pr_opened' | 'cv_diagnose_end' | (string & {});
21
+ export type MirrorEventType = 'message' | 'stream_start' | 'reasoning_delta' | 'stream_delta' | 'stream_end' | 'tool_call' | 'tool_result' | 'status' | 'token_usage' | 'error' | 'cancel' | 'mode_change' | 'pane_info' | 'pane_output' | 'zde_lite_state' | 'session_end' | 'reload' | 'clear' | 'todo_list' | 'prompt_entry' | 'ask_user' | 'queue_state' | 'command_jobs' | 'accounts' | 'open_accounts' | 'hook_approval_request' | 'widget_prompt' | 'widget_select' | 'widget_questionnaire' | 'widget_dismiss' | 'widget_settings' | 'context_progress_bar_config' | 'zlicky_cursor' | 'zlicky_highlight' | 'zlicky_clear' | 'zlicky_status' | 'zlicky_capture_request' | 'cv_run_start' | 'cv_run_tick' | 'cv_run_end' | 'cv_diagnose_start' | 'cv_diagnose_classification' | 'cv_diagnose_proposal' | 'cv_diagnose_pr_opened' | 'cv_diagnose_end' | (string & {});
22
22
  export interface MirrorEvent {
23
23
  type: MirrorEventType;
24
24
  timestamp: string;
@@ -199,6 +199,7 @@ export interface MirrorPaneViewportResult {
199
199
  error?: string;
200
200
  }
201
201
  export interface MirrorPaneRecord {
202
+ commandJobs?: import('../agent/runtime/command_jobs.js').CommandReceipt[];
202
203
  title?: string;
203
204
  paneType?: string;
204
205
  provider?: string;
@@ -2826,6 +2826,13 @@ export class MirrorStreamServer {
2826
2826
  // Replay cached pane content that may have been evicted from event history,
2827
2827
  // so new clients always see it regardless of history depth.
2828
2828
  for (const [pid, info] of this.panes.entries()) {
2829
+ if (info.commandJobs) {
2830
+ res.write(`data: ${JSON.stringify({
2831
+ type: 'command_jobs',
2832
+ timestamp: new Date().toISOString(),
2833
+ data: { pane_id: pid, jobs: info.commandJobs },
2834
+ })}\n\n`);
2835
+ }
2829
2836
  if ((info.paneType === 'doc' || info.paneType === 'feed') && info.docContent) {
2830
2837
  res.write(`data: ${JSON.stringify({
2831
2838
  type: 'pane_output',
@@ -95,6 +95,7 @@ export class PaneRegistry {
95
95
  provider: prev?.provider,
96
96
  model: prev?.model,
97
97
  status: prev?.status,
98
+ commandJobs: prev?.commandJobs,
98
99
  // Preserve cached content across a relayout — SSE reconnect replay
99
100
  // (publishPaneInfo → doc/terminal snapshots) depends on it; a bare
100
101
  // relayout must not blank a pane until its next output event.
@@ -114,19 +115,24 @@ export class PaneRegistry {
114
115
  });
115
116
  }
116
117
  if (!next.has(0)) {
117
- next.set(0, { title: 'Overseer', paneType: 'ztc', isVisible: true });
118
+ next.set(0, { ...this.panes.get(0), title: 'Overseer', paneType: 'ztc', isVisible: true });
118
119
  }
119
120
  this.panes = next;
120
121
  }
121
122
  /** Derive runtime status + cache pane content from a published event. */
122
123
  applyEvent(event) {
123
- const paneId = typeof event.data.pane_id === 'number' ? event.data.pane_id : undefined;
124
+ const paneId = typeof event.data.pane_id === 'number' ? event.data.pane_id : event.type === 'command_jobs' ? 0 : undefined;
124
125
  if (paneId === undefined)
125
126
  return;
127
+ if (paneId === 0 && event.type === 'command_jobs')
128
+ this.ensureOverseer();
126
129
  const pane = this.panes.get(paneId);
127
130
  if (!pane)
128
131
  return;
129
132
  switch (event.type) {
133
+ case 'command_jobs':
134
+ pane.commandJobs = Array.isArray(event.data.jobs) ? event.data.jobs.slice(0, 60) : [];
135
+ break;
130
136
  case 'stream_start':
131
137
  pane.status = 'streaming';
132
138
  break;
@@ -5,6 +5,7 @@
5
5
  * Domain modules (Colony, monitoring dashboards, etc.) extend it
6
6
  * by implementing this interface and registering via registerViewPlugin().
7
7
  */
8
+ export type { CommandReceipt as CommandJobView } from '../agent/runtime/command_jobs.js';
8
9
  import type { IncomingMessage, ServerResponse } from 'http';
9
10
  import type { MirrorEvent, MirrorPaneRecord } from './mirror_stream.js';
10
11
  /**