acpx 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/{cli-BGYGVo3b.js → cli-8dP_TqBp.js} +4 -4
- package/dist/{cli-BGYGVo3b.js.map → cli-8dP_TqBp.js.map} +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +930 -252
- package/dist/cli.js.map +1 -1
- package/dist/{client-FzXPdgP7.d.ts → client-C4iJBO0j.d.ts} +30 -3
- package/dist/client-C4iJBO0j.d.ts.map +1 -0
- package/dist/{flags-D706STfk.js → flags--2oX_ubW.js} +96 -39
- package/dist/flags--2oX_ubW.js.map +1 -0
- package/dist/{flows-hcjHmU7P.js → flows-e4umXVbY.js} +401 -336
- package/dist/flows-e4umXVbY.js.map +1 -0
- package/dist/flows.d.ts +21 -1
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{live-checkpoint-B9ctAuqV.js → live-checkpoint-CuFft_Nd.js} +1614 -931
- package/dist/live-checkpoint-CuFft_Nd.js.map +1 -0
- package/dist/{output-BL9XRWzS.js → output-Di77Yugq.js} +1153 -719
- package/dist/output-Di77Yugq.js.map +1 -0
- package/dist/runtime.d.ts +30 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +579 -425
- package/dist/runtime.js.map +1 -1
- package/dist/{session-options-BJyG6zEH.d.ts → session-options-Bh1bIqQ2.d.ts} +14 -1
- package/dist/{session-options-BJyG6zEH.d.ts.map → session-options-Bh1bIqQ2.d.ts.map} +1 -1
- package/package.json +21 -12
- package/skills/acpx/SKILL.md +22 -5
- package/dist/client-FzXPdgP7.d.ts.map +0 -1
- package/dist/flags-D706STfk.js.map +0 -1
- package/dist/flows-hcjHmU7P.js.map +0 -1
- package/dist/live-checkpoint-B9ctAuqV.js.map +0 -1
- package/dist/output-BL9XRWzS.js.map +0 -1
|
@@ -3,12 +3,12 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs$1 from "node:fs/promises";
|
|
5
5
|
import os from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
6
7
|
import { execFile, spawn } from "node:child_process";
|
|
7
8
|
import { Readable, Writable } from "node:stream";
|
|
8
9
|
import { ClientSideConnection, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
|
|
9
10
|
import readline from "node:readline/promises";
|
|
10
11
|
import { promisify } from "node:util";
|
|
11
|
-
import { randomUUID } from "node:crypto";
|
|
12
12
|
//#region src/errors.ts
|
|
13
13
|
var AcpxOperationalError = class extends Error {
|
|
14
14
|
outputCode;
|
|
@@ -79,6 +79,15 @@ var AgentDisconnectedError = class extends AcpxOperationalError {
|
|
|
79
79
|
this.signal = signal;
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
|
+
var UnsupportedPromptContentError = class extends AcpxOperationalError {
|
|
83
|
+
constructor(message) {
|
|
84
|
+
super(message, {
|
|
85
|
+
outputCode: "USAGE",
|
|
86
|
+
detailCode: "UNSUPPORTED_PROMPT_CONTENT",
|
|
87
|
+
origin: "acp"
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
82
91
|
var SessionResumeRequiredError = class extends AcpxOperationalError {
|
|
83
92
|
constructor(message, options) {
|
|
84
93
|
super(message, {
|
|
@@ -235,16 +244,15 @@ function extractAcpErrorInternal(value, depth) {
|
|
|
235
244
|
if (direct) return direct;
|
|
236
245
|
const record = asRecord$7(value);
|
|
237
246
|
if (!record) return;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const nested = extractAcpErrorInternal(record.cause, depth + 1);
|
|
247
|
+
return extractNestedAcpError(record, depth);
|
|
248
|
+
}
|
|
249
|
+
function extractNestedAcpError(record, depth) {
|
|
250
|
+
for (const key of [
|
|
251
|
+
"error",
|
|
252
|
+
"acp",
|
|
253
|
+
"cause"
|
|
254
|
+
]) if (key in record) {
|
|
255
|
+
const nested = extractAcpErrorInternal(record[key], depth + 1);
|
|
248
256
|
if (nested) return nested;
|
|
249
257
|
}
|
|
250
258
|
}
|
|
@@ -296,7 +304,15 @@ function asRecord$6(value) {
|
|
|
296
304
|
function isAuthRequiredMessage(value) {
|
|
297
305
|
if (!value) return false;
|
|
298
306
|
const normalized = value.toLowerCase();
|
|
299
|
-
return
|
|
307
|
+
return [
|
|
308
|
+
"auth required",
|
|
309
|
+
"authentication required",
|
|
310
|
+
"authorization required",
|
|
311
|
+
"credential required",
|
|
312
|
+
"credentials required",
|
|
313
|
+
"token required",
|
|
314
|
+
"login required"
|
|
315
|
+
].some((needle) => normalized.includes(needle));
|
|
300
316
|
}
|
|
301
317
|
function isAcpAuthRequiredPayload(acp) {
|
|
302
318
|
if (!acp) return false;
|
|
@@ -304,12 +320,16 @@ function isAcpAuthRequiredPayload(acp) {
|
|
|
304
320
|
if (isAuthRequiredMessage(acp.message)) return true;
|
|
305
321
|
const data = asRecord$6(acp.data);
|
|
306
322
|
if (!data) return false;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
323
|
+
return hasAuthRequiredData(data);
|
|
324
|
+
}
|
|
325
|
+
function hasAuthRequiredData(data) {
|
|
326
|
+
return data.authRequired === true || hasNonEmptyString(data.methodId) || hasNonEmptyArray(data.methods);
|
|
327
|
+
}
|
|
328
|
+
function hasNonEmptyString(value) {
|
|
329
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
330
|
+
}
|
|
331
|
+
function hasNonEmptyArray(value) {
|
|
332
|
+
return Array.isArray(value) && value.length > 0;
|
|
313
333
|
}
|
|
314
334
|
function isOutputErrorCode(value) {
|
|
315
335
|
return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
|
|
@@ -357,20 +377,25 @@ function mapErrorCode(error) {
|
|
|
357
377
|
}
|
|
358
378
|
function normalizeOutputError(error, options = {}) {
|
|
359
379
|
const meta = readOutputErrorMeta(error);
|
|
360
|
-
|
|
361
|
-
if (meta.outputCode) code = meta.outputCode;
|
|
362
|
-
if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) code = "NO_SESSION";
|
|
380
|
+
const code = resolveOutputErrorCode(error, options, meta);
|
|
363
381
|
const acp = options.acp ?? meta.acp ?? extractAcpError(error);
|
|
364
|
-
const detailCode = meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
|
|
365
382
|
return {
|
|
366
383
|
code,
|
|
367
384
|
message: formatErrorMessage(error),
|
|
368
|
-
detailCode,
|
|
385
|
+
detailCode: resolveDetailCode(error, acp, options, meta),
|
|
369
386
|
origin: meta.origin ?? options.origin,
|
|
370
387
|
retryable: meta.retryable ?? options.retryable,
|
|
371
388
|
acp
|
|
372
389
|
};
|
|
373
390
|
}
|
|
391
|
+
function resolveOutputErrorCode(error, options, meta) {
|
|
392
|
+
const code = meta.outputCode ?? mapErrorCode(error) ?? options.defaultCode ?? "RUNTIME";
|
|
393
|
+
if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) return "NO_SESSION";
|
|
394
|
+
return code;
|
|
395
|
+
}
|
|
396
|
+
function resolveDetailCode(error, acp, options, meta) {
|
|
397
|
+
return meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
|
|
398
|
+
}
|
|
374
399
|
/**
|
|
375
400
|
* Returns true when an error from `client.prompt()` looks transient and
|
|
376
401
|
* can reasonably be retried (e.g. model-API 400/500, network hiccups that
|
|
@@ -380,15 +405,18 @@ function normalizeOutputError(error, options = {}) {
|
|
|
380
405
|
* invalid params, timeout, permission) return false.
|
|
381
406
|
*/
|
|
382
407
|
function isRetryablePromptError(error) {
|
|
383
|
-
if (error
|
|
384
|
-
if (isTimeoutLike(error) || isNoSessionLike(error) || isUsageLike(error)) return false;
|
|
408
|
+
if (isNonRetryablePromptError(error)) return false;
|
|
385
409
|
const acp = extractAcpError(error);
|
|
386
410
|
if (!acp) return false;
|
|
387
|
-
if (acp
|
|
388
|
-
if (isAcpAuthRequiredPayload(acp)) return false;
|
|
389
|
-
if (acp.code === -32601 || acp.code === -32602) return false;
|
|
411
|
+
if (isPermanentPromptAcpError(acp)) return false;
|
|
390
412
|
return acp.code === -32603 || acp.code === -32700;
|
|
391
413
|
}
|
|
414
|
+
function isNonRetryablePromptError(error) {
|
|
415
|
+
return error instanceof PermissionDeniedError || error instanceof PermissionPromptUnavailableError || isTimeoutLike(error) || isNoSessionLike(error) || isUsageLike(error);
|
|
416
|
+
}
|
|
417
|
+
function isPermanentPromptAcpError(acp) {
|
|
418
|
+
return acp.code === -32001 || acp.code === -32002 || acp.code === -32601 || acp.code === -32602 || isAcpAuthRequiredPayload(acp);
|
|
419
|
+
}
|
|
392
420
|
function exitCodeForOutputErrorCode(code) {
|
|
393
421
|
switch (code) {
|
|
394
422
|
case "USAGE": return EXIT_CODES.USAGE;
|
|
@@ -403,13 +431,13 @@ function exitCodeForOutputErrorCode(code) {
|
|
|
403
431
|
//#region src/agent-registry.ts
|
|
404
432
|
const ACP_ADAPTER_PACKAGE_RANGES = {
|
|
405
433
|
pi: "^0.0.26",
|
|
406
|
-
codex: "^0.
|
|
407
|
-
claude: "^0.
|
|
434
|
+
codex: "^0.0.44",
|
|
435
|
+
claude: "^0.36.1"
|
|
408
436
|
};
|
|
409
437
|
const AGENT_REGISTRY = {
|
|
410
438
|
pi: `npx pi-acp@${ACP_ADAPTER_PACKAGE_RANGES.pi}`,
|
|
411
439
|
openclaw: "openclaw acp",
|
|
412
|
-
codex: `npx @
|
|
440
|
+
codex: `npx -y @agentclientprotocol/codex-acp@${ACP_ADAPTER_PACKAGE_RANGES.codex}`,
|
|
413
441
|
claude: `npx -y @agentclientprotocol/claude-agent-acp@${ACP_ADAPTER_PACKAGE_RANGES.claude}`,
|
|
414
442
|
gemini: "gemini --acp",
|
|
415
443
|
cursor: "cursor-agent acp",
|
|
@@ -426,7 +454,7 @@ const AGENT_REGISTRY = {
|
|
|
426
454
|
};
|
|
427
455
|
const BUILT_IN_AGENT_PACKAGES = {
|
|
428
456
|
codex: {
|
|
429
|
-
packageName: "@
|
|
457
|
+
packageName: "@agentclientprotocol/codex-acp",
|
|
430
458
|
packageRange: ACP_ADAPTER_PACKAGE_RANGES.codex,
|
|
431
459
|
preferredBinName: "codex-acp",
|
|
432
460
|
fallbackCommand: AGENT_REGISTRY.codex,
|
|
@@ -498,26 +526,37 @@ function resolveInstalledBuiltInAgentLaunch(agentCommand, options = {}) {
|
|
|
498
526
|
const existsSync = options.existsSync ?? fs.existsSync;
|
|
499
527
|
const resolvePackageRoot = options.resolvePackageRoot ?? defaultResolvePackageRoot;
|
|
500
528
|
try {
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
if (!existsSync(binPath)) return;
|
|
529
|
+
const resolved = resolveInstalledBuiltInAgentPackage(spec, {
|
|
530
|
+
readFileSync,
|
|
531
|
+
existsSync,
|
|
532
|
+
resolvePackageRoot
|
|
533
|
+
});
|
|
534
|
+
if (!resolved) return;
|
|
508
535
|
return {
|
|
509
536
|
source: "installed",
|
|
510
537
|
command: process.execPath,
|
|
511
|
-
args: [binPath],
|
|
538
|
+
args: [resolved.binPath],
|
|
512
539
|
packageName: spec.packageName,
|
|
513
540
|
packageRange: spec.packageRange,
|
|
514
|
-
packageVersion:
|
|
515
|
-
binPath
|
|
541
|
+
packageVersion: resolved.packageVersion,
|
|
542
|
+
binPath: resolved.binPath
|
|
516
543
|
};
|
|
517
544
|
} catch {
|
|
518
545
|
return;
|
|
519
546
|
}
|
|
520
547
|
}
|
|
548
|
+
function resolveInstalledBuiltInAgentPackage(spec, options) {
|
|
549
|
+
const packageRoot = options.resolvePackageRoot(spec.packageName);
|
|
550
|
+
const manifest = JSON.parse(options.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
551
|
+
if (manifest.name !== spec.packageName) return;
|
|
552
|
+
const relativeBinPath = resolvePackageBin(spec, manifest);
|
|
553
|
+
if (!relativeBinPath) return;
|
|
554
|
+
const binPath = path.resolve(packageRoot, relativeBinPath);
|
|
555
|
+
return options.existsSync(binPath) ? {
|
|
556
|
+
packageVersion: manifest.version,
|
|
557
|
+
binPath
|
|
558
|
+
} : void 0;
|
|
559
|
+
}
|
|
521
560
|
function resolvePackageExecBuiltInAgentLaunch(agentCommand, options = {}) {
|
|
522
561
|
const spec = findBuiltInAgentPackage(agentCommand);
|
|
523
562
|
if (!spec) return;
|
|
@@ -633,6 +672,9 @@ function isBase64Data(value) {
|
|
|
633
672
|
function isImageMimeType(value) {
|
|
634
673
|
return /^image\/[A-Za-z0-9.+-]+$/i.test(value);
|
|
635
674
|
}
|
|
675
|
+
function isAudioMimeType(value) {
|
|
676
|
+
return /^audio\/[A-Za-z0-9.+-]+$/i.test(value);
|
|
677
|
+
}
|
|
636
678
|
function isTextBlock(value) {
|
|
637
679
|
const record = asRecord$5(value);
|
|
638
680
|
return record?.type === "text" && typeof record.text === "string";
|
|
@@ -641,6 +683,10 @@ function isImageBlock(value) {
|
|
|
641
683
|
const record = asRecord$5(value);
|
|
642
684
|
return record?.type === "image" && isNonEmptyString(record.mimeType) && isImageMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
|
|
643
685
|
}
|
|
686
|
+
function isAudioBlock(value) {
|
|
687
|
+
const record = asRecord$5(value);
|
|
688
|
+
return record?.type === "audio" && isNonEmptyString(record.mimeType) && isAudioMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
|
|
689
|
+
}
|
|
644
690
|
function isResourceLinkBlock(value) {
|
|
645
691
|
const record = asRecord$5(value);
|
|
646
692
|
return record?.type === "resource_link" && isNonEmptyString(record.uri) && (record.title === void 0 || typeof record.title === "string") && (record.name === void 0 || typeof record.name === "string");
|
|
@@ -654,35 +700,84 @@ function isResourceBlock(value) {
|
|
|
654
700
|
const record = asRecord$5(value);
|
|
655
701
|
return record?.type === "resource" && isResourcePayload(record.resource);
|
|
656
702
|
}
|
|
703
|
+
const CONTENT_BLOCK_VALIDATORS = [
|
|
704
|
+
isTextBlock,
|
|
705
|
+
isImageBlock,
|
|
706
|
+
isAudioBlock,
|
|
707
|
+
isResourceLinkBlock,
|
|
708
|
+
isResourceBlock
|
|
709
|
+
];
|
|
657
710
|
function isContentBlock(value) {
|
|
658
|
-
return
|
|
711
|
+
return CONTENT_BLOCK_VALIDATORS.some((validator) => validator(value));
|
|
712
|
+
}
|
|
713
|
+
const CONTENT_BLOCK_ERROR_VALIDATORS = {
|
|
714
|
+
text: validateTextContentBlock,
|
|
715
|
+
image: validateImageContentBlock,
|
|
716
|
+
audio: validateAudioContentBlock,
|
|
717
|
+
resource_link: validateResourceLinkContentBlock,
|
|
718
|
+
resource: validateResourceContentBlock
|
|
719
|
+
};
|
|
720
|
+
function contentBlockErrorValidator(type) {
|
|
721
|
+
return Object.hasOwn(CONTENT_BLOCK_ERROR_VALIDATORS, type) ? CONTENT_BLOCK_ERROR_VALIDATORS[type] : void 0;
|
|
722
|
+
}
|
|
723
|
+
function validateTextContentBlock(record, index) {
|
|
724
|
+
return typeof record.text === "string" ? void 0 : `prompt[${index}] text block must include a string text field`;
|
|
725
|
+
}
|
|
726
|
+
function validateImageContentBlock(record, index) {
|
|
727
|
+
if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] image block must include a non-empty mimeType`;
|
|
728
|
+
if (!isImageMimeType(record.mimeType)) return `prompt[${index}] image block mimeType must start with image/`;
|
|
729
|
+
if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] image block must include non-empty base64 data`;
|
|
730
|
+
return isBase64Data(record.data) ? void 0 : `prompt[${index}] image block data must be valid base64`;
|
|
731
|
+
}
|
|
732
|
+
function validateAudioContentBlock(record, index) {
|
|
733
|
+
if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] audio block must include a non-empty mimeType`;
|
|
734
|
+
if (!isAudioMimeType(record.mimeType)) return `prompt[${index}] audio block mimeType must start with audio/`;
|
|
735
|
+
if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] audio block must include non-empty base64 data`;
|
|
736
|
+
return isBase64Data(record.data) ? void 0 : `prompt[${index}] audio block data must be valid base64`;
|
|
737
|
+
}
|
|
738
|
+
function validateResourceLinkContentBlock(record, index) {
|
|
739
|
+
if (!isNonEmptyString(record.uri)) return `prompt[${index}] resource_link block must include a non-empty uri`;
|
|
740
|
+
if (record.title !== void 0 && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string when present`;
|
|
741
|
+
if (record.name !== void 0 && typeof record.name !== "string") return `prompt[${index}] resource_link block name must be a string when present`;
|
|
742
|
+
}
|
|
743
|
+
function validateResourceContentBlock(record, index) {
|
|
744
|
+
if (!asRecord$5(record.resource)) return `prompt[${index}] resource block must include a resource object`;
|
|
745
|
+
return isResourcePayload(record.resource) ? void 0 : `prompt[${index}] resource block resource must include a non-empty uri and optional text`;
|
|
659
746
|
}
|
|
660
747
|
function getContentBlockValidationError(value, index) {
|
|
661
748
|
const record = asRecord$5(value);
|
|
662
749
|
if (!record || typeof record.type !== "string") return `prompt[${index}] must be an ACP content block object`;
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
case "image":
|
|
666
|
-
if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] image block must include a non-empty mimeType`;
|
|
667
|
-
if (!isImageMimeType(record.mimeType)) return `prompt[${index}] image block mimeType must start with image/`;
|
|
668
|
-
if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] image block must include non-empty base64 data`;
|
|
669
|
-
if (!isBase64Data(record.data)) return `prompt[${index}] image block data must be valid base64`;
|
|
670
|
-
return;
|
|
671
|
-
case "resource_link":
|
|
672
|
-
if (!isNonEmptyString(record.uri)) return `prompt[${index}] resource_link block must include a non-empty uri`;
|
|
673
|
-
if (record.title !== void 0 && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string when present`;
|
|
674
|
-
if (record.name !== void 0 && typeof record.name !== "string") return `prompt[${index}] resource_link block name must be a string when present`;
|
|
675
|
-
return;
|
|
676
|
-
case "resource":
|
|
677
|
-
if (!asRecord$5(record.resource)) return `prompt[${index}] resource block must include a resource object`;
|
|
678
|
-
if (!isResourcePayload(record.resource)) return `prompt[${index}] resource block resource must include a non-empty uri and optional text`;
|
|
679
|
-
return;
|
|
680
|
-
default: return `prompt[${index}] has unsupported content block type ${JSON.stringify(record.type)}`;
|
|
681
|
-
}
|
|
750
|
+
const validator = contentBlockErrorValidator(record.type);
|
|
751
|
+
return validator ? validator(record, index) : `prompt[${index}] has unsupported content block type ${JSON.stringify(record.type)}`;
|
|
682
752
|
}
|
|
683
753
|
function isPromptInput(value) {
|
|
684
754
|
return Array.isArray(value) && value.every((entry) => isContentBlock(entry));
|
|
685
755
|
}
|
|
756
|
+
function promptCapabilityRequirement(block) {
|
|
757
|
+
switch (block.type) {
|
|
758
|
+
case "image": return {
|
|
759
|
+
blockType: "image",
|
|
760
|
+
capability: "image"
|
|
761
|
+
};
|
|
762
|
+
case "audio": return {
|
|
763
|
+
blockType: "audio",
|
|
764
|
+
capability: "audio"
|
|
765
|
+
};
|
|
766
|
+
case "resource": return {
|
|
767
|
+
blockType: "resource",
|
|
768
|
+
capability: "embeddedContext"
|
|
769
|
+
};
|
|
770
|
+
default: return;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function getUnsupportedPromptContentMessage(prompt, agentCapabilities) {
|
|
774
|
+
for (const [index, block] of prompt.entries()) {
|
|
775
|
+
const requirement = promptCapabilityRequirement(block);
|
|
776
|
+
if (!requirement) continue;
|
|
777
|
+
if (agentCapabilities?.promptCapabilities?.[requirement.capability] === true) continue;
|
|
778
|
+
return `prompt[${index}] ${requirement.blockType} content requires agentCapabilities.promptCapabilities.${requirement.capability}`;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
686
781
|
function textPrompt(text) {
|
|
687
782
|
return [{
|
|
688
783
|
type: "text",
|
|
@@ -716,15 +811,118 @@ function mergePromptSourceWithText(source, suffixText) {
|
|
|
716
811
|
return [...prompt, ...textPrompt(appended)];
|
|
717
812
|
}
|
|
718
813
|
function promptToDisplayText(prompt) {
|
|
719
|
-
return prompt.map((block) =>
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
}
|
|
727
|
-
|
|
814
|
+
return prompt.map((block) => contentBlockDisplayText(block)).filter((entry) => entry.trim().length > 0).join("\n\n").trim();
|
|
815
|
+
}
|
|
816
|
+
function contentBlockDisplayText(block) {
|
|
817
|
+
switch (block.type) {
|
|
818
|
+
case "text": return block.text;
|
|
819
|
+
case "resource_link": return block.title ?? block.name ?? block.uri;
|
|
820
|
+
case "resource": return resourceBlockDisplayText(block);
|
|
821
|
+
case "image": return `[image] ${block.mimeType}`;
|
|
822
|
+
case "audio": return `[audio] ${block.mimeType}`;
|
|
823
|
+
default: return "";
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
function resourceBlockDisplayText(block) {
|
|
827
|
+
return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
|
|
828
|
+
}
|
|
829
|
+
//#endregion
|
|
830
|
+
//#region src/acp/jsonrpc.ts
|
|
831
|
+
function asRecord$4(value) {
|
|
832
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
833
|
+
return value;
|
|
834
|
+
}
|
|
835
|
+
function hasValidId(value) {
|
|
836
|
+
return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
837
|
+
}
|
|
838
|
+
function isErrorObject(value) {
|
|
839
|
+
const record = asRecord$4(value);
|
|
840
|
+
return !!record && typeof record.code === "number" && Number.isFinite(record.code) && typeof record.message === "string";
|
|
841
|
+
}
|
|
842
|
+
function hasResultOrError(value) {
|
|
843
|
+
const hasResult = Object.hasOwn(value, "result");
|
|
844
|
+
const hasError = Object.hasOwn(value, "error");
|
|
845
|
+
if (hasResult && hasError) return false;
|
|
846
|
+
if (!hasResult && !hasError) return false;
|
|
847
|
+
if (hasError && !isErrorObject(value.error)) return false;
|
|
848
|
+
return true;
|
|
849
|
+
}
|
|
850
|
+
function hasMethod(value) {
|
|
851
|
+
return typeof value.method === "string" && value.method.length > 0;
|
|
852
|
+
}
|
|
853
|
+
function isJsonRpcRequest(value) {
|
|
854
|
+
return hasMethod(value) && Object.hasOwn(value, "id") && hasValidId(value.id);
|
|
855
|
+
}
|
|
856
|
+
function isJsonRpcNotificationRecord(value) {
|
|
857
|
+
return hasMethod(value) && !Object.hasOwn(value, "id");
|
|
858
|
+
}
|
|
859
|
+
function isJsonRpcResponse(value) {
|
|
860
|
+
if (hasMethod(value) || !Object.hasOwn(value, "id") || !hasValidId(value.id)) return false;
|
|
861
|
+
return hasResultOrError(value);
|
|
862
|
+
}
|
|
863
|
+
function isAcpJsonRpcMessage(value) {
|
|
864
|
+
const record = asRecord$4(value);
|
|
865
|
+
if (!record || record.jsonrpc !== "2.0") return false;
|
|
866
|
+
return isJsonRpcNotificationRecord(record) || isJsonRpcRequest(record) || isJsonRpcResponse(record);
|
|
867
|
+
}
|
|
868
|
+
function isJsonRpcNotification(message) {
|
|
869
|
+
return Object.hasOwn(message, "method") && typeof message.method === "string" && !Object.hasOwn(message, "id");
|
|
870
|
+
}
|
|
871
|
+
function isSessionUpdateNotification(message) {
|
|
872
|
+
return isJsonRpcNotification(message) && message.method === "session/update";
|
|
873
|
+
}
|
|
874
|
+
function extractSessionUpdateNotification(message) {
|
|
875
|
+
if (!isSessionUpdateNotification(message)) return;
|
|
876
|
+
const params = asRecord$4(message.params);
|
|
877
|
+
if (!params) return;
|
|
878
|
+
const sessionId = typeof params.sessionId === "string" ? params.sessionId : null;
|
|
879
|
+
if (!sessionId) return;
|
|
880
|
+
const update = asRecord$4(params.update);
|
|
881
|
+
if (!update || typeof update.sessionUpdate !== "string") return;
|
|
882
|
+
return {
|
|
883
|
+
sessionId,
|
|
884
|
+
update
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function parsePromptStopReason(message) {
|
|
888
|
+
if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
|
|
889
|
+
const record = asRecord$4(message.result);
|
|
890
|
+
if (!record) return;
|
|
891
|
+
return typeof record.stopReason === "string" ? record.stopReason : void 0;
|
|
892
|
+
}
|
|
893
|
+
function parseJsonRpcErrorMessage(message) {
|
|
894
|
+
if (!Object.hasOwn(message, "error")) return;
|
|
895
|
+
const errorRecord = asRecord$4(message.error);
|
|
896
|
+
if (!errorRecord || typeof errorRecord.message !== "string") return;
|
|
897
|
+
return errorRecord.message;
|
|
898
|
+
}
|
|
899
|
+
//#endregion
|
|
900
|
+
//#region src/session/event-log.ts
|
|
901
|
+
const DEFAULT_EVENT_SEGMENT_MAX_BYTES = 64 * 1024 * 1024;
|
|
902
|
+
function sessionBaseDir$1() {
|
|
903
|
+
return path.join(os.homedir(), ".acpx", "sessions");
|
|
904
|
+
}
|
|
905
|
+
function safeSessionId(sessionId) {
|
|
906
|
+
return encodeURIComponent(sessionId);
|
|
907
|
+
}
|
|
908
|
+
function sessionEventActivePath(sessionId) {
|
|
909
|
+
return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.ndjson`);
|
|
910
|
+
}
|
|
911
|
+
function sessionEventSegmentPath(sessionId, segment) {
|
|
912
|
+
return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.${segment}.ndjson`);
|
|
913
|
+
}
|
|
914
|
+
function sessionEventLockPath(sessionId) {
|
|
915
|
+
return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.lock`);
|
|
916
|
+
}
|
|
917
|
+
function defaultSessionEventLog(sessionId) {
|
|
918
|
+
return {
|
|
919
|
+
active_path: sessionEventActivePath(sessionId),
|
|
920
|
+
segment_count: 5,
|
|
921
|
+
max_segment_bytes: DEFAULT_EVENT_SEGMENT_MAX_BYTES,
|
|
922
|
+
max_segments: 5,
|
|
923
|
+
last_write_at: void 0,
|
|
924
|
+
last_write_error: null
|
|
925
|
+
};
|
|
728
926
|
}
|
|
729
927
|
//#endregion
|
|
730
928
|
//#region src/acp/agent-session-id.ts
|
|
@@ -790,173 +988,18 @@ function serializeSessionRecordForDisk(record) {
|
|
|
790
988
|
updated_at: canonical.updated_at,
|
|
791
989
|
cumulative_token_usage: canonical.cumulative_token_usage,
|
|
792
990
|
request_token_usage: canonical.request_token_usage,
|
|
793
|
-
acpx: canonical.acpx
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const timings = /* @__PURE__ */ new Map();
|
|
801
|
-
function hrNow() {
|
|
802
|
-
return process.hrtime.bigint();
|
|
803
|
-
}
|
|
804
|
-
function durationMs(start) {
|
|
805
|
-
return Number(process.hrtime.bigint() - start) / 1e6;
|
|
806
|
-
}
|
|
807
|
-
function roundMetric(value) {
|
|
808
|
-
return Number(value.toFixed(3));
|
|
809
|
-
}
|
|
810
|
-
function incrementPerfCounter(name, delta = 1) {
|
|
811
|
-
counters.set(name, (counters.get(name) ?? 0) + delta);
|
|
812
|
-
}
|
|
813
|
-
function setPerfGauge(name, value) {
|
|
814
|
-
gauges.set(name, value);
|
|
815
|
-
}
|
|
816
|
-
function recordPerfDuration(name, durationMsValue) {
|
|
817
|
-
const next = timings.get(name) ?? {
|
|
818
|
-
count: 0,
|
|
819
|
-
totalMs: 0,
|
|
820
|
-
maxMs: 0
|
|
821
|
-
};
|
|
822
|
-
next.count += 1;
|
|
823
|
-
next.totalMs += durationMsValue;
|
|
824
|
-
next.maxMs = Math.max(next.maxMs, durationMsValue);
|
|
825
|
-
timings.set(name, next);
|
|
826
|
-
}
|
|
827
|
-
async function measurePerf(name, run) {
|
|
828
|
-
const startedAt = hrNow();
|
|
829
|
-
try {
|
|
830
|
-
return await run();
|
|
831
|
-
} finally {
|
|
832
|
-
recordPerfDuration(name, durationMs(startedAt));
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
function startPerfTimer(name) {
|
|
836
|
-
const startedAt = hrNow();
|
|
837
|
-
return () => {
|
|
838
|
-
const elapsedMs = durationMs(startedAt);
|
|
839
|
-
recordPerfDuration(name, elapsedMs);
|
|
840
|
-
return elapsedMs;
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
function getPerfMetricsSnapshot() {
|
|
844
|
-
return {
|
|
845
|
-
counters: Object.fromEntries(counters.entries()),
|
|
846
|
-
gauges: Object.fromEntries(gauges.entries()),
|
|
847
|
-
timings: Object.fromEntries([...timings.entries()].map(([name, bucket]) => [name, {
|
|
848
|
-
count: bucket.count,
|
|
849
|
-
totalMs: roundMetric(bucket.totalMs),
|
|
850
|
-
maxMs: roundMetric(bucket.maxMs)
|
|
851
|
-
}]))
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
|
-
function resetPerfMetrics() {
|
|
855
|
-
counters.clear();
|
|
856
|
-
gauges.clear();
|
|
857
|
-
timings.clear();
|
|
858
|
-
}
|
|
859
|
-
function formatPerfMetric(name, durationMsValue) {
|
|
860
|
-
return `${name}=${roundMetric(durationMsValue)}ms`;
|
|
861
|
-
}
|
|
862
|
-
//#endregion
|
|
863
|
-
//#region src/persisted-key-policy.ts
|
|
864
|
-
const SNAKE_CASE_KEY = /^[a-z][a-z0-9_]*$/;
|
|
865
|
-
const ZED_TAG_KEYS = new Set([
|
|
866
|
-
"User",
|
|
867
|
-
"Agent",
|
|
868
|
-
"Resume",
|
|
869
|
-
"Text",
|
|
870
|
-
"Mention",
|
|
871
|
-
"Image",
|
|
872
|
-
"Thinking",
|
|
873
|
-
"RedactedThinking",
|
|
874
|
-
"ToolUse"
|
|
875
|
-
]);
|
|
876
|
-
const MAP_OBJECT_PATHS = new Set(["request_token_usage", "messages.Agent.tool_results"]);
|
|
877
|
-
const OPAQUE_VALUE_PATHS = new Set([
|
|
878
|
-
"agent_capabilities",
|
|
879
|
-
"messages.Agent.content.ToolUse.input",
|
|
880
|
-
"acpx.desired_config_options",
|
|
881
|
-
"acpx.config_options"
|
|
882
|
-
]);
|
|
883
|
-
function isRecord(value) {
|
|
884
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
885
|
-
}
|
|
886
|
-
function joinPath(path) {
|
|
887
|
-
return path.join(".");
|
|
888
|
-
}
|
|
889
|
-
function isAllowedKey(path, key) {
|
|
890
|
-
if (ZED_TAG_KEYS.has(key)) return true;
|
|
891
|
-
return false;
|
|
892
|
-
}
|
|
893
|
-
function shouldSkipKeyRule(path) {
|
|
894
|
-
return MAP_OBJECT_PATHS.has(joinPath(path));
|
|
895
|
-
}
|
|
896
|
-
function shouldSkipDescend(path) {
|
|
897
|
-
return OPAQUE_VALUE_PATHS.has(joinPath(path)) || isToolResultOutputPath(path);
|
|
898
|
-
}
|
|
899
|
-
function isToolResultOutputPath(path) {
|
|
900
|
-
if (path.length < 5 || path[path.length - 1] !== "output") return false;
|
|
901
|
-
const toolResultsIndex = path.lastIndexOf("tool_results");
|
|
902
|
-
if (toolResultsIndex === -1 || toolResultsIndex + 2 !== path.length - 1) return false;
|
|
903
|
-
return path.slice(0, toolResultsIndex + 1).join(".") === "messages.Agent.tool_results";
|
|
904
|
-
}
|
|
905
|
-
function collectViolations(value, path, violations) {
|
|
906
|
-
if (Array.isArray(value)) {
|
|
907
|
-
for (const entry of value) collectViolations(entry, path, violations);
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
if (!isRecord(value)) return;
|
|
911
|
-
const skipKeyRule = shouldSkipKeyRule(path);
|
|
912
|
-
for (const [key, child] of Object.entries(value)) {
|
|
913
|
-
if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
|
|
914
|
-
const childPath = [...path, key];
|
|
915
|
-
if (shouldSkipDescend(childPath)) continue;
|
|
916
|
-
collectViolations(child, childPath, violations);
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
function findPersistedKeyPolicyViolations(value) {
|
|
920
|
-
const violations = [];
|
|
921
|
-
collectViolations(value, [], violations);
|
|
922
|
-
return violations;
|
|
923
|
-
}
|
|
924
|
-
function assertPersistedKeyPolicy(value) {
|
|
925
|
-
const violations = findPersistedKeyPolicyViolations(value);
|
|
926
|
-
if (violations.length === 0) return;
|
|
927
|
-
throw new Error(`Persisted key policy violation (expected snake_case keys): ${violations.join(", ")}`);
|
|
928
|
-
}
|
|
929
|
-
//#endregion
|
|
930
|
-
//#region src/session/event-log.ts
|
|
931
|
-
const DEFAULT_EVENT_SEGMENT_MAX_BYTES = 64 * 1024 * 1024;
|
|
932
|
-
function sessionBaseDir$1() {
|
|
933
|
-
return path.join(os.homedir(), ".acpx", "sessions");
|
|
934
|
-
}
|
|
935
|
-
function safeSessionId(sessionId) {
|
|
936
|
-
return encodeURIComponent(sessionId);
|
|
937
|
-
}
|
|
938
|
-
function sessionEventActivePath(sessionId) {
|
|
939
|
-
return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.ndjson`);
|
|
940
|
-
}
|
|
941
|
-
function sessionEventSegmentPath(sessionId, segment) {
|
|
942
|
-
return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.${segment}.ndjson`);
|
|
943
|
-
}
|
|
944
|
-
function sessionEventLockPath(sessionId) {
|
|
945
|
-
return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.lock`);
|
|
946
|
-
}
|
|
947
|
-
function defaultSessionEventLog(sessionId) {
|
|
948
|
-
return {
|
|
949
|
-
active_path: sessionEventActivePath(sessionId),
|
|
950
|
-
segment_count: 5,
|
|
951
|
-
max_segment_bytes: DEFAULT_EVENT_SEGMENT_MAX_BYTES,
|
|
952
|
-
max_segments: 5,
|
|
953
|
-
last_write_at: void 0,
|
|
954
|
-
last_write_error: null
|
|
991
|
+
acpx: canonical.acpx,
|
|
992
|
+
imported_from: canonical.importedFrom ? {
|
|
993
|
+
record_id: canonical.importedFrom.recordId,
|
|
994
|
+
cwd_original: canonical.importedFrom.cwdOriginal,
|
|
995
|
+
exported_by: canonical.importedFrom.exportedBy,
|
|
996
|
+
exported_at: canonical.importedFrom.exportedAt
|
|
997
|
+
} : void 0
|
|
955
998
|
};
|
|
956
999
|
}
|
|
957
1000
|
//#endregion
|
|
958
1001
|
//#region src/session/persistence/parse.ts
|
|
959
|
-
function asRecord$
|
|
1002
|
+
function asRecord$3(value) {
|
|
960
1003
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
961
1004
|
return value;
|
|
962
1005
|
}
|
|
@@ -968,7 +1011,7 @@ function isStringArray(value) {
|
|
|
968
1011
|
}
|
|
969
1012
|
function parseTokenUsage(raw) {
|
|
970
1013
|
if (raw === void 0 || raw === null) return;
|
|
971
|
-
const record = asRecord$
|
|
1014
|
+
const record = asRecord$3(raw);
|
|
972
1015
|
if (!record) return null;
|
|
973
1016
|
const usage = {};
|
|
974
1017
|
for (const field of [
|
|
@@ -979,14 +1022,17 @@ function parseTokenUsage(raw) {
|
|
|
979
1022
|
]) {
|
|
980
1023
|
const value = record[field];
|
|
981
1024
|
if (value === void 0) continue;
|
|
982
|
-
if (
|
|
1025
|
+
if (!isNonNegativeFiniteNumber(value)) return null;
|
|
983
1026
|
usage[field] = value;
|
|
984
1027
|
}
|
|
985
1028
|
return usage;
|
|
986
1029
|
}
|
|
1030
|
+
function isNonNegativeFiniteNumber(value) {
|
|
1031
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1032
|
+
}
|
|
987
1033
|
function parseRequestTokenUsage(raw) {
|
|
988
1034
|
if (raw === void 0 || raw === null) return;
|
|
989
|
-
const record = asRecord$
|
|
1035
|
+
const record = asRecord$3(raw);
|
|
990
1036
|
if (!record) return null;
|
|
991
1037
|
const usage = {};
|
|
992
1038
|
for (const [key, value] of Object.entries(record)) {
|
|
@@ -997,62 +1043,81 @@ function parseRequestTokenUsage(raw) {
|
|
|
997
1043
|
return usage;
|
|
998
1044
|
}
|
|
999
1045
|
function isSessionMessageImage(raw) {
|
|
1000
|
-
const record = asRecord$
|
|
1046
|
+
const record = asRecord$3(raw);
|
|
1001
1047
|
if (!record || typeof record.source !== "string") return false;
|
|
1002
1048
|
if (record.size === void 0 || record.size === null) return true;
|
|
1003
|
-
const size = asRecord$
|
|
1004
|
-
return !!size &&
|
|
1049
|
+
const size = asRecord$3(record.size);
|
|
1050
|
+
return !!size && isFiniteNumber(size.width) && isFiniteNumber(size.height);
|
|
1051
|
+
}
|
|
1052
|
+
function isSessionMessageAudio(raw) {
|
|
1053
|
+
const record = asRecord$3(raw);
|
|
1054
|
+
return !!record && typeof record.source === "string" && typeof record.mime_type === "string";
|
|
1055
|
+
}
|
|
1056
|
+
function isFiniteNumber(value) {
|
|
1057
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
1005
1058
|
}
|
|
1006
1059
|
function isUserContent(raw) {
|
|
1007
|
-
const record = asRecord$
|
|
1060
|
+
const record = asRecord$3(raw);
|
|
1008
1061
|
if (!record) return false;
|
|
1009
1062
|
if (typeof record.Text === "string") return true;
|
|
1010
1063
|
if (record.Mention !== void 0) {
|
|
1011
|
-
const mention = asRecord$
|
|
1064
|
+
const mention = asRecord$3(record.Mention);
|
|
1012
1065
|
return !!mention && typeof mention.uri === "string" && typeof mention.content === "string";
|
|
1013
1066
|
}
|
|
1014
1067
|
if (record.Image !== void 0) return isSessionMessageImage(record.Image);
|
|
1068
|
+
if (record.Audio !== void 0) return isSessionMessageAudio(record.Audio);
|
|
1015
1069
|
return false;
|
|
1016
1070
|
}
|
|
1017
1071
|
function isToolUse(raw) {
|
|
1018
|
-
const record = asRecord$
|
|
1019
|
-
return !!record &&
|
|
1072
|
+
const record = asRecord$3(raw);
|
|
1073
|
+
return !!record && hasStringFields(record, [
|
|
1074
|
+
"id",
|
|
1075
|
+
"name",
|
|
1076
|
+
"raw_input"
|
|
1077
|
+
]) && hasOwn$1(record, "input") && typeof record.is_input_complete === "boolean" && isOptionalString(record.thought_signature);
|
|
1078
|
+
}
|
|
1079
|
+
function hasStringFields(record, keys) {
|
|
1080
|
+
return keys.every((key) => typeof record[key] === "string");
|
|
1081
|
+
}
|
|
1082
|
+
function isOptionalString(value) {
|
|
1083
|
+
return value === void 0 || value === null || typeof value === "string";
|
|
1020
1084
|
}
|
|
1021
1085
|
function isToolResultContent(raw) {
|
|
1022
|
-
const record = asRecord$
|
|
1086
|
+
const record = asRecord$3(raw);
|
|
1023
1087
|
if (!record) return false;
|
|
1024
1088
|
if (typeof record.Text === "string") return true;
|
|
1025
1089
|
if (record.Image !== void 0) return isSessionMessageImage(record.Image);
|
|
1026
1090
|
return false;
|
|
1027
1091
|
}
|
|
1028
1092
|
function isToolResult(raw) {
|
|
1029
|
-
const record = asRecord$
|
|
1093
|
+
const record = asRecord$3(raw);
|
|
1030
1094
|
return !!record && typeof record.tool_use_id === "string" && typeof record.tool_name === "string" && typeof record.is_error === "boolean" && isToolResultContent(record.content);
|
|
1031
1095
|
}
|
|
1032
1096
|
function isAgentContent(raw) {
|
|
1033
|
-
const record = asRecord$
|
|
1097
|
+
const record = asRecord$3(raw);
|
|
1034
1098
|
if (!record) return false;
|
|
1035
1099
|
if (typeof record.Text === "string") return true;
|
|
1036
|
-
if (record.Thinking !== void 0)
|
|
1037
|
-
const thinking = asRecord$4(record.Thinking);
|
|
1038
|
-
return !!thinking && typeof thinking.text === "string" && (thinking.signature === void 0 || thinking.signature === null || typeof thinking.signature === "string");
|
|
1039
|
-
}
|
|
1100
|
+
if (record.Thinking !== void 0) return isThinkingContent(record.Thinking);
|
|
1040
1101
|
if (typeof record.RedactedThinking === "string") return true;
|
|
1041
1102
|
if (record.ToolUse !== void 0) return isToolUse(record.ToolUse);
|
|
1042
1103
|
return false;
|
|
1043
1104
|
}
|
|
1105
|
+
function isThinkingContent(raw) {
|
|
1106
|
+
const thinking = asRecord$3(raw);
|
|
1107
|
+
return !!thinking && typeof thinking.text === "string" && isOptionalString(thinking.signature);
|
|
1108
|
+
}
|
|
1044
1109
|
function isUserMessage$1(raw) {
|
|
1045
|
-
const record = asRecord$
|
|
1110
|
+
const record = asRecord$3(raw);
|
|
1046
1111
|
if (!record || record.User === void 0) return false;
|
|
1047
|
-
const user = asRecord$
|
|
1112
|
+
const user = asRecord$3(record.User);
|
|
1048
1113
|
return !!user && typeof user.id === "string" && Array.isArray(user.content) && user.content.every((entry) => isUserContent(entry));
|
|
1049
1114
|
}
|
|
1050
1115
|
function isAgentMessage$1(raw) {
|
|
1051
|
-
const record = asRecord$
|
|
1116
|
+
const record = asRecord$3(raw);
|
|
1052
1117
|
if (!record || record.Agent === void 0) return false;
|
|
1053
|
-
const agent = asRecord$
|
|
1118
|
+
const agent = asRecord$3(record.Agent);
|
|
1054
1119
|
if (!agent || !Array.isArray(agent.content) || !agent.content.every(isAgentContent)) return false;
|
|
1055
|
-
const toolResults = asRecord$
|
|
1120
|
+
const toolResults = asRecord$3(agent.tool_results);
|
|
1056
1121
|
if (!toolResults) return false;
|
|
1057
1122
|
return Object.values(toolResults).every(isToolResult);
|
|
1058
1123
|
}
|
|
@@ -1060,56 +1125,88 @@ function isConversationMessage(raw) {
|
|
|
1060
1125
|
return raw === "Resume" || isUserMessage$1(raw) || isAgentMessage$1(raw);
|
|
1061
1126
|
}
|
|
1062
1127
|
function parseConversationRecord(record) {
|
|
1063
|
-
if (!
|
|
1064
|
-
|
|
1128
|
+
if (!hasValidConversationCore(record)) return;
|
|
1129
|
+
const title = parseConversationTitle(record.title);
|
|
1130
|
+
if (title === INVALID_VALUE) return;
|
|
1065
1131
|
const cumulativeTokenUsage = parseTokenUsage(record.cumulative_token_usage);
|
|
1066
1132
|
const requestTokenUsage = parseRequestTokenUsage(record.request_token_usage);
|
|
1067
1133
|
if (cumulativeTokenUsage === null || requestTokenUsage === null) return;
|
|
1068
1134
|
return {
|
|
1069
|
-
title
|
|
1135
|
+
title,
|
|
1070
1136
|
messages: record.messages,
|
|
1071
1137
|
updated_at: record.updated_at,
|
|
1072
1138
|
cumulative_token_usage: cumulativeTokenUsage ?? {},
|
|
1073
1139
|
request_token_usage: requestTokenUsage ?? {}
|
|
1074
1140
|
};
|
|
1075
1141
|
}
|
|
1142
|
+
const INVALID_VALUE = Symbol("invalid");
|
|
1143
|
+
function parseConversationTitle(value) {
|
|
1144
|
+
if (value === void 0 || value === null || typeof value === "string") return value;
|
|
1145
|
+
return INVALID_VALUE;
|
|
1146
|
+
}
|
|
1147
|
+
function hasValidConversationCore(record) {
|
|
1148
|
+
return Array.isArray(record.messages) && record.messages.every(isConversationMessage) && typeof record.updated_at === "string";
|
|
1149
|
+
}
|
|
1076
1150
|
function parseAcpxState(raw) {
|
|
1077
|
-
const record = asRecord$
|
|
1151
|
+
const record = asRecord$3(raw);
|
|
1078
1152
|
if (!record) return;
|
|
1079
1153
|
const state = {};
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
const parsed = {};
|
|
1086
|
-
for (const [key, value] of Object.entries(desiredConfigOptions)) if (typeof key === "string" && typeof value === "string") parsed[key] = value;
|
|
1087
|
-
if (Object.keys(parsed).length > 0) state.desired_config_options = parsed;
|
|
1088
|
-
}
|
|
1089
|
-
if (typeof record.current_model_id === "string") state.current_model_id = record.current_model_id;
|
|
1154
|
+
assignBooleanTrue(state, "reset_on_next_ensure", record.reset_on_next_ensure);
|
|
1155
|
+
assignStringState(state, "current_mode_id", record.current_mode_id);
|
|
1156
|
+
assignStringState(state, "desired_mode_id", record.desired_mode_id);
|
|
1157
|
+
assignDesiredConfigOptions(state, record.desired_config_options);
|
|
1158
|
+
assignStringState(state, "current_model_id", record.current_model_id);
|
|
1090
1159
|
if (isStringArray(record.available_models)) state.available_models = [...record.available_models];
|
|
1091
1160
|
if (isStringArray(record.available_commands)) state.available_commands = [...record.available_commands];
|
|
1092
1161
|
if (Array.isArray(record.config_options)) state.config_options = record.config_options;
|
|
1093
|
-
|
|
1094
|
-
if (sessionOptions) {
|
|
1095
|
-
const parsedSessionOptions = {};
|
|
1096
|
-
if (typeof sessionOptions.model === "string") parsedSessionOptions.model = sessionOptions.model;
|
|
1097
|
-
if (isStringArray(sessionOptions.allowed_tools)) parsedSessionOptions.allowed_tools = [...sessionOptions.allowed_tools];
|
|
1098
|
-
if (typeof sessionOptions.max_turns === "number" && Number.isInteger(sessionOptions.max_turns) && sessionOptions.max_turns > 0) parsedSessionOptions.max_turns = sessionOptions.max_turns;
|
|
1099
|
-
const rawSystemPrompt = sessionOptions.system_prompt;
|
|
1100
|
-
if (typeof rawSystemPrompt === "string" && rawSystemPrompt.length > 0) parsedSessionOptions.system_prompt = rawSystemPrompt;
|
|
1101
|
-
else {
|
|
1102
|
-
const appendRecord = asRecord$4(rawSystemPrompt);
|
|
1103
|
-
if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) parsedSessionOptions.system_prompt = { append: appendRecord.append };
|
|
1104
|
-
}
|
|
1105
|
-
if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
|
|
1106
|
-
}
|
|
1162
|
+
assignParsedSessionOptions(state, record.session_options);
|
|
1107
1163
|
return state;
|
|
1108
1164
|
}
|
|
1165
|
+
function assignBooleanTrue(state, key, value) {
|
|
1166
|
+
if (value === true) state[key] = true;
|
|
1167
|
+
}
|
|
1168
|
+
function assignStringState(state, key, value) {
|
|
1169
|
+
if (typeof value === "string") state[key] = value;
|
|
1170
|
+
}
|
|
1171
|
+
function assignDesiredConfigOptions(state, raw) {
|
|
1172
|
+
const desiredConfigOptions = asRecord$3(raw);
|
|
1173
|
+
if (!desiredConfigOptions) return;
|
|
1174
|
+
const parsed = Object.fromEntries(Object.entries(desiredConfigOptions).filter((entry) => {
|
|
1175
|
+
const [, value] = entry;
|
|
1176
|
+
return typeof value === "string";
|
|
1177
|
+
}));
|
|
1178
|
+
if (Object.keys(parsed).length > 0) state.desired_config_options = parsed;
|
|
1179
|
+
}
|
|
1180
|
+
function assignParsedSessionOptions(state, raw) {
|
|
1181
|
+
const sessionOptions = asRecord$3(raw);
|
|
1182
|
+
if (!sessionOptions) return;
|
|
1183
|
+
const parsedSessionOptions = {};
|
|
1184
|
+
assignSessionOptionModel(parsedSessionOptions, sessionOptions.model);
|
|
1185
|
+
assignSessionOptionAllowedTools(parsedSessionOptions, sessionOptions.allowed_tools);
|
|
1186
|
+
assignSessionOptionMaxTurns(parsedSessionOptions, sessionOptions.max_turns);
|
|
1187
|
+
assignSessionOptionSystemPrompt(parsedSessionOptions, sessionOptions.system_prompt);
|
|
1188
|
+
if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
|
|
1189
|
+
}
|
|
1190
|
+
function assignSessionOptionModel(options, value) {
|
|
1191
|
+
if (typeof value === "string") options.model = value;
|
|
1192
|
+
}
|
|
1193
|
+
function assignSessionOptionAllowedTools(options, value) {
|
|
1194
|
+
if (isStringArray(value)) options.allowed_tools = [...value];
|
|
1195
|
+
}
|
|
1196
|
+
function assignSessionOptionMaxTurns(options, value) {
|
|
1197
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) options.max_turns = value;
|
|
1198
|
+
}
|
|
1199
|
+
function assignSessionOptionSystemPrompt(options, value) {
|
|
1200
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1201
|
+
options.system_prompt = value;
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
const appendRecord = asRecord$3(value);
|
|
1205
|
+
if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) options.system_prompt = { append: appendRecord.append };
|
|
1206
|
+
}
|
|
1109
1207
|
function parseEventLog(raw, sessionId) {
|
|
1110
|
-
const record = asRecord$
|
|
1111
|
-
if (!record) return defaultSessionEventLog(sessionId);
|
|
1112
|
-
if (typeof record.active_path !== "string" || typeof record.segment_count !== "number" || !Number.isInteger(record.segment_count) || record.segment_count < 1 || typeof record.max_segment_bytes !== "number" || !Number.isInteger(record.max_segment_bytes) || record.max_segment_bytes < 1 || typeof record.max_segments !== "number" || !Number.isInteger(record.max_segments) || record.max_segments < 1) return defaultSessionEventLog(sessionId);
|
|
1208
|
+
const record = asRecord$3(raw);
|
|
1209
|
+
if (!record || !hasValidEventLogCore(record)) return defaultSessionEventLog(sessionId);
|
|
1113
1210
|
return {
|
|
1114
1211
|
active_path: record.active_path,
|
|
1115
1212
|
segment_count: record.segment_count,
|
|
@@ -1119,6 +1216,33 @@ function parseEventLog(raw, sessionId) {
|
|
|
1119
1216
|
last_write_error: record.last_write_error == null || typeof record.last_write_error === "string" ? record.last_write_error : null
|
|
1120
1217
|
};
|
|
1121
1218
|
}
|
|
1219
|
+
function hasValidEventLogCore(record) {
|
|
1220
|
+
return typeof record.active_path === "string" && isPositiveInteger(record.segment_count) && isPositiveInteger(record.max_segment_bytes) && isPositiveInteger(record.max_segments);
|
|
1221
|
+
}
|
|
1222
|
+
function isPositiveInteger(value) {
|
|
1223
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
1224
|
+
}
|
|
1225
|
+
function parseImportedFrom(raw) {
|
|
1226
|
+
if (raw == null) return;
|
|
1227
|
+
const record = asRecord$3(raw);
|
|
1228
|
+
if (!record || typeof record.record_id !== "string" || typeof record.cwd_original !== "string" || typeof record.exported_by !== "string" || typeof record.exported_at !== "string") return null;
|
|
1229
|
+
return {
|
|
1230
|
+
recordId: record.record_id,
|
|
1231
|
+
cwdOriginal: record.cwd_original,
|
|
1232
|
+
exportedBy: record.exported_by,
|
|
1233
|
+
exportedAt: record.exported_at
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
function parseSessionRecordMetadata(record) {
|
|
1237
|
+
const lastRequestId = normalizeOptionalString(record.last_request_id);
|
|
1238
|
+
if (lastRequestId === null) return null;
|
|
1239
|
+
const importedFrom = parseImportedFrom(record.imported_from);
|
|
1240
|
+
if (importedFrom === null) return null;
|
|
1241
|
+
return {
|
|
1242
|
+
lastRequestId,
|
|
1243
|
+
importedFrom
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1122
1246
|
function normalizeOptionalName(value) {
|
|
1123
1247
|
if (value == null) return;
|
|
1124
1248
|
if (typeof value !== "string") return null;
|
|
@@ -1151,25 +1275,27 @@ function normalizeOptionalSignal(value) {
|
|
|
1151
1275
|
return Symbol("invalid");
|
|
1152
1276
|
}
|
|
1153
1277
|
function parseSessionRecord(raw) {
|
|
1154
|
-
const record = asRecord$
|
|
1278
|
+
const record = asRecord$3(raw);
|
|
1155
1279
|
if (!record) return null;
|
|
1156
1280
|
if (record.schema !== "acpx.session.v1") return null;
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1281
|
+
const optionals = validSessionOptionals({
|
|
1282
|
+
name: normalizeOptionalName(record.name),
|
|
1283
|
+
pid: normalizeOptionalPid(record.pid),
|
|
1284
|
+
closed: normalizeOptionalBoolean(record.closed, false),
|
|
1285
|
+
closedAt: normalizeOptionalString(record.closed_at),
|
|
1286
|
+
agentStartedAt: normalizeOptionalString(record.agent_started_at),
|
|
1287
|
+
lastPromptAt: normalizeOptionalString(record.last_prompt_at),
|
|
1288
|
+
lastAgentExitCode: normalizeOptionalExitCode(record.last_agent_exit_code),
|
|
1289
|
+
lastAgentExitSignal: normalizeOptionalSignal(record.last_agent_exit_signal),
|
|
1290
|
+
lastAgentExitAt: normalizeOptionalString(record.last_agent_exit_at),
|
|
1291
|
+
lastAgentDisconnectReason: normalizeOptionalString(record.last_agent_disconnect_reason)
|
|
1292
|
+
});
|
|
1293
|
+
if (!hasValidSessionRecordCore(record) || !optionals) return null;
|
|
1168
1294
|
const conversation = parseConversationRecord(record);
|
|
1169
1295
|
if (!conversation) return null;
|
|
1170
1296
|
const eventLog = parseEventLog(record.event_log, record.acpx_record_id);
|
|
1171
|
-
const
|
|
1172
|
-
if (
|
|
1297
|
+
const metadata = parseSessionRecordMetadata(record);
|
|
1298
|
+
if (!metadata) return null;
|
|
1173
1299
|
return {
|
|
1174
1300
|
schema: SESSION_RECORD_SCHEMA,
|
|
1175
1301
|
acpxRecordId: record.acpx_record_id,
|
|
@@ -1177,42 +1303,209 @@ function parseSessionRecord(raw) {
|
|
|
1177
1303
|
agentSessionId: normalizeRuntimeSessionId(record.agent_session_id),
|
|
1178
1304
|
agentCommand: record.agent_command,
|
|
1179
1305
|
cwd: record.cwd,
|
|
1180
|
-
name,
|
|
1306
|
+
name: optionals.name,
|
|
1181
1307
|
createdAt: record.created_at,
|
|
1182
1308
|
lastUsedAt: record.last_used_at,
|
|
1183
1309
|
lastSeq: record.last_seq,
|
|
1184
|
-
lastRequestId,
|
|
1310
|
+
lastRequestId: metadata.lastRequestId,
|
|
1185
1311
|
eventLog,
|
|
1186
|
-
closed,
|
|
1187
|
-
closedAt,
|
|
1188
|
-
pid,
|
|
1189
|
-
agentStartedAt,
|
|
1190
|
-
lastPromptAt,
|
|
1191
|
-
lastAgentExitCode,
|
|
1192
|
-
lastAgentExitSignal,
|
|
1193
|
-
lastAgentExitAt,
|
|
1194
|
-
lastAgentDisconnectReason,
|
|
1312
|
+
closed: optionals.closed,
|
|
1313
|
+
closedAt: optionals.closedAt,
|
|
1314
|
+
pid: optionals.pid,
|
|
1315
|
+
agentStartedAt: optionals.agentStartedAt,
|
|
1316
|
+
lastPromptAt: optionals.lastPromptAt,
|
|
1317
|
+
lastAgentExitCode: optionals.lastAgentExitCode,
|
|
1318
|
+
lastAgentExitSignal: optionals.lastAgentExitSignal,
|
|
1319
|
+
lastAgentExitAt: optionals.lastAgentExitAt,
|
|
1320
|
+
lastAgentDisconnectReason: optionals.lastAgentDisconnectReason,
|
|
1195
1321
|
protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
|
|
1196
|
-
agentCapabilities: asRecord$
|
|
1322
|
+
agentCapabilities: asRecord$3(record.agent_capabilities),
|
|
1197
1323
|
title: conversation.title,
|
|
1198
1324
|
messages: conversation.messages,
|
|
1199
1325
|
updated_at: conversation.updated_at,
|
|
1200
1326
|
cumulative_token_usage: conversation.cumulative_token_usage,
|
|
1201
1327
|
request_token_usage: conversation.request_token_usage,
|
|
1202
|
-
acpx: parseAcpxState(record.acpx)
|
|
1328
|
+
acpx: parseAcpxState(record.acpx),
|
|
1329
|
+
importedFrom: metadata.importedFrom
|
|
1203
1330
|
};
|
|
1204
1331
|
}
|
|
1332
|
+
function hasValidSessionRecordCore(record) {
|
|
1333
|
+
return hasStringFields(record, [
|
|
1334
|
+
"acpx_record_id",
|
|
1335
|
+
"acp_session_id",
|
|
1336
|
+
"agent_command",
|
|
1337
|
+
"cwd",
|
|
1338
|
+
"created_at",
|
|
1339
|
+
"last_used_at"
|
|
1340
|
+
]) && typeof record.last_seq === "number" && Number.isInteger(record.last_seq) && record.last_seq >= 0;
|
|
1341
|
+
}
|
|
1342
|
+
function validSessionOptionals(options) {
|
|
1343
|
+
if (hasNullOptionalSessionFields(options) || hasInvalidExitStatus(options)) return null;
|
|
1344
|
+
return options;
|
|
1345
|
+
}
|
|
1346
|
+
function hasNullOptionalSessionFields(options) {
|
|
1347
|
+
return [
|
|
1348
|
+
options.name,
|
|
1349
|
+
options.pid,
|
|
1350
|
+
options.closed,
|
|
1351
|
+
options.closedAt,
|
|
1352
|
+
options.agentStartedAt,
|
|
1353
|
+
options.lastPromptAt,
|
|
1354
|
+
options.lastAgentExitAt,
|
|
1355
|
+
options.lastAgentDisconnectReason
|
|
1356
|
+
].some((value) => value === null);
|
|
1357
|
+
}
|
|
1358
|
+
function hasInvalidExitStatus(options) {
|
|
1359
|
+
return typeof options.lastAgentExitCode === "symbol" || typeof options.lastAgentExitSignal === "symbol";
|
|
1360
|
+
}
|
|
1361
|
+
//#endregion
|
|
1362
|
+
//#region src/perf-metrics.ts
|
|
1363
|
+
const counters = /* @__PURE__ */ new Map();
|
|
1364
|
+
const gauges = /* @__PURE__ */ new Map();
|
|
1365
|
+
const timings = /* @__PURE__ */ new Map();
|
|
1366
|
+
function hrNow() {
|
|
1367
|
+
return process.hrtime.bigint();
|
|
1368
|
+
}
|
|
1369
|
+
function durationMs(start) {
|
|
1370
|
+
return Number(process.hrtime.bigint() - start) / 1e6;
|
|
1371
|
+
}
|
|
1372
|
+
function roundMetric(value) {
|
|
1373
|
+
return Number(value.toFixed(3));
|
|
1374
|
+
}
|
|
1375
|
+
function incrementPerfCounter(name, delta = 1) {
|
|
1376
|
+
counters.set(name, (counters.get(name) ?? 0) + delta);
|
|
1377
|
+
}
|
|
1378
|
+
function setPerfGauge(name, value) {
|
|
1379
|
+
gauges.set(name, value);
|
|
1380
|
+
}
|
|
1381
|
+
function recordPerfDuration(name, durationMsValue) {
|
|
1382
|
+
const next = timings.get(name) ?? {
|
|
1383
|
+
count: 0,
|
|
1384
|
+
totalMs: 0,
|
|
1385
|
+
maxMs: 0
|
|
1386
|
+
};
|
|
1387
|
+
next.count += 1;
|
|
1388
|
+
next.totalMs += durationMsValue;
|
|
1389
|
+
next.maxMs = Math.max(next.maxMs, durationMsValue);
|
|
1390
|
+
timings.set(name, next);
|
|
1391
|
+
}
|
|
1392
|
+
async function measurePerf(name, run) {
|
|
1393
|
+
const startedAt = hrNow();
|
|
1394
|
+
try {
|
|
1395
|
+
return await run();
|
|
1396
|
+
} finally {
|
|
1397
|
+
recordPerfDuration(name, durationMs(startedAt));
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
function startPerfTimer(name) {
|
|
1401
|
+
const startedAt = hrNow();
|
|
1402
|
+
return () => {
|
|
1403
|
+
const elapsedMs = durationMs(startedAt);
|
|
1404
|
+
recordPerfDuration(name, elapsedMs);
|
|
1405
|
+
return elapsedMs;
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
function getPerfMetricsSnapshot() {
|
|
1409
|
+
return {
|
|
1410
|
+
counters: Object.fromEntries(counters.entries()),
|
|
1411
|
+
gauges: Object.fromEntries(gauges.entries()),
|
|
1412
|
+
timings: Object.fromEntries([...timings.entries()].map(([name, bucket]) => [name, {
|
|
1413
|
+
count: bucket.count,
|
|
1414
|
+
totalMs: roundMetric(bucket.totalMs),
|
|
1415
|
+
maxMs: roundMetric(bucket.maxMs)
|
|
1416
|
+
}]))
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function resetPerfMetrics() {
|
|
1420
|
+
counters.clear();
|
|
1421
|
+
gauges.clear();
|
|
1422
|
+
timings.clear();
|
|
1423
|
+
}
|
|
1424
|
+
function formatPerfMetric(name, durationMsValue) {
|
|
1425
|
+
return `${name}=${roundMetric(durationMsValue)}ms`;
|
|
1426
|
+
}
|
|
1427
|
+
//#endregion
|
|
1428
|
+
//#region src/persisted-key-policy.ts
|
|
1429
|
+
const SNAKE_CASE_KEY = /^[a-z][a-z0-9_]*$/;
|
|
1430
|
+
const ZED_TAG_KEYS = new Set([
|
|
1431
|
+
"User",
|
|
1432
|
+
"Agent",
|
|
1433
|
+
"Resume",
|
|
1434
|
+
"Text",
|
|
1435
|
+
"Mention",
|
|
1436
|
+
"Image",
|
|
1437
|
+
"Audio",
|
|
1438
|
+
"Thinking",
|
|
1439
|
+
"RedactedThinking",
|
|
1440
|
+
"ToolUse"
|
|
1441
|
+
]);
|
|
1442
|
+
const MAP_OBJECT_PATHS = new Set(["request_token_usage", "messages.Agent.tool_results"]);
|
|
1443
|
+
const OPAQUE_VALUE_PATHS = new Set([
|
|
1444
|
+
"agent_capabilities",
|
|
1445
|
+
"messages.Agent.content.ToolUse.input",
|
|
1446
|
+
"acpx.desired_config_options",
|
|
1447
|
+
"acpx.config_options"
|
|
1448
|
+
]);
|
|
1449
|
+
function isRecord(value) {
|
|
1450
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1451
|
+
}
|
|
1452
|
+
function joinPath(path) {
|
|
1453
|
+
return path.join(".");
|
|
1454
|
+
}
|
|
1455
|
+
function isAllowedKey(path, key) {
|
|
1456
|
+
if (ZED_TAG_KEYS.has(key)) return true;
|
|
1457
|
+
return false;
|
|
1458
|
+
}
|
|
1459
|
+
function shouldSkipKeyRule(path) {
|
|
1460
|
+
return MAP_OBJECT_PATHS.has(joinPath(path));
|
|
1461
|
+
}
|
|
1462
|
+
function shouldSkipDescend(path) {
|
|
1463
|
+
return OPAQUE_VALUE_PATHS.has(joinPath(path)) || isToolResultOutputPath(path);
|
|
1464
|
+
}
|
|
1465
|
+
function isToolResultOutputTail(path, toolResultsIndex) {
|
|
1466
|
+
return toolResultsIndex !== -1 && toolResultsIndex + 2 === path.length - 1;
|
|
1467
|
+
}
|
|
1468
|
+
function isToolResultOutputPath(path) {
|
|
1469
|
+
if (path.length < 5 || path[path.length - 1] !== "output") return false;
|
|
1470
|
+
const toolResultsIndex = path.lastIndexOf("tool_results");
|
|
1471
|
+
if (!isToolResultOutputTail(path, toolResultsIndex)) return false;
|
|
1472
|
+
return path.slice(0, toolResultsIndex + 1).join(".") === "messages.Agent.tool_results";
|
|
1473
|
+
}
|
|
1474
|
+
function collectViolations(value, path, violations) {
|
|
1475
|
+
if (Array.isArray(value)) {
|
|
1476
|
+
for (const entry of value) collectViolations(entry, path, violations);
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
if (!isRecord(value)) return;
|
|
1480
|
+
const skipKeyRule = shouldSkipKeyRule(path);
|
|
1481
|
+
for (const [key, child] of Object.entries(value)) collectKeyViolation(child, key, path, skipKeyRule, violations);
|
|
1482
|
+
}
|
|
1483
|
+
function collectKeyViolation(child, key, path, skipKeyRule, violations) {
|
|
1484
|
+
if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
|
|
1485
|
+
const childPath = [...path, key];
|
|
1486
|
+
if (!shouldSkipDescend(childPath)) collectViolations(child, childPath, violations);
|
|
1487
|
+
}
|
|
1488
|
+
function findPersistedKeyPolicyViolations(value) {
|
|
1489
|
+
const violations = [];
|
|
1490
|
+
collectViolations(value, [], violations);
|
|
1491
|
+
return violations;
|
|
1492
|
+
}
|
|
1493
|
+
function assertPersistedKeyPolicy(value) {
|
|
1494
|
+
const violations = findPersistedKeyPolicyViolations(value);
|
|
1495
|
+
if (violations.length === 0) return;
|
|
1496
|
+
throw new Error(`Persisted key policy violation (expected snake_case keys): ${violations.join(", ")}`);
|
|
1497
|
+
}
|
|
1205
1498
|
//#endregion
|
|
1206
1499
|
//#region src/session/persistence/index.ts
|
|
1207
1500
|
const SESSION_INDEX_SCHEMA = "acpx.session-index.v1";
|
|
1208
|
-
function asRecord$
|
|
1501
|
+
function asRecord$2(value) {
|
|
1209
1502
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
1210
1503
|
return value;
|
|
1211
1504
|
}
|
|
1212
1505
|
function parseIndexEntry(raw) {
|
|
1213
|
-
const record = asRecord$
|
|
1506
|
+
const record = asRecord$2(raw);
|
|
1214
1507
|
if (!record) return;
|
|
1215
|
-
if (
|
|
1508
|
+
if (!hasRequiredIndexEntryFields(record)) return;
|
|
1216
1509
|
if (record.name !== void 0 && typeof record.name !== "string") return;
|
|
1217
1510
|
return {
|
|
1218
1511
|
file: record.file,
|
|
@@ -1225,6 +1518,16 @@ function parseIndexEntry(raw) {
|
|
|
1225
1518
|
lastUsedAt: record.lastUsedAt
|
|
1226
1519
|
};
|
|
1227
1520
|
}
|
|
1521
|
+
function hasRequiredIndexEntryFields(record) {
|
|
1522
|
+
return [
|
|
1523
|
+
"file",
|
|
1524
|
+
"acpxRecordId",
|
|
1525
|
+
"acpSessionId",
|
|
1526
|
+
"agentCommand",
|
|
1527
|
+
"cwd",
|
|
1528
|
+
"lastUsedAt"
|
|
1529
|
+
].every((key) => typeof record[key] === "string") && typeof record.closed === "boolean";
|
|
1530
|
+
}
|
|
1228
1531
|
function sessionIndexPath(sessionDir) {
|
|
1229
1532
|
return path.join(sessionDir, "index.json");
|
|
1230
1533
|
}
|
|
@@ -1244,7 +1547,7 @@ async function readSessionIndex(sessionDir) {
|
|
|
1244
1547
|
const filePath = sessionIndexPath(sessionDir);
|
|
1245
1548
|
try {
|
|
1246
1549
|
const payload = await fs$1.readFile(filePath, "utf8");
|
|
1247
|
-
const record = asRecord$
|
|
1550
|
+
const record = asRecord$2(JSON.parse(payload));
|
|
1248
1551
|
if (!record || record.schema !== SESSION_INDEX_SCHEMA || !Array.isArray(record.files)) return;
|
|
1249
1552
|
const files = record.files.filter((entry) => typeof entry === "string");
|
|
1250
1553
|
if (files.length !== record.files.length || !Array.isArray(record.entries)) return;
|
|
@@ -1436,13 +1739,17 @@ async function findSessionByDirectoryWalk(options) {
|
|
|
1436
1739
|
for (;;) {
|
|
1437
1740
|
const match = sessions.find((session) => matchesSessionEntry(session, current, normalizedName));
|
|
1438
1741
|
if (match) return await loadRecordFromIndexEntry(match);
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
if (parent === current) return;
|
|
1742
|
+
const parent = nextWalkParent(current, walkBoundary, walkRoot);
|
|
1743
|
+
if (!parent) return;
|
|
1442
1744
|
current = parent;
|
|
1443
|
-
if (!isWithinBoundary(walkBoundary, current)) return;
|
|
1444
1745
|
}
|
|
1445
1746
|
}
|
|
1747
|
+
function nextWalkParent(current, walkBoundary, walkRoot) {
|
|
1748
|
+
if (current === walkBoundary || current === walkRoot) return;
|
|
1749
|
+
const parent = path.dirname(current);
|
|
1750
|
+
if (parent === current || !isWithinBoundary(walkBoundary, parent)) return;
|
|
1751
|
+
return parent;
|
|
1752
|
+
}
|
|
1446
1753
|
function closedAtOrLastUsedAt(record) {
|
|
1447
1754
|
return record.closedAt ?? record.lastUsedAt;
|
|
1448
1755
|
}
|
|
@@ -1451,14 +1758,7 @@ function isSessionStreamFile(fileName, safeId) {
|
|
|
1451
1758
|
}
|
|
1452
1759
|
async function pruneSessions(options = {}) {
|
|
1453
1760
|
await ensureSessionDir();
|
|
1454
|
-
|
|
1455
|
-
if (options.agentCommand) eligible = eligible.filter((entry) => entry.agentCommand === options.agentCommand);
|
|
1456
|
-
const cutoff = options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0);
|
|
1457
|
-
const records = [];
|
|
1458
|
-
for (const entry of eligible) {
|
|
1459
|
-
const record = await loadRecordFromIndexEntry(entry);
|
|
1460
|
-
if (record && (!cutoff || closedAtOrLastUsedAt(record) < cutoff.toISOString())) records.push(record);
|
|
1461
|
-
}
|
|
1761
|
+
const records = await loadPrunableRecords(filterPruneCandidates(await loadSessionIndexEntries(), options.agentCommand), options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0));
|
|
1462
1762
|
if (options.dryRun) return {
|
|
1463
1763
|
pruned: records,
|
|
1464
1764
|
bytesFreed: 0,
|
|
@@ -1470,24 +1770,7 @@ async function pruneSessions(options = {}) {
|
|
|
1470
1770
|
if (options.includeHistory) try {
|
|
1471
1771
|
dirEntries = await fs$1.readdir(sessionDir);
|
|
1472
1772
|
} catch {}
|
|
1473
|
-
for (const record of records)
|
|
1474
|
-
const safeId = encodeURIComponent(record.acpxRecordId);
|
|
1475
|
-
const jsonFile = path.join(sessionDir, `${safeId}.json`);
|
|
1476
|
-
try {
|
|
1477
|
-
const stat = await fs$1.stat(jsonFile);
|
|
1478
|
-
bytesFreed += stat.size;
|
|
1479
|
-
} catch {}
|
|
1480
|
-
await fs$1.unlink(jsonFile).catch(() => void 0);
|
|
1481
|
-
if (options.includeHistory) for (const name of dirEntries) {
|
|
1482
|
-
if (!isSessionStreamFile(name, safeId)) continue;
|
|
1483
|
-
const filePath = path.join(sessionDir, name);
|
|
1484
|
-
try {
|
|
1485
|
-
const stat = await fs$1.stat(filePath);
|
|
1486
|
-
bytesFreed += stat.size;
|
|
1487
|
-
} catch {}
|
|
1488
|
-
await fs$1.unlink(filePath).catch(() => void 0);
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1773
|
+
for (const record of records) bytesFreed += await pruneSessionFiles(record, sessionDir, dirEntries, options.includeHistory === true);
|
|
1491
1774
|
await rebuildSessionIndex(sessionDir).catch(() => {});
|
|
1492
1775
|
return {
|
|
1493
1776
|
pruned: records,
|
|
@@ -1495,6 +1778,35 @@ async function pruneSessions(options = {}) {
|
|
|
1495
1778
|
dryRun: false
|
|
1496
1779
|
};
|
|
1497
1780
|
}
|
|
1781
|
+
function filterPruneCandidates(entries, agentCommand) {
|
|
1782
|
+
return entries.filter((entry) => entry.closed && (!agentCommand || entry.agentCommand === agentCommand));
|
|
1783
|
+
}
|
|
1784
|
+
async function loadPrunableRecords(entries, cutoff) {
|
|
1785
|
+
const records = [];
|
|
1786
|
+
const cutoffIso = cutoff?.toISOString();
|
|
1787
|
+
for (const entry of entries) {
|
|
1788
|
+
const record = await loadRecordFromIndexEntry(entry);
|
|
1789
|
+
if (record && isBeforeCutoff(record, cutoffIso)) records.push(record);
|
|
1790
|
+
}
|
|
1791
|
+
return records;
|
|
1792
|
+
}
|
|
1793
|
+
function isBeforeCutoff(record, cutoffIso) {
|
|
1794
|
+
return !cutoffIso || closedAtOrLastUsedAt(record) < cutoffIso;
|
|
1795
|
+
}
|
|
1796
|
+
async function pruneSessionFiles(record, sessionDir, dirEntries, includeHistory) {
|
|
1797
|
+
const safeId = encodeURIComponent(record.acpxRecordId);
|
|
1798
|
+
let bytesFreed = await unlinkCountingBytes(path.join(sessionDir, `${safeId}.json`));
|
|
1799
|
+
if (includeHistory) for (const name of dirEntries.filter((entry) => isSessionStreamFile(entry, safeId))) bytesFreed += await unlinkCountingBytes(path.join(sessionDir, name));
|
|
1800
|
+
return bytesFreed;
|
|
1801
|
+
}
|
|
1802
|
+
async function unlinkCountingBytes(filePath) {
|
|
1803
|
+
let bytes = 0;
|
|
1804
|
+
try {
|
|
1805
|
+
bytes = (await fs$1.stat(filePath)).size;
|
|
1806
|
+
} catch {}
|
|
1807
|
+
await fs$1.unlink(filePath).catch(() => void 0);
|
|
1808
|
+
return bytes;
|
|
1809
|
+
}
|
|
1498
1810
|
//#endregion
|
|
1499
1811
|
//#region src/permission-prompt.ts
|
|
1500
1812
|
async function promptForPermission(options) {
|
|
@@ -1691,22 +2003,67 @@ function pickOption(options, kinds) {
|
|
|
1691
2003
|
const match = options.find((option) => option.kind === kind);
|
|
1692
2004
|
if (match) return match;
|
|
1693
2005
|
}
|
|
1694
|
-
}
|
|
2006
|
+
}
|
|
2007
|
+
const TOOL_KIND_TITLE_MATCHERS = [
|
|
2008
|
+
{
|
|
2009
|
+
kind: "read",
|
|
2010
|
+
needles: ["read", "cat"]
|
|
2011
|
+
},
|
|
2012
|
+
{
|
|
2013
|
+
kind: "search",
|
|
2014
|
+
needles: [
|
|
2015
|
+
"search",
|
|
2016
|
+
"find",
|
|
2017
|
+
"grep"
|
|
2018
|
+
]
|
|
2019
|
+
},
|
|
2020
|
+
{
|
|
2021
|
+
kind: "edit",
|
|
2022
|
+
needles: [
|
|
2023
|
+
"write",
|
|
2024
|
+
"edit",
|
|
2025
|
+
"patch"
|
|
2026
|
+
]
|
|
2027
|
+
},
|
|
2028
|
+
{
|
|
2029
|
+
kind: "delete",
|
|
2030
|
+
needles: ["delete", "remove"]
|
|
2031
|
+
},
|
|
2032
|
+
{
|
|
2033
|
+
kind: "move",
|
|
2034
|
+
needles: ["move", "rename"]
|
|
2035
|
+
},
|
|
2036
|
+
{
|
|
2037
|
+
kind: "execute",
|
|
2038
|
+
needles: [
|
|
2039
|
+
"run",
|
|
2040
|
+
"execute",
|
|
2041
|
+
"bash"
|
|
2042
|
+
]
|
|
2043
|
+
},
|
|
2044
|
+
{
|
|
2045
|
+
kind: "fetch",
|
|
2046
|
+
needles: [
|
|
2047
|
+
"fetch",
|
|
2048
|
+
"http",
|
|
2049
|
+
"url"
|
|
2050
|
+
]
|
|
2051
|
+
},
|
|
2052
|
+
{
|
|
2053
|
+
kind: "think",
|
|
2054
|
+
needles: ["think"]
|
|
2055
|
+
}
|
|
2056
|
+
];
|
|
1695
2057
|
function inferToolKind(params) {
|
|
1696
2058
|
if (params.toolCall.kind) return params.toolCall.kind;
|
|
1697
2059
|
const title = params.toolCall.title?.trim().toLowerCase();
|
|
1698
2060
|
if (!title) return;
|
|
1699
2061
|
const head = title.split(":", 1)[0]?.trim();
|
|
1700
2062
|
if (!head) return;
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
if (head.includes("move") || head.includes("rename")) return "move";
|
|
1706
|
-
if (head.includes("run") || head.includes("execute") || head.includes("bash")) return "execute";
|
|
1707
|
-
if (head.includes("fetch") || head.includes("http") || head.includes("url")) return "fetch";
|
|
1708
|
-
if (head.includes("think")) return "think";
|
|
1709
|
-
return "other";
|
|
2063
|
+
return titleHeadToolKind(head) ?? "other";
|
|
2064
|
+
}
|
|
2065
|
+
function titleHeadToolKind(head) {
|
|
2066
|
+
return TOOL_KIND_TITLE_MATCHERS.find(({ needles }) => needles.some((needle) => head.includes(needle)))?.kind;
|
|
1710
2067
|
}
|
|
1711
2068
|
function isAutoApprovedReadKind(kind) {
|
|
1712
2069
|
return kind === "read" || kind === "search";
|
|
@@ -1801,6 +2158,44 @@ function buildEscalationEvent(params, matchedRule) {
|
|
|
1801
2158
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1802
2159
|
};
|
|
1803
2160
|
}
|
|
2161
|
+
function selectedOrFirst(options, allowOption) {
|
|
2162
|
+
return { response: selected((allowOption ?? options[0]).optionId) };
|
|
2163
|
+
}
|
|
2164
|
+
function selectedOrCancelled(option) {
|
|
2165
|
+
return { response: option ? selected(option.optionId) : cancelled() };
|
|
2166
|
+
}
|
|
2167
|
+
async function resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption) {
|
|
2168
|
+
if (canPromptForPermission$1()) return resolveInteractivePromptResult(params, allowOption, rejectOption);
|
|
2169
|
+
const escalation = buildEscalationEvent(params, policyMatch.matchedRule);
|
|
2170
|
+
return {
|
|
2171
|
+
response: withEscalationMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), escalation),
|
|
2172
|
+
escalation
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
async function resolveInteractivePromptResult(params, allowOption, rejectOption) {
|
|
2176
|
+
const approved = await promptForToolPermission(params);
|
|
2177
|
+
if (approved && allowOption) return { response: selected(allowOption.optionId) };
|
|
2178
|
+
if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
|
|
2179
|
+
return { response: cancelled() };
|
|
2180
|
+
}
|
|
2181
|
+
function resolvePolicyMatch(params, policyMatch, options, allowOption, rejectOption) {
|
|
2182
|
+
if (policyMatch?.action === "approve") return selectedOrFirst(options, allowOption);
|
|
2183
|
+
if (policyMatch?.action === "deny") return selectedOrCancelled(rejectOption);
|
|
2184
|
+
if (policyMatch?.action === "escalate") return resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption);
|
|
2185
|
+
}
|
|
2186
|
+
function resolveModeMatch(options, mode, allowOption, rejectOption) {
|
|
2187
|
+
if (mode === "approve-all") return selectedOrFirst(options, allowOption);
|
|
2188
|
+
if (mode === "deny-all") return selectedOrCancelled(rejectOption);
|
|
2189
|
+
}
|
|
2190
|
+
function resolveNonInteractivePermission(nonInteractivePolicy, rejectOption) {
|
|
2191
|
+
if (nonInteractivePolicy === "fail") throw new PermissionPromptUnavailableError();
|
|
2192
|
+
return selectedOrCancelled(rejectOption);
|
|
2193
|
+
}
|
|
2194
|
+
async function resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption) {
|
|
2195
|
+
if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return { response: selected(allowOption.optionId) };
|
|
2196
|
+
if (!canPromptForPermission$1()) return resolveNonInteractivePermission(nonInteractivePolicy, rejectOption);
|
|
2197
|
+
return resolveInteractivePromptResult(params, allowOption, rejectOption);
|
|
2198
|
+
}
|
|
1804
2199
|
function permissionModeSatisfies(actual, required) {
|
|
1805
2200
|
return PERMISSION_MODE_RANK[actual] >= PERMISSION_MODE_RANK[required];
|
|
1806
2201
|
}
|
|
@@ -1809,46 +2204,11 @@ async function resolvePermissionRequestWithDetails(params, mode, nonInteractiveP
|
|
|
1809
2204
|
if (options.length === 0) return { response: cancelled() };
|
|
1810
2205
|
const allowOption = pickOption(options, ["allow_once", "allow_always"]);
|
|
1811
2206
|
const rejectOption = pickOption(options, ["reject_once", "reject_always"]);
|
|
1812
|
-
const
|
|
1813
|
-
if (
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
if (policyMatch?.action === "deny") {
|
|
1818
|
-
if (rejectOption) return { response: selected(rejectOption.optionId) };
|
|
1819
|
-
return { response: cancelled() };
|
|
1820
|
-
}
|
|
1821
|
-
if (policyMatch?.action === "escalate") {
|
|
1822
|
-
if (canPromptForPermission$1()) {
|
|
1823
|
-
const approved = await promptForToolPermission(params);
|
|
1824
|
-
if (approved && allowOption) return { response: selected(allowOption.optionId) };
|
|
1825
|
-
if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
|
|
1826
|
-
return { response: cancelled() };
|
|
1827
|
-
}
|
|
1828
|
-
const escalation = buildEscalationEvent(params, policyMatch.matchedRule);
|
|
1829
|
-
return {
|
|
1830
|
-
response: withEscalationMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), escalation),
|
|
1831
|
-
escalation
|
|
1832
|
-
};
|
|
1833
|
-
}
|
|
1834
|
-
if (mode === "approve-all") {
|
|
1835
|
-
if (allowOption) return { response: selected(allowOption.optionId) };
|
|
1836
|
-
return { response: selected(options[0].optionId) };
|
|
1837
|
-
}
|
|
1838
|
-
if (mode === "deny-all") {
|
|
1839
|
-
if (rejectOption) return { response: selected(rejectOption.optionId) };
|
|
1840
|
-
return { response: cancelled() };
|
|
1841
|
-
}
|
|
1842
|
-
if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return { response: selected(allowOption.optionId) };
|
|
1843
|
-
if (!canPromptForPermission$1()) {
|
|
1844
|
-
if (nonInteractivePolicy === "fail") throw new PermissionPromptUnavailableError();
|
|
1845
|
-
if (rejectOption) return { response: selected(rejectOption.optionId) };
|
|
1846
|
-
return { response: cancelled() };
|
|
1847
|
-
}
|
|
1848
|
-
const approved = await promptForToolPermission(params);
|
|
1849
|
-
if (approved && allowOption) return { response: selected(allowOption.optionId) };
|
|
1850
|
-
if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
|
|
1851
|
-
return { response: cancelled() };
|
|
2207
|
+
const resolvedByPolicy = await resolvePolicyMatch(params, matchPermissionPolicy(params, policy), options, allowOption, rejectOption);
|
|
2208
|
+
if (resolvedByPolicy) return resolvedByPolicy;
|
|
2209
|
+
const resolvedByMode = resolveModeMatch(options, mode, allowOption, rejectOption);
|
|
2210
|
+
if (resolvedByMode) return resolvedByMode;
|
|
2211
|
+
return resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption);
|
|
1852
2212
|
}
|
|
1853
2213
|
const DECISION_FALLBACK_ORDER = {
|
|
1854
2214
|
allow_once: ["allow_once", "allow_always"],
|
|
@@ -1875,21 +2235,35 @@ function readWindowsEnvValue(env, key) {
|
|
|
1875
2235
|
const matchedKey = Object.keys(env).find((entry) => entry.toUpperCase() === key);
|
|
1876
2236
|
return matchedKey ? env[matchedKey] : void 0;
|
|
1877
2237
|
}
|
|
1878
|
-
function
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
2238
|
+
function windowsExecutableExtensions(env) {
|
|
2239
|
+
return (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
|
|
2240
|
+
}
|
|
2241
|
+
function commandCandidates(command, env) {
|
|
2242
|
+
if (path.extname(command).length > 0) return [command];
|
|
2243
|
+
return windowsExecutableExtensions(env).map((extension) => `${command}${extension}`);
|
|
2244
|
+
}
|
|
2245
|
+
function commandHasPath(command) {
|
|
2246
|
+
return command.includes("/") || command.includes("\\") || path.isAbsolute(command);
|
|
2247
|
+
}
|
|
2248
|
+
function resolveWindowsPathCommand(command, env) {
|
|
2249
|
+
const candidates = commandCandidates(command, env);
|
|
1882
2250
|
const pathValue = readWindowsEnvValue(env, "PATH");
|
|
1883
2251
|
if (!pathValue) return;
|
|
1884
2252
|
for (const directory of pathValue.split(";")) {
|
|
1885
|
-
const
|
|
1886
|
-
if (
|
|
1887
|
-
for (const candidate of candidates) {
|
|
1888
|
-
const resolved = path.join(trimmedDirectory, candidate);
|
|
1889
|
-
if (fs.existsSync(resolved)) return resolved;
|
|
1890
|
-
}
|
|
2253
|
+
const resolved = findExistingCommandInDirectory(directory, candidates);
|
|
2254
|
+
if (resolved) return resolved;
|
|
1891
2255
|
}
|
|
1892
2256
|
}
|
|
2257
|
+
function findExistingCommandInDirectory(directory, candidates) {
|
|
2258
|
+
const trimmedDirectory = directory.trim();
|
|
2259
|
+
if (trimmedDirectory.length === 0) return;
|
|
2260
|
+
return candidates.map((candidate) => path.join(trimmedDirectory, candidate)).find((resolved) => fs.existsSync(resolved));
|
|
2261
|
+
}
|
|
2262
|
+
function resolveWindowsCommand(command, env = process.env) {
|
|
2263
|
+
const candidates = commandCandidates(command, env);
|
|
2264
|
+
if (commandHasPath(command)) return candidates.find((candidate) => fs.existsSync(candidate));
|
|
2265
|
+
return resolveWindowsPathCommand(command, env);
|
|
2266
|
+
}
|
|
1893
2267
|
function shouldUseWindowsBatchShell(command, platform = process.platform, env = process.env) {
|
|
1894
2268
|
if (platform !== "win32") return false;
|
|
1895
2269
|
const resolvedCommand = resolveWindowsCommand(command, env) ?? command;
|
|
@@ -1982,32 +2356,16 @@ function splitCommandLine(value) {
|
|
|
1982
2356
|
let quote = null;
|
|
1983
2357
|
let escaping = false;
|
|
1984
2358
|
for (const ch of value) {
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
if (ch === quote) quote = null;
|
|
1996
|
-
else current += ch;
|
|
1997
|
-
continue;
|
|
1998
|
-
}
|
|
1999
|
-
if (ch === "'" || ch === "\"") {
|
|
2000
|
-
quote = ch;
|
|
2001
|
-
continue;
|
|
2002
|
-
}
|
|
2003
|
-
if (/\s/.test(ch)) {
|
|
2004
|
-
if (current.length > 0) {
|
|
2005
|
-
parts.push(current);
|
|
2006
|
-
current = "";
|
|
2007
|
-
}
|
|
2008
|
-
continue;
|
|
2009
|
-
}
|
|
2010
|
-
current += ch;
|
|
2359
|
+
const next = readCommandLineChar({
|
|
2360
|
+
ch,
|
|
2361
|
+
current,
|
|
2362
|
+
quote,
|
|
2363
|
+
escaping,
|
|
2364
|
+
parts
|
|
2365
|
+
});
|
|
2366
|
+
current = next.current;
|
|
2367
|
+
quote = next.quote;
|
|
2368
|
+
escaping = next.escaping;
|
|
2011
2369
|
}
|
|
2012
2370
|
if (escaping) current += "\\";
|
|
2013
2371
|
if (quote) throw new Error("Invalid --agent command: unterminated quote");
|
|
@@ -2018,6 +2376,59 @@ function splitCommandLine(value) {
|
|
|
2018
2376
|
args: parts.slice(1)
|
|
2019
2377
|
};
|
|
2020
2378
|
}
|
|
2379
|
+
function readCommandLineChar(state) {
|
|
2380
|
+
if (state.escaping) return {
|
|
2381
|
+
current: state.current + state.ch,
|
|
2382
|
+
quote: state.quote,
|
|
2383
|
+
escaping: false
|
|
2384
|
+
};
|
|
2385
|
+
if (state.ch === "\\" && state.quote !== "'") return {
|
|
2386
|
+
current: state.current,
|
|
2387
|
+
quote: state.quote,
|
|
2388
|
+
escaping: true
|
|
2389
|
+
};
|
|
2390
|
+
if (state.quote) return readQuotedCommandLineChar({
|
|
2391
|
+
ch: state.ch,
|
|
2392
|
+
current: state.current,
|
|
2393
|
+
quote: state.quote
|
|
2394
|
+
});
|
|
2395
|
+
return readUnquotedCommandLineChar(state);
|
|
2396
|
+
}
|
|
2397
|
+
function readQuotedCommandLineChar(state) {
|
|
2398
|
+
if (state.ch === state.quote) return {
|
|
2399
|
+
current: state.current,
|
|
2400
|
+
quote: null,
|
|
2401
|
+
escaping: false
|
|
2402
|
+
};
|
|
2403
|
+
return {
|
|
2404
|
+
current: state.current + state.ch,
|
|
2405
|
+
quote: state.quote,
|
|
2406
|
+
escaping: false
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
function readUnquotedCommandLineChar(state) {
|
|
2410
|
+
if (state.ch === "'" || state.ch === "\"") return {
|
|
2411
|
+
current: state.current,
|
|
2412
|
+
quote: state.ch,
|
|
2413
|
+
escaping: false
|
|
2414
|
+
};
|
|
2415
|
+
if (/\s/.test(state.ch)) {
|
|
2416
|
+
flushCommandLinePart(state.parts, state.current);
|
|
2417
|
+
return {
|
|
2418
|
+
current: "",
|
|
2419
|
+
quote: null,
|
|
2420
|
+
escaping: false
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
return {
|
|
2424
|
+
current: state.current + state.ch,
|
|
2425
|
+
quote: null,
|
|
2426
|
+
escaping: false
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
function flushCommandLinePart(parts, current) {
|
|
2430
|
+
if (current.length > 0) parts.push(current);
|
|
2431
|
+
}
|
|
2021
2432
|
function asAbsoluteCwd(cwd) {
|
|
2022
2433
|
return path.resolve(cwd);
|
|
2023
2434
|
}
|
|
@@ -2231,17 +2642,28 @@ async function ensureCopilotAcpSupport(command) {
|
|
|
2231
2642
|
function buildClaudeCodeOptionsMeta(options) {
|
|
2232
2643
|
if (!options) return;
|
|
2233
2644
|
const claudeCodeOptions = {};
|
|
2234
|
-
|
|
2235
|
-
if (Array.isArray(options.allowedTools)) claudeCodeOptions.allowedTools = [...options.allowedTools];
|
|
2236
|
-
if (typeof options.maxTurns === "number") claudeCodeOptions.maxTurns = options.maxTurns;
|
|
2645
|
+
assignClaudeCodeOptions(claudeCodeOptions, options);
|
|
2237
2646
|
const meta = {};
|
|
2238
2647
|
if (Object.keys(claudeCodeOptions).length > 0) meta.claudeCode = { options: claudeCodeOptions };
|
|
2239
|
-
|
|
2240
|
-
if (typeof systemPrompt === "string" && systemPrompt.length > 0) meta.systemPrompt = systemPrompt;
|
|
2241
|
-
else if (systemPrompt && typeof systemPrompt === "object" && typeof systemPrompt.append === "string" && systemPrompt.append.length > 0) meta.systemPrompt = { append: systemPrompt.append };
|
|
2648
|
+
assignClaudeCodeSystemPrompt(meta, options.systemPrompt);
|
|
2242
2649
|
if (Object.keys(meta).length === 0) return;
|
|
2243
2650
|
return meta;
|
|
2244
2651
|
}
|
|
2652
|
+
function assignClaudeCodeOptions(target, options) {
|
|
2653
|
+
if (typeof options.model === "string" && options.model.trim().length > 0) target.model = options.model;
|
|
2654
|
+
if (Array.isArray(options.allowedTools)) target.allowedTools = [...options.allowedTools];
|
|
2655
|
+
if (typeof options.maxTurns === "number") target.maxTurns = options.maxTurns;
|
|
2656
|
+
}
|
|
2657
|
+
function assignClaudeCodeSystemPrompt(target, systemPrompt) {
|
|
2658
|
+
if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
|
|
2659
|
+
target.systemPrompt = systemPrompt;
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
if (isAppendSystemPrompt(systemPrompt)) target.systemPrompt = { append: systemPrompt.append };
|
|
2663
|
+
}
|
|
2664
|
+
function isAppendSystemPrompt(value) {
|
|
2665
|
+
return !!value && typeof value === "object" && typeof value.append === "string" && value.append.length > 0;
|
|
2666
|
+
}
|
|
2245
2667
|
function resolveClaudeCodeExecutable(platform = process.platform, env = process.env) {
|
|
2246
2668
|
if (platform !== "win32") return;
|
|
2247
2669
|
if (readWindowsEnvValue(env, "CLAUDE_CODE_EXECUTABLE")) return;
|
|
@@ -2286,18 +2708,21 @@ function buildAgentEnvironment(authCredentials) {
|
|
|
2286
2708
|
const env = { ...process.env };
|
|
2287
2709
|
promotePrefixedAuthEnvironment(env);
|
|
2288
2710
|
if (!authCredentials) return env;
|
|
2289
|
-
for (const [methodId, credential] of Object.entries(authCredentials))
|
|
2290
|
-
if (typeof credential !== "string" || credential.trim().length === 0) continue;
|
|
2291
|
-
if (!methodId.includes("=") && !methodId.includes("\0") && env[methodId] == null) env[methodId] = credential;
|
|
2292
|
-
const normalized = toEnvToken(methodId);
|
|
2293
|
-
if (normalized) {
|
|
2294
|
-
const prefixed = `${AUTH_ENV_PREFIX}${normalized}`;
|
|
2295
|
-
if (env[prefixed] == null) env[prefixed] = credential;
|
|
2296
|
-
if (env[normalized] == null) env[normalized] = credential;
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2711
|
+
for (const [methodId, credential] of Object.entries(authCredentials)) assignAuthCredentialEnv(env, methodId, credential);
|
|
2299
2712
|
return env;
|
|
2300
2713
|
}
|
|
2714
|
+
function assignAuthCredentialEnv(env, methodId, credential) {
|
|
2715
|
+
if (typeof credential !== "string" || credential.trim().length === 0) return;
|
|
2716
|
+
if (!methodId.includes("=") && !methodId.includes("\0") && env[methodId] == null) env[methodId] = credential;
|
|
2717
|
+
const normalized = toEnvToken(methodId);
|
|
2718
|
+
if (normalized) {
|
|
2719
|
+
assignIfMissing(env, `${AUTH_ENV_PREFIX}${normalized}`, credential);
|
|
2720
|
+
assignIfMissing(env, normalized, credential);
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
function assignIfMissing(env, key, value) {
|
|
2724
|
+
if (env[key] == null) env[key] = value;
|
|
2725
|
+
}
|
|
2301
2726
|
function resolveConfiguredAuthCredential(methodId, authCredentials) {
|
|
2302
2727
|
const configCredentials = authCredentials ?? {};
|
|
2303
2728
|
return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)];
|
|
@@ -2315,71 +2740,6 @@ function buildAgentSpawnOptions(cwd, authCredentials) {
|
|
|
2315
2740
|
};
|
|
2316
2741
|
}
|
|
2317
2742
|
//#endregion
|
|
2318
|
-
//#region src/acp/jsonrpc.ts
|
|
2319
|
-
function asRecord$2(value) {
|
|
2320
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2321
|
-
return value;
|
|
2322
|
-
}
|
|
2323
|
-
function hasValidId(value) {
|
|
2324
|
-
return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
2325
|
-
}
|
|
2326
|
-
function isErrorObject(value) {
|
|
2327
|
-
const record = asRecord$2(value);
|
|
2328
|
-
return !!record && typeof record.code === "number" && Number.isFinite(record.code) && typeof record.message === "string";
|
|
2329
|
-
}
|
|
2330
|
-
function hasResultOrError(value) {
|
|
2331
|
-
const hasResult = Object.hasOwn(value, "result");
|
|
2332
|
-
const hasError = Object.hasOwn(value, "error");
|
|
2333
|
-
if (hasResult && hasError) return false;
|
|
2334
|
-
if (!hasResult && !hasError) return false;
|
|
2335
|
-
if (hasError && !isErrorObject(value.error)) return false;
|
|
2336
|
-
return true;
|
|
2337
|
-
}
|
|
2338
|
-
function isAcpJsonRpcMessage(value) {
|
|
2339
|
-
const record = asRecord$2(value);
|
|
2340
|
-
if (!record || record.jsonrpc !== "2.0") return false;
|
|
2341
|
-
const hasMethod = typeof record.method === "string" && record.method.length > 0;
|
|
2342
|
-
const hasId = Object.hasOwn(record, "id");
|
|
2343
|
-
if (hasMethod && !hasId) return true;
|
|
2344
|
-
if (hasMethod && hasId) return hasValidId(record.id);
|
|
2345
|
-
if (!hasMethod && hasId) {
|
|
2346
|
-
if (!hasValidId(record.id)) return false;
|
|
2347
|
-
return hasResultOrError(record);
|
|
2348
|
-
}
|
|
2349
|
-
return false;
|
|
2350
|
-
}
|
|
2351
|
-
function isJsonRpcNotification(message) {
|
|
2352
|
-
return Object.hasOwn(message, "method") && typeof message.method === "string" && !Object.hasOwn(message, "id");
|
|
2353
|
-
}
|
|
2354
|
-
function isSessionUpdateNotification(message) {
|
|
2355
|
-
return isJsonRpcNotification(message) && message.method === "session/update";
|
|
2356
|
-
}
|
|
2357
|
-
function extractSessionUpdateNotification(message) {
|
|
2358
|
-
if (!isSessionUpdateNotification(message)) return;
|
|
2359
|
-
const params = asRecord$2(message.params);
|
|
2360
|
-
if (!params) return;
|
|
2361
|
-
const sessionId = typeof params.sessionId === "string" ? params.sessionId : null;
|
|
2362
|
-
if (!sessionId) return;
|
|
2363
|
-
const update = asRecord$2(params.update);
|
|
2364
|
-
if (!update || typeof update.sessionUpdate !== "string") return;
|
|
2365
|
-
return {
|
|
2366
|
-
sessionId,
|
|
2367
|
-
update
|
|
2368
|
-
};
|
|
2369
|
-
}
|
|
2370
|
-
function parsePromptStopReason(message) {
|
|
2371
|
-
if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
|
|
2372
|
-
const record = asRecord$2(message.result);
|
|
2373
|
-
if (!record) return;
|
|
2374
|
-
return typeof record.stopReason === "string" ? record.stopReason : void 0;
|
|
2375
|
-
}
|
|
2376
|
-
function parseJsonRpcErrorMessage(message) {
|
|
2377
|
-
if (!Object.hasOwn(message, "error")) return;
|
|
2378
|
-
const errorRecord = asRecord$2(message.error);
|
|
2379
|
-
if (!errorRecord || typeof errorRecord.message !== "string") return;
|
|
2380
|
-
return errorRecord.message;
|
|
2381
|
-
}
|
|
2382
|
-
//#endregion
|
|
2383
2743
|
//#region src/acp/session-control-errors.ts
|
|
2384
2744
|
const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = new Set([-32601, -32602]);
|
|
2385
2745
|
function asRecord$1(value) {
|
|
@@ -2710,27 +3070,35 @@ var TerminalManager = class {
|
|
|
2710
3070
|
async signalProcess(terminal, signal) {
|
|
2711
3071
|
const pid = terminal.process.pid;
|
|
2712
3072
|
if (terminal.killProcessGroup && pid && process.platform === "win32") {
|
|
2713
|
-
|
|
2714
|
-
for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
|
|
2715
|
-
if (this.isRunning(terminal)) {
|
|
2716
|
-
await killWindowsProcessTree(pid, signal);
|
|
2717
|
-
return;
|
|
2718
|
-
}
|
|
2719
|
-
for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal);
|
|
3073
|
+
await this.signalWindowsProcessGroup(terminal, pid, signal);
|
|
2720
3074
|
return;
|
|
2721
3075
|
}
|
|
2722
3076
|
if (terminal.killProcessGroup && pid) {
|
|
2723
|
-
|
|
2724
|
-
for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
|
|
2725
|
-
if (process.platform !== "win32" && hasLiveProcessGroup(pid)) {
|
|
2726
|
-
sendSignal(-pid, signal);
|
|
2727
|
-
return;
|
|
2728
|
-
}
|
|
2729
|
-
for (const descendantPid of terminal.descendantPids) sendSignal(descendantPid, signal);
|
|
3077
|
+
await this.signalPosixProcessGroup(terminal, pid, signal);
|
|
2730
3078
|
return;
|
|
2731
3079
|
}
|
|
2732
3080
|
terminal.process.kill(signal);
|
|
2733
3081
|
}
|
|
3082
|
+
async signalWindowsProcessGroup(terminal, pid, signal) {
|
|
3083
|
+
await this.captureDescendantPids(terminal, pid);
|
|
3084
|
+
if (this.isRunning(terminal)) {
|
|
3085
|
+
await killWindowsProcessTree(pid, signal);
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3088
|
+
for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal);
|
|
3089
|
+
}
|
|
3090
|
+
async signalPosixProcessGroup(terminal, pid, signal) {
|
|
3091
|
+
await this.captureDescendantPids(terminal, pid);
|
|
3092
|
+
if (hasLiveProcessGroup(pid)) {
|
|
3093
|
+
sendSignal(-pid, signal);
|
|
3094
|
+
return;
|
|
3095
|
+
}
|
|
3096
|
+
for (const descendantPid of terminal.descendantPids) sendSignal(descendantPid, signal);
|
|
3097
|
+
}
|
|
3098
|
+
async captureDescendantPids(terminal, pid) {
|
|
3099
|
+
if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
|
|
3100
|
+
for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
|
|
3101
|
+
}
|
|
2734
3102
|
async waitForCleanupAfterSignal(terminal) {
|
|
2735
3103
|
return await Promise.race([this.waitForTerminalAndTrackedDescendants(terminal).then(() => true), waitMs(this.killGraceMs).then(() => false)]);
|
|
2736
3104
|
}
|
|
@@ -2790,16 +3158,7 @@ async function listDescendantPids(rootPid) {
|
|
|
2790
3158
|
return [];
|
|
2791
3159
|
}
|
|
2792
3160
|
const childrenByParent = /* @__PURE__ */ new Map();
|
|
2793
|
-
for (const line of output.split("\n"))
|
|
2794
|
-
const match = line.trim().match(/^(\d+)\s+(\d+)$/);
|
|
2795
|
-
if (!match) continue;
|
|
2796
|
-
const pid = Number(match[1]);
|
|
2797
|
-
const parentPid = Number(match[2]);
|
|
2798
|
-
if (!Number.isInteger(pid) || !Number.isInteger(parentPid) || pid <= 0 || parentPid <= 0) continue;
|
|
2799
|
-
const children = childrenByParent.get(parentPid);
|
|
2800
|
-
if (children) children.push(pid);
|
|
2801
|
-
else childrenByParent.set(parentPid, [pid]);
|
|
2802
|
-
}
|
|
3161
|
+
for (const line of output.split("\n")) addProcessListLine(childrenByParent, line);
|
|
2803
3162
|
const descendants = [];
|
|
2804
3163
|
const queue = [...childrenByParent.get(rootPid) ?? []];
|
|
2805
3164
|
for (let index = 0; index < queue.length; index += 1) {
|
|
@@ -2809,6 +3168,24 @@ async function listDescendantPids(rootPid) {
|
|
|
2809
3168
|
}
|
|
2810
3169
|
return descendants;
|
|
2811
3170
|
}
|
|
3171
|
+
function addProcessListLine(childrenByParent, line) {
|
|
3172
|
+
const parsed = parseProcessListLine(line);
|
|
3173
|
+
if (!parsed) return;
|
|
3174
|
+
const children = childrenByParent.get(parsed.parentPid);
|
|
3175
|
+
if (children) children.push(parsed.pid);
|
|
3176
|
+
else childrenByParent.set(parsed.parentPid, [parsed.pid]);
|
|
3177
|
+
}
|
|
3178
|
+
function parseProcessListLine(line) {
|
|
3179
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)$/);
|
|
3180
|
+
if (!match) return;
|
|
3181
|
+
const pid = Number(match[1]);
|
|
3182
|
+
const parentPid = Number(match[2]);
|
|
3183
|
+
if (!Number.isInteger(pid) || !Number.isInteger(parentPid) || pid <= 0 || parentPid <= 0) return;
|
|
3184
|
+
return {
|
|
3185
|
+
pid,
|
|
3186
|
+
parentPid
|
|
3187
|
+
};
|
|
3188
|
+
}
|
|
2812
3189
|
async function runProcessListCommand() {
|
|
2813
3190
|
if (process.platform === "win32") return await runWindowsProcessListCommand();
|
|
2814
3191
|
return await new Promise((resolve, reject) => {
|
|
@@ -2979,6 +3356,20 @@ const DRAIN_POLL_INTERVAL_MS = 20;
|
|
|
2979
3356
|
const AGENT_CLOSE_TERM_GRACE_MS = 1500;
|
|
2980
3357
|
const AGENT_CLOSE_KILL_GRACE_MS = 1e3;
|
|
2981
3358
|
const STARTUP_STDERR_MAX_CHARS = 8192;
|
|
3359
|
+
function toReconnectedSessionResult(response) {
|
|
3360
|
+
return {
|
|
3361
|
+
agentSessionId: extractRuntimeSessionId(response?._meta),
|
|
3362
|
+
configOptions: response?.configOptions ?? void 0,
|
|
3363
|
+
models: response?.models ?? void 0
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
3366
|
+
function childProcessIsRunning(agent) {
|
|
3367
|
+
if (!agent) return false;
|
|
3368
|
+
return agent.exitCode == null && agent.signalCode == null && !agent.killed;
|
|
3369
|
+
}
|
|
3370
|
+
function cancelledPermissionResponse() {
|
|
3371
|
+
return { outcome: { outcome: "cancelled" } };
|
|
3372
|
+
}
|
|
2982
3373
|
function shouldSuppressSdkConsoleError(args) {
|
|
2983
3374
|
if (args.length === 0) return false;
|
|
2984
3375
|
return typeof args[0] === "string" && args[0] === "Error handling request";
|
|
@@ -2993,6 +3384,19 @@ function installSdkConsoleErrorSuppression() {
|
|
|
2993
3384
|
console.error = originalConsoleError;
|
|
2994
3385
|
};
|
|
2995
3386
|
}
|
|
3387
|
+
function enqueueNdJsonLine(agentCommand, line, controller) {
|
|
3388
|
+
const trimmedLine = line.trim();
|
|
3389
|
+
if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) return;
|
|
3390
|
+
try {
|
|
3391
|
+
const message = JSON.parse(trimmedLine);
|
|
3392
|
+
controller.enqueue(message);
|
|
3393
|
+
} catch (err) {
|
|
3394
|
+
console.error("Failed to parse JSON message:", trimmedLine, err);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
function enqueueNdJsonLines(agentCommand, lines, controller) {
|
|
3398
|
+
for (const line of lines) enqueueNdJsonLine(agentCommand, line, controller);
|
|
3399
|
+
}
|
|
2996
3400
|
function createNdJsonMessageStream(agentCommand, output, input) {
|
|
2997
3401
|
const textEncoder = new TextEncoder();
|
|
2998
3402
|
const textDecoder = new TextDecoder();
|
|
@@ -3008,16 +3412,7 @@ function createNdJsonMessageStream(agentCommand, output, input) {
|
|
|
3008
3412
|
content += textDecoder.decode(value, { stream: true });
|
|
3009
3413
|
const lines = content.split("\n");
|
|
3010
3414
|
content = lines.pop() || "";
|
|
3011
|
-
|
|
3012
|
-
const trimmedLine = line.trim();
|
|
3013
|
-
if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) continue;
|
|
3014
|
-
try {
|
|
3015
|
-
const message = JSON.parse(trimmedLine);
|
|
3016
|
-
controller.enqueue(message);
|
|
3017
|
-
} catch (err) {
|
|
3018
|
-
console.error("Failed to parse JSON message:", trimmedLine, err);
|
|
3019
|
-
}
|
|
3020
|
-
}
|
|
3415
|
+
enqueueNdJsonLines(agentCommand, lines, controller);
|
|
3021
3416
|
}
|
|
3022
3417
|
} finally {
|
|
3023
3418
|
reader.releaseLock();
|
|
@@ -3105,7 +3500,7 @@ var AcpClient = class {
|
|
|
3105
3500
|
}
|
|
3106
3501
|
getAgentLifecycleSnapshot() {
|
|
3107
3502
|
const pid = this.agent?.pid ?? this.lastKnownPid;
|
|
3108
|
-
const running =
|
|
3503
|
+
const running = childProcessIsRunning(this.agent);
|
|
3109
3504
|
return {
|
|
3110
3505
|
pid,
|
|
3111
3506
|
startedAt: this.agentStartedAt,
|
|
@@ -3116,9 +3511,15 @@ var AcpClient = class {
|
|
|
3116
3511
|
supportsLoadSession() {
|
|
3117
3512
|
return Boolean(this.initResult?.agentCapabilities?.loadSession);
|
|
3118
3513
|
}
|
|
3514
|
+
supportsResumeSession() {
|
|
3515
|
+
return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.resume);
|
|
3516
|
+
}
|
|
3119
3517
|
supportsCloseSession() {
|
|
3120
3518
|
return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.close);
|
|
3121
3519
|
}
|
|
3520
|
+
supportsListSessions() {
|
|
3521
|
+
return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.list);
|
|
3522
|
+
}
|
|
3122
3523
|
setEventHandlers(handlers) {
|
|
3123
3524
|
this.eventHandlers = { ...handlers };
|
|
3124
3525
|
}
|
|
@@ -3126,17 +3527,20 @@ var AcpClient = class {
|
|
|
3126
3527
|
this.eventHandlers = {};
|
|
3127
3528
|
}
|
|
3128
3529
|
updateRuntimeOptions(options) {
|
|
3530
|
+
const shouldRefreshPermissionPolicy = options.permissionMode !== void 0 || options.nonInteractivePermissions !== void 0;
|
|
3129
3531
|
if (options.permissionMode) this.options.permissionMode = options.permissionMode;
|
|
3130
3532
|
if (options.nonInteractivePermissions !== void 0) this.options.nonInteractivePermissions = options.nonInteractivePermissions;
|
|
3131
3533
|
if (Object.prototype.hasOwnProperty.call(options, "permissionPolicy")) this.options.permissionPolicy = options.permissionPolicy;
|
|
3132
3534
|
if (options.terminal !== void 0) this.options.terminal = options.terminal;
|
|
3133
|
-
|
|
3134
|
-
this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
3135
|
-
this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
3136
|
-
}
|
|
3535
|
+
this.refreshRuntimePermissionPolicy(shouldRefreshPermissionPolicy);
|
|
3137
3536
|
if (options.suppressSdkConsoleErrors !== void 0) this.options.suppressSdkConsoleErrors = options.suppressSdkConsoleErrors;
|
|
3138
3537
|
if (options.verbose !== void 0) this.options.verbose = options.verbose;
|
|
3139
3538
|
}
|
|
3539
|
+
refreshRuntimePermissionPolicy(enabled) {
|
|
3540
|
+
if (!enabled) return;
|
|
3541
|
+
this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
3542
|
+
this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
3543
|
+
}
|
|
3140
3544
|
hasReusableSession(sessionId) {
|
|
3141
3545
|
return this.connection != null && this.agent != null && isChildProcessRunning(this.agent) && this.loadedSessionId === sessionId;
|
|
3142
3546
|
}
|
|
@@ -3148,32 +3552,10 @@ var AcpClient = class {
|
|
|
3148
3552
|
async start() {
|
|
3149
3553
|
if (this.connection && this.agent && isChildProcessRunning(this.agent)) return;
|
|
3150
3554
|
if (this.connection || this.agent) await this.close();
|
|
3151
|
-
const
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
args = await resolveGeminiCommandArgs(spawnCommand, args);
|
|
3156
|
-
if (isQoderAcpCommand(spawnCommand, args)) args = buildQoderAcpCommandArgs(args, this.options);
|
|
3157
|
-
if (resolvedBuiltInLaunch?.source === "installed") this.log(`spawning installed built-in agent ${resolvedBuiltInLaunch.packageName}${resolvedBuiltInLaunch.packageVersion ? `@${resolvedBuiltInLaunch.packageVersion}` : ""} via ${spawnCommand} ${args.join(" ")}`);
|
|
3158
|
-
else if (resolvedBuiltInLaunch?.source === "package-exec") this.log(`spawning built-in agent ${resolvedBuiltInLaunch.packageName}@${resolvedBuiltInLaunch.packageRange} via current Node package exec bridge ${spawnCommand} ${args.join(" ")}`);
|
|
3159
|
-
else this.log(`spawning agent: ${spawnCommand} ${args.join(" ")}`);
|
|
3160
|
-
const geminiAcp = isGeminiAcpCommand(spawnCommand, args);
|
|
3161
|
-
if (isCopilotAcpCommand(spawnCommand, args)) await ensureCopilotAcpSupport(spawnCommand);
|
|
3162
|
-
const agentSpawnOptions = buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials);
|
|
3163
|
-
if (isClaudeAcpCommand(spawnCommand, args)) {
|
|
3164
|
-
const claudeExe = resolveClaudeCodeExecutable(process.platform, agentSpawnOptions.env);
|
|
3165
|
-
if (claudeExe) {
|
|
3166
|
-
agentSpawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
|
|
3167
|
-
this.log(`resolved system Claude Code executable: ${claudeExe}`);
|
|
3168
|
-
}
|
|
3169
|
-
}
|
|
3170
|
-
const spawnedChild = spawn(spawnCommand, args, buildSpawnCommandOptions(spawnCommand, agentSpawnOptions));
|
|
3171
|
-
try {
|
|
3172
|
-
await waitForSpawn$1(spawnedChild);
|
|
3173
|
-
} catch (error) {
|
|
3174
|
-
throw new AgentSpawnError(this.options.agentCommand, error);
|
|
3175
|
-
}
|
|
3176
|
-
const child = requireAgentStdio(spawnedChild);
|
|
3555
|
+
const launch = await this.resolveAgentLaunchPlan();
|
|
3556
|
+
this.logAgentLaunch(launch);
|
|
3557
|
+
await this.ensureLaunchSupport(launch);
|
|
3558
|
+
const child = await this.spawnAgentProcess(launch);
|
|
3177
3559
|
this.closing = false;
|
|
3178
3560
|
this.agentStartedAt = isoNow$1();
|
|
3179
3561
|
this.lastAgentExit = void 0;
|
|
@@ -3187,7 +3569,69 @@ var AcpClient = class {
|
|
|
3187
3569
|
});
|
|
3188
3570
|
const input = Writable.toWeb(child.stdin);
|
|
3189
3571
|
const output = Readable.toWeb(child.stdout);
|
|
3190
|
-
const
|
|
3572
|
+
const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output));
|
|
3573
|
+
const connection = this.createConnection(stream);
|
|
3574
|
+
connection.signal.addEventListener("abort", () => {
|
|
3575
|
+
this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
|
|
3576
|
+
}, { once: true });
|
|
3577
|
+
const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
|
|
3578
|
+
await this.initializeAgentConnection({
|
|
3579
|
+
child,
|
|
3580
|
+
connection,
|
|
3581
|
+
startupFailure,
|
|
3582
|
+
startupStderr,
|
|
3583
|
+
launch
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3586
|
+
async resolveAgentLaunchPlan() {
|
|
3587
|
+
const configuredCommand = splitCommandLine(this.options.agentCommand);
|
|
3588
|
+
const resolvedBuiltInLaunch = resolveBuiltInAgentLaunch(this.options.agentCommand);
|
|
3589
|
+
const spawnCommand = resolvedBuiltInLaunch?.command ?? configuredCommand.command;
|
|
3590
|
+
let args = resolvedBuiltInLaunch?.args ?? configuredCommand.args;
|
|
3591
|
+
args = await resolveGeminiCommandArgs(spawnCommand, args);
|
|
3592
|
+
if (isQoderAcpCommand(spawnCommand, args)) args = buildQoderAcpCommandArgs(args, this.options);
|
|
3593
|
+
return {
|
|
3594
|
+
spawnCommand,
|
|
3595
|
+
args,
|
|
3596
|
+
resolvedBuiltInLaunch,
|
|
3597
|
+
geminiAcp: isGeminiAcpCommand(spawnCommand, args),
|
|
3598
|
+
copilotAcp: isCopilotAcpCommand(spawnCommand, args),
|
|
3599
|
+
claudeAcp: isClaudeAcpCommand(spawnCommand, args),
|
|
3600
|
+
spawnOptions: buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials)
|
|
3601
|
+
};
|
|
3602
|
+
}
|
|
3603
|
+
logAgentLaunch(plan) {
|
|
3604
|
+
const launch = plan.resolvedBuiltInLaunch;
|
|
3605
|
+
if (launch?.source === "installed") {
|
|
3606
|
+
this.log(`spawning installed built-in agent ${launch.packageName}${launch.packageVersion ? `@${launch.packageVersion}` : ""} via ${plan.spawnCommand} ${plan.args.join(" ")}`);
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
if (launch?.source === "package-exec") {
|
|
3610
|
+
this.log(`spawning built-in agent ${launch.packageName}@${launch.packageRange} via current Node package exec bridge ${plan.spawnCommand} ${plan.args.join(" ")}`);
|
|
3611
|
+
return;
|
|
3612
|
+
}
|
|
3613
|
+
this.log(`spawning agent: ${plan.spawnCommand} ${plan.args.join(" ")}`);
|
|
3614
|
+
}
|
|
3615
|
+
async ensureLaunchSupport(plan) {
|
|
3616
|
+
if (plan.copilotAcp) await ensureCopilotAcpSupport(plan.spawnCommand);
|
|
3617
|
+
if (!plan.claudeAcp) return;
|
|
3618
|
+
const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env);
|
|
3619
|
+
if (claudeExe) {
|
|
3620
|
+
plan.spawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
|
|
3621
|
+
this.log(`resolved system Claude Code executable: ${claudeExe}`);
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
async spawnAgentProcess(plan) {
|
|
3625
|
+
const spawnedChild = spawn(plan.spawnCommand, plan.args, buildSpawnCommandOptions(plan.spawnCommand, plan.spawnOptions));
|
|
3626
|
+
try {
|
|
3627
|
+
await waitForSpawn$1(spawnedChild);
|
|
3628
|
+
} catch (error) {
|
|
3629
|
+
throw new AgentSpawnError(this.options.agentCommand, error);
|
|
3630
|
+
}
|
|
3631
|
+
return requireAgentStdio(spawnedChild);
|
|
3632
|
+
}
|
|
3633
|
+
createConnection(stream) {
|
|
3634
|
+
return new ClientSideConnection(() => ({
|
|
3191
3635
|
sessionUpdate: async (params) => {
|
|
3192
3636
|
await this.handleSessionUpdate(params);
|
|
3193
3637
|
},
|
|
@@ -3215,49 +3659,51 @@ var AcpClient = class {
|
|
|
3215
3659
|
releaseTerminal: async (params) => {
|
|
3216
3660
|
return this.handleReleaseTerminal(params);
|
|
3217
3661
|
}
|
|
3218
|
-
}),
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
}, { once: true });
|
|
3222
|
-
const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
|
|
3662
|
+
}), stream);
|
|
3663
|
+
}
|
|
3664
|
+
async initializeAgentConnection(params) {
|
|
3223
3665
|
try {
|
|
3224
|
-
const initResult = await Promise.race([(
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
fs: {
|
|
3229
|
-
readTextFile: true,
|
|
3230
|
-
writeTextFile: true
|
|
3231
|
-
},
|
|
3232
|
-
terminal: this.options.terminal !== false
|
|
3233
|
-
},
|
|
3234
|
-
clientInfo: {
|
|
3235
|
-
name: "acpx",
|
|
3236
|
-
version: "0.1.0"
|
|
3237
|
-
}
|
|
3238
|
-
});
|
|
3239
|
-
const initialized = geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
|
|
3240
|
-
await this.authenticateIfRequired(connection, initialized.authMethods ?? []);
|
|
3241
|
-
return initialized;
|
|
3242
|
-
})(), startupFailure.promise]);
|
|
3243
|
-
startupFailure.dispose();
|
|
3244
|
-
this.connection = connection;
|
|
3245
|
-
this.agent = child;
|
|
3666
|
+
const initResult = await Promise.race([this.initializeProtocolConnection(params.connection, params.launch.geminiAcp), params.startupFailure.promise]);
|
|
3667
|
+
params.startupFailure.dispose();
|
|
3668
|
+
this.connection = params.connection;
|
|
3669
|
+
this.agent = params.child;
|
|
3246
3670
|
this.initResult = initResult;
|
|
3247
3671
|
this.log(`initialized protocol version ${initResult.protocolVersion}`);
|
|
3248
3672
|
} catch (error) {
|
|
3249
|
-
|
|
3250
|
-
const normalizedError = await this.normalizeInitializeError(error, child, startupStderr);
|
|
3251
|
-
try {
|
|
3252
|
-
child.kill();
|
|
3253
|
-
} catch {}
|
|
3254
|
-
if (geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(spawnCommand), {
|
|
3255
|
-
cause: error,
|
|
3256
|
-
retryable: true
|
|
3257
|
-
});
|
|
3258
|
-
throw normalizedError;
|
|
3673
|
+
await this.handleInitializeFailure(params, error);
|
|
3259
3674
|
}
|
|
3260
3675
|
}
|
|
3676
|
+
async initializeProtocolConnection(connection, geminiAcp) {
|
|
3677
|
+
const initializePromise = connection.initialize({
|
|
3678
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
3679
|
+
clientCapabilities: {
|
|
3680
|
+
fs: {
|
|
3681
|
+
readTextFile: true,
|
|
3682
|
+
writeTextFile: true
|
|
3683
|
+
},
|
|
3684
|
+
terminal: this.options.terminal !== false
|
|
3685
|
+
},
|
|
3686
|
+
clientInfo: {
|
|
3687
|
+
name: "acpx",
|
|
3688
|
+
version: "0.1.0"
|
|
3689
|
+
}
|
|
3690
|
+
});
|
|
3691
|
+
const initialized = geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
|
|
3692
|
+
await this.authenticateIfRequired(connection, initialized.authMethods ?? []);
|
|
3693
|
+
return initialized;
|
|
3694
|
+
}
|
|
3695
|
+
async handleInitializeFailure(params, error) {
|
|
3696
|
+
params.startupFailure.dispose();
|
|
3697
|
+
const normalizedError = await this.normalizeInitializeError(error, params.child, params.startupStderr);
|
|
3698
|
+
try {
|
|
3699
|
+
params.child.kill();
|
|
3700
|
+
} catch {}
|
|
3701
|
+
if (params.launch.geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(params.launch.spawnCommand), {
|
|
3702
|
+
cause: error,
|
|
3703
|
+
retryable: true
|
|
3704
|
+
});
|
|
3705
|
+
throw normalizedError;
|
|
3706
|
+
}
|
|
3261
3707
|
createTappedStream(base) {
|
|
3262
3708
|
const onAcpMessage = () => this.eventHandlers.onAcpMessage;
|
|
3263
3709
|
const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage;
|
|
@@ -3330,10 +3776,7 @@ var AcpClient = class {
|
|
|
3330
3776
|
async loadSessionWithOptions(sessionId, cwd = this.options.cwd, options = {}) {
|
|
3331
3777
|
const connection = this.getConnection();
|
|
3332
3778
|
const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
|
|
3333
|
-
const previousSuppression = this.
|
|
3334
|
-
const previousReplaySuppression = this.suppressReplaySessionUpdateMessages;
|
|
3335
|
-
this.suppressSessionUpdates = previousSuppression || Boolean(options.suppressReplayUpdates);
|
|
3336
|
-
this.suppressReplaySessionUpdateMessages = previousReplaySuppression || Boolean(options.suppressReplayUpdates);
|
|
3779
|
+
const previousSuppression = this.applySessionUpdateSuppression(Boolean(options.suppressReplayUpdates));
|
|
3337
3780
|
let response;
|
|
3338
3781
|
try {
|
|
3339
3782
|
response = await this.runConnectionRequest(() => connection.loadSession({
|
|
@@ -3343,24 +3786,44 @@ var AcpClient = class {
|
|
|
3343
3786
|
}));
|
|
3344
3787
|
await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
|
|
3345
3788
|
} finally {
|
|
3346
|
-
this.
|
|
3347
|
-
this.suppressReplaySessionUpdateMessages = previousReplaySuppression;
|
|
3789
|
+
this.restoreSessionUpdateSuppression(previousSuppression);
|
|
3348
3790
|
}
|
|
3349
3791
|
this.loadedSessionId = sessionId;
|
|
3350
|
-
return
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3792
|
+
return toReconnectedSessionResult(response);
|
|
3793
|
+
}
|
|
3794
|
+
async resumeSession(sessionId, cwd = this.options.cwd) {
|
|
3795
|
+
const connection = this.getConnection();
|
|
3796
|
+
const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
|
|
3797
|
+
const response = await this.runConnectionRequest(() => connection.resumeSession({
|
|
3798
|
+
sessionId,
|
|
3799
|
+
cwd: sessionCwd,
|
|
3800
|
+
mcpServers: this.options.mcpServers ?? []
|
|
3801
|
+
}));
|
|
3802
|
+
this.loadedSessionId = sessionId;
|
|
3803
|
+
return toReconnectedSessionResult(response);
|
|
3804
|
+
}
|
|
3805
|
+
applySessionUpdateSuppression(enabled) {
|
|
3806
|
+
const previous = {
|
|
3807
|
+
suppressSessionUpdates: this.suppressSessionUpdates,
|
|
3808
|
+
suppressReplaySessionUpdateMessages: this.suppressReplaySessionUpdateMessages
|
|
3354
3809
|
};
|
|
3810
|
+
this.suppressSessionUpdates = previous.suppressSessionUpdates || enabled;
|
|
3811
|
+
this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages || enabled;
|
|
3812
|
+
return previous;
|
|
3813
|
+
}
|
|
3814
|
+
restoreSessionUpdateSuppression(previous) {
|
|
3815
|
+
this.suppressSessionUpdates = previous.suppressSessionUpdates;
|
|
3816
|
+
this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages;
|
|
3355
3817
|
}
|
|
3356
3818
|
async prompt(sessionId, prompt) {
|
|
3357
3819
|
const connection = this.getConnection();
|
|
3820
|
+
const normalizedPrompt = this.normalizePromptForAgent(prompt);
|
|
3358
3821
|
const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
|
|
3359
3822
|
let promptPromise;
|
|
3360
3823
|
try {
|
|
3361
3824
|
promptPromise = this.runConnectionRequest(() => connection.prompt({
|
|
3362
3825
|
sessionId,
|
|
3363
|
-
prompt:
|
|
3826
|
+
prompt: normalizedPrompt
|
|
3364
3827
|
}));
|
|
3365
3828
|
} catch (error) {
|
|
3366
3829
|
restoreConsoleError?.();
|
|
@@ -3371,13 +3834,9 @@ var AcpClient = class {
|
|
|
3371
3834
|
promise: promptPromise
|
|
3372
3835
|
};
|
|
3373
3836
|
try {
|
|
3374
|
-
|
|
3375
|
-
const permissionFailure = this.consumePromptPermissionFailure(sessionId);
|
|
3376
|
-
if (permissionFailure) throw permissionFailure;
|
|
3377
|
-
return response;
|
|
3837
|
+
return this.returnPromptResponseOrPermissionFailure(sessionId, await promptPromise);
|
|
3378
3838
|
} catch (error) {
|
|
3379
|
-
|
|
3380
|
-
if (permissionFailure) throw permissionFailure;
|
|
3839
|
+
this.throwPromptPermissionFailureIfPresent(sessionId);
|
|
3381
3840
|
throw error;
|
|
3382
3841
|
} finally {
|
|
3383
3842
|
restoreConsoleError?.();
|
|
@@ -3387,6 +3846,20 @@ var AcpClient = class {
|
|
|
3387
3846
|
this.promptPermissionFailures.delete(sessionId);
|
|
3388
3847
|
}
|
|
3389
3848
|
}
|
|
3849
|
+
normalizePromptForAgent(prompt) {
|
|
3850
|
+
const normalizedPrompt = typeof prompt === "string" ? textPrompt(prompt) : prompt;
|
|
3851
|
+
const unsupportedPromptContent = getUnsupportedPromptContentMessage(normalizedPrompt, this.initResult?.agentCapabilities);
|
|
3852
|
+
if (unsupportedPromptContent) throw new UnsupportedPromptContentError(unsupportedPromptContent);
|
|
3853
|
+
return normalizedPrompt;
|
|
3854
|
+
}
|
|
3855
|
+
returnPromptResponseOrPermissionFailure(sessionId, response) {
|
|
3856
|
+
this.throwPromptPermissionFailureIfPresent(sessionId);
|
|
3857
|
+
return response;
|
|
3858
|
+
}
|
|
3859
|
+
throwPromptPermissionFailureIfPresent(sessionId) {
|
|
3860
|
+
const permissionFailure = this.consumePromptPermissionFailure(sessionId);
|
|
3861
|
+
if (permissionFailure) throw permissionFailure;
|
|
3862
|
+
}
|
|
3390
3863
|
async setSessionMode(sessionId, modeId) {
|
|
3391
3864
|
const connection = this.getConnection();
|
|
3392
3865
|
try {
|
|
@@ -3437,6 +3910,10 @@ var AcpClient = class {
|
|
|
3437
3910
|
await this.runConnectionRequest(() => connection.closeSession({ sessionId }));
|
|
3438
3911
|
if (this.loadedSessionId === sessionId) this.loadedSessionId = void 0;
|
|
3439
3912
|
}
|
|
3913
|
+
async listSessions(params = {}) {
|
|
3914
|
+
const connection = this.getConnection();
|
|
3915
|
+
return await this.runConnectionRequest(() => connection.listSessions(params));
|
|
3916
|
+
}
|
|
3440
3917
|
async requestCancelActivePrompt() {
|
|
3441
3918
|
const active = this.activePrompt;
|
|
3442
3919
|
if (!active) return false;
|
|
@@ -3486,25 +3963,28 @@ var AcpClient = class {
|
|
|
3486
3963
|
}
|
|
3487
3964
|
async terminateAgentProcess(child) {
|
|
3488
3965
|
const stdinCloseGraceMs = resolveAgentCloseAfterStdinEndMs(this.options.agentCommand);
|
|
3489
|
-
|
|
3490
|
-
child.stdin.end();
|
|
3491
|
-
} catch {}
|
|
3966
|
+
this.endAgentStdin(child);
|
|
3492
3967
|
let exited = await waitForChildExit(child, stdinCloseGraceMs);
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
child.kill("SIGTERM");
|
|
3496
|
-
} catch {}
|
|
3497
|
-
exited = await waitForChildExit(child, AGENT_CLOSE_TERM_GRACE_MS);
|
|
3498
|
-
}
|
|
3499
|
-
if (!exited && isChildProcessRunning(child)) {
|
|
3968
|
+
exited = await this.killAgentIfRunning(child, exited, "SIGTERM", AGENT_CLOSE_TERM_GRACE_MS);
|
|
3969
|
+
if (!exited) {
|
|
3500
3970
|
this.log(`agent did not exit after ${AGENT_CLOSE_TERM_GRACE_MS}ms; forcing SIGKILL`);
|
|
3501
|
-
|
|
3502
|
-
child.kill("SIGKILL");
|
|
3503
|
-
} catch {}
|
|
3504
|
-
exited = await waitForChildExit(child, AGENT_CLOSE_KILL_GRACE_MS);
|
|
3971
|
+
exited = await this.killAgentIfRunning(child, exited, "SIGKILL", AGENT_CLOSE_KILL_GRACE_MS);
|
|
3505
3972
|
}
|
|
3506
3973
|
this.detachAgentHandles(child, !exited);
|
|
3507
3974
|
}
|
|
3975
|
+
endAgentStdin(child) {
|
|
3976
|
+
if (child.stdin.destroyed) return;
|
|
3977
|
+
try {
|
|
3978
|
+
child.stdin.end();
|
|
3979
|
+
} catch {}
|
|
3980
|
+
}
|
|
3981
|
+
async killAgentIfRunning(child, alreadyExited, signal, waitMs) {
|
|
3982
|
+
if (alreadyExited || !isChildProcessRunning(child)) return alreadyExited;
|
|
3983
|
+
try {
|
|
3984
|
+
child.kill(signal);
|
|
3985
|
+
} catch {}
|
|
3986
|
+
return await waitForChildExit(child, waitMs);
|
|
3987
|
+
}
|
|
3508
3988
|
detachAgentHandles(agent, unref) {
|
|
3509
3989
|
const stdin = agent.stdin;
|
|
3510
3990
|
const stdout = agent.stdout;
|
|
@@ -3625,49 +4105,71 @@ var AcpClient = class {
|
|
|
3625
4105
|
this.log(`authenticated with method ${selected.methodId} (${selected.source})`);
|
|
3626
4106
|
}
|
|
3627
4107
|
async handlePermissionRequest(params) {
|
|
3628
|
-
if (this.cancellingSessionIds.has(params.sessionId)) return
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
inferredKind: inferToolKind(params)
|
|
3636
|
-
}, { signal });
|
|
3637
|
-
if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
|
|
3638
|
-
this.recordPermissionDecision("cancelled");
|
|
3639
|
-
return { outcome: { outcome: "cancelled" } };
|
|
3640
|
-
}
|
|
3641
|
-
if (decision) {
|
|
3642
|
-
const response = decisionToResponse(params, decision);
|
|
3643
|
-
this.recordPermissionDecision(classifyPermissionDecision(params, response));
|
|
3644
|
-
return response;
|
|
3645
|
-
}
|
|
3646
|
-
} catch (error) {
|
|
3647
|
-
if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
|
|
3648
|
-
this.recordPermissionDecision("cancelled");
|
|
3649
|
-
return { outcome: { outcome: "cancelled" } };
|
|
3650
|
-
}
|
|
3651
|
-
this.log(`onPermissionRequest threw, falling through to mode-based resolver: ${error instanceof Error ? error.message : String(error)}`);
|
|
3652
|
-
}
|
|
4108
|
+
if (this.cancellingSessionIds.has(params.sessionId)) return cancelledPermissionResponse();
|
|
4109
|
+
const hostResponse = await this.tryHandlePermissionRequestWithHost(params);
|
|
4110
|
+
if (hostResponse) return hostResponse;
|
|
4111
|
+
const { response, recorded } = await this.resolvePermissionRequestFromMode(params);
|
|
4112
|
+
if (!recorded) {
|
|
4113
|
+
const decision = classifyPermissionDecision(params, response);
|
|
4114
|
+
this.recordPermissionDecision(decision);
|
|
3653
4115
|
}
|
|
3654
|
-
|
|
4116
|
+
return response;
|
|
4117
|
+
}
|
|
4118
|
+
async tryHandlePermissionRequestWithHost(params) {
|
|
4119
|
+
if (!this.options.onPermissionRequest) return;
|
|
4120
|
+
const signal = this.cancellationSignalForSession(params.sessionId);
|
|
3655
4121
|
try {
|
|
3656
|
-
const
|
|
3657
|
-
|
|
3658
|
-
|
|
4122
|
+
const decision = await this.options.onPermissionRequest({
|
|
4123
|
+
sessionId: params.sessionId,
|
|
4124
|
+
raw: params,
|
|
4125
|
+
inferredKind: inferToolKind(params)
|
|
4126
|
+
}, { signal });
|
|
4127
|
+
return this.hostPermissionDecisionResponse(params, signal, decision);
|
|
3659
4128
|
} catch (error) {
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
4129
|
+
return this.hostPermissionErrorResponse(params, signal, error);
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
hostPermissionDecisionResponse(params, signal, decision) {
|
|
4133
|
+
if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
|
|
4134
|
+
this.recordPermissionDecision("cancelled");
|
|
4135
|
+
return cancelledPermissionResponse();
|
|
3666
4136
|
}
|
|
3667
|
-
|
|
3668
|
-
|
|
4137
|
+
if (!decision) return;
|
|
4138
|
+
const response = decisionToResponse(params, decision);
|
|
4139
|
+
this.recordPermissionDecision(classifyPermissionDecision(params, response));
|
|
3669
4140
|
return response;
|
|
3670
4141
|
}
|
|
4142
|
+
hostPermissionErrorResponse(params, signal, error) {
|
|
4143
|
+
if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
|
|
4144
|
+
this.recordPermissionDecision("cancelled");
|
|
4145
|
+
return cancelledPermissionResponse();
|
|
4146
|
+
}
|
|
4147
|
+
this.log(`onPermissionRequest threw, falling through to mode-based resolver: ${error instanceof Error ? error.message : String(error)}`);
|
|
4148
|
+
}
|
|
4149
|
+
async resolvePermissionRequestFromMode(params) {
|
|
4150
|
+
try {
|
|
4151
|
+
const result = await resolvePermissionRequestWithDetails(params, this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny", this.options.permissionPolicy);
|
|
4152
|
+
this.emitPermissionEscalation(result.escalation);
|
|
4153
|
+
return {
|
|
4154
|
+
response: result.response,
|
|
4155
|
+
recorded: false
|
|
4156
|
+
};
|
|
4157
|
+
} catch (error) {
|
|
4158
|
+
return this.handleModePermissionError(params.sessionId, error);
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
emitPermissionEscalation(escalation) {
|
|
4162
|
+
if (escalation) this.eventHandlers.onPermissionEscalation?.(escalation);
|
|
4163
|
+
}
|
|
4164
|
+
handleModePermissionError(sessionId, error) {
|
|
4165
|
+
if (!(error instanceof PermissionPromptUnavailableError)) throw error;
|
|
4166
|
+
this.notePromptPermissionFailure(sessionId, error);
|
|
4167
|
+
this.recordPermissionDecision("cancelled");
|
|
4168
|
+
return {
|
|
4169
|
+
response: cancelledPermissionResponse(),
|
|
4170
|
+
recorded: true
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
3671
4173
|
attachAgentLifecycleObservers(child) {
|
|
3672
4174
|
child.once("exit", (exitCode, signal) => {
|
|
3673
4175
|
this.recordAgentExit("process_exit", exitCode, signal);
|
|
@@ -3837,25 +4339,54 @@ var AcpClient = class {
|
|
|
3837
4339
|
}
|
|
3838
4340
|
};
|
|
3839
4341
|
//#endregion
|
|
4342
|
+
//#region src/runtime/engine/lifecycle.ts
|
|
4343
|
+
function applyLifecycleSnapshotToRecord(record, snapshot) {
|
|
4344
|
+
if (!snapshot) return;
|
|
4345
|
+
record.pid = snapshot.running ? snapshot.pid : void 0;
|
|
4346
|
+
record.agentStartedAt = snapshot.startedAt;
|
|
4347
|
+
if (snapshot.lastExit) {
|
|
4348
|
+
record.lastAgentExitCode = snapshot.lastExit.exitCode;
|
|
4349
|
+
record.lastAgentExitSignal = snapshot.lastExit.signal;
|
|
4350
|
+
record.lastAgentExitAt = snapshot.lastExit.exitedAt;
|
|
4351
|
+
record.lastAgentDisconnectReason = snapshot.lastExit.reason;
|
|
4352
|
+
return;
|
|
4353
|
+
}
|
|
4354
|
+
record.lastAgentExitCode = void 0;
|
|
4355
|
+
record.lastAgentExitSignal = void 0;
|
|
4356
|
+
record.lastAgentExitAt = void 0;
|
|
4357
|
+
record.lastAgentDisconnectReason = void 0;
|
|
4358
|
+
}
|
|
4359
|
+
function reconcileAgentSessionId(record, agentSessionId) {
|
|
4360
|
+
const normalized = normalizeRuntimeSessionId(agentSessionId);
|
|
4361
|
+
if (!normalized) return;
|
|
4362
|
+
record.agentSessionId = normalized;
|
|
4363
|
+
}
|
|
4364
|
+
function sessionHasAgentMessages(recordOrConversation) {
|
|
4365
|
+
return recordOrConversation.messages.some((message) => typeof message === "object" && message !== null && "Agent" in message);
|
|
4366
|
+
}
|
|
4367
|
+
function applyConversation(record, conversation) {
|
|
4368
|
+
record.title = conversation.title;
|
|
4369
|
+
record.updated_at = conversation.updated_at;
|
|
4370
|
+
record.messages = conversation.messages;
|
|
4371
|
+
record.cumulative_token_usage = conversation.cumulative_token_usage;
|
|
4372
|
+
record.request_token_usage = conversation.request_token_usage;
|
|
4373
|
+
}
|
|
4374
|
+
//#endregion
|
|
3840
4375
|
//#region src/runtime/engine/session-options.ts
|
|
3841
4376
|
function mergeSessionOptions(preferred, fallback) {
|
|
3842
4377
|
const merged = { ...fallback };
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
4378
|
+
assignDefinedOption(merged, "model", preferred?.model);
|
|
4379
|
+
assignDefinedOption(merged, "allowedTools", preferred?.allowedTools);
|
|
4380
|
+
assignDefinedOption(merged, "maxTurns", preferred?.maxTurns);
|
|
4381
|
+
assignDefinedOption(merged, "systemPrompt", preferred?.systemPrompt);
|
|
3847
4382
|
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
3848
4383
|
}
|
|
4384
|
+
function assignDefinedOption(target, key, value) {
|
|
4385
|
+
if (value !== void 0) target[key] = value;
|
|
4386
|
+
}
|
|
3849
4387
|
function persistSessionOptions(record, options) {
|
|
3850
|
-
const
|
|
3851
|
-
|
|
3852
|
-
const next = options && {
|
|
3853
|
-
model: typeof options.model === "string" ? options.model : void 0,
|
|
3854
|
-
allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
|
|
3855
|
-
max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
|
|
3856
|
-
system_prompt: normalizedSystemPrompt
|
|
3857
|
-
};
|
|
3858
|
-
if (Boolean(next && (typeof next.model === "string" && next.model.trim().length > 0 || Array.isArray(next.allowed_tools) || typeof next.max_turns === "number" || next.system_prompt !== void 0)) && next) {
|
|
4388
|
+
const next = options === void 0 ? void 0 : persistedSessionOptions(options);
|
|
4389
|
+
if (next !== void 0) {
|
|
3859
4390
|
record.acpx = {
|
|
3860
4391
|
...record.acpx,
|
|
3861
4392
|
session_options: next
|
|
@@ -3869,14 +4400,49 @@ function sessionOptionsFromRecord(record) {
|
|
|
3869
4400
|
const stored = record.acpx?.session_options;
|
|
3870
4401
|
if (!stored) return;
|
|
3871
4402
|
const sessionOptions = {};
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
if (typeof storedSystemPrompt === "string" && storedSystemPrompt.length > 0) sessionOptions.systemPrompt = storedSystemPrompt;
|
|
3877
|
-
else if (storedSystemPrompt && typeof storedSystemPrompt === "object" && typeof storedSystemPrompt.append === "string" && storedSystemPrompt.append.length > 0) sessionOptions.systemPrompt = { append: storedSystemPrompt.append };
|
|
4403
|
+
assignStoredOption(sessionOptions, "model", nonEmptyString(stored.model));
|
|
4404
|
+
assignStoredOption(sessionOptions, "allowedTools", storedAllowedTools(stored.allowed_tools));
|
|
4405
|
+
assignStoredOption(sessionOptions, "maxTurns", storedMaxTurns(stored.max_turns));
|
|
4406
|
+
assignStoredOption(sessionOptions, "systemPrompt", storedSystemPromptOption(stored.system_prompt));
|
|
3878
4407
|
return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
|
|
3879
4408
|
}
|
|
4409
|
+
function persistedSessionOptions(options) {
|
|
4410
|
+
const next = {
|
|
4411
|
+
model: nonEmptyString(options.model),
|
|
4412
|
+
allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
|
|
4413
|
+
max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
|
|
4414
|
+
system_prompt: normalizeSystemPromptOption(options.systemPrompt)
|
|
4415
|
+
};
|
|
4416
|
+
return hasPersistedSessionOptions(next) ? next : void 0;
|
|
4417
|
+
}
|
|
4418
|
+
function hasPersistedSessionOptions(options) {
|
|
4419
|
+
return options.model !== void 0 || options.allowed_tools !== void 0 || options.max_turns !== void 0 || options.system_prompt !== void 0;
|
|
4420
|
+
}
|
|
4421
|
+
function normalizeSystemPromptOption(value) {
|
|
4422
|
+
const prompt = nonEmptyString(value);
|
|
4423
|
+
if (prompt !== void 0) return prompt;
|
|
4424
|
+
const append = appendedSystemPrompt(value);
|
|
4425
|
+
return append === void 0 ? void 0 : { append };
|
|
4426
|
+
}
|
|
4427
|
+
function appendedSystemPrompt(value) {
|
|
4428
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
4429
|
+
return nonEmptyString(value.append);
|
|
4430
|
+
}
|
|
4431
|
+
function assignStoredOption(target, key, value) {
|
|
4432
|
+
assignDefinedOption(target, key, value);
|
|
4433
|
+
}
|
|
4434
|
+
function storedAllowedTools(value) {
|
|
4435
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : void 0;
|
|
4436
|
+
}
|
|
4437
|
+
function storedMaxTurns(value) {
|
|
4438
|
+
return typeof value === "number" ? value : void 0;
|
|
4439
|
+
}
|
|
4440
|
+
function storedSystemPromptOption(value) {
|
|
4441
|
+
return normalizeSystemPromptOption(value);
|
|
4442
|
+
}
|
|
4443
|
+
function nonEmptyString(value) {
|
|
4444
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
4445
|
+
}
|
|
3880
4446
|
//#endregion
|
|
3881
4447
|
//#region src/session/conversation-model.ts
|
|
3882
4448
|
const MAX_RUNTIME_MESSAGES = 200;
|
|
@@ -3903,13 +4469,17 @@ function normalizeAgentName(value) {
|
|
|
3903
4469
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
3904
4470
|
}
|
|
3905
4471
|
function extractText(content) {
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
return content.
|
|
4472
|
+
switch (content.type) {
|
|
4473
|
+
case "text": return content.text;
|
|
4474
|
+
case "resource_link": return content.title ?? content.name ?? content.uri;
|
|
4475
|
+
case "resource": return extractResourceText(content);
|
|
4476
|
+
case "audio": return `[audio] ${content.mimeType}`;
|
|
4477
|
+
default: return;
|
|
3911
4478
|
}
|
|
3912
4479
|
}
|
|
4480
|
+
function extractResourceText(content) {
|
|
4481
|
+
return "text" in content.resource && typeof content.resource.text === "string" ? content.resource.text : content.resource.uri;
|
|
4482
|
+
}
|
|
3913
4483
|
function contentToUserContent(content) {
|
|
3914
4484
|
if (content.type === "text") return { Text: content.text };
|
|
3915
4485
|
if (content.type === "resource_link") {
|
|
@@ -3919,17 +4489,22 @@ function contentToUserContent(content) {
|
|
|
3919
4489
|
content: value
|
|
3920
4490
|
} };
|
|
3921
4491
|
}
|
|
3922
|
-
if (content.type === "resource")
|
|
3923
|
-
if ("text" in content.resource && typeof content.resource.text === "string") return { Text: content.resource.text };
|
|
3924
|
-
return { Mention: {
|
|
3925
|
-
uri: content.resource.uri,
|
|
3926
|
-
content: content.resource.uri
|
|
3927
|
-
} };
|
|
3928
|
-
}
|
|
4492
|
+
if (content.type === "resource") return resourceToUserContent(content);
|
|
3929
4493
|
if (content.type === "image") return { Image: {
|
|
3930
4494
|
source: content.data,
|
|
3931
4495
|
size: null
|
|
3932
4496
|
} };
|
|
4497
|
+
if (content.type === "audio") return { Audio: {
|
|
4498
|
+
source: content.data,
|
|
4499
|
+
mime_type: content.mimeType
|
|
4500
|
+
} };
|
|
4501
|
+
}
|
|
4502
|
+
function resourceToUserContent(content) {
|
|
4503
|
+
if ("text" in content.resource && typeof content.resource.text === "string") return { Text: content.resource.text };
|
|
4504
|
+
return { Mention: {
|
|
4505
|
+
uri: content.resource.uri,
|
|
4506
|
+
content: content.resource.uri
|
|
4507
|
+
} };
|
|
3933
4508
|
}
|
|
3934
4509
|
function nextUserMessageId() {
|
|
3935
4510
|
return randomUUID();
|
|
@@ -4031,38 +4606,67 @@ function ensureToolUseContent(agent, toolCallId) {
|
|
|
4031
4606
|
}
|
|
4032
4607
|
function upsertToolResult(agent, toolCallId, patch) {
|
|
4033
4608
|
const existing = agent.tool_results[toolCallId];
|
|
4609
|
+
const fallback = existingToolResultValues(existing);
|
|
4034
4610
|
const next = {
|
|
4035
4611
|
tool_use_id: toolCallId,
|
|
4036
|
-
tool_name: patch.tool_name ??
|
|
4037
|
-
is_error: patch.is_error ??
|
|
4038
|
-
content: patch.content ??
|
|
4039
|
-
output: patch.output ??
|
|
4612
|
+
tool_name: patch.tool_name ?? fallback.tool_name,
|
|
4613
|
+
is_error: patch.is_error ?? fallback.is_error,
|
|
4614
|
+
content: patch.content ?? fallback.content,
|
|
4615
|
+
output: patch.output ?? fallback.output
|
|
4040
4616
|
};
|
|
4041
4617
|
agent.tool_results[toolCallId] = next;
|
|
4042
4618
|
}
|
|
4619
|
+
function existingToolResultValues(existing) {
|
|
4620
|
+
if (existing) return existing;
|
|
4621
|
+
return {
|
|
4622
|
+
tool_use_id: "",
|
|
4623
|
+
tool_name: "tool_call",
|
|
4624
|
+
is_error: false,
|
|
4625
|
+
content: { Text: "" },
|
|
4626
|
+
output: void 0
|
|
4627
|
+
};
|
|
4628
|
+
}
|
|
4043
4629
|
function applyToolCallUpdate(agent, update) {
|
|
4044
4630
|
const tool = ensureToolUseContent(agent, update.toolCallId);
|
|
4631
|
+
applyToolIdentityUpdate(tool, update);
|
|
4632
|
+
applyToolInputUpdate(tool, update);
|
|
4633
|
+
applyToolStatusUpdate(tool, update);
|
|
4634
|
+
applyToolResultUpdate(agent, tool, update);
|
|
4635
|
+
}
|
|
4636
|
+
function applyToolIdentityUpdate(tool, update) {
|
|
4045
4637
|
if (hasOwn(update, "title")) tool.name = normalizeAgentName(update.title) ?? tool.name ?? "tool_call";
|
|
4046
4638
|
if (hasOwn(update, "kind")) {
|
|
4047
4639
|
const kindName = normalizeAgentName(update.kind);
|
|
4048
4640
|
if (!tool.name || tool.name === "tool_call") tool.name = kindName ?? tool.name;
|
|
4049
4641
|
}
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
}
|
|
4642
|
+
}
|
|
4643
|
+
function applyToolInputUpdate(tool, update) {
|
|
4644
|
+
if (!hasOwn(update, "rawInput")) return;
|
|
4645
|
+
const rawInput = deepClone(update.rawInput);
|
|
4646
|
+
tool.input = rawInput ?? {};
|
|
4647
|
+
tool.raw_input = toRawInput(rawInput);
|
|
4648
|
+
}
|
|
4649
|
+
function applyToolStatusUpdate(tool, update) {
|
|
4055
4650
|
if (hasOwn(update, "status")) tool.is_input_complete = statusIndicatesComplete(update.status);
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4651
|
+
}
|
|
4652
|
+
function applyToolResultUpdate(agent, tool, update) {
|
|
4653
|
+
if (!hasToolResultPatch(update)) return;
|
|
4654
|
+
const status = update.status;
|
|
4655
|
+
const output = hasOwn(update, "rawOutput") ? deepClone(update.rawOutput) : void 0;
|
|
4656
|
+
upsertToolResult(agent, update.toolCallId, {
|
|
4657
|
+
tool_name: tool.name,
|
|
4658
|
+
is_error: statusIndicatesError(status),
|
|
4659
|
+
content: output === void 0 ? void 0 : toToolResultContent(output),
|
|
4660
|
+
output
|
|
4661
|
+
});
|
|
4662
|
+
}
|
|
4663
|
+
function hasToolResultPatch(update) {
|
|
4664
|
+
return [
|
|
4665
|
+
"rawOutput",
|
|
4666
|
+
"status",
|
|
4667
|
+
"title",
|
|
4668
|
+
"kind"
|
|
4669
|
+
].some((key) => hasOwn(update, key));
|
|
4066
4670
|
}
|
|
4067
4671
|
function asRecord(value) {
|
|
4068
4672
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
@@ -4093,9 +4697,12 @@ function usageToTokenUsage(update) {
|
|
|
4093
4697
|
"cachedReadTokens"
|
|
4094
4698
|
])
|
|
4095
4699
|
};
|
|
4096
|
-
if (normalized
|
|
4700
|
+
if (!hasTokenUsageValue(normalized)) return;
|
|
4097
4701
|
return normalized;
|
|
4098
4702
|
}
|
|
4703
|
+
function hasTokenUsageValue(usage) {
|
|
4704
|
+
return Object.values(usage).some((value) => value !== void 0);
|
|
4705
|
+
}
|
|
4099
4706
|
function ensureAcpxState$1(state) {
|
|
4100
4707
|
return state ?? {};
|
|
4101
4708
|
}
|
|
@@ -4134,14 +4741,21 @@ function cloneSessionAcpxState(state) {
|
|
|
4134
4741
|
available_models: state.available_models ? [...state.available_models] : void 0,
|
|
4135
4742
|
available_commands: state.available_commands ? [...state.available_commands] : void 0,
|
|
4136
4743
|
config_options: state.config_options ? deepClone(state.config_options) : void 0,
|
|
4137
|
-
session_options: state.session_options
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4744
|
+
session_options: cloneSessionOptions(state.session_options)
|
|
4745
|
+
};
|
|
4746
|
+
}
|
|
4747
|
+
function cloneSessionOptions(options) {
|
|
4748
|
+
if (!options) return;
|
|
4749
|
+
return {
|
|
4750
|
+
model: options.model,
|
|
4751
|
+
allowed_tools: options.allowed_tools ? [...options.allowed_tools] : void 0,
|
|
4752
|
+
max_turns: options.max_turns,
|
|
4753
|
+
...options.system_prompt !== void 0 ? { system_prompt: cloneSystemPromptOption(options.system_prompt) } : {}
|
|
4143
4754
|
};
|
|
4144
4755
|
}
|
|
4756
|
+
function cloneSystemPromptOption(option) {
|
|
4757
|
+
return typeof option === "string" ? option : { append: option.append };
|
|
4758
|
+
}
|
|
4145
4759
|
function recordPromptSubmission(conversation, prompt, timestamp = isoNow()) {
|
|
4146
4760
|
const userContent = (typeof prompt === "string" ? textPrompt(prompt) : prompt).map((content) => contentToUserContent(content)).filter((content) => content !== void 0);
|
|
4147
4761
|
if (userContent.length === 0) return;
|
|
@@ -4174,57 +4788,70 @@ function hasAgentReplyAfterPrompt(conversation, promptMessageId) {
|
|
|
4174
4788
|
function recordSessionUpdate(conversation, state, notification, timestamp = isoNow()) {
|
|
4175
4789
|
const acpx = ensureAcpxState$1(state);
|
|
4176
4790
|
const update = notification.update;
|
|
4177
|
-
|
|
4178
|
-
case "user_message_chunk": {
|
|
4179
|
-
const userContent = contentToUserContent(update.content);
|
|
4180
|
-
if (userContent) conversation.messages.push({ User: {
|
|
4181
|
-
id: nextUserMessageId(),
|
|
4182
|
-
content: [userContent]
|
|
4183
|
-
} });
|
|
4184
|
-
break;
|
|
4185
|
-
}
|
|
4186
|
-
case "agent_message_chunk": {
|
|
4187
|
-
const text = extractText(update.content);
|
|
4188
|
-
if (text) appendAgentText(ensureAgentMessage(conversation), text);
|
|
4189
|
-
break;
|
|
4190
|
-
}
|
|
4191
|
-
case "agent_thought_chunk": {
|
|
4192
|
-
const text = extractText(update.content);
|
|
4193
|
-
if (text) appendAgentThinking(ensureAgentMessage(conversation), text);
|
|
4194
|
-
break;
|
|
4195
|
-
}
|
|
4196
|
-
case "tool_call":
|
|
4197
|
-
case "tool_call_update":
|
|
4198
|
-
applyToolCallUpdate(ensureAgentMessage(conversation), update);
|
|
4199
|
-
break;
|
|
4200
|
-
case "usage_update": {
|
|
4201
|
-
const usage = usageToTokenUsage(update);
|
|
4202
|
-
if (usage) {
|
|
4203
|
-
conversation.cumulative_token_usage = usage;
|
|
4204
|
-
const userId = lastUserMessageId(conversation);
|
|
4205
|
-
if (userId) conversation.request_token_usage[userId] = usage;
|
|
4206
|
-
}
|
|
4207
|
-
break;
|
|
4208
|
-
}
|
|
4209
|
-
case "session_info_update":
|
|
4210
|
-
if (hasOwn(update, "title")) conversation.title = update.title ?? null;
|
|
4211
|
-
if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
|
|
4212
|
-
break;
|
|
4213
|
-
case "available_commands_update":
|
|
4214
|
-
acpx.available_commands = update.availableCommands.map((entry) => entry.name).filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
4215
|
-
break;
|
|
4216
|
-
case "current_mode_update":
|
|
4217
|
-
acpx.current_mode_id = update.currentModeId;
|
|
4218
|
-
break;
|
|
4219
|
-
case "config_option_update":
|
|
4220
|
-
acpx.config_options = deepClone(update.configOptions);
|
|
4221
|
-
break;
|
|
4222
|
-
default: break;
|
|
4223
|
-
}
|
|
4791
|
+
applySessionUpdate(conversation, acpx, update);
|
|
4224
4792
|
updateConversationTimestamp(conversation, timestamp);
|
|
4225
4793
|
trimConversationForRuntime(conversation);
|
|
4226
4794
|
return acpx;
|
|
4227
4795
|
}
|
|
4796
|
+
function applySessionUpdate(conversation, acpx, update) {
|
|
4797
|
+
const handler = SESSION_UPDATE_HANDLERS[update.sessionUpdate];
|
|
4798
|
+
handler?.(conversation, acpx, update);
|
|
4799
|
+
}
|
|
4800
|
+
const SESSION_UPDATE_HANDLERS = {
|
|
4801
|
+
user_message_chunk: (conversation, _acpx, update) => {
|
|
4802
|
+
if (update.sessionUpdate === "user_message_chunk") appendUserMessageChunk(conversation, update.content);
|
|
4803
|
+
},
|
|
4804
|
+
agent_message_chunk: (conversation, _acpx, update) => {
|
|
4805
|
+
if (update.sessionUpdate === "agent_message_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentText);
|
|
4806
|
+
},
|
|
4807
|
+
agent_thought_chunk: (conversation, _acpx, update) => {
|
|
4808
|
+
if (update.sessionUpdate === "agent_thought_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentThinking);
|
|
4809
|
+
},
|
|
4810
|
+
tool_call: (conversation, _acpx, update) => {
|
|
4811
|
+
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
|
|
4812
|
+
},
|
|
4813
|
+
tool_call_update: (conversation, _acpx, update) => {
|
|
4814
|
+
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
|
|
4815
|
+
},
|
|
4816
|
+
usage_update: (conversation, _acpx, update) => {
|
|
4817
|
+
if (update.sessionUpdate === "usage_update") applyUsageUpdate(conversation, update);
|
|
4818
|
+
},
|
|
4819
|
+
session_info_update: (conversation, _acpx, update) => {
|
|
4820
|
+
if (update.sessionUpdate === "session_info_update") applySessionInfoUpdate(conversation, update);
|
|
4821
|
+
},
|
|
4822
|
+
available_commands_update: (_conversation, acpx, update) => {
|
|
4823
|
+
if (update.sessionUpdate === "available_commands_update") acpx.available_commands = update.availableCommands.map((entry) => entry.name).filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
4824
|
+
},
|
|
4825
|
+
current_mode_update: (_conversation, acpx, update) => {
|
|
4826
|
+
if (update.sessionUpdate === "current_mode_update") acpx.current_mode_id = update.currentModeId;
|
|
4827
|
+
},
|
|
4828
|
+
config_option_update: (_conversation, acpx, update) => {
|
|
4829
|
+
if (update.sessionUpdate === "config_option_update") acpx.config_options = deepClone(update.configOptions);
|
|
4830
|
+
}
|
|
4831
|
+
};
|
|
4832
|
+
function appendUserMessageChunk(conversation, content) {
|
|
4833
|
+
const userContent = contentToUserContent(content);
|
|
4834
|
+
if (!userContent) return;
|
|
4835
|
+
conversation.messages.push({ User: {
|
|
4836
|
+
id: nextUserMessageId(),
|
|
4837
|
+
content: [userContent]
|
|
4838
|
+
} });
|
|
4839
|
+
}
|
|
4840
|
+
function appendAgentMessageChunk(conversation, content, append) {
|
|
4841
|
+
const text = extractText(content);
|
|
4842
|
+
if (text) append(ensureAgentMessage(conversation), text);
|
|
4843
|
+
}
|
|
4844
|
+
function applyUsageUpdate(conversation, update) {
|
|
4845
|
+
const usage = usageToTokenUsage(update);
|
|
4846
|
+
if (!usage) return;
|
|
4847
|
+
conversation.cumulative_token_usage = usage;
|
|
4848
|
+
const userId = lastUserMessageId(conversation);
|
|
4849
|
+
if (userId) conversation.request_token_usage[userId] = usage;
|
|
4850
|
+
}
|
|
4851
|
+
function applySessionInfoUpdate(conversation, update) {
|
|
4852
|
+
if (hasOwn(update, "title")) conversation.title = update.title ?? null;
|
|
4853
|
+
if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
|
|
4854
|
+
}
|
|
4228
4855
|
function recordClientOperation(conversation, state, operation, timestamp = isoNow()) {
|
|
4229
4856
|
const acpx = ensureAcpxState$1(state);
|
|
4230
4857
|
updateConversationTimestamp(conversation, timestamp);
|
|
@@ -4233,25 +4860,36 @@ function recordClientOperation(conversation, state, operation, timestamp = isoNo
|
|
|
4233
4860
|
}
|
|
4234
4861
|
function trimConversationForRuntime(conversation) {
|
|
4235
4862
|
if (conversation.messages.length > MAX_RUNTIME_MESSAGES) conversation.messages = conversation.messages.slice(-MAX_RUNTIME_MESSAGES);
|
|
4236
|
-
for (const message of conversation.messages)
|
|
4237
|
-
if (!isAgentMessage(message)) {
|
|
4238
|
-
if (isUserMessage(message)) message.User.content = message.User.content.map((content) => {
|
|
4239
|
-
if ("Text" in content) return { Text: trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS) };
|
|
4240
|
-
return content;
|
|
4241
|
-
});
|
|
4242
|
-
continue;
|
|
4243
|
-
}
|
|
4244
|
-
for (const content of message.Agent.content) if ("Text" in content) content.Text = trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS);
|
|
4245
|
-
else if ("Thinking" in content) content.Thinking.text = trimRuntimeText(content.Thinking.text, MAX_RUNTIME_THINKING_CHARS);
|
|
4246
|
-
else if ("ToolUse" in content) content.ToolUse.raw_input = trimRuntimeText(content.ToolUse.raw_input, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4247
|
-
for (const result of Object.values(message.Agent.tool_results)) {
|
|
4248
|
-
if ("Text" in result.content) result.content.Text = trimRuntimeText(result.content.Text, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4249
|
-
if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4250
|
-
}
|
|
4251
|
-
}
|
|
4863
|
+
for (const message of conversation.messages) trimRuntimeMessage(message);
|
|
4252
4864
|
const requestUsageEntries = Object.entries(conversation.request_token_usage);
|
|
4253
4865
|
if (requestUsageEntries.length > MAX_RUNTIME_REQUEST_TOKEN_USAGE) conversation.request_token_usage = Object.fromEntries(requestUsageEntries.slice(-MAX_RUNTIME_REQUEST_TOKEN_USAGE));
|
|
4254
4866
|
}
|
|
4867
|
+
function trimRuntimeMessage(message) {
|
|
4868
|
+
if (isUserMessage(message)) {
|
|
4869
|
+
trimRuntimeUserMessage(message.User);
|
|
4870
|
+
return;
|
|
4871
|
+
}
|
|
4872
|
+
if (isAgentMessage(message)) trimRuntimeAgentMessage(message.Agent);
|
|
4873
|
+
}
|
|
4874
|
+
function trimRuntimeUserMessage(message) {
|
|
4875
|
+
message.content = message.content.map((content) => {
|
|
4876
|
+
if ("Text" in content) return { Text: trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS) };
|
|
4877
|
+
return content;
|
|
4878
|
+
});
|
|
4879
|
+
}
|
|
4880
|
+
function trimRuntimeAgentMessage(message) {
|
|
4881
|
+
for (const content of message.content) trimRuntimeAgentContent(content);
|
|
4882
|
+
for (const result of Object.values(message.tool_results)) trimRuntimeToolResult(result);
|
|
4883
|
+
}
|
|
4884
|
+
function trimRuntimeAgentContent(content) {
|
|
4885
|
+
if ("Text" in content) content.Text = trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS);
|
|
4886
|
+
else if ("Thinking" in content) content.Thinking.text = trimRuntimeText(content.Thinking.text, MAX_RUNTIME_THINKING_CHARS);
|
|
4887
|
+
else if ("ToolUse" in content) content.ToolUse.raw_input = trimRuntimeText(content.ToolUse.raw_input, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4888
|
+
}
|
|
4889
|
+
function trimRuntimeToolResult(result) {
|
|
4890
|
+
if ("Text" in result.content) result.content.Text = trimRuntimeText(result.content.Text, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4891
|
+
if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4892
|
+
}
|
|
4255
4893
|
//#endregion
|
|
4256
4894
|
//#region src/session/config-options.ts
|
|
4257
4895
|
function applyConfigOptionsToRecord(record, result) {
|
|
@@ -4373,39 +5011,6 @@ async function applyRequestedModelIfAdvertised(params) {
|
|
|
4373
5011
|
return true;
|
|
4374
5012
|
}
|
|
4375
5013
|
//#endregion
|
|
4376
|
-
//#region src/runtime/engine/lifecycle.ts
|
|
4377
|
-
function applyLifecycleSnapshotToRecord(record, snapshot) {
|
|
4378
|
-
if (!snapshot) return;
|
|
4379
|
-
record.pid = snapshot.pid;
|
|
4380
|
-
record.agentStartedAt = snapshot.startedAt;
|
|
4381
|
-
if (snapshot.lastExit) {
|
|
4382
|
-
record.lastAgentExitCode = snapshot.lastExit.exitCode;
|
|
4383
|
-
record.lastAgentExitSignal = snapshot.lastExit.signal;
|
|
4384
|
-
record.lastAgentExitAt = snapshot.lastExit.exitedAt;
|
|
4385
|
-
record.lastAgentDisconnectReason = snapshot.lastExit.reason;
|
|
4386
|
-
return;
|
|
4387
|
-
}
|
|
4388
|
-
record.lastAgentExitCode = void 0;
|
|
4389
|
-
record.lastAgentExitSignal = void 0;
|
|
4390
|
-
record.lastAgentExitAt = void 0;
|
|
4391
|
-
record.lastAgentDisconnectReason = void 0;
|
|
4392
|
-
}
|
|
4393
|
-
function reconcileAgentSessionId(record, agentSessionId) {
|
|
4394
|
-
const normalized = normalizeRuntimeSessionId(agentSessionId);
|
|
4395
|
-
if (!normalized) return;
|
|
4396
|
-
record.agentSessionId = normalized;
|
|
4397
|
-
}
|
|
4398
|
-
function sessionHasAgentMessages(recordOrConversation) {
|
|
4399
|
-
return recordOrConversation.messages.some((message) => typeof message === "object" && message !== null && "Agent" in message);
|
|
4400
|
-
}
|
|
4401
|
-
function applyConversation(record, conversation) {
|
|
4402
|
-
record.title = conversation.title;
|
|
4403
|
-
record.updated_at = conversation.updated_at;
|
|
4404
|
-
record.messages = conversation.messages;
|
|
4405
|
-
record.cumulative_token_usage = conversation.cumulative_token_usage;
|
|
4406
|
-
record.request_token_usage = conversation.request_token_usage;
|
|
4407
|
-
}
|
|
4408
|
-
//#endregion
|
|
4409
5014
|
//#region src/runtime/engine/reconnect.ts
|
|
4410
5015
|
function isProcessAlive(pid) {
|
|
4411
5016
|
if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
|
|
@@ -4418,15 +5023,19 @@ function isProcessAlive(pid) {
|
|
|
4418
5023
|
}
|
|
4419
5024
|
const SESSION_LOAD_UNSUPPORTED_CODES = new Set([-32601, -32602]);
|
|
4420
5025
|
function shouldFallbackToNewSession(error, record) {
|
|
4421
|
-
if (error
|
|
4422
|
-
if (isAcpResourceNotFoundError(error)) return true;
|
|
5026
|
+
if (isHardReconnectFailure(error)) return false;
|
|
4423
5027
|
const acp = extractAcpError(error);
|
|
4424
|
-
if (
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
5028
|
+
if (isAcpResourceNotFoundError(error) || isUnsupportedSessionLoadAcpError(acp)) return true;
|
|
5029
|
+
return !sessionHasAgentMessages(record) && isFallbackSafeEmptySessionError(error, acp);
|
|
5030
|
+
}
|
|
5031
|
+
function isHardReconnectFailure(error) {
|
|
5032
|
+
return error instanceof TimeoutError || error instanceof InterruptedError;
|
|
5033
|
+
}
|
|
5034
|
+
function isUnsupportedSessionLoadAcpError(acp) {
|
|
5035
|
+
return !!acp && SESSION_LOAD_UNSUPPORTED_CODES.has(acp.code);
|
|
5036
|
+
}
|
|
5037
|
+
function isFallbackSafeEmptySessionError(error, acp) {
|
|
5038
|
+
return isAcpQueryClosedBeforeResponseError(error) || acp?.code === -32603;
|
|
4430
5039
|
}
|
|
4431
5040
|
function requiresSameSession(resumePolicy) {
|
|
4432
5041
|
return resumePolicy === "same-session-only";
|
|
@@ -4483,18 +5092,14 @@ function restoreOriginalSessionState(params) {
|
|
|
4483
5092
|
async function connectAndLoadSession(options) {
|
|
4484
5093
|
const record = options.record;
|
|
4485
5094
|
const client = options.client;
|
|
4486
|
-
const sameSessionOnly = requiresSameSession(options.resumePolicy);
|
|
5095
|
+
const sameSessionOnly = requiresSameSession(options.resumePolicy) || Boolean(record.importedFrom);
|
|
4487
5096
|
const originalSessionId = record.acpSessionId;
|
|
4488
5097
|
const originalAgentSessionId = record.agentSessionId;
|
|
4489
5098
|
const desiredModeId = getDesiredModeId(record.acpx);
|
|
4490
5099
|
const desiredModelId = getDesiredModelId(record.acpx);
|
|
4491
5100
|
const desiredConfigOptions = getDesiredConfigOptions(record.acpx);
|
|
4492
5101
|
const storedProcessAlive = isProcessAlive(record.pid);
|
|
4493
|
-
|
|
4494
|
-
if (options.verbose) {
|
|
4495
|
-
if (storedProcessAlive) process.stderr.write(`[acpx] saved session pid ${record.pid} is running; reconnecting with loadSession\n`);
|
|
4496
|
-
else if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session/load\n`);
|
|
4497
|
-
}
|
|
5102
|
+
logReconnectAttempt(record, storedProcessAlive, Boolean(record.pid) && !storedProcessAlive, options.verbose);
|
|
4498
5103
|
const reusingLoadedSession = client.hasReusableSession(record.acpSessionId);
|
|
4499
5104
|
if (reusingLoadedSession) incrementPerfCounter("runtime.connect_and_load.reused_session");
|
|
4500
5105
|
else await withTimeout(client.start(), options.timeoutMs);
|
|
@@ -4509,82 +5114,35 @@ async function connectAndLoadSession(options) {
|
|
|
4509
5114
|
let createdFreshSession = false;
|
|
4510
5115
|
let pendingAgentSessionId = record.agentSessionId;
|
|
4511
5116
|
let sessionModels;
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
pendingAgentSessionId
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
createdFreshSession = true;
|
|
4542
|
-
pendingAgentSessionId = createdSession.agentSessionId;
|
|
4543
|
-
applyConfigOptionsToRecord(record, createdSession);
|
|
4544
|
-
sessionModels = createdSession.models;
|
|
4545
|
-
}
|
|
4546
|
-
if (createdFreshSession) {
|
|
4547
|
-
try {
|
|
4548
|
-
await replayDesiredMode({
|
|
4549
|
-
client,
|
|
4550
|
-
sessionId,
|
|
4551
|
-
desiredModeId,
|
|
4552
|
-
previousSessionId: originalSessionId,
|
|
4553
|
-
timeoutMs: options.timeoutMs,
|
|
4554
|
-
verbose: options.verbose
|
|
4555
|
-
});
|
|
4556
|
-
await replayDesiredModel({
|
|
4557
|
-
client,
|
|
4558
|
-
sessionId,
|
|
4559
|
-
desiredModelId,
|
|
4560
|
-
previousSessionId: originalSessionId,
|
|
4561
|
-
record,
|
|
4562
|
-
models: sessionModels,
|
|
4563
|
-
timeoutMs: options.timeoutMs,
|
|
4564
|
-
verbose: options.verbose
|
|
4565
|
-
});
|
|
4566
|
-
await replayDesiredConfigOptions({
|
|
4567
|
-
client,
|
|
4568
|
-
sessionId,
|
|
4569
|
-
desiredConfigOptions,
|
|
4570
|
-
previousSessionId: originalSessionId,
|
|
4571
|
-
timeoutMs: options.timeoutMs,
|
|
4572
|
-
verbose: options.verbose
|
|
4573
|
-
});
|
|
4574
|
-
} catch (error) {
|
|
4575
|
-
restoreOriginalSessionState({
|
|
4576
|
-
record,
|
|
4577
|
-
sessionId: originalSessionId,
|
|
4578
|
-
agentSessionId: originalAgentSessionId
|
|
4579
|
-
});
|
|
4580
|
-
if (options.verbose) process.stderr.write(`[acpx] ${formatErrorMessage(error)}\n`);
|
|
4581
|
-
throw error;
|
|
4582
|
-
}
|
|
4583
|
-
record.acpSessionId = sessionId;
|
|
4584
|
-
reconcileAgentSessionId(record, pendingAgentSessionId);
|
|
4585
|
-
}
|
|
4586
|
-
syncAdvertisedModelState(record, sessionModels);
|
|
4587
|
-
if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
|
|
5117
|
+
const loadState = await loadOrCreateRuntimeSession({
|
|
5118
|
+
client,
|
|
5119
|
+
record,
|
|
5120
|
+
reusingLoadedSession,
|
|
5121
|
+
sameSessionOnly,
|
|
5122
|
+
timeoutMs: options.timeoutMs
|
|
5123
|
+
});
|
|
5124
|
+
resumed = loadState.resumed;
|
|
5125
|
+
loadError = loadState.loadError;
|
|
5126
|
+
sessionId = loadState.sessionId;
|
|
5127
|
+
createdFreshSession = loadState.createdFreshSession;
|
|
5128
|
+
pendingAgentSessionId = loadState.pendingAgentSessionId;
|
|
5129
|
+
sessionModels = loadState.sessionModels;
|
|
5130
|
+
await replayFreshSessionPreferences({
|
|
5131
|
+
client,
|
|
5132
|
+
record,
|
|
5133
|
+
createdFreshSession,
|
|
5134
|
+
sessionId,
|
|
5135
|
+
pendingAgentSessionId,
|
|
5136
|
+
originalSessionId,
|
|
5137
|
+
originalAgentSessionId,
|
|
5138
|
+
desiredModeId,
|
|
5139
|
+
desiredModelId,
|
|
5140
|
+
desiredConfigOptions,
|
|
5141
|
+
sessionModels,
|
|
5142
|
+
timeoutMs: options.timeoutMs,
|
|
5143
|
+
verbose: options.verbose
|
|
5144
|
+
});
|
|
5145
|
+
applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId);
|
|
4588
5146
|
options.onSessionIdResolved?.(sessionId);
|
|
4589
5147
|
return {
|
|
4590
5148
|
sessionId,
|
|
@@ -4593,6 +5151,131 @@ async function connectAndLoadSession(options) {
|
|
|
4593
5151
|
loadError
|
|
4594
5152
|
};
|
|
4595
5153
|
}
|
|
5154
|
+
function applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId) {
|
|
5155
|
+
syncAdvertisedModelState(record, sessionModels);
|
|
5156
|
+
if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
|
|
5157
|
+
}
|
|
5158
|
+
function logReconnectAttempt(record, storedProcessAlive, shouldReconnect, verbose) {
|
|
5159
|
+
if (!verbose) return;
|
|
5160
|
+
if (storedProcessAlive) {
|
|
5161
|
+
process.stderr.write(`[acpx] saved session pid ${record.pid} is running; reconnecting to saved ACP session\n`);
|
|
5162
|
+
return;
|
|
5163
|
+
}
|
|
5164
|
+
if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session reconnect\n`);
|
|
5165
|
+
}
|
|
5166
|
+
async function replayFreshSessionPreferences(params) {
|
|
5167
|
+
if (!params.createdFreshSession) return;
|
|
5168
|
+
try {
|
|
5169
|
+
await replayDesiredMode({
|
|
5170
|
+
client: params.client,
|
|
5171
|
+
sessionId: params.sessionId,
|
|
5172
|
+
desiredModeId: params.desiredModeId,
|
|
5173
|
+
previousSessionId: params.originalSessionId,
|
|
5174
|
+
timeoutMs: params.timeoutMs,
|
|
5175
|
+
verbose: params.verbose
|
|
5176
|
+
});
|
|
5177
|
+
await replayDesiredModel({
|
|
5178
|
+
client: params.client,
|
|
5179
|
+
sessionId: params.sessionId,
|
|
5180
|
+
desiredModelId: params.desiredModelId,
|
|
5181
|
+
previousSessionId: params.originalSessionId,
|
|
5182
|
+
record: params.record,
|
|
5183
|
+
models: params.sessionModels,
|
|
5184
|
+
timeoutMs: params.timeoutMs,
|
|
5185
|
+
verbose: params.verbose
|
|
5186
|
+
});
|
|
5187
|
+
await replayDesiredConfigOptions({
|
|
5188
|
+
client: params.client,
|
|
5189
|
+
sessionId: params.sessionId,
|
|
5190
|
+
desiredConfigOptions: params.desiredConfigOptions,
|
|
5191
|
+
previousSessionId: params.originalSessionId,
|
|
5192
|
+
timeoutMs: params.timeoutMs,
|
|
5193
|
+
verbose: params.verbose
|
|
5194
|
+
});
|
|
5195
|
+
} catch (error) {
|
|
5196
|
+
restoreOriginalSessionState({
|
|
5197
|
+
record: params.record,
|
|
5198
|
+
sessionId: params.originalSessionId,
|
|
5199
|
+
agentSessionId: params.originalAgentSessionId
|
|
5200
|
+
});
|
|
5201
|
+
if (params.verbose) process.stderr.write(`[acpx] ${formatErrorMessage(error)}\n`);
|
|
5202
|
+
throw error;
|
|
5203
|
+
}
|
|
5204
|
+
params.record.acpSessionId = params.sessionId;
|
|
5205
|
+
reconcileAgentSessionId(params.record, params.pendingAgentSessionId);
|
|
5206
|
+
}
|
|
5207
|
+
async function loadOrCreateRuntimeSession(params) {
|
|
5208
|
+
if (params.reusingLoadedSession) return {
|
|
5209
|
+
sessionId: params.record.acpSessionId,
|
|
5210
|
+
pendingAgentSessionId: params.record.agentSessionId,
|
|
5211
|
+
sessionModels: void 0,
|
|
5212
|
+
resumed: true,
|
|
5213
|
+
createdFreshSession: false
|
|
5214
|
+
};
|
|
5215
|
+
if (params.client.supportsResumeSession()) return await resumeRuntimeSession(params);
|
|
5216
|
+
if (params.client.supportsLoadSession()) return await loadRuntimeSession(params);
|
|
5217
|
+
if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
|
|
5218
|
+
record: params.record,
|
|
5219
|
+
reason: "agent does not support session/resume or session/load"
|
|
5220
|
+
});
|
|
5221
|
+
return await createFreshRuntimeSession(params.client, params.record, params.timeoutMs);
|
|
5222
|
+
}
|
|
5223
|
+
async function resumeRuntimeSession(params) {
|
|
5224
|
+
try {
|
|
5225
|
+
const resumeResult = await withTimeout(params.client.resumeSession(params.record.acpSessionId, params.record.cwd), params.timeoutMs);
|
|
5226
|
+
reconcileAgentSessionId(params.record, resumeResult.agentSessionId);
|
|
5227
|
+
applyConfigOptionsToRecord(params.record, resumeResult);
|
|
5228
|
+
return {
|
|
5229
|
+
sessionId: params.record.acpSessionId,
|
|
5230
|
+
pendingAgentSessionId: params.record.agentSessionId,
|
|
5231
|
+
sessionModels: resumeResult.models,
|
|
5232
|
+
resumed: true,
|
|
5233
|
+
createdFreshSession: false
|
|
5234
|
+
};
|
|
5235
|
+
} catch (error) {
|
|
5236
|
+
return await recoverRuntimeSessionLoadFailure(params, error);
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
async function loadRuntimeSession(params) {
|
|
5240
|
+
try {
|
|
5241
|
+
const loadResult = await withTimeout(params.client.loadSessionWithOptions(params.record.acpSessionId, params.record.cwd, { suppressReplayUpdates: true }), params.timeoutMs);
|
|
5242
|
+
reconcileAgentSessionId(params.record, loadResult.agentSessionId);
|
|
5243
|
+
applyConfigOptionsToRecord(params.record, loadResult);
|
|
5244
|
+
return {
|
|
5245
|
+
sessionId: params.record.acpSessionId,
|
|
5246
|
+
pendingAgentSessionId: params.record.agentSessionId,
|
|
5247
|
+
sessionModels: loadResult.models,
|
|
5248
|
+
resumed: true,
|
|
5249
|
+
createdFreshSession: false
|
|
5250
|
+
};
|
|
5251
|
+
} catch (error) {
|
|
5252
|
+
return await recoverRuntimeSessionLoadFailure(params, error);
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
5255
|
+
async function recoverRuntimeSessionLoadFailure(params, error) {
|
|
5256
|
+
const loadError = formatErrorMessage(error);
|
|
5257
|
+
if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
|
|
5258
|
+
record: params.record,
|
|
5259
|
+
reason: loadError,
|
|
5260
|
+
cause: error
|
|
5261
|
+
});
|
|
5262
|
+
if (!shouldFallbackToNewSession(error, params.record)) throw error;
|
|
5263
|
+
return {
|
|
5264
|
+
...await createFreshRuntimeSession(params.client, params.record, params.timeoutMs),
|
|
5265
|
+
loadError
|
|
5266
|
+
};
|
|
5267
|
+
}
|
|
5268
|
+
async function createFreshRuntimeSession(client, record, timeoutMs) {
|
|
5269
|
+
const createdSession = await withTimeout(client.createSession(record.cwd), timeoutMs);
|
|
5270
|
+
applyConfigOptionsToRecord(record, createdSession);
|
|
5271
|
+
return {
|
|
5272
|
+
sessionId: createdSession.sessionId,
|
|
5273
|
+
pendingAgentSessionId: createdSession.agentSessionId,
|
|
5274
|
+
sessionModels: createdSession.models,
|
|
5275
|
+
resumed: false,
|
|
5276
|
+
createdFreshSession: true
|
|
5277
|
+
};
|
|
5278
|
+
}
|
|
4596
5279
|
//#endregion
|
|
4597
5280
|
//#region src/runtime/engine/connected-session.ts
|
|
4598
5281
|
function createActiveSessionController(params) {
|
|
@@ -4785,6 +5468,6 @@ var LiveSessionCheckpoint = class {
|
|
|
4785
5468
|
}
|
|
4786
5469
|
};
|
|
4787
5470
|
//#endregion
|
|
4788
|
-
export {
|
|
5471
|
+
export { defaultSessionEventLog as $, findGitRepositoryRoot as A, EXIT_CODES as At, assertPersistedKeyPolicy as B, QueueConnectionError as Bt, applyConversation as C, formatErrorMessage as Ct, permissionModeSatisfies as D, isAcpResourceNotFoundError as Dt, AcpClient as E, extractAcpError as Et, listSessionsForAgent as F, PERMISSION_MODES as Ft, recordPerfDuration as G, getPerfMetricsSnapshot as H, normalizeName as I, PERMISSION_POLICY_ACTIONS as It, startPerfTimer as J, resetPerfMetrics as K, pruneSessions as L, SESSION_RECORD_SCHEMA as Lt, findSessionByDirectoryWalk as M, OUTPUT_ERROR_CODES as Mt, isoNow$2 as N, OUTPUT_ERROR_ORIGINS as Nt, DEFAULT_HISTORY_LIMIT as O, toAcpErrorPayload as Ot, listSessions as P, OUTPUT_FORMATS as Pt, DEFAULT_EVENT_SEGMENT_MAX_BYTES as Q, resolveSessionRecord as R, AcpxOperationalError as Rt, sessionOptionsFromRecord as S, exitCodeForOutputErrorCode as St, reconcileAgentSessionId as T, normalizeOutputError as Tt, incrementPerfCounter as U, formatPerfMetric as V, QueueProtocolError as Vt, measurePerf as W, serializeSessionRecordForDisk as X, parseSessionRecord as Y, normalizeRuntimeSessionId as Z, recordPromptSubmission as _, withTimeout as _t, applyRequestedModelIfAdvertised as a, isAcpJsonRpcMessage as at, mergeSessionOptions as b, normalizeAgentName$1 as bt, setDesiredConfigOption as c, PromptInputValidationError as ct, syncAdvertisedModelState as d, parsePromptSource as dt, sessionBaseDir$1 as et, applyConfigOptionsToRecord as f, promptToDisplayText as ft, recordClientOperation as g, withInterrupt as gt, createSessionConversation as h, TimeoutError as ht, connectAndLoadSession as i, extractSessionUpdateNotification as it, findSession as j, NON_INTERACTIVE_PERMISSION_POLICIES as jt, absolutePath as k, AUTH_POLICIES as kt, setDesiredModeId as l, isPromptInput as lt, cloneSessionConversation as m, InterruptedError as mt, runPromptTurn as n, sessionEventLockPath as nt, assertRequestedModelSupported as o, parseJsonRpcErrorMessage as ot, cloneSessionAcpxState as p, textPrompt as pt, setPerfGauge as q, withConnectedSession as r, sessionEventSegmentPath as rt, setCurrentModelId as s, parsePromptStopReason as st, LiveSessionCheckpoint as t, sessionEventActivePath as tt, setDesiredModelId as u, mergePromptSourceWithText as ut, recordSessionUpdate as v, DEFAULT_AGENT_NAME as vt, applyLifecycleSnapshotToRecord as w, isRetryablePromptError as wt, persistSessionOptions as x, resolveAgentCommand as xt, trimConversationForRuntime as y, listBuiltInAgents as yt, writeSessionRecord as z, AgentSpawnError as zt };
|
|
4789
5472
|
|
|
4790
|
-
//# sourceMappingURL=live-checkpoint-
|
|
5473
|
+
//# sourceMappingURL=live-checkpoint-CuFft_Nd.js.map
|