@zixt/host 0.0.38 → 0.0.39

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 (2) hide show
  1. package/dist/index.js +503 -37
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import { homedir } from "node:os";
31
31
  // package.json
32
32
  var package_default = {
33
33
  name: "@zixt/host",
34
- version: "0.0.38",
34
+ version: "0.0.39",
35
35
  type: "module",
36
36
  exports: {
37
37
  ".": "./src/client.ts",
@@ -15299,6 +15299,8 @@ var HostTelemetry = external_exports.object({
15299
15299
  * legacy Hosts must receive the original strict refusal frame.
15300
15300
  */
15301
15301
  providerOperationGrantRetry: external_exports.literal(true).optional(),
15302
+ /** Accepts durable human follow-ups for an active runner turn. */
15303
+ liveRunnerInput: external_exports.literal(true).optional(),
15302
15304
  linearToolPack: external_exports.boolean().default(false),
15303
15305
  providerToolPacks: external_exports.array(ProviderToolPackCapability).max(20).superRefine((capabilities, ctx) => {
15304
15306
  const providers = capabilities.map(({ provider }) => provider);
@@ -15866,6 +15868,8 @@ var Task = external_exports.object({
15866
15868
  /** Follow-ups accepted but not yet folded into a run; the thread renders
15867
15869
  * exactly this many trailing prompts as queued rather than answered. */
15868
15870
  pendingMessageCount: external_exports.number().int().min(0).default(0),
15871
+ /** Delivery state for each trailing pending prompt, in transcript order. */
15872
+ pendingMessageStates: external_exports.array(external_exports.enum(["queued", "sending", "sent"])).default([]),
15869
15873
  /** Present after cancellation; absent on legacy/non-cancelled tasks. */
15870
15874
  cancellation: TaskCancellation.nullable().default(null),
15871
15875
  /**
@@ -17292,9 +17296,17 @@ var BrowserCommandFrame = external_exports.object({
17292
17296
  browserSessionId: external_exports.string().min(1).max(200),
17293
17297
  command: BrowserCommand
17294
17298
  });
17299
+ var TaskInput = external_exports.object({
17300
+ type: external_exports.literal("task.input"),
17301
+ taskId: TaskId,
17302
+ epoch: external_exports.number().int().min(1),
17303
+ inputId: external_exports.uuid(),
17304
+ text: external_exports.string().min(1).max(1e5)
17305
+ });
17295
17306
  var DurableDownMessage = external_exports.discriminatedUnion("type", [
17296
17307
  TaskAssign,
17297
17308
  TaskCancel,
17309
+ TaskInput,
17298
17310
  SecretsGrant,
17299
17311
  ApprovalDecision,
17300
17312
  ConnectionsGrant
@@ -17338,6 +17350,13 @@ var TaskTitle = external_exports.object({
17338
17350
  epoch: external_exports.number().int().min(1),
17339
17351
  title: external_exports.string().min(1).max(300)
17340
17352
  });
17353
+ var TaskInputResult = external_exports.object({
17354
+ type: external_exports.literal("task.input.result"),
17355
+ taskId: TaskId,
17356
+ epoch: external_exports.number().int().min(1),
17357
+ inputId: external_exports.uuid(),
17358
+ accepted: external_exports.boolean()
17359
+ });
17341
17360
  var TaskResult = external_exports.object({
17342
17361
  type: external_exports.literal("task.result"),
17343
17362
  taskId: TaskId,
@@ -17365,7 +17384,9 @@ var TaskResult = external_exports.object({
17365
17384
  detail: external_exports.string().max(1e4).optional(),
17366
17385
  evidenceClass: external_exports.enum(["agent_claimed", "host_observed"])
17367
17386
  })
17368
- ).max(200).optional()
17387
+ ).max(200).optional(),
17388
+ /** Inputs the provider accepted into this exact run before it completed. */
17389
+ acceptedInputIds: external_exports.array(external_exports.uuid()).max(1e3).optional()
17369
17390
  });
17370
17391
  var DurableTaskResult = TaskResult.extend({ resultId: external_exports.uuid() });
17371
17392
  var HostReport = external_exports.object({
@@ -17427,6 +17448,7 @@ var UpMessage = external_exports.discriminatedUnion("type", [
17427
17448
  TaskWorkingContextReport,
17428
17449
  TaskRunnerRuntimeReport,
17429
17450
  TaskTitle,
17451
+ TaskInputResult,
17430
17452
  TaskResult,
17431
17453
  HostReport,
17432
17454
  HostConsoleReport,
@@ -19796,6 +19818,10 @@ var HostClient = class _HostClient {
19796
19818
  onUnwindStalled;
19797
19819
  /** Exact identity for each active run; unlike model output this is safe to acknowledge. */
19798
19820
  activeAssignments = /* @__PURE__ */ new Map();
19821
+ liveInputHandlers = /* @__PURE__ */ new Map();
19822
+ liveInputBacklog = /* @__PURE__ */ new Map();
19823
+ liveInputInFlight = /* @__PURE__ */ new Map();
19824
+ acceptedLiveInputIds = /* @__PURE__ */ new Map();
19799
19825
  /**
19800
19826
  * Connection-loss runs confirmed stopped locally but not yet acknowledged
19801
19827
  * by a new cloud socket generation. Replayed only inside hello.
@@ -20311,6 +20337,11 @@ var HostClient = class _HostClient {
20311
20337
  label: safe(fact.label, 2e3),
20312
20338
  ...fact.detail !== void 0 ? { detail: safe(fact.detail, 1e4) } : {}
20313
20339
  }))
20340
+ } : {},
20341
+ ...this.acceptedLiveInputIds.get(`${assign.taskId}:${assign.epoch}`)?.size ? {
20342
+ acceptedInputIds: [
20343
+ ...this.acceptedLiveInputIds.get(`${assign.taskId}:${assign.epoch}`)
20344
+ ]
20314
20345
  } : {}
20315
20346
  };
20316
20347
  }
@@ -20561,6 +20592,7 @@ var HostClient = class _HostClient {
20561
20592
  ...measuredCapabilities,
20562
20593
  linearToolPack: measured.capabilities?.linearToolPack ?? false,
20563
20594
  providerOperationGrantRetry: true,
20595
+ liveRunnerInput: true,
20564
20596
  hostConsole: true,
20565
20597
  ...workerWatchdogIsActive() ? { taskAuthorityLifecycle: true } : {}
20566
20598
  },
@@ -20604,6 +20636,9 @@ var HostClient = class _HostClient {
20604
20636
  message.purpose === "credential_rollover" ? "credential_rollover" : "cloud_cancel"
20605
20637
  );
20606
20638
  return;
20639
+ case "task.input":
20640
+ this.queueLiveInput(message);
20641
+ return;
20607
20642
  case "secrets.grant": {
20608
20643
  const key = `${message.taskId}:${message.epoch}`;
20609
20644
  if (!this.cancels.has(key)) return;
@@ -20667,6 +20702,51 @@ var HostClient = class _HostClient {
20667
20702
  }
20668
20703
  }
20669
20704
  }
20705
+ sendLiveInputResult(input, accepted) {
20706
+ this.sendUp({
20707
+ type: "task.input.result",
20708
+ taskId: input.taskId,
20709
+ epoch: input.epoch,
20710
+ inputId: input.inputId,
20711
+ accepted
20712
+ });
20713
+ }
20714
+ deliverLiveInput(key, input) {
20715
+ const accepted = this.acceptedLiveInputIds.get(key);
20716
+ if (accepted?.has(input.inputId)) {
20717
+ this.sendLiveInputResult(input, true);
20718
+ return;
20719
+ }
20720
+ const inFlight = this.liveInputInFlight.get(key) ?? /* @__PURE__ */ new Set();
20721
+ if (inFlight.has(input.inputId)) return;
20722
+ const handler5 = this.liveInputHandlers.get(key);
20723
+ if (!handler5) {
20724
+ const backlog = this.liveInputBacklog.get(key) ?? [];
20725
+ if (!backlog.some((candidate) => candidate.inputId === input.inputId)) backlog.push(input);
20726
+ this.liveInputBacklog.set(key, backlog);
20727
+ return;
20728
+ }
20729
+ inFlight.add(input.inputId);
20730
+ this.liveInputInFlight.set(key, inFlight);
20731
+ void handler5({ inputId: input.inputId, text: input.text }).then((wasAccepted) => {
20732
+ if (wasAccepted) {
20733
+ const acceptedIds = this.acceptedLiveInputIds.get(key) ?? /* @__PURE__ */ new Set();
20734
+ acceptedIds.add(input.inputId);
20735
+ this.acceptedLiveInputIds.set(key, acceptedIds);
20736
+ }
20737
+ this.sendLiveInputResult(input, wasAccepted);
20738
+ }).catch(() => this.sendLiveInputResult(input, false)).finally(() => {
20739
+ this.liveInputInFlight.get(key)?.delete(input.inputId);
20740
+ });
20741
+ }
20742
+ queueLiveInput(input) {
20743
+ const key = `${input.taskId}:${input.epoch}`;
20744
+ if (!this.cancels.has(key)) {
20745
+ this.sendLiveInputResult(input, false);
20746
+ return;
20747
+ }
20748
+ this.deliverLiveInput(key, input);
20749
+ }
20670
20750
  startTask(assign) {
20671
20751
  const key = `${assign.taskId}:${assign.epoch}`;
20672
20752
  if (this.activeRuns.has(key)) return;
@@ -21217,6 +21297,19 @@ var HostClient = class _HostClient {
21217
21297
  runnerRuntime: emitRunnerRuntime,
21218
21298
  siblingTasks: () => [...this.liveTasks.values()].filter((live) => live.agentId === assign.agentId && live.taskId !== assign.taskId).map(({ taskId, title }) => ({ taskId, title })),
21219
21299
  fetchAttachment: (attachmentId) => this.fetchAttachment(attachmentId, authorityController.signal),
21300
+ followUps: (handler5) => {
21301
+ if (cancelled) return () => {
21302
+ };
21303
+ this.liveInputHandlers.set(cancelKey, handler5);
21304
+ const backlog = this.liveInputBacklog.get(cancelKey) ?? [];
21305
+ this.liveInputBacklog.delete(cancelKey);
21306
+ for (const input of backlog) this.deliverLiveInput(cancelKey, input);
21307
+ return () => {
21308
+ if (this.liveInputHandlers.get(cancelKey) === handler5) {
21309
+ this.liveInputHandlers.delete(cancelKey);
21310
+ }
21311
+ };
21312
+ },
21220
21313
  secrets,
21221
21314
  connections,
21222
21315
  providers,
@@ -21294,6 +21387,13 @@ var HostClient = class _HostClient {
21294
21387
  this.agentOpWaiters.delete(cancelKey);
21295
21388
  this.rejectOperationGrantWaiters(cancelKey);
21296
21389
  this.rejectBrowserCredentialWaiters(cancelKey);
21390
+ this.liveInputHandlers.delete(cancelKey);
21391
+ for (const input of this.liveInputBacklog.get(cancelKey) ?? []) {
21392
+ this.sendLiveInputResult(input, false);
21393
+ }
21394
+ this.liveInputBacklog.delete(cancelKey);
21395
+ this.liveInputInFlight.delete(cancelKey);
21396
+ this.acceptedLiveInputIds.delete(cancelKey);
21297
21397
  sensitiveValues?.clear();
21298
21398
  sensitiveValues = null;
21299
21399
  if (stopReason === "cloud_cancel") {
@@ -33108,6 +33208,7 @@ let expectedBytes = null;
33108
33208
  let config = null;
33109
33209
  let launchPending = false;
33110
33210
  let stderrTail = '';
33211
+ let postReleaseInput = Buffer.alloc(0);
33111
33212
  let reported = false;
33112
33213
  const report = (result) => {
33113
33214
  if (reported) return;
@@ -33125,7 +33226,6 @@ prelaunchTimeout.unref();
33125
33226
 
33126
33227
  const launchTarget = (release) => {
33127
33228
  try {
33128
- config = release;
33129
33229
  const command = config.comspec || config.command;
33130
33230
  const args = config.comspec
33131
33231
  ? ['/d', '/s', '/c', '"' + [config.command, ...config.args].map(quote).join(' ') + '"']
@@ -33172,11 +33272,16 @@ const launchTarget = (release) => {
33172
33272
  maybeReport();
33173
33273
  });
33174
33274
  if (config.prompt) target.stdin.write(config.prompt);
33275
+ if (postReleaseInput.length) {
33276
+ target.stdin.write(postReleaseInput);
33277
+ postReleaseInput = Buffer.alloc(0);
33278
+ }
33175
33279
  if (stdinEnded) target.stdin.end();
33176
33280
  };
33177
33281
 
33178
33282
  const launch = (release) => {
33179
33283
  clearTimeout(prelaunchTimeout);
33284
+ config = release;
33180
33285
  launchPending = true;
33181
33286
  if (process.platform === 'win32') {
33182
33287
  launchTarget(release);
@@ -33211,6 +33316,11 @@ process.stdin.on('data', (chunk) => {
33211
33316
  target.stdin.write(chunk);
33212
33317
  return;
33213
33318
  }
33319
+ if (config) {
33320
+ postReleaseInput = Buffer.concat([postReleaseInput, chunk]);
33321
+ if (postReleaseInput.length > maxReleaseBytes) process.exit(1);
33322
+ return;
33323
+ }
33214
33324
  pending = Buffer.concat([pending, chunk]);
33215
33325
  if (pending.length > maxReleaseBytes + 256) process.exit(1);
33216
33326
  if (!containmentReady) {
@@ -33232,9 +33342,9 @@ process.stdin.on('data', (chunk) => {
33232
33342
  pending = pending.subarray(newline + 1);
33233
33343
  }
33234
33344
  if (pending.length < expectedBytes) return;
33235
- if (pending.length !== expectedBytes) process.exit(1);
33236
33345
  try {
33237
- const release = JSON.parse(pending.toString('utf8'));
33346
+ const release = JSON.parse(pending.subarray(0, expectedBytes).toString('utf8'));
33347
+ postReleaseInput = pending.subarray(expectedBytes);
33238
33348
  pending = Buffer.alloc(0);
33239
33349
  launch(release);
33240
33350
  } catch {
@@ -34038,7 +34148,8 @@ function createCliRunner(adapter, opts = {}) {
34038
34148
  cwd,
34039
34149
  command: resolvedCommand,
34040
34150
  commandPrefixArgs: prefixArgs,
34041
- env
34151
+ env,
34152
+ liveInput: opts.liveInput ?? opts.command === void 0
34042
34153
  });
34043
34154
  const runEnv = prepared.env ? { ...env, ...prepared.env } : env;
34044
34155
  let attachmentSection = "";
@@ -34227,7 +34338,8 @@ ${attachmentSection}` : prompt;
34227
34338
  ...detail.ephemeral === void 0 ? {} : { ephemeral: detail.ephemeral }
34228
34339
  });
34229
34340
  },
34230
- createParser: prepared.createParser,
34341
+ createParser: (onStream) => prepared.createParser(onStream, mode2),
34342
+ ...task.followUps ? { registerFollowUps: task.followUps } : {},
34231
34343
  notFoundMessage: adapter.notFoundMessage,
34232
34344
  cliLabel: adapter.type,
34233
34345
  // Durable evidence for the next Host process: which assignment this
@@ -34564,6 +34676,8 @@ function runCliProcess(options) {
34564
34676
  detached: platform !== "win32"
34565
34677
  });
34566
34678
  const parser = options.createParser(options.onStream);
34679
+ let unregisterFollowUps;
34680
+ const liveInputSettlements = /* @__PURE__ */ new Set();
34567
34681
  let stderrBuf = "";
34568
34682
  let settled = false;
34569
34683
  let terminalResult;
@@ -34592,12 +34706,15 @@ function runCliProcess(options) {
34592
34706
  if (settled) return;
34593
34707
  settled = true;
34594
34708
  clearInterval(timer);
34709
+ unregisterFollowUps?.();
34710
+ parser.stop?.();
34595
34711
  resolve14(result);
34596
34712
  };
34597
34713
  const terminate = (result) => {
34598
34714
  if (settled || forcedResult) return;
34599
34715
  forcedResult = result;
34600
34716
  clearInterval(timer);
34717
+ child.stdin?.end();
34601
34718
  void containmentReady.then(async () => {
34602
34719
  if (windowsContainment) {
34603
34720
  await windowsContainment.terminate();
@@ -34613,7 +34730,12 @@ function runCliProcess(options) {
34613
34730
  } : {}
34614
34731
  });
34615
34732
  }).then(
34616
- () => settle4(result),
34733
+ async () => {
34734
+ unregisterFollowUps?.();
34735
+ parser.stop?.();
34736
+ await Promise.allSettled([...liveInputSettlements]);
34737
+ settle4(result);
34738
+ },
34617
34739
  () => settle4({
34618
34740
  outcome: "failed",
34619
34741
  summary: PROCESS_CLEANUP_FAILURE,
@@ -34768,7 +34890,7 @@ function runCliProcess(options) {
34768
34890
  cwd: options.cwd,
34769
34891
  env: options.env,
34770
34892
  ...options.guardian.comspec ? { comspec: options.guardian.comspec } : {},
34771
- prompt: options.prompt
34893
+ prompt: parser.start ? "" : options.prompt
34772
34894
  }),
34773
34895
  "utf8"
34774
34896
  );
@@ -34776,9 +34898,39 @@ function runCliProcess(options) {
34776
34898
  `);
34777
34899
  child.stdin?.write(release);
34778
34900
  } else {
34779
- child.stdin?.write(options.prompt);
34780
34901
  }
34781
- child.stdin?.end();
34902
+ if (parser.start) {
34903
+ const write = (data) => new Promise((resolveWrite, rejectWrite) => {
34904
+ if (!child.stdin || child.stdin.destroyed || settled || forcedResult) {
34905
+ rejectWrite(new Error("runner input is unavailable"));
34906
+ return;
34907
+ }
34908
+ child.stdin.write(data, (error52) => {
34909
+ if (error52) rejectWrite(error52);
34910
+ else resolveWrite();
34911
+ });
34912
+ });
34913
+ try {
34914
+ await parser.start(write, options.prompt);
34915
+ if (parser.steer && options.registerFollowUps) {
34916
+ unregisterFollowUps = options.registerFollowUps((input) => {
34917
+ const settlement = parser.steer(input);
34918
+ liveInputSettlements.add(settlement);
34919
+ void settlement.finally(() => liveInputSettlements.delete(settlement));
34920
+ return settlement;
34921
+ });
34922
+ }
34923
+ } catch {
34924
+ terminate({
34925
+ outcome: "failed",
34926
+ summary: `${options.cliLabel} input protocol could not be started`,
34927
+ usage: parser.usage()
34928
+ });
34929
+ }
34930
+ } else {
34931
+ if (!options.guardian) child.stdin?.write(options.prompt);
34932
+ child.stdin?.end();
34933
+ }
34782
34934
  })();
34783
34935
  });
34784
34936
  }
@@ -34964,6 +35116,7 @@ var claudeCodeAdapter = {
34964
35116
  notFoundMessage: CLAUDE_NOT_FOUND_MESSAGE,
34965
35117
  missingApiKeyMessage: 'Runner auth is set to "API key" but no ANTHROPIC_API_KEY credential is granted to this task. Add it in Resources \u2192 Credentials (org-wide or scoped to this agent).',
34966
35118
  async prepareRun(input) {
35119
+ const liveInput = input.liveInput && input.task.followUps !== void 0;
34967
35120
  const baseArgs = await buildArgs(
34968
35121
  input.task,
34969
35122
  input.runner,
@@ -34972,7 +35125,8 @@ var claudeCodeAdapter = {
34972
35125
  input.preparedWorkspace,
34973
35126
  input.taskRoot,
34974
35127
  input.cwd,
34975
- input.gitDetected
35128
+ input.gitDetected,
35129
+ liveInput
34976
35130
  );
34977
35131
  const sessionId = input.task.spec.sessionKey ?? randomUUID11();
34978
35132
  const observeRuntime = createRuntimeReporter(input, sessionId);
@@ -34983,7 +35137,7 @@ var claudeCodeAdapter = {
34983
35137
  ],
34984
35138
  prompt: buildRunnerPrompt(input.task),
34985
35139
  recoveryPrompt: buildRunnerPrompt(input.task, true),
34986
- createParser: (onStream) => createStreamParser(onStream, { onSessionModel: observeRuntime }),
35140
+ createParser: (onStream) => liveInput ? createClaudeLiveParser(onStream, observeRuntime) : createStreamParser(onStream, { onSessionModel: observeRuntime }),
34987
35141
  // Self-heal both directions: a crashed first run leaves a transcript
34988
35142
  // (new → conflict), a lost workspace breaks resume (resume → not
34989
35143
  // found). One flip covers both.
@@ -35024,7 +35178,7 @@ function delay2(ms) {
35024
35178
  timer.unref?.();
35025
35179
  });
35026
35180
  }
35027
- async function buildArgs(task, runner, artifacts, mcp, preparedWorkspace, taskRoot, workspacePath = taskRoot ?? "", gitDetected = false) {
35181
+ async function buildArgs(task, runner, artifacts, mcp, preparedWorkspace, taskRoot, workspacePath = taskRoot ?? "", gitDetected = false, liveInput = false) {
35028
35182
  const args = [
35029
35183
  // Hermetic, non-interactive run — no local slash commands or global state.
35030
35184
  "--disable-slash-commands",
@@ -35038,6 +35192,7 @@ async function buildArgs(task, runner, artifacts, mcp, preparedWorkspace, taskRo
35038
35192
  // guardrails via runner permission hooks are the follow-up (PRD GR-5).
35039
35193
  "--dangerously-skip-permissions"
35040
35194
  ];
35195
+ if (liveInput) args.push("--input-format", "stream-json", "--replay-user-messages");
35041
35196
  if (runner.effort) args.push("--effort", runner.effort);
35042
35197
  if (runner.model) args.push("--model", runner.model);
35043
35198
  const systemPrompt = workspaceSystemPrompt(
@@ -35069,6 +35224,47 @@ async function buildArgs(task, runner, artifacts, mcp, preparedWorkspace, taskRo
35069
35224
  );
35070
35225
  return args;
35071
35226
  }
35227
+ function createClaudeLiveParser(onStream, onSessionModel) {
35228
+ let write = null;
35229
+ const acknowledgements = /* @__PURE__ */ new Map();
35230
+ const input = (uuid3, text) => `${JSON.stringify({
35231
+ type: "user",
35232
+ uuid: uuid3,
35233
+ message: { role: "user", content: text },
35234
+ parent_tool_use_id: null
35235
+ })}
35236
+ `;
35237
+ const parser = createStreamParser(onStream, {
35238
+ onSessionModel,
35239
+ onUserReplay: (uuid3) => {
35240
+ const acknowledge = acknowledgements.get(uuid3);
35241
+ if (!acknowledge) return;
35242
+ acknowledgements.delete(uuid3);
35243
+ acknowledge(true);
35244
+ }
35245
+ });
35246
+ return {
35247
+ ...parser,
35248
+ async start(writer, prompt) {
35249
+ write = writer;
35250
+ await writer(input(randomUUID11(), prompt));
35251
+ },
35252
+ async steer(followUp) {
35253
+ if (!write) return false;
35254
+ return await new Promise((resolve14) => {
35255
+ acknowledgements.set(followUp.inputId, resolve14);
35256
+ void write(input(followUp.inputId, followUp.text)).catch(() => {
35257
+ if (acknowledgements.delete(followUp.inputId)) resolve14(false);
35258
+ });
35259
+ });
35260
+ },
35261
+ stop() {
35262
+ write = null;
35263
+ for (const acknowledge of acknowledgements.values()) acknowledge(false);
35264
+ acknowledgements.clear();
35265
+ }
35266
+ };
35267
+ }
35072
35268
  function createStreamParser(onStream, hooks = {}) {
35073
35269
  let buffer = "";
35074
35270
  let responseText = "";
@@ -35096,6 +35292,10 @@ function createStreamParser(onStream, hooks = {}) {
35096
35292
  const type = json2["type"];
35097
35293
  if (type === "system" && json2["subtype"] === "init") {
35098
35294
  if (typeof json2["model"] === "string" && json2["model"]) hooks.onSessionModel?.(json2["model"]);
35295
+ } else if (type === "user") {
35296
+ if (json2["isReplay"] === true && typeof json2["uuid"] === "string") {
35297
+ hooks.onUserReplay?.(json2["uuid"]);
35298
+ }
35099
35299
  } else if (type === "assistant") {
35100
35300
  const message = json2["message"];
35101
35301
  applyUsage(message?.["usage"]);
@@ -35129,12 +35329,13 @@ function createStreamParser(onStream, hooks = {}) {
35129
35329
  const errors = Array.isArray(json2["errors"]) ? json2["errors"].filter((e) => typeof e === "string") : [];
35130
35330
  const base = typeof json2["result"] === "string" && json2["result"].trim() ? json2["result"] : "";
35131
35331
  const message = [base, ...errors].filter(Boolean).join("; ") || "Unknown error";
35132
- return {
35332
+ const result2 = {
35133
35333
  outcome: "failed",
35134
35334
  summary: improveErrorMessage(message),
35135
35335
  usage: usage(),
35136
35336
  sessionConflict: isSessionConflict(errors.join(" "))
35137
35337
  };
35338
+ return hooks.shouldCompleteResult?.() === false ? void 0 : result2;
35138
35339
  }
35139
35340
  const finalText = typeof json2["result"] === "string" && json2["result"].trim() ? json2["result"] : responseText;
35140
35341
  if (!finalText.trim() && outputTokens === 0) {
@@ -35145,7 +35346,8 @@ function createStreamParser(onStream, hooks = {}) {
35145
35346
  emptyResult: true
35146
35347
  };
35147
35348
  }
35148
- return { outcome: "done", summary: finalText, usage: usage() };
35349
+ const result = { outcome: "done", summary: finalText, usage: usage() };
35350
+ return hooks.shouldCompleteResult?.() === false ? void 0 : result;
35149
35351
  } else if (type === "error") {
35150
35352
  const message = typeof json2["error"] === "string" ? json2["error"] : JSON.stringify(json2);
35151
35353
  return {
@@ -35213,6 +35415,7 @@ function improveErrorMessage(error52) {
35213
35415
 
35214
35416
  // src/runners/codex.ts
35215
35417
  import { mkdir as mkdir11, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
35418
+ import { randomUUID as randomUUID12 } from "node:crypto";
35216
35419
  import { homedir as homedir6 } from "node:os";
35217
35420
  import { join as join16 } from "node:path";
35218
35421
  var CODEX_NOT_FOUND_MESSAGE = "The `codex` CLI was not found on this Machine. Install it (npm install -g @openai/codex) and sign in with `codex login`, or switch the agent to API-key auth.";
@@ -35251,20 +35454,23 @@ function createCodexAdapter(threadIndexRoot) {
35251
35454
  missingApiKeyMessage: 'Runner auth is set to "API key" but no OPENAI_API_KEY credential is granted to this task. Add it in Resources \u2192 Credentials (org-wide or scoped to this agent).',
35252
35455
  async prepareRun(input) {
35253
35456
  const { task, runner, mcp } = input;
35254
- const flags = [
35457
+ const liveInput = input.liveInput && task.followUps !== void 0;
35458
+ const flags = liveInput ? [
35459
+ "app-server",
35460
+ "--stdio",
35461
+ // Replace machine-configured MCP servers while retaining auth.json.
35462
+ "-c",
35463
+ "mcp_servers={}"
35464
+ ] : [
35255
35465
  "--json",
35256
- // The machine's config.toml (its MCP servers, plugins, hooks) stays
35257
- // out of Zixt-managed sessions; auth.json machine login still applies.
35258
35466
  "--ignore-user-config",
35259
- // A teammate workspace is not necessarily a git repository.
35260
35467
  "--skip-git-repo-check",
35261
- // Headless runs can't answer approval prompts, and guardrails are
35262
- // instructions-first (PRD GR); the OS sandbox remains the documented
35263
- // out-of-scope boundary, same as Claude's --dangerously-skip-permissions.
35264
35468
  "--dangerously-bypass-approvals-and-sandbox"
35265
35469
  ];
35266
- if (runner.model) flags.push("--model", runner.model);
35267
- if (runner.effort) flags.push("-c", `model_reasoning_effort=${tomlString(runner.effort)}`);
35470
+ if (!liveInput && runner.model) flags.push("--model", runner.model);
35471
+ if (!liveInput && runner.effort) {
35472
+ flags.push("-c", `model_reasoning_effort=${tomlString(runner.effort)}`);
35473
+ }
35268
35474
  const env = { ZIXT_RUN_TOKEN: mcp.runToken };
35269
35475
  flags.push("-c", `mcp_servers.zixt.url=${tomlString(mcp.mcpUrl)}`);
35270
35476
  flags.push(
@@ -35302,13 +35508,13 @@ function createCodexAdapter(threadIndexRoot) {
35302
35508
  task.siblingTasks(),
35303
35509
  input.gitDetected
35304
35510
  );
35305
- const withPlatformInstructions = (prompt2) => platformInstructions ? `<zixt_platform_instructions>
35511
+ const withPlatformInstructions = (value) => platformInstructions ? `<zixt_platform_instructions>
35306
35512
  ${platformInstructions}
35307
35513
  </zixt_platform_instructions>
35308
35514
 
35309
- ${prompt2}` : prompt2;
35310
- const prompt = withPlatformInstructions(buildRunnerPrompt(task));
35311
- const recoveryPrompt = withPlatformInstructions(buildRunnerPrompt(task, true));
35515
+ ${value}` : value;
35516
+ const prompt = liveInput ? buildRunnerPrompt(task) : withPlatformInstructions(buildRunnerPrompt(task));
35517
+ const recoveryPrompt = liveInput ? buildRunnerPrompt(task, true) : withPlatformInstructions(buildRunnerPrompt(task, true));
35312
35518
  const sessionKey = task.spec.sessionKey;
35313
35519
  const indexPath = sessionKey ? threadIndexPath(threadIndexRoot, task.agentId, sessionKey) : null;
35314
35520
  const recordedThreadId = indexPath ? await readThreadId(indexPath) : null;
@@ -35322,20 +35528,28 @@ ${prompt2}` : prompt2;
35322
35528
  };
35323
35529
  return {
35324
35530
  argsFor: (mode) => {
35325
- if (mode === "resume") {
35326
- return recordedThreadId ? ["exec", "resume", recordedThreadId, ...flags, "-"] : null;
35327
- }
35328
- return ["exec", ...flags, "-"];
35531
+ if (mode === "resume" && !recordedThreadId) return null;
35532
+ if (liveInput) return flags;
35533
+ return mode === "resume" ? ["exec", "resume", recordedThreadId, ...flags, "-"] : ["exec", ...flags, "-"];
35329
35534
  },
35330
35535
  prompt,
35331
35536
  recoveryPrompt,
35332
35537
  env,
35333
- createParser: (onStream) => createCodexStreamParser(onStream, {
35334
- onThreadStarted: (threadId) => {
35538
+ createParser: (onStream, mode = "new") => {
35539
+ const onThreadStarted = (threadId) => {
35335
35540
  rememberThread(threadId);
35336
35541
  observeRuntime(threadId);
35337
- }
35338
- }),
35542
+ };
35543
+ return liveInput ? createCodexAppServerParser(onStream, {
35544
+ mode,
35545
+ recordedThreadId,
35546
+ cwd: input.cwd,
35547
+ ...runner.model ? { model: runner.model } : {},
35548
+ ...runner.effort ? { effort: runner.effort } : {},
35549
+ developerInstructions: platformInstructions ?? "",
35550
+ onThreadStarted
35551
+ }) : createCodexStreamParser(onStream, { onThreadStarted });
35552
+ },
35339
35553
  // Only one flip is meaningful: a resume whose rollout vanished starts
35340
35554
  // over. Codex assigns new-session ids itself, so a new session can
35341
35555
  // never collide the way a pre-specified id can.
@@ -35372,6 +35586,258 @@ function createCodexRunner(opts = {}) {
35372
35586
  const adapter = createCodexAdapter(opts.threadIndexRoot ?? defaultCodexThreadIndexRoot());
35373
35587
  return createCliRunner(adapter, opts);
35374
35588
  }
35589
+ function createCodexAppServerParser(onStream, options) {
35590
+ let write = null;
35591
+ let prompt = "";
35592
+ let buffer = "";
35593
+ let threadId = null;
35594
+ let activeTurnId = null;
35595
+ let responseText = "";
35596
+ let inputTokens = 0;
35597
+ let outputTokens = 0;
35598
+ let terminal;
35599
+ let stopped = false;
35600
+ const steerWaiters = /* @__PURE__ */ new Map();
35601
+ const turnReadyWaiters = /* @__PURE__ */ new Set();
35602
+ const usage = () => ({ inputTokens, outputTokens });
35603
+ const settleTurnReadiness = (ready) => {
35604
+ for (const resolve14 of turnReadyWaiters) resolve14(ready);
35605
+ turnReadyWaiters.clear();
35606
+ };
35607
+ const send = async (message) => {
35608
+ if (!write || stopped) throw new Error("Codex app-server input is unavailable");
35609
+ await write(`${JSON.stringify({ jsonrpc: "2.0", ...message })}
35610
+ `);
35611
+ };
35612
+ const threadParams = () => ({
35613
+ cwd: options.cwd,
35614
+ approvalPolicy: "never",
35615
+ sandbox: "danger-full-access",
35616
+ ...options.model ? { model: options.model } : {},
35617
+ ...options.developerInstructions ? { developerInstructions: options.developerInstructions } : {}
35618
+ });
35619
+ const startTurn = async () => {
35620
+ if (!threadId) throw new Error("Codex app-server did not provide a thread id");
35621
+ await send({
35622
+ id: "turn:start",
35623
+ method: "turn/start",
35624
+ params: {
35625
+ threadId,
35626
+ input: [{ type: "text", text: prompt }],
35627
+ clientUserMessageId: randomUUID12(),
35628
+ ...options.model ? { model: options.model } : {},
35629
+ ...options.effort ? { effort: options.effort } : {}
35630
+ }
35631
+ });
35632
+ };
35633
+ const itemCompleted = (item) => {
35634
+ const type = item["type"];
35635
+ if (type === "agentMessage" && typeof item["text"] === "string") {
35636
+ responseText = item["text"];
35637
+ onStream("thought", truncateThought(item["text"]));
35638
+ return;
35639
+ }
35640
+ if (type === "reasoning") {
35641
+ const summary = Array.isArray(item["summary"]) ? item["summary"].filter((part) => typeof part === "string") : [];
35642
+ if (summary.length) onStream("thought", truncateThought(summary.join("\n")));
35643
+ return;
35644
+ }
35645
+ if (type === "commandExecution") {
35646
+ const parameter = String(item["command"] ?? "").slice(0, 2e3);
35647
+ onStream("action", `shell${parameter ? `: ${parameter.slice(0, 100)}` : ""}`, {
35648
+ tool: "shell",
35649
+ ...parameter ? { parameter } : {},
35650
+ ephemeral: true
35651
+ });
35652
+ return;
35653
+ }
35654
+ if (type === "mcpToolCall") {
35655
+ const tool = `${String(item["server"] ?? "mcp")}.${String(item["tool"] ?? "tool")}`.slice(
35656
+ 0,
35657
+ 200
35658
+ );
35659
+ const args = item["arguments"];
35660
+ const parameter = (typeof args === "string" ? args : args ? JSON.stringify(args) : "").slice(
35661
+ 0,
35662
+ 2e3
35663
+ );
35664
+ onStream("action", `${tool}${parameter ? `: ${parameter.slice(0, 100)}` : ""}`, {
35665
+ tool,
35666
+ ...parameter ? { parameter } : {},
35667
+ ephemeral: true
35668
+ });
35669
+ return;
35670
+ }
35671
+ if (type === "fileChange") {
35672
+ const changes = Array.isArray(item["changes"]) ? item["changes"].map((change) => String(change["path"] ?? "")).filter(Boolean) : [];
35673
+ const parameter = changes.join(", ").slice(0, 2e3);
35674
+ onStream("action", `file_change${parameter ? `: ${parameter.slice(0, 100)}` : ""}`, {
35675
+ tool: "file_change",
35676
+ ...parameter ? { parameter } : {},
35677
+ ephemeral: true,
35678
+ ...changes.length ? { localPaths: changes } : {}
35679
+ });
35680
+ }
35681
+ };
35682
+ const processLine = (line) => {
35683
+ if (!line.trim()) return void 0;
35684
+ let message;
35685
+ try {
35686
+ message = JSON.parse(line);
35687
+ } catch {
35688
+ return void 0;
35689
+ }
35690
+ const id = typeof message["id"] === "string" ? message["id"] : null;
35691
+ const error52 = message["error"];
35692
+ if (id && error52) {
35693
+ const detail = typeof error52["message"] === "string" ? error52["message"] : JSON.stringify(error52);
35694
+ if (id.startsWith("steer:")) {
35695
+ const inputId = id.slice("steer:".length);
35696
+ steerWaiters.get(inputId)?.(false);
35697
+ steerWaiters.delete(inputId);
35698
+ return void 0;
35699
+ }
35700
+ terminal = {
35701
+ outcome: "failed",
35702
+ summary: improveCodexErrorMessage(detail),
35703
+ usage: usage(),
35704
+ sessionConflict: isCodexSessionConflict(detail)
35705
+ };
35706
+ return terminal;
35707
+ }
35708
+ const result = message["result"];
35709
+ if (id === "initialize" && result) {
35710
+ void send({ method: "initialized" }).then(
35711
+ () => send({
35712
+ id: "thread:open",
35713
+ method: options.mode === "resume" ? "thread/resume" : "thread/start",
35714
+ params: options.mode === "resume" ? { ...threadParams(), threadId: options.recordedThreadId } : threadParams()
35715
+ })
35716
+ ).catch(() => {
35717
+ });
35718
+ } else if (id === "thread:open" && result) {
35719
+ const thread = result["thread"];
35720
+ if (typeof thread?.["id"] === "string") {
35721
+ threadId = thread["id"];
35722
+ options.onThreadStarted(threadId);
35723
+ void startTurn().catch(() => {
35724
+ });
35725
+ }
35726
+ } else if (id === "turn:start" && result) {
35727
+ const turn = result["turn"];
35728
+ if (typeof turn?.["id"] === "string") {
35729
+ activeTurnId = turn["id"];
35730
+ }
35731
+ } else if (id?.startsWith("steer:") && result) {
35732
+ const inputId = id.slice("steer:".length);
35733
+ const accepted = typeof result["turnId"] === "string";
35734
+ steerWaiters.get(inputId)?.(accepted);
35735
+ steerWaiters.delete(inputId);
35736
+ }
35737
+ const method = message["method"];
35738
+ const params = message["params"];
35739
+ if (method === "thread/started") {
35740
+ const thread = params?.["thread"];
35741
+ if (!threadId && typeof thread?.["id"] === "string") {
35742
+ threadId = thread["id"];
35743
+ options.onThreadStarted(threadId);
35744
+ }
35745
+ } else if (method === "turn/started") {
35746
+ const turn = params?.["turn"];
35747
+ if (typeof turn?.["id"] === "string") {
35748
+ activeTurnId = turn["id"];
35749
+ settleTurnReadiness(true);
35750
+ }
35751
+ } else if (method === "item/completed") {
35752
+ const item = params?.["item"];
35753
+ if (item && typeof item === "object") itemCompleted(item);
35754
+ } else if (method === "thread/tokenUsage/updated") {
35755
+ const tokenUsage = params?.["tokenUsage"];
35756
+ const total = tokenUsage?.["total"];
35757
+ if (typeof total?.["inputTokens"] === "number") inputTokens = total["inputTokens"];
35758
+ if (typeof total?.["outputTokens"] === "number") outputTokens = total["outputTokens"];
35759
+ } else if (method === "turn/completed") {
35760
+ const turn = params?.["turn"];
35761
+ activeTurnId = null;
35762
+ settleTurnReadiness(false);
35763
+ const status = turn?.["status"];
35764
+ const turnError = turn?.["error"];
35765
+ const detail = typeof turnError?.["message"] === "string" ? turnError["message"] : "Codex turn failed";
35766
+ terminal = status === "completed" ? { outcome: "done", summary: responseText, usage: usage() } : { outcome: "failed", summary: improveCodexErrorMessage(detail), usage: usage() };
35767
+ return terminal;
35768
+ }
35769
+ return void 0;
35770
+ };
35771
+ return {
35772
+ usage,
35773
+ async start(writer, initialPrompt) {
35774
+ write = writer;
35775
+ prompt = initialPrompt;
35776
+ await send({
35777
+ id: "initialize",
35778
+ method: "initialize",
35779
+ params: { clientInfo: { name: "zixt", version: "1" } }
35780
+ });
35781
+ },
35782
+ async steer(input) {
35783
+ if (stopped) return false;
35784
+ if (!activeTurnId) {
35785
+ const ready = await new Promise((resolve14) => turnReadyWaiters.add(resolve14));
35786
+ if (!ready || stopped) return false;
35787
+ }
35788
+ if (!threadId || !activeTurnId) return false;
35789
+ return await new Promise((resolve14) => {
35790
+ steerWaiters.set(input.inputId, resolve14);
35791
+ void send({
35792
+ id: `steer:${input.inputId}`,
35793
+ method: "turn/steer",
35794
+ params: {
35795
+ threadId,
35796
+ expectedTurnId: activeTurnId,
35797
+ input: [{ type: "text", text: input.text }],
35798
+ clientUserMessageId: input.inputId
35799
+ }
35800
+ }).catch(() => {
35801
+ if (steerWaiters.delete(input.inputId)) resolve14(false);
35802
+ });
35803
+ });
35804
+ },
35805
+ stop() {
35806
+ stopped = true;
35807
+ write = null;
35808
+ settleTurnReadiness(false);
35809
+ for (const resolve14 of steerWaiters.values()) resolve14(false);
35810
+ steerWaiters.clear();
35811
+ },
35812
+ push(chunk) {
35813
+ buffer += chunk;
35814
+ const lines = buffer.split(/\r?\n/);
35815
+ buffer = lines.pop() ?? "";
35816
+ for (const line of lines) {
35817
+ const parsed = processLine(line);
35818
+ if (parsed) return parsed;
35819
+ }
35820
+ return void 0;
35821
+ },
35822
+ finish(stderr, exitCode = 0) {
35823
+ if (buffer.trim()) {
35824
+ const parsed = processLine(buffer);
35825
+ buffer = "";
35826
+ if (parsed) return parsed;
35827
+ }
35828
+ if (terminal) return terminal;
35829
+ const detail = stderr.trim() || "Codex app-server exited before the turn completed";
35830
+ return {
35831
+ outcome: "failed",
35832
+ summary: improveCodexErrorMessage(
35833
+ exitCode ? `codex exited with code ${exitCode}: ${detail}` : detail
35834
+ ),
35835
+ usage: usage(),
35836
+ sessionConflict: isCodexSessionConflict(detail)
35837
+ };
35838
+ }
35839
+ };
35840
+ }
35375
35841
  function createCodexStreamParser(onStream, hooks = {}) {
35376
35842
  let buffer = "";
35377
35843
  let responseText = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zixt/host",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/client.ts",