@zixt/host 0.0.38 → 0.0.40
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/dist/index.js +550 -46
- 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.
|
|
34
|
+
version: "0.0.40",
|
|
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
|
/**
|
|
@@ -15903,6 +15907,8 @@ var TaskGitSummary = external_exports.object({
|
|
|
15903
15907
|
isWorktree: external_exports.boolean().optional(),
|
|
15904
15908
|
/** False means the Task used this repository, then removed its checkout after integration. */
|
|
15905
15909
|
available: external_exports.boolean().optional(),
|
|
15910
|
+
/** False preserves the Task branch after a shared checkout switches back. */
|
|
15911
|
+
checkedOut: external_exports.boolean().optional(),
|
|
15906
15912
|
branch: taskGitSummaryLabel(512, "branch").nullable(),
|
|
15907
15913
|
ahead: external_exports.number().int().min(0),
|
|
15908
15914
|
behind: external_exports.number().int().min(0),
|
|
@@ -16143,6 +16149,8 @@ var TaskWorkingContextBase = external_exports.object({
|
|
|
16143
16149
|
isWorktree: external_exports.boolean(),
|
|
16144
16150
|
/** Absent is legacy-current; false preserves a cleaned-up Task repository as history. */
|
|
16145
16151
|
available: external_exports.boolean().optional(),
|
|
16152
|
+
/** Absent is legacy-current; false means this retained branch is no longer checked out. */
|
|
16153
|
+
checkedOut: external_exports.boolean().optional(),
|
|
16146
16154
|
/** Null is an observed detached HEAD, never an unknown branch. */
|
|
16147
16155
|
branch: safeWorkingContextLabel(512, "branch").nullable(),
|
|
16148
16156
|
headSha: external_exports.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/).nullable(),
|
|
@@ -17292,9 +17300,17 @@ var BrowserCommandFrame = external_exports.object({
|
|
|
17292
17300
|
browserSessionId: external_exports.string().min(1).max(200),
|
|
17293
17301
|
command: BrowserCommand
|
|
17294
17302
|
});
|
|
17303
|
+
var TaskInput = external_exports.object({
|
|
17304
|
+
type: external_exports.literal("task.input"),
|
|
17305
|
+
taskId: TaskId,
|
|
17306
|
+
epoch: external_exports.number().int().min(1),
|
|
17307
|
+
inputId: external_exports.uuid(),
|
|
17308
|
+
text: external_exports.string().min(1).max(1e5)
|
|
17309
|
+
});
|
|
17295
17310
|
var DurableDownMessage = external_exports.discriminatedUnion("type", [
|
|
17296
17311
|
TaskAssign,
|
|
17297
17312
|
TaskCancel,
|
|
17313
|
+
TaskInput,
|
|
17298
17314
|
SecretsGrant,
|
|
17299
17315
|
ApprovalDecision,
|
|
17300
17316
|
ConnectionsGrant
|
|
@@ -17338,6 +17354,13 @@ var TaskTitle = external_exports.object({
|
|
|
17338
17354
|
epoch: external_exports.number().int().min(1),
|
|
17339
17355
|
title: external_exports.string().min(1).max(300)
|
|
17340
17356
|
});
|
|
17357
|
+
var TaskInputResult = external_exports.object({
|
|
17358
|
+
type: external_exports.literal("task.input.result"),
|
|
17359
|
+
taskId: TaskId,
|
|
17360
|
+
epoch: external_exports.number().int().min(1),
|
|
17361
|
+
inputId: external_exports.uuid(),
|
|
17362
|
+
accepted: external_exports.boolean()
|
|
17363
|
+
});
|
|
17341
17364
|
var TaskResult = external_exports.object({
|
|
17342
17365
|
type: external_exports.literal("task.result"),
|
|
17343
17366
|
taskId: TaskId,
|
|
@@ -17365,7 +17388,9 @@ var TaskResult = external_exports.object({
|
|
|
17365
17388
|
detail: external_exports.string().max(1e4).optional(),
|
|
17366
17389
|
evidenceClass: external_exports.enum(["agent_claimed", "host_observed"])
|
|
17367
17390
|
})
|
|
17368
|
-
).max(200).optional()
|
|
17391
|
+
).max(200).optional(),
|
|
17392
|
+
/** Inputs the provider accepted into this exact run before it completed. */
|
|
17393
|
+
acceptedInputIds: external_exports.array(external_exports.uuid()).max(1e3).optional()
|
|
17369
17394
|
});
|
|
17370
17395
|
var DurableTaskResult = TaskResult.extend({ resultId: external_exports.uuid() });
|
|
17371
17396
|
var HostReport = external_exports.object({
|
|
@@ -17427,6 +17452,7 @@ var UpMessage = external_exports.discriminatedUnion("type", [
|
|
|
17427
17452
|
TaskWorkingContextReport,
|
|
17428
17453
|
TaskRunnerRuntimeReport,
|
|
17429
17454
|
TaskTitle,
|
|
17455
|
+
TaskInputResult,
|
|
17430
17456
|
TaskResult,
|
|
17431
17457
|
HostReport,
|
|
17432
17458
|
HostConsoleReport,
|
|
@@ -19796,6 +19822,10 @@ var HostClient = class _HostClient {
|
|
|
19796
19822
|
onUnwindStalled;
|
|
19797
19823
|
/** Exact identity for each active run; unlike model output this is safe to acknowledge. */
|
|
19798
19824
|
activeAssignments = /* @__PURE__ */ new Map();
|
|
19825
|
+
liveInputHandlers = /* @__PURE__ */ new Map();
|
|
19826
|
+
liveInputBacklog = /* @__PURE__ */ new Map();
|
|
19827
|
+
liveInputInFlight = /* @__PURE__ */ new Map();
|
|
19828
|
+
acceptedLiveInputIds = /* @__PURE__ */ new Map();
|
|
19799
19829
|
/**
|
|
19800
19830
|
* Connection-loss runs confirmed stopped locally but not yet acknowledged
|
|
19801
19831
|
* by a new cloud socket generation. Replayed only inside hello.
|
|
@@ -20311,6 +20341,11 @@ var HostClient = class _HostClient {
|
|
|
20311
20341
|
label: safe(fact.label, 2e3),
|
|
20312
20342
|
...fact.detail !== void 0 ? { detail: safe(fact.detail, 1e4) } : {}
|
|
20313
20343
|
}))
|
|
20344
|
+
} : {},
|
|
20345
|
+
...this.acceptedLiveInputIds.get(`${assign.taskId}:${assign.epoch}`)?.size ? {
|
|
20346
|
+
acceptedInputIds: [
|
|
20347
|
+
...this.acceptedLiveInputIds.get(`${assign.taskId}:${assign.epoch}`)
|
|
20348
|
+
]
|
|
20314
20349
|
} : {}
|
|
20315
20350
|
};
|
|
20316
20351
|
}
|
|
@@ -20561,6 +20596,7 @@ var HostClient = class _HostClient {
|
|
|
20561
20596
|
...measuredCapabilities,
|
|
20562
20597
|
linearToolPack: measured.capabilities?.linearToolPack ?? false,
|
|
20563
20598
|
providerOperationGrantRetry: true,
|
|
20599
|
+
liveRunnerInput: true,
|
|
20564
20600
|
hostConsole: true,
|
|
20565
20601
|
...workerWatchdogIsActive() ? { taskAuthorityLifecycle: true } : {}
|
|
20566
20602
|
},
|
|
@@ -20604,6 +20640,9 @@ var HostClient = class _HostClient {
|
|
|
20604
20640
|
message.purpose === "credential_rollover" ? "credential_rollover" : "cloud_cancel"
|
|
20605
20641
|
);
|
|
20606
20642
|
return;
|
|
20643
|
+
case "task.input":
|
|
20644
|
+
this.queueLiveInput(message);
|
|
20645
|
+
return;
|
|
20607
20646
|
case "secrets.grant": {
|
|
20608
20647
|
const key = `${message.taskId}:${message.epoch}`;
|
|
20609
20648
|
if (!this.cancels.has(key)) return;
|
|
@@ -20667,6 +20706,51 @@ var HostClient = class _HostClient {
|
|
|
20667
20706
|
}
|
|
20668
20707
|
}
|
|
20669
20708
|
}
|
|
20709
|
+
sendLiveInputResult(input, accepted) {
|
|
20710
|
+
this.sendUp({
|
|
20711
|
+
type: "task.input.result",
|
|
20712
|
+
taskId: input.taskId,
|
|
20713
|
+
epoch: input.epoch,
|
|
20714
|
+
inputId: input.inputId,
|
|
20715
|
+
accepted
|
|
20716
|
+
});
|
|
20717
|
+
}
|
|
20718
|
+
deliverLiveInput(key, input) {
|
|
20719
|
+
const accepted = this.acceptedLiveInputIds.get(key);
|
|
20720
|
+
if (accepted?.has(input.inputId)) {
|
|
20721
|
+
this.sendLiveInputResult(input, true);
|
|
20722
|
+
return;
|
|
20723
|
+
}
|
|
20724
|
+
const inFlight = this.liveInputInFlight.get(key) ?? /* @__PURE__ */ new Set();
|
|
20725
|
+
if (inFlight.has(input.inputId)) return;
|
|
20726
|
+
const handler5 = this.liveInputHandlers.get(key);
|
|
20727
|
+
if (!handler5) {
|
|
20728
|
+
const backlog = this.liveInputBacklog.get(key) ?? [];
|
|
20729
|
+
if (!backlog.some((candidate) => candidate.inputId === input.inputId)) backlog.push(input);
|
|
20730
|
+
this.liveInputBacklog.set(key, backlog);
|
|
20731
|
+
return;
|
|
20732
|
+
}
|
|
20733
|
+
inFlight.add(input.inputId);
|
|
20734
|
+
this.liveInputInFlight.set(key, inFlight);
|
|
20735
|
+
void handler5({ inputId: input.inputId, text: input.text }).then((wasAccepted) => {
|
|
20736
|
+
if (wasAccepted) {
|
|
20737
|
+
const acceptedIds = this.acceptedLiveInputIds.get(key) ?? /* @__PURE__ */ new Set();
|
|
20738
|
+
acceptedIds.add(input.inputId);
|
|
20739
|
+
this.acceptedLiveInputIds.set(key, acceptedIds);
|
|
20740
|
+
}
|
|
20741
|
+
this.sendLiveInputResult(input, wasAccepted);
|
|
20742
|
+
}).catch(() => this.sendLiveInputResult(input, false)).finally(() => {
|
|
20743
|
+
this.liveInputInFlight.get(key)?.delete(input.inputId);
|
|
20744
|
+
});
|
|
20745
|
+
}
|
|
20746
|
+
queueLiveInput(input) {
|
|
20747
|
+
const key = `${input.taskId}:${input.epoch}`;
|
|
20748
|
+
if (!this.cancels.has(key)) {
|
|
20749
|
+
this.sendLiveInputResult(input, false);
|
|
20750
|
+
return;
|
|
20751
|
+
}
|
|
20752
|
+
this.deliverLiveInput(key, input);
|
|
20753
|
+
}
|
|
20670
20754
|
startTask(assign) {
|
|
20671
20755
|
const key = `${assign.taskId}:${assign.epoch}`;
|
|
20672
20756
|
if (this.activeRuns.has(key)) return;
|
|
@@ -21217,6 +21301,19 @@ var HostClient = class _HostClient {
|
|
|
21217
21301
|
runnerRuntime: emitRunnerRuntime,
|
|
21218
21302
|
siblingTasks: () => [...this.liveTasks.values()].filter((live) => live.agentId === assign.agentId && live.taskId !== assign.taskId).map(({ taskId, title }) => ({ taskId, title })),
|
|
21219
21303
|
fetchAttachment: (attachmentId) => this.fetchAttachment(attachmentId, authorityController.signal),
|
|
21304
|
+
followUps: (handler5) => {
|
|
21305
|
+
if (cancelled) return () => {
|
|
21306
|
+
};
|
|
21307
|
+
this.liveInputHandlers.set(cancelKey, handler5);
|
|
21308
|
+
const backlog = this.liveInputBacklog.get(cancelKey) ?? [];
|
|
21309
|
+
this.liveInputBacklog.delete(cancelKey);
|
|
21310
|
+
for (const input of backlog) this.deliverLiveInput(cancelKey, input);
|
|
21311
|
+
return () => {
|
|
21312
|
+
if (this.liveInputHandlers.get(cancelKey) === handler5) {
|
|
21313
|
+
this.liveInputHandlers.delete(cancelKey);
|
|
21314
|
+
}
|
|
21315
|
+
};
|
|
21316
|
+
},
|
|
21220
21317
|
secrets,
|
|
21221
21318
|
connections,
|
|
21222
21319
|
providers,
|
|
@@ -21294,6 +21391,13 @@ var HostClient = class _HostClient {
|
|
|
21294
21391
|
this.agentOpWaiters.delete(cancelKey);
|
|
21295
21392
|
this.rejectOperationGrantWaiters(cancelKey);
|
|
21296
21393
|
this.rejectBrowserCredentialWaiters(cancelKey);
|
|
21394
|
+
this.liveInputHandlers.delete(cancelKey);
|
|
21395
|
+
for (const input of this.liveInputBacklog.get(cancelKey) ?? []) {
|
|
21396
|
+
this.sendLiveInputResult(input, false);
|
|
21397
|
+
}
|
|
21398
|
+
this.liveInputBacklog.delete(cancelKey);
|
|
21399
|
+
this.liveInputInFlight.delete(cancelKey);
|
|
21400
|
+
this.acceptedLiveInputIds.delete(cancelKey);
|
|
21297
21401
|
sensitiveValues?.clear();
|
|
21298
21402
|
sensitiveValues = null;
|
|
21299
21403
|
if (stopReason === "cloud_cancel") {
|
|
@@ -32969,6 +33073,7 @@ async function repositoryState(directory, git, env, signal) {
|
|
|
32969
33073
|
root,
|
|
32970
33074
|
isWorktree: gitDirectory !== commonDirectory,
|
|
32971
33075
|
available: true,
|
|
33076
|
+
checkedOut: true,
|
|
32972
33077
|
branch,
|
|
32973
33078
|
headSha,
|
|
32974
33079
|
upstream,
|
|
@@ -33005,13 +33110,42 @@ async function collectWorkingContext(input) {
|
|
|
33005
33110
|
...uniqueRepositories.length > 0 ? { repositories: uniqueRepositories } : {}
|
|
33006
33111
|
});
|
|
33007
33112
|
}
|
|
33008
|
-
function preserveObservedRepositories(current, previous, fixedRepositoryRoots) {
|
|
33113
|
+
function preserveObservedRepositories(current, previous, fixedRepositoryRoots, initial = null) {
|
|
33009
33114
|
const fixed = new Set(fixedRepositoryRoots);
|
|
33010
33115
|
const currentRepositories = current.repositories ?? [];
|
|
33011
33116
|
const currentRoots = new Set(currentRepositories.map(({ root }) => root));
|
|
33012
|
-
const
|
|
33013
|
-
|
|
33014
|
-
|
|
33117
|
+
const previousByRoot = new Map(
|
|
33118
|
+
(previous?.repositories ?? []).map((repository) => [repository.root, repository])
|
|
33119
|
+
);
|
|
33120
|
+
const initialByRoot = new Map(
|
|
33121
|
+
(initial?.repositories ?? []).map((repository) => [repository.root, repository])
|
|
33122
|
+
);
|
|
33123
|
+
const sameRef = (left, right) => left.branch === right.branch && (left.branch !== null || left.headSha === right.headSha);
|
|
33124
|
+
const liveRepositories = currentRepositories.map((repository) => {
|
|
33125
|
+
if (!fixed.has(repository.root)) return repository;
|
|
33126
|
+
const previousRepository = previousByRoot.get(repository.root);
|
|
33127
|
+
const initialRepository = initialByRoot.get(repository.root);
|
|
33128
|
+
if (!previousRepository || !initialRepository || !sameRef(repository, initialRepository)) {
|
|
33129
|
+
return repository;
|
|
33130
|
+
}
|
|
33131
|
+
if (previousRepository.checkedOut !== false && sameRef(previousRepository, initialRepository)) {
|
|
33132
|
+
return repository;
|
|
33133
|
+
}
|
|
33134
|
+
return {
|
|
33135
|
+
...previousRepository,
|
|
33136
|
+
root: repository.root,
|
|
33137
|
+
isWorktree: repository.isWorktree,
|
|
33138
|
+
available: true,
|
|
33139
|
+
checkedOut: false
|
|
33140
|
+
};
|
|
33141
|
+
});
|
|
33142
|
+
const liveTaskRepositories = liveRepositories.filter(({ root }) => !fixed.has(root));
|
|
33143
|
+
const liveFixedRepositories = liveRepositories.filter(({ root }) => fixed.has(root));
|
|
33144
|
+
const retained = (previous?.repositories ?? []).filter(({ root }) => !fixed.has(root) && !currentRoots.has(root)).map((repository) => ({
|
|
33145
|
+
...repository,
|
|
33146
|
+
available: false,
|
|
33147
|
+
checkedOut: false
|
|
33148
|
+
}));
|
|
33015
33149
|
const repositories = [...liveTaskRepositories, ...retained, ...liveFixedRepositories].slice(
|
|
33016
33150
|
0,
|
|
33017
33151
|
20
|
|
@@ -33108,6 +33242,7 @@ let expectedBytes = null;
|
|
|
33108
33242
|
let config = null;
|
|
33109
33243
|
let launchPending = false;
|
|
33110
33244
|
let stderrTail = '';
|
|
33245
|
+
let postReleaseInput = Buffer.alloc(0);
|
|
33111
33246
|
let reported = false;
|
|
33112
33247
|
const report = (result) => {
|
|
33113
33248
|
if (reported) return;
|
|
@@ -33125,7 +33260,6 @@ prelaunchTimeout.unref();
|
|
|
33125
33260
|
|
|
33126
33261
|
const launchTarget = (release) => {
|
|
33127
33262
|
try {
|
|
33128
|
-
config = release;
|
|
33129
33263
|
const command = config.comspec || config.command;
|
|
33130
33264
|
const args = config.comspec
|
|
33131
33265
|
? ['/d', '/s', '/c', '"' + [config.command, ...config.args].map(quote).join(' ') + '"']
|
|
@@ -33172,11 +33306,16 @@ const launchTarget = (release) => {
|
|
|
33172
33306
|
maybeReport();
|
|
33173
33307
|
});
|
|
33174
33308
|
if (config.prompt) target.stdin.write(config.prompt);
|
|
33309
|
+
if (postReleaseInput.length) {
|
|
33310
|
+
target.stdin.write(postReleaseInput);
|
|
33311
|
+
postReleaseInput = Buffer.alloc(0);
|
|
33312
|
+
}
|
|
33175
33313
|
if (stdinEnded) target.stdin.end();
|
|
33176
33314
|
};
|
|
33177
33315
|
|
|
33178
33316
|
const launch = (release) => {
|
|
33179
33317
|
clearTimeout(prelaunchTimeout);
|
|
33318
|
+
config = release;
|
|
33180
33319
|
launchPending = true;
|
|
33181
33320
|
if (process.platform === 'win32') {
|
|
33182
33321
|
launchTarget(release);
|
|
@@ -33211,6 +33350,11 @@ process.stdin.on('data', (chunk) => {
|
|
|
33211
33350
|
target.stdin.write(chunk);
|
|
33212
33351
|
return;
|
|
33213
33352
|
}
|
|
33353
|
+
if (config) {
|
|
33354
|
+
postReleaseInput = Buffer.concat([postReleaseInput, chunk]);
|
|
33355
|
+
if (postReleaseInput.length > maxReleaseBytes) process.exit(1);
|
|
33356
|
+
return;
|
|
33357
|
+
}
|
|
33214
33358
|
pending = Buffer.concat([pending, chunk]);
|
|
33215
33359
|
if (pending.length > maxReleaseBytes + 256) process.exit(1);
|
|
33216
33360
|
if (!containmentReady) {
|
|
@@ -33232,9 +33376,9 @@ process.stdin.on('data', (chunk) => {
|
|
|
33232
33376
|
pending = pending.subarray(newline + 1);
|
|
33233
33377
|
}
|
|
33234
33378
|
if (pending.length < expectedBytes) return;
|
|
33235
|
-
if (pending.length !== expectedBytes) process.exit(1);
|
|
33236
33379
|
try {
|
|
33237
|
-
const release = JSON.parse(pending.toString('utf8'));
|
|
33380
|
+
const release = JSON.parse(pending.subarray(0, expectedBytes).toString('utf8'));
|
|
33381
|
+
postReleaseInput = pending.subarray(expectedBytes);
|
|
33238
33382
|
pending = Buffer.alloc(0);
|
|
33239
33383
|
launch(release);
|
|
33240
33384
|
} catch {
|
|
@@ -34038,7 +34182,8 @@ function createCliRunner(adapter, opts = {}) {
|
|
|
34038
34182
|
cwd,
|
|
34039
34183
|
command: resolvedCommand,
|
|
34040
34184
|
commandPrefixArgs: prefixArgs,
|
|
34041
|
-
env
|
|
34185
|
+
env,
|
|
34186
|
+
liveInput: opts.liveInput ?? opts.command === void 0
|
|
34042
34187
|
});
|
|
34043
34188
|
const runEnv = prepared.env ? { ...env, ...prepared.env } : env;
|
|
34044
34189
|
let attachmentSection = "";
|
|
@@ -34060,6 +34205,7 @@ ${attachmentSection}` : prompt;
|
|
|
34060
34205
|
let activeWorkingContextReport = null;
|
|
34061
34206
|
let previousWorkingContext = "";
|
|
34062
34207
|
let previousWorkingContextValue = null;
|
|
34208
|
+
let initialWorkingContextValue = null;
|
|
34063
34209
|
const observedWorkingDirectories = /* @__PURE__ */ new Set();
|
|
34064
34210
|
let observedPathRefreshPending = false;
|
|
34065
34211
|
const workingContextAbort = new AbortController();
|
|
@@ -34077,12 +34223,15 @@ ${attachmentSection}` : prompt;
|
|
|
34077
34223
|
git,
|
|
34078
34224
|
env: workingContextEnvironments.git,
|
|
34079
34225
|
signal: workingContextAbort.signal
|
|
34226
|
+
}).then((context) => {
|
|
34227
|
+
initialWorkingContextValue ??= context;
|
|
34228
|
+
return preserveObservedRepositories(
|
|
34229
|
+
context,
|
|
34230
|
+
previousWorkingContextValue,
|
|
34231
|
+
[cwd, ...preparedWorkspace ? [preparedWorkspace.path] : []],
|
|
34232
|
+
initialWorkingContextValue
|
|
34233
|
+
);
|
|
34080
34234
|
}).then(
|
|
34081
|
-
(context) => preserveObservedRepositories(context, previousWorkingContextValue, [
|
|
34082
|
-
cwd,
|
|
34083
|
-
...preparedWorkspace ? [preparedWorkspace.path] : []
|
|
34084
|
-
])
|
|
34085
|
-
).then(
|
|
34086
34235
|
(context) => pullRequests.enrich(context, ghContextCommand, {
|
|
34087
34236
|
...options.forcePullRequest ? { force: true } : {},
|
|
34088
34237
|
...gitContextCommand ? { gitCommand: gitContextCommand } : {},
|
|
@@ -34227,7 +34376,8 @@ ${attachmentSection}` : prompt;
|
|
|
34227
34376
|
...detail.ephemeral === void 0 ? {} : { ephemeral: detail.ephemeral }
|
|
34228
34377
|
});
|
|
34229
34378
|
},
|
|
34230
|
-
createParser: prepared.createParser,
|
|
34379
|
+
createParser: (onStream) => prepared.createParser(onStream, mode2),
|
|
34380
|
+
...task.followUps ? { registerFollowUps: task.followUps } : {},
|
|
34231
34381
|
notFoundMessage: adapter.notFoundMessage,
|
|
34232
34382
|
cliLabel: adapter.type,
|
|
34233
34383
|
// Durable evidence for the next Host process: which assignment this
|
|
@@ -34564,6 +34714,8 @@ function runCliProcess(options) {
|
|
|
34564
34714
|
detached: platform !== "win32"
|
|
34565
34715
|
});
|
|
34566
34716
|
const parser = options.createParser(options.onStream);
|
|
34717
|
+
let unregisterFollowUps;
|
|
34718
|
+
const liveInputSettlements = /* @__PURE__ */ new Set();
|
|
34567
34719
|
let stderrBuf = "";
|
|
34568
34720
|
let settled = false;
|
|
34569
34721
|
let terminalResult;
|
|
@@ -34592,12 +34744,15 @@ function runCliProcess(options) {
|
|
|
34592
34744
|
if (settled) return;
|
|
34593
34745
|
settled = true;
|
|
34594
34746
|
clearInterval(timer);
|
|
34747
|
+
unregisterFollowUps?.();
|
|
34748
|
+
parser.stop?.();
|
|
34595
34749
|
resolve14(result);
|
|
34596
34750
|
};
|
|
34597
34751
|
const terminate = (result) => {
|
|
34598
34752
|
if (settled || forcedResult) return;
|
|
34599
34753
|
forcedResult = result;
|
|
34600
34754
|
clearInterval(timer);
|
|
34755
|
+
child.stdin?.end();
|
|
34601
34756
|
void containmentReady.then(async () => {
|
|
34602
34757
|
if (windowsContainment) {
|
|
34603
34758
|
await windowsContainment.terminate();
|
|
@@ -34613,7 +34768,12 @@ function runCliProcess(options) {
|
|
|
34613
34768
|
} : {}
|
|
34614
34769
|
});
|
|
34615
34770
|
}).then(
|
|
34616
|
-
() =>
|
|
34771
|
+
async () => {
|
|
34772
|
+
unregisterFollowUps?.();
|
|
34773
|
+
parser.stop?.();
|
|
34774
|
+
await Promise.allSettled([...liveInputSettlements]);
|
|
34775
|
+
settle4(result);
|
|
34776
|
+
},
|
|
34617
34777
|
() => settle4({
|
|
34618
34778
|
outcome: "failed",
|
|
34619
34779
|
summary: PROCESS_CLEANUP_FAILURE,
|
|
@@ -34768,7 +34928,7 @@ function runCliProcess(options) {
|
|
|
34768
34928
|
cwd: options.cwd,
|
|
34769
34929
|
env: options.env,
|
|
34770
34930
|
...options.guardian.comspec ? { comspec: options.guardian.comspec } : {},
|
|
34771
|
-
prompt: options.prompt
|
|
34931
|
+
prompt: parser.start ? "" : options.prompt
|
|
34772
34932
|
}),
|
|
34773
34933
|
"utf8"
|
|
34774
34934
|
);
|
|
@@ -34776,9 +34936,39 @@ function runCliProcess(options) {
|
|
|
34776
34936
|
`);
|
|
34777
34937
|
child.stdin?.write(release);
|
|
34778
34938
|
} else {
|
|
34779
|
-
child.stdin?.write(options.prompt);
|
|
34780
34939
|
}
|
|
34781
|
-
|
|
34940
|
+
if (parser.start) {
|
|
34941
|
+
const write = (data) => new Promise((resolveWrite, rejectWrite) => {
|
|
34942
|
+
if (!child.stdin || child.stdin.destroyed || settled || forcedResult) {
|
|
34943
|
+
rejectWrite(new Error("runner input is unavailable"));
|
|
34944
|
+
return;
|
|
34945
|
+
}
|
|
34946
|
+
child.stdin.write(data, (error52) => {
|
|
34947
|
+
if (error52) rejectWrite(error52);
|
|
34948
|
+
else resolveWrite();
|
|
34949
|
+
});
|
|
34950
|
+
});
|
|
34951
|
+
try {
|
|
34952
|
+
await parser.start(write, options.prompt);
|
|
34953
|
+
if (parser.steer && options.registerFollowUps) {
|
|
34954
|
+
unregisterFollowUps = options.registerFollowUps((input) => {
|
|
34955
|
+
const settlement = parser.steer(input);
|
|
34956
|
+
liveInputSettlements.add(settlement);
|
|
34957
|
+
void settlement.finally(() => liveInputSettlements.delete(settlement));
|
|
34958
|
+
return settlement;
|
|
34959
|
+
});
|
|
34960
|
+
}
|
|
34961
|
+
} catch {
|
|
34962
|
+
terminate({
|
|
34963
|
+
outcome: "failed",
|
|
34964
|
+
summary: `${options.cliLabel} input protocol could not be started`,
|
|
34965
|
+
usage: parser.usage()
|
|
34966
|
+
});
|
|
34967
|
+
}
|
|
34968
|
+
} else {
|
|
34969
|
+
if (!options.guardian) child.stdin?.write(options.prompt);
|
|
34970
|
+
child.stdin?.end();
|
|
34971
|
+
}
|
|
34782
34972
|
})();
|
|
34783
34973
|
});
|
|
34784
34974
|
}
|
|
@@ -34964,6 +35154,7 @@ var claudeCodeAdapter = {
|
|
|
34964
35154
|
notFoundMessage: CLAUDE_NOT_FOUND_MESSAGE,
|
|
34965
35155
|
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
35156
|
async prepareRun(input) {
|
|
35157
|
+
const liveInput = input.liveInput && input.task.followUps !== void 0;
|
|
34967
35158
|
const baseArgs = await buildArgs(
|
|
34968
35159
|
input.task,
|
|
34969
35160
|
input.runner,
|
|
@@ -34972,7 +35163,8 @@ var claudeCodeAdapter = {
|
|
|
34972
35163
|
input.preparedWorkspace,
|
|
34973
35164
|
input.taskRoot,
|
|
34974
35165
|
input.cwd,
|
|
34975
|
-
input.gitDetected
|
|
35166
|
+
input.gitDetected,
|
|
35167
|
+
liveInput
|
|
34976
35168
|
);
|
|
34977
35169
|
const sessionId = input.task.spec.sessionKey ?? randomUUID11();
|
|
34978
35170
|
const observeRuntime = createRuntimeReporter(input, sessionId);
|
|
@@ -34983,7 +35175,7 @@ var claudeCodeAdapter = {
|
|
|
34983
35175
|
],
|
|
34984
35176
|
prompt: buildRunnerPrompt(input.task),
|
|
34985
35177
|
recoveryPrompt: buildRunnerPrompt(input.task, true),
|
|
34986
|
-
createParser: (onStream) => createStreamParser(onStream, { onSessionModel: observeRuntime }),
|
|
35178
|
+
createParser: (onStream) => liveInput ? createClaudeLiveParser(onStream, observeRuntime) : createStreamParser(onStream, { onSessionModel: observeRuntime }),
|
|
34987
35179
|
// Self-heal both directions: a crashed first run leaves a transcript
|
|
34988
35180
|
// (new → conflict), a lost workspace breaks resume (resume → not
|
|
34989
35181
|
// found). One flip covers both.
|
|
@@ -35024,7 +35216,7 @@ function delay2(ms) {
|
|
|
35024
35216
|
timer.unref?.();
|
|
35025
35217
|
});
|
|
35026
35218
|
}
|
|
35027
|
-
async function buildArgs(task, runner, artifacts, mcp, preparedWorkspace, taskRoot, workspacePath = taskRoot ?? "", gitDetected = false) {
|
|
35219
|
+
async function buildArgs(task, runner, artifacts, mcp, preparedWorkspace, taskRoot, workspacePath = taskRoot ?? "", gitDetected = false, liveInput = false) {
|
|
35028
35220
|
const args = [
|
|
35029
35221
|
// Hermetic, non-interactive run — no local slash commands or global state.
|
|
35030
35222
|
"--disable-slash-commands",
|
|
@@ -35038,6 +35230,7 @@ async function buildArgs(task, runner, artifacts, mcp, preparedWorkspace, taskRo
|
|
|
35038
35230
|
// guardrails via runner permission hooks are the follow-up (PRD GR-5).
|
|
35039
35231
|
"--dangerously-skip-permissions"
|
|
35040
35232
|
];
|
|
35233
|
+
if (liveInput) args.push("--input-format", "stream-json", "--replay-user-messages");
|
|
35041
35234
|
if (runner.effort) args.push("--effort", runner.effort);
|
|
35042
35235
|
if (runner.model) args.push("--model", runner.model);
|
|
35043
35236
|
const systemPrompt = workspaceSystemPrompt(
|
|
@@ -35069,6 +35262,47 @@ async function buildArgs(task, runner, artifacts, mcp, preparedWorkspace, taskRo
|
|
|
35069
35262
|
);
|
|
35070
35263
|
return args;
|
|
35071
35264
|
}
|
|
35265
|
+
function createClaudeLiveParser(onStream, onSessionModel) {
|
|
35266
|
+
let write = null;
|
|
35267
|
+
const acknowledgements = /* @__PURE__ */ new Map();
|
|
35268
|
+
const input = (uuid3, text) => `${JSON.stringify({
|
|
35269
|
+
type: "user",
|
|
35270
|
+
uuid: uuid3,
|
|
35271
|
+
message: { role: "user", content: text },
|
|
35272
|
+
parent_tool_use_id: null
|
|
35273
|
+
})}
|
|
35274
|
+
`;
|
|
35275
|
+
const parser = createStreamParser(onStream, {
|
|
35276
|
+
onSessionModel,
|
|
35277
|
+
onUserReplay: (uuid3) => {
|
|
35278
|
+
const acknowledge = acknowledgements.get(uuid3);
|
|
35279
|
+
if (!acknowledge) return;
|
|
35280
|
+
acknowledgements.delete(uuid3);
|
|
35281
|
+
acknowledge(true);
|
|
35282
|
+
}
|
|
35283
|
+
});
|
|
35284
|
+
return {
|
|
35285
|
+
...parser,
|
|
35286
|
+
async start(writer, prompt) {
|
|
35287
|
+
write = writer;
|
|
35288
|
+
await writer(input(randomUUID11(), prompt));
|
|
35289
|
+
},
|
|
35290
|
+
async steer(followUp) {
|
|
35291
|
+
if (!write) return false;
|
|
35292
|
+
return await new Promise((resolve14) => {
|
|
35293
|
+
acknowledgements.set(followUp.inputId, resolve14);
|
|
35294
|
+
void write(input(followUp.inputId, followUp.text)).catch(() => {
|
|
35295
|
+
if (acknowledgements.delete(followUp.inputId)) resolve14(false);
|
|
35296
|
+
});
|
|
35297
|
+
});
|
|
35298
|
+
},
|
|
35299
|
+
stop() {
|
|
35300
|
+
write = null;
|
|
35301
|
+
for (const acknowledge of acknowledgements.values()) acknowledge(false);
|
|
35302
|
+
acknowledgements.clear();
|
|
35303
|
+
}
|
|
35304
|
+
};
|
|
35305
|
+
}
|
|
35072
35306
|
function createStreamParser(onStream, hooks = {}) {
|
|
35073
35307
|
let buffer = "";
|
|
35074
35308
|
let responseText = "";
|
|
@@ -35096,6 +35330,10 @@ function createStreamParser(onStream, hooks = {}) {
|
|
|
35096
35330
|
const type = json2["type"];
|
|
35097
35331
|
if (type === "system" && json2["subtype"] === "init") {
|
|
35098
35332
|
if (typeof json2["model"] === "string" && json2["model"]) hooks.onSessionModel?.(json2["model"]);
|
|
35333
|
+
} else if (type === "user") {
|
|
35334
|
+
if (json2["isReplay"] === true && typeof json2["uuid"] === "string") {
|
|
35335
|
+
hooks.onUserReplay?.(json2["uuid"]);
|
|
35336
|
+
}
|
|
35099
35337
|
} else if (type === "assistant") {
|
|
35100
35338
|
const message = json2["message"];
|
|
35101
35339
|
applyUsage(message?.["usage"]);
|
|
@@ -35129,12 +35367,13 @@ function createStreamParser(onStream, hooks = {}) {
|
|
|
35129
35367
|
const errors = Array.isArray(json2["errors"]) ? json2["errors"].filter((e) => typeof e === "string") : [];
|
|
35130
35368
|
const base = typeof json2["result"] === "string" && json2["result"].trim() ? json2["result"] : "";
|
|
35131
35369
|
const message = [base, ...errors].filter(Boolean).join("; ") || "Unknown error";
|
|
35132
|
-
|
|
35370
|
+
const result2 = {
|
|
35133
35371
|
outcome: "failed",
|
|
35134
35372
|
summary: improveErrorMessage(message),
|
|
35135
35373
|
usage: usage(),
|
|
35136
35374
|
sessionConflict: isSessionConflict(errors.join(" "))
|
|
35137
35375
|
};
|
|
35376
|
+
return hooks.shouldCompleteResult?.() === false ? void 0 : result2;
|
|
35138
35377
|
}
|
|
35139
35378
|
const finalText = typeof json2["result"] === "string" && json2["result"].trim() ? json2["result"] : responseText;
|
|
35140
35379
|
if (!finalText.trim() && outputTokens === 0) {
|
|
@@ -35145,7 +35384,8 @@ function createStreamParser(onStream, hooks = {}) {
|
|
|
35145
35384
|
emptyResult: true
|
|
35146
35385
|
};
|
|
35147
35386
|
}
|
|
35148
|
-
|
|
35387
|
+
const result = { outcome: "done", summary: finalText, usage: usage() };
|
|
35388
|
+
return hooks.shouldCompleteResult?.() === false ? void 0 : result;
|
|
35149
35389
|
} else if (type === "error") {
|
|
35150
35390
|
const message = typeof json2["error"] === "string" ? json2["error"] : JSON.stringify(json2);
|
|
35151
35391
|
return {
|
|
@@ -35213,6 +35453,7 @@ function improveErrorMessage(error52) {
|
|
|
35213
35453
|
|
|
35214
35454
|
// src/runners/codex.ts
|
|
35215
35455
|
import { mkdir as mkdir11, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
|
|
35456
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
35216
35457
|
import { homedir as homedir6 } from "node:os";
|
|
35217
35458
|
import { join as join16 } from "node:path";
|
|
35218
35459
|
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 +35492,23 @@ function createCodexAdapter(threadIndexRoot) {
|
|
|
35251
35492
|
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
35493
|
async prepareRun(input) {
|
|
35253
35494
|
const { task, runner, mcp } = input;
|
|
35254
|
-
const
|
|
35495
|
+
const liveInput = input.liveInput && task.followUps !== void 0;
|
|
35496
|
+
const flags = liveInput ? [
|
|
35497
|
+
"app-server",
|
|
35498
|
+
"--stdio",
|
|
35499
|
+
// Replace machine-configured MCP servers while retaining auth.json.
|
|
35500
|
+
"-c",
|
|
35501
|
+
"mcp_servers={}"
|
|
35502
|
+
] : [
|
|
35255
35503
|
"--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
35504
|
"--ignore-user-config",
|
|
35259
|
-
// A teammate workspace is not necessarily a git repository.
|
|
35260
35505
|
"--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
35506
|
"--dangerously-bypass-approvals-and-sandbox"
|
|
35265
35507
|
];
|
|
35266
|
-
if (runner.model) flags.push("--model", runner.model);
|
|
35267
|
-
if (runner.effort)
|
|
35508
|
+
if (!liveInput && runner.model) flags.push("--model", runner.model);
|
|
35509
|
+
if (!liveInput && runner.effort) {
|
|
35510
|
+
flags.push("-c", `model_reasoning_effort=${tomlString(runner.effort)}`);
|
|
35511
|
+
}
|
|
35268
35512
|
const env = { ZIXT_RUN_TOKEN: mcp.runToken };
|
|
35269
35513
|
flags.push("-c", `mcp_servers.zixt.url=${tomlString(mcp.mcpUrl)}`);
|
|
35270
35514
|
flags.push(
|
|
@@ -35302,13 +35546,13 @@ function createCodexAdapter(threadIndexRoot) {
|
|
|
35302
35546
|
task.siblingTasks(),
|
|
35303
35547
|
input.gitDetected
|
|
35304
35548
|
);
|
|
35305
|
-
const withPlatformInstructions = (
|
|
35549
|
+
const withPlatformInstructions = (value) => platformInstructions ? `<zixt_platform_instructions>
|
|
35306
35550
|
${platformInstructions}
|
|
35307
35551
|
</zixt_platform_instructions>
|
|
35308
35552
|
|
|
35309
|
-
${
|
|
35310
|
-
const prompt = withPlatformInstructions(buildRunnerPrompt(task));
|
|
35311
|
-
const recoveryPrompt = withPlatformInstructions(buildRunnerPrompt(task, true));
|
|
35553
|
+
${value}` : value;
|
|
35554
|
+
const prompt = liveInput ? buildRunnerPrompt(task) : withPlatformInstructions(buildRunnerPrompt(task));
|
|
35555
|
+
const recoveryPrompt = liveInput ? buildRunnerPrompt(task, true) : withPlatformInstructions(buildRunnerPrompt(task, true));
|
|
35312
35556
|
const sessionKey = task.spec.sessionKey;
|
|
35313
35557
|
const indexPath = sessionKey ? threadIndexPath(threadIndexRoot, task.agentId, sessionKey) : null;
|
|
35314
35558
|
const recordedThreadId = indexPath ? await readThreadId(indexPath) : null;
|
|
@@ -35322,20 +35566,28 @@ ${prompt2}` : prompt2;
|
|
|
35322
35566
|
};
|
|
35323
35567
|
return {
|
|
35324
35568
|
argsFor: (mode) => {
|
|
35325
|
-
if (mode === "resume")
|
|
35326
|
-
|
|
35327
|
-
|
|
35328
|
-
return ["exec", ...flags, "-"];
|
|
35569
|
+
if (mode === "resume" && !recordedThreadId) return null;
|
|
35570
|
+
if (liveInput) return flags;
|
|
35571
|
+
return mode === "resume" ? ["exec", "resume", recordedThreadId, ...flags, "-"] : ["exec", ...flags, "-"];
|
|
35329
35572
|
},
|
|
35330
35573
|
prompt,
|
|
35331
35574
|
recoveryPrompt,
|
|
35332
35575
|
env,
|
|
35333
|
-
createParser: (onStream) =>
|
|
35334
|
-
onThreadStarted
|
|
35576
|
+
createParser: (onStream, mode = "new") => {
|
|
35577
|
+
const onThreadStarted = (threadId) => {
|
|
35335
35578
|
rememberThread(threadId);
|
|
35336
35579
|
observeRuntime(threadId);
|
|
35337
|
-
}
|
|
35338
|
-
|
|
35580
|
+
};
|
|
35581
|
+
return liveInput ? createCodexAppServerParser(onStream, {
|
|
35582
|
+
mode,
|
|
35583
|
+
recordedThreadId,
|
|
35584
|
+
cwd: input.cwd,
|
|
35585
|
+
...runner.model ? { model: runner.model } : {},
|
|
35586
|
+
...runner.effort ? { effort: runner.effort } : {},
|
|
35587
|
+
developerInstructions: platformInstructions ?? "",
|
|
35588
|
+
onThreadStarted
|
|
35589
|
+
}) : createCodexStreamParser(onStream, { onThreadStarted });
|
|
35590
|
+
},
|
|
35339
35591
|
// Only one flip is meaningful: a resume whose rollout vanished starts
|
|
35340
35592
|
// over. Codex assigns new-session ids itself, so a new session can
|
|
35341
35593
|
// never collide the way a pre-specified id can.
|
|
@@ -35372,6 +35624,258 @@ function createCodexRunner(opts = {}) {
|
|
|
35372
35624
|
const adapter = createCodexAdapter(opts.threadIndexRoot ?? defaultCodexThreadIndexRoot());
|
|
35373
35625
|
return createCliRunner(adapter, opts);
|
|
35374
35626
|
}
|
|
35627
|
+
function createCodexAppServerParser(onStream, options) {
|
|
35628
|
+
let write = null;
|
|
35629
|
+
let prompt = "";
|
|
35630
|
+
let buffer = "";
|
|
35631
|
+
let threadId = null;
|
|
35632
|
+
let activeTurnId = null;
|
|
35633
|
+
let responseText = "";
|
|
35634
|
+
let inputTokens = 0;
|
|
35635
|
+
let outputTokens = 0;
|
|
35636
|
+
let terminal;
|
|
35637
|
+
let stopped = false;
|
|
35638
|
+
const steerWaiters = /* @__PURE__ */ new Map();
|
|
35639
|
+
const turnReadyWaiters = /* @__PURE__ */ new Set();
|
|
35640
|
+
const usage = () => ({ inputTokens, outputTokens });
|
|
35641
|
+
const settleTurnReadiness = (ready) => {
|
|
35642
|
+
for (const resolve14 of turnReadyWaiters) resolve14(ready);
|
|
35643
|
+
turnReadyWaiters.clear();
|
|
35644
|
+
};
|
|
35645
|
+
const send = async (message) => {
|
|
35646
|
+
if (!write || stopped) throw new Error("Codex app-server input is unavailable");
|
|
35647
|
+
await write(`${JSON.stringify({ jsonrpc: "2.0", ...message })}
|
|
35648
|
+
`);
|
|
35649
|
+
};
|
|
35650
|
+
const threadParams = () => ({
|
|
35651
|
+
cwd: options.cwd,
|
|
35652
|
+
approvalPolicy: "never",
|
|
35653
|
+
sandbox: "danger-full-access",
|
|
35654
|
+
...options.model ? { model: options.model } : {},
|
|
35655
|
+
...options.developerInstructions ? { developerInstructions: options.developerInstructions } : {}
|
|
35656
|
+
});
|
|
35657
|
+
const startTurn = async () => {
|
|
35658
|
+
if (!threadId) throw new Error("Codex app-server did not provide a thread id");
|
|
35659
|
+
await send({
|
|
35660
|
+
id: "turn:start",
|
|
35661
|
+
method: "turn/start",
|
|
35662
|
+
params: {
|
|
35663
|
+
threadId,
|
|
35664
|
+
input: [{ type: "text", text: prompt }],
|
|
35665
|
+
clientUserMessageId: randomUUID12(),
|
|
35666
|
+
...options.model ? { model: options.model } : {},
|
|
35667
|
+
...options.effort ? { effort: options.effort } : {}
|
|
35668
|
+
}
|
|
35669
|
+
});
|
|
35670
|
+
};
|
|
35671
|
+
const itemCompleted = (item) => {
|
|
35672
|
+
const type = item["type"];
|
|
35673
|
+
if (type === "agentMessage" && typeof item["text"] === "string") {
|
|
35674
|
+
responseText = item["text"];
|
|
35675
|
+
onStream("thought", truncateThought(item["text"]));
|
|
35676
|
+
return;
|
|
35677
|
+
}
|
|
35678
|
+
if (type === "reasoning") {
|
|
35679
|
+
const summary = Array.isArray(item["summary"]) ? item["summary"].filter((part) => typeof part === "string") : [];
|
|
35680
|
+
if (summary.length) onStream("thought", truncateThought(summary.join("\n")));
|
|
35681
|
+
return;
|
|
35682
|
+
}
|
|
35683
|
+
if (type === "commandExecution") {
|
|
35684
|
+
const parameter = String(item["command"] ?? "").slice(0, 2e3);
|
|
35685
|
+
onStream("action", `shell${parameter ? `: ${parameter.slice(0, 100)}` : ""}`, {
|
|
35686
|
+
tool: "shell",
|
|
35687
|
+
...parameter ? { parameter } : {},
|
|
35688
|
+
ephemeral: true
|
|
35689
|
+
});
|
|
35690
|
+
return;
|
|
35691
|
+
}
|
|
35692
|
+
if (type === "mcpToolCall") {
|
|
35693
|
+
const tool = `${String(item["server"] ?? "mcp")}.${String(item["tool"] ?? "tool")}`.slice(
|
|
35694
|
+
0,
|
|
35695
|
+
200
|
|
35696
|
+
);
|
|
35697
|
+
const args = item["arguments"];
|
|
35698
|
+
const parameter = (typeof args === "string" ? args : args ? JSON.stringify(args) : "").slice(
|
|
35699
|
+
0,
|
|
35700
|
+
2e3
|
|
35701
|
+
);
|
|
35702
|
+
onStream("action", `${tool}${parameter ? `: ${parameter.slice(0, 100)}` : ""}`, {
|
|
35703
|
+
tool,
|
|
35704
|
+
...parameter ? { parameter } : {},
|
|
35705
|
+
ephemeral: true
|
|
35706
|
+
});
|
|
35707
|
+
return;
|
|
35708
|
+
}
|
|
35709
|
+
if (type === "fileChange") {
|
|
35710
|
+
const changes = Array.isArray(item["changes"]) ? item["changes"].map((change) => String(change["path"] ?? "")).filter(Boolean) : [];
|
|
35711
|
+
const parameter = changes.join(", ").slice(0, 2e3);
|
|
35712
|
+
onStream("action", `file_change${parameter ? `: ${parameter.slice(0, 100)}` : ""}`, {
|
|
35713
|
+
tool: "file_change",
|
|
35714
|
+
...parameter ? { parameter } : {},
|
|
35715
|
+
ephemeral: true,
|
|
35716
|
+
...changes.length ? { localPaths: changes } : {}
|
|
35717
|
+
});
|
|
35718
|
+
}
|
|
35719
|
+
};
|
|
35720
|
+
const processLine = (line) => {
|
|
35721
|
+
if (!line.trim()) return void 0;
|
|
35722
|
+
let message;
|
|
35723
|
+
try {
|
|
35724
|
+
message = JSON.parse(line);
|
|
35725
|
+
} catch {
|
|
35726
|
+
return void 0;
|
|
35727
|
+
}
|
|
35728
|
+
const id = typeof message["id"] === "string" ? message["id"] : null;
|
|
35729
|
+
const error52 = message["error"];
|
|
35730
|
+
if (id && error52) {
|
|
35731
|
+
const detail = typeof error52["message"] === "string" ? error52["message"] : JSON.stringify(error52);
|
|
35732
|
+
if (id.startsWith("steer:")) {
|
|
35733
|
+
const inputId = id.slice("steer:".length);
|
|
35734
|
+
steerWaiters.get(inputId)?.(false);
|
|
35735
|
+
steerWaiters.delete(inputId);
|
|
35736
|
+
return void 0;
|
|
35737
|
+
}
|
|
35738
|
+
terminal = {
|
|
35739
|
+
outcome: "failed",
|
|
35740
|
+
summary: improveCodexErrorMessage(detail),
|
|
35741
|
+
usage: usage(),
|
|
35742
|
+
sessionConflict: isCodexSessionConflict(detail)
|
|
35743
|
+
};
|
|
35744
|
+
return terminal;
|
|
35745
|
+
}
|
|
35746
|
+
const result = message["result"];
|
|
35747
|
+
if (id === "initialize" && result) {
|
|
35748
|
+
void send({ method: "initialized" }).then(
|
|
35749
|
+
() => send({
|
|
35750
|
+
id: "thread:open",
|
|
35751
|
+
method: options.mode === "resume" ? "thread/resume" : "thread/start",
|
|
35752
|
+
params: options.mode === "resume" ? { ...threadParams(), threadId: options.recordedThreadId } : threadParams()
|
|
35753
|
+
})
|
|
35754
|
+
).catch(() => {
|
|
35755
|
+
});
|
|
35756
|
+
} else if (id === "thread:open" && result) {
|
|
35757
|
+
const thread = result["thread"];
|
|
35758
|
+
if (typeof thread?.["id"] === "string") {
|
|
35759
|
+
threadId = thread["id"];
|
|
35760
|
+
options.onThreadStarted(threadId);
|
|
35761
|
+
void startTurn().catch(() => {
|
|
35762
|
+
});
|
|
35763
|
+
}
|
|
35764
|
+
} else if (id === "turn:start" && result) {
|
|
35765
|
+
const turn = result["turn"];
|
|
35766
|
+
if (typeof turn?.["id"] === "string") {
|
|
35767
|
+
activeTurnId = turn["id"];
|
|
35768
|
+
}
|
|
35769
|
+
} else if (id?.startsWith("steer:") && result) {
|
|
35770
|
+
const inputId = id.slice("steer:".length);
|
|
35771
|
+
const accepted = typeof result["turnId"] === "string";
|
|
35772
|
+
steerWaiters.get(inputId)?.(accepted);
|
|
35773
|
+
steerWaiters.delete(inputId);
|
|
35774
|
+
}
|
|
35775
|
+
const method = message["method"];
|
|
35776
|
+
const params = message["params"];
|
|
35777
|
+
if (method === "thread/started") {
|
|
35778
|
+
const thread = params?.["thread"];
|
|
35779
|
+
if (!threadId && typeof thread?.["id"] === "string") {
|
|
35780
|
+
threadId = thread["id"];
|
|
35781
|
+
options.onThreadStarted(threadId);
|
|
35782
|
+
}
|
|
35783
|
+
} else if (method === "turn/started") {
|
|
35784
|
+
const turn = params?.["turn"];
|
|
35785
|
+
if (typeof turn?.["id"] === "string") {
|
|
35786
|
+
activeTurnId = turn["id"];
|
|
35787
|
+
settleTurnReadiness(true);
|
|
35788
|
+
}
|
|
35789
|
+
} else if (method === "item/completed") {
|
|
35790
|
+
const item = params?.["item"];
|
|
35791
|
+
if (item && typeof item === "object") itemCompleted(item);
|
|
35792
|
+
} else if (method === "thread/tokenUsage/updated") {
|
|
35793
|
+
const tokenUsage = params?.["tokenUsage"];
|
|
35794
|
+
const total = tokenUsage?.["total"];
|
|
35795
|
+
if (typeof total?.["inputTokens"] === "number") inputTokens = total["inputTokens"];
|
|
35796
|
+
if (typeof total?.["outputTokens"] === "number") outputTokens = total["outputTokens"];
|
|
35797
|
+
} else if (method === "turn/completed") {
|
|
35798
|
+
const turn = params?.["turn"];
|
|
35799
|
+
activeTurnId = null;
|
|
35800
|
+
settleTurnReadiness(false);
|
|
35801
|
+
const status = turn?.["status"];
|
|
35802
|
+
const turnError = turn?.["error"];
|
|
35803
|
+
const detail = typeof turnError?.["message"] === "string" ? turnError["message"] : "Codex turn failed";
|
|
35804
|
+
terminal = status === "completed" ? { outcome: "done", summary: responseText, usage: usage() } : { outcome: "failed", summary: improveCodexErrorMessage(detail), usage: usage() };
|
|
35805
|
+
return terminal;
|
|
35806
|
+
}
|
|
35807
|
+
return void 0;
|
|
35808
|
+
};
|
|
35809
|
+
return {
|
|
35810
|
+
usage,
|
|
35811
|
+
async start(writer, initialPrompt) {
|
|
35812
|
+
write = writer;
|
|
35813
|
+
prompt = initialPrompt;
|
|
35814
|
+
await send({
|
|
35815
|
+
id: "initialize",
|
|
35816
|
+
method: "initialize",
|
|
35817
|
+
params: { clientInfo: { name: "zixt", version: "1" } }
|
|
35818
|
+
});
|
|
35819
|
+
},
|
|
35820
|
+
async steer(input) {
|
|
35821
|
+
if (stopped) return false;
|
|
35822
|
+
if (!activeTurnId) {
|
|
35823
|
+
const ready = await new Promise((resolve14) => turnReadyWaiters.add(resolve14));
|
|
35824
|
+
if (!ready || stopped) return false;
|
|
35825
|
+
}
|
|
35826
|
+
if (!threadId || !activeTurnId) return false;
|
|
35827
|
+
return await new Promise((resolve14) => {
|
|
35828
|
+
steerWaiters.set(input.inputId, resolve14);
|
|
35829
|
+
void send({
|
|
35830
|
+
id: `steer:${input.inputId}`,
|
|
35831
|
+
method: "turn/steer",
|
|
35832
|
+
params: {
|
|
35833
|
+
threadId,
|
|
35834
|
+
expectedTurnId: activeTurnId,
|
|
35835
|
+
input: [{ type: "text", text: input.text }],
|
|
35836
|
+
clientUserMessageId: input.inputId
|
|
35837
|
+
}
|
|
35838
|
+
}).catch(() => {
|
|
35839
|
+
if (steerWaiters.delete(input.inputId)) resolve14(false);
|
|
35840
|
+
});
|
|
35841
|
+
});
|
|
35842
|
+
},
|
|
35843
|
+
stop() {
|
|
35844
|
+
stopped = true;
|
|
35845
|
+
write = null;
|
|
35846
|
+
settleTurnReadiness(false);
|
|
35847
|
+
for (const resolve14 of steerWaiters.values()) resolve14(false);
|
|
35848
|
+
steerWaiters.clear();
|
|
35849
|
+
},
|
|
35850
|
+
push(chunk) {
|
|
35851
|
+
buffer += chunk;
|
|
35852
|
+
const lines = buffer.split(/\r?\n/);
|
|
35853
|
+
buffer = lines.pop() ?? "";
|
|
35854
|
+
for (const line of lines) {
|
|
35855
|
+
const parsed = processLine(line);
|
|
35856
|
+
if (parsed) return parsed;
|
|
35857
|
+
}
|
|
35858
|
+
return void 0;
|
|
35859
|
+
},
|
|
35860
|
+
finish(stderr, exitCode = 0) {
|
|
35861
|
+
if (buffer.trim()) {
|
|
35862
|
+
const parsed = processLine(buffer);
|
|
35863
|
+
buffer = "";
|
|
35864
|
+
if (parsed) return parsed;
|
|
35865
|
+
}
|
|
35866
|
+
if (terminal) return terminal;
|
|
35867
|
+
const detail = stderr.trim() || "Codex app-server exited before the turn completed";
|
|
35868
|
+
return {
|
|
35869
|
+
outcome: "failed",
|
|
35870
|
+
summary: improveCodexErrorMessage(
|
|
35871
|
+
exitCode ? `codex exited with code ${exitCode}: ${detail}` : detail
|
|
35872
|
+
),
|
|
35873
|
+
usage: usage(),
|
|
35874
|
+
sessionConflict: isCodexSessionConflict(detail)
|
|
35875
|
+
};
|
|
35876
|
+
}
|
|
35877
|
+
};
|
|
35878
|
+
}
|
|
35375
35879
|
function createCodexStreamParser(onStream, hooks = {}) {
|
|
35376
35880
|
let buffer = "";
|
|
35377
35881
|
let responseText = "";
|