acpx 0.8.0 → 0.9.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 +7 -4
- package/dist/{cli-BGYGVo3b.js → cli-Bf3yjqzE.js} +4 -4
- package/dist/{cli-BGYGVo3b.js.map → cli-Bf3yjqzE.js.map} +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +545 -247
- package/dist/cli.js.map +1 -1
- package/dist/{client-FzXPdgP7.d.ts → client-BssohYqM.d.ts} +30 -3
- package/dist/client-BssohYqM.d.ts.map +1 -0
- package/dist/{flags-D706STfk.js → flags-C-rwARqg.js} +96 -39
- package/dist/flags-C-rwARqg.js.map +1 -0
- package/dist/{flows-hcjHmU7P.js → flows-WLs26_5Y.js} +400 -335
- package/dist/flows-WLs26_5Y.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-D5d-K9s1.js} +1355 -700
- package/dist/live-checkpoint-D5d-K9s1.js.map +1 -0
- package/dist/{output-BL9XRWzS.js → output-DPg20dvn.js} +1151 -717
- package/dist/output-DPg20dvn.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-CFudjdkU.d.ts} +7 -1
- package/dist/session-options-CFudjdkU.d.ts.map +1 -0
- package/package.json +15 -12
- package/skills/acpx/SKILL.md +11 -3
- 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
- package/dist/session-options-BJyG6zEH.d.ts.map +0 -1
|
@@ -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,20 @@ 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;
|
|
728
828
|
}
|
|
729
829
|
//#endregion
|
|
730
830
|
//#region src/acp/agent-session-id.ts
|
|
@@ -869,6 +969,7 @@ const ZED_TAG_KEYS = new Set([
|
|
|
869
969
|
"Text",
|
|
870
970
|
"Mention",
|
|
871
971
|
"Image",
|
|
972
|
+
"Audio",
|
|
872
973
|
"Thinking",
|
|
873
974
|
"RedactedThinking",
|
|
874
975
|
"ToolUse"
|
|
@@ -896,10 +997,13 @@ function shouldSkipKeyRule(path) {
|
|
|
896
997
|
function shouldSkipDescend(path) {
|
|
897
998
|
return OPAQUE_VALUE_PATHS.has(joinPath(path)) || isToolResultOutputPath(path);
|
|
898
999
|
}
|
|
1000
|
+
function isToolResultOutputTail(path, toolResultsIndex) {
|
|
1001
|
+
return toolResultsIndex !== -1 && toolResultsIndex + 2 === path.length - 1;
|
|
1002
|
+
}
|
|
899
1003
|
function isToolResultOutputPath(path) {
|
|
900
1004
|
if (path.length < 5 || path[path.length - 1] !== "output") return false;
|
|
901
1005
|
const toolResultsIndex = path.lastIndexOf("tool_results");
|
|
902
|
-
if (
|
|
1006
|
+
if (!isToolResultOutputTail(path, toolResultsIndex)) return false;
|
|
903
1007
|
return path.slice(0, toolResultsIndex + 1).join(".") === "messages.Agent.tool_results";
|
|
904
1008
|
}
|
|
905
1009
|
function collectViolations(value, path, violations) {
|
|
@@ -909,12 +1013,12 @@ function collectViolations(value, path, violations) {
|
|
|
909
1013
|
}
|
|
910
1014
|
if (!isRecord(value)) return;
|
|
911
1015
|
const skipKeyRule = shouldSkipKeyRule(path);
|
|
912
|
-
for (const [key, child] of Object.entries(value))
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1016
|
+
for (const [key, child] of Object.entries(value)) collectKeyViolation(child, key, path, skipKeyRule, violations);
|
|
1017
|
+
}
|
|
1018
|
+
function collectKeyViolation(child, key, path, skipKeyRule, violations) {
|
|
1019
|
+
if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
|
|
1020
|
+
const childPath = [...path, key];
|
|
1021
|
+
if (!shouldSkipDescend(childPath)) collectViolations(child, childPath, violations);
|
|
918
1022
|
}
|
|
919
1023
|
function findPersistedKeyPolicyViolations(value) {
|
|
920
1024
|
const violations = [];
|
|
@@ -979,11 +1083,14 @@ function parseTokenUsage(raw) {
|
|
|
979
1083
|
]) {
|
|
980
1084
|
const value = record[field];
|
|
981
1085
|
if (value === void 0) continue;
|
|
982
|
-
if (
|
|
1086
|
+
if (!isNonNegativeFiniteNumber(value)) return null;
|
|
983
1087
|
usage[field] = value;
|
|
984
1088
|
}
|
|
985
1089
|
return usage;
|
|
986
1090
|
}
|
|
1091
|
+
function isNonNegativeFiniteNumber(value) {
|
|
1092
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1093
|
+
}
|
|
987
1094
|
function parseRequestTokenUsage(raw) {
|
|
988
1095
|
if (raw === void 0 || raw === null) return;
|
|
989
1096
|
const record = asRecord$4(raw);
|
|
@@ -1001,7 +1108,14 @@ function isSessionMessageImage(raw) {
|
|
|
1001
1108
|
if (!record || typeof record.source !== "string") return false;
|
|
1002
1109
|
if (record.size === void 0 || record.size === null) return true;
|
|
1003
1110
|
const size = asRecord$4(record.size);
|
|
1004
|
-
return !!size &&
|
|
1111
|
+
return !!size && isFiniteNumber(size.width) && isFiniteNumber(size.height);
|
|
1112
|
+
}
|
|
1113
|
+
function isSessionMessageAudio(raw) {
|
|
1114
|
+
const record = asRecord$4(raw);
|
|
1115
|
+
return !!record && typeof record.source === "string" && typeof record.mime_type === "string";
|
|
1116
|
+
}
|
|
1117
|
+
function isFiniteNumber(value) {
|
|
1118
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
1005
1119
|
}
|
|
1006
1120
|
function isUserContent(raw) {
|
|
1007
1121
|
const record = asRecord$4(raw);
|
|
@@ -1012,11 +1126,22 @@ function isUserContent(raw) {
|
|
|
1012
1126
|
return !!mention && typeof mention.uri === "string" && typeof mention.content === "string";
|
|
1013
1127
|
}
|
|
1014
1128
|
if (record.Image !== void 0) return isSessionMessageImage(record.Image);
|
|
1129
|
+
if (record.Audio !== void 0) return isSessionMessageAudio(record.Audio);
|
|
1015
1130
|
return false;
|
|
1016
1131
|
}
|
|
1017
1132
|
function isToolUse(raw) {
|
|
1018
1133
|
const record = asRecord$4(raw);
|
|
1019
|
-
return !!record &&
|
|
1134
|
+
return !!record && hasStringFields(record, [
|
|
1135
|
+
"id",
|
|
1136
|
+
"name",
|
|
1137
|
+
"raw_input"
|
|
1138
|
+
]) && hasOwn$1(record, "input") && typeof record.is_input_complete === "boolean" && isOptionalString(record.thought_signature);
|
|
1139
|
+
}
|
|
1140
|
+
function hasStringFields(record, keys) {
|
|
1141
|
+
return keys.every((key) => typeof record[key] === "string");
|
|
1142
|
+
}
|
|
1143
|
+
function isOptionalString(value) {
|
|
1144
|
+
return value === void 0 || value === null || typeof value === "string";
|
|
1020
1145
|
}
|
|
1021
1146
|
function isToolResultContent(raw) {
|
|
1022
1147
|
const record = asRecord$4(raw);
|
|
@@ -1033,14 +1158,15 @@ function isAgentContent(raw) {
|
|
|
1033
1158
|
const record = asRecord$4(raw);
|
|
1034
1159
|
if (!record) return false;
|
|
1035
1160
|
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
|
-
}
|
|
1161
|
+
if (record.Thinking !== void 0) return isThinkingContent(record.Thinking);
|
|
1040
1162
|
if (typeof record.RedactedThinking === "string") return true;
|
|
1041
1163
|
if (record.ToolUse !== void 0) return isToolUse(record.ToolUse);
|
|
1042
1164
|
return false;
|
|
1043
1165
|
}
|
|
1166
|
+
function isThinkingContent(raw) {
|
|
1167
|
+
const thinking = asRecord$4(raw);
|
|
1168
|
+
return !!thinking && typeof thinking.text === "string" && isOptionalString(thinking.signature);
|
|
1169
|
+
}
|
|
1044
1170
|
function isUserMessage$1(raw) {
|
|
1045
1171
|
const record = asRecord$4(raw);
|
|
1046
1172
|
if (!record || record.User === void 0) return false;
|
|
@@ -1060,56 +1186,88 @@ function isConversationMessage(raw) {
|
|
|
1060
1186
|
return raw === "Resume" || isUserMessage$1(raw) || isAgentMessage$1(raw);
|
|
1061
1187
|
}
|
|
1062
1188
|
function parseConversationRecord(record) {
|
|
1063
|
-
if (!
|
|
1064
|
-
|
|
1189
|
+
if (!hasValidConversationCore(record)) return;
|
|
1190
|
+
const title = parseConversationTitle(record.title);
|
|
1191
|
+
if (title === INVALID_VALUE) return;
|
|
1065
1192
|
const cumulativeTokenUsage = parseTokenUsage(record.cumulative_token_usage);
|
|
1066
1193
|
const requestTokenUsage = parseRequestTokenUsage(record.request_token_usage);
|
|
1067
1194
|
if (cumulativeTokenUsage === null || requestTokenUsage === null) return;
|
|
1068
1195
|
return {
|
|
1069
|
-
title
|
|
1196
|
+
title,
|
|
1070
1197
|
messages: record.messages,
|
|
1071
1198
|
updated_at: record.updated_at,
|
|
1072
1199
|
cumulative_token_usage: cumulativeTokenUsage ?? {},
|
|
1073
1200
|
request_token_usage: requestTokenUsage ?? {}
|
|
1074
1201
|
};
|
|
1075
1202
|
}
|
|
1203
|
+
const INVALID_VALUE = Symbol("invalid");
|
|
1204
|
+
function parseConversationTitle(value) {
|
|
1205
|
+
if (value === void 0 || value === null || typeof value === "string") return value;
|
|
1206
|
+
return INVALID_VALUE;
|
|
1207
|
+
}
|
|
1208
|
+
function hasValidConversationCore(record) {
|
|
1209
|
+
return Array.isArray(record.messages) && record.messages.every(isConversationMessage) && typeof record.updated_at === "string";
|
|
1210
|
+
}
|
|
1076
1211
|
function parseAcpxState(raw) {
|
|
1077
1212
|
const record = asRecord$4(raw);
|
|
1078
1213
|
if (!record) return;
|
|
1079
1214
|
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;
|
|
1215
|
+
assignBooleanTrue(state, "reset_on_next_ensure", record.reset_on_next_ensure);
|
|
1216
|
+
assignStringState(state, "current_mode_id", record.current_mode_id);
|
|
1217
|
+
assignStringState(state, "desired_mode_id", record.desired_mode_id);
|
|
1218
|
+
assignDesiredConfigOptions(state, record.desired_config_options);
|
|
1219
|
+
assignStringState(state, "current_model_id", record.current_model_id);
|
|
1090
1220
|
if (isStringArray(record.available_models)) state.available_models = [...record.available_models];
|
|
1091
1221
|
if (isStringArray(record.available_commands)) state.available_commands = [...record.available_commands];
|
|
1092
1222
|
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
|
-
}
|
|
1223
|
+
assignParsedSessionOptions(state, record.session_options);
|
|
1107
1224
|
return state;
|
|
1108
1225
|
}
|
|
1226
|
+
function assignBooleanTrue(state, key, value) {
|
|
1227
|
+
if (value === true) state[key] = true;
|
|
1228
|
+
}
|
|
1229
|
+
function assignStringState(state, key, value) {
|
|
1230
|
+
if (typeof value === "string") state[key] = value;
|
|
1231
|
+
}
|
|
1232
|
+
function assignDesiredConfigOptions(state, raw) {
|
|
1233
|
+
const desiredConfigOptions = asRecord$4(raw);
|
|
1234
|
+
if (!desiredConfigOptions) return;
|
|
1235
|
+
const parsed = Object.fromEntries(Object.entries(desiredConfigOptions).filter((entry) => {
|
|
1236
|
+
const [, value] = entry;
|
|
1237
|
+
return typeof value === "string";
|
|
1238
|
+
}));
|
|
1239
|
+
if (Object.keys(parsed).length > 0) state.desired_config_options = parsed;
|
|
1240
|
+
}
|
|
1241
|
+
function assignParsedSessionOptions(state, raw) {
|
|
1242
|
+
const sessionOptions = asRecord$4(raw);
|
|
1243
|
+
if (!sessionOptions) return;
|
|
1244
|
+
const parsedSessionOptions = {};
|
|
1245
|
+
assignSessionOptionModel(parsedSessionOptions, sessionOptions.model);
|
|
1246
|
+
assignSessionOptionAllowedTools(parsedSessionOptions, sessionOptions.allowed_tools);
|
|
1247
|
+
assignSessionOptionMaxTurns(parsedSessionOptions, sessionOptions.max_turns);
|
|
1248
|
+
assignSessionOptionSystemPrompt(parsedSessionOptions, sessionOptions.system_prompt);
|
|
1249
|
+
if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
|
|
1250
|
+
}
|
|
1251
|
+
function assignSessionOptionModel(options, value) {
|
|
1252
|
+
if (typeof value === "string") options.model = value;
|
|
1253
|
+
}
|
|
1254
|
+
function assignSessionOptionAllowedTools(options, value) {
|
|
1255
|
+
if (isStringArray(value)) options.allowed_tools = [...value];
|
|
1256
|
+
}
|
|
1257
|
+
function assignSessionOptionMaxTurns(options, value) {
|
|
1258
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) options.max_turns = value;
|
|
1259
|
+
}
|
|
1260
|
+
function assignSessionOptionSystemPrompt(options, value) {
|
|
1261
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1262
|
+
options.system_prompt = value;
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const appendRecord = asRecord$4(value);
|
|
1266
|
+
if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) options.system_prompt = { append: appendRecord.append };
|
|
1267
|
+
}
|
|
1109
1268
|
function parseEventLog(raw, sessionId) {
|
|
1110
1269
|
const record = asRecord$4(raw);
|
|
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);
|
|
1270
|
+
if (!record || !hasValidEventLogCore(record)) return defaultSessionEventLog(sessionId);
|
|
1113
1271
|
return {
|
|
1114
1272
|
active_path: record.active_path,
|
|
1115
1273
|
segment_count: record.segment_count,
|
|
@@ -1119,6 +1277,12 @@ function parseEventLog(raw, sessionId) {
|
|
|
1119
1277
|
last_write_error: record.last_write_error == null || typeof record.last_write_error === "string" ? record.last_write_error : null
|
|
1120
1278
|
};
|
|
1121
1279
|
}
|
|
1280
|
+
function hasValidEventLogCore(record) {
|
|
1281
|
+
return typeof record.active_path === "string" && isPositiveInteger(record.segment_count) && isPositiveInteger(record.max_segment_bytes) && isPositiveInteger(record.max_segments);
|
|
1282
|
+
}
|
|
1283
|
+
function isPositiveInteger(value) {
|
|
1284
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
1285
|
+
}
|
|
1122
1286
|
function normalizeOptionalName(value) {
|
|
1123
1287
|
if (value == null) return;
|
|
1124
1288
|
if (typeof value !== "string") return null;
|
|
@@ -1154,17 +1318,19 @@ function parseSessionRecord(raw) {
|
|
|
1154
1318
|
const record = asRecord$4(raw);
|
|
1155
1319
|
if (!record) return null;
|
|
1156
1320
|
if (record.schema !== "acpx.session.v1") return null;
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1321
|
+
const optionals = validSessionOptionals({
|
|
1322
|
+
name: normalizeOptionalName(record.name),
|
|
1323
|
+
pid: normalizeOptionalPid(record.pid),
|
|
1324
|
+
closed: normalizeOptionalBoolean(record.closed, false),
|
|
1325
|
+
closedAt: normalizeOptionalString(record.closed_at),
|
|
1326
|
+
agentStartedAt: normalizeOptionalString(record.agent_started_at),
|
|
1327
|
+
lastPromptAt: normalizeOptionalString(record.last_prompt_at),
|
|
1328
|
+
lastAgentExitCode: normalizeOptionalExitCode(record.last_agent_exit_code),
|
|
1329
|
+
lastAgentExitSignal: normalizeOptionalSignal(record.last_agent_exit_signal),
|
|
1330
|
+
lastAgentExitAt: normalizeOptionalString(record.last_agent_exit_at),
|
|
1331
|
+
lastAgentDisconnectReason: normalizeOptionalString(record.last_agent_disconnect_reason)
|
|
1332
|
+
});
|
|
1333
|
+
if (!hasValidSessionRecordCore(record) || !optionals) return null;
|
|
1168
1334
|
const conversation = parseConversationRecord(record);
|
|
1169
1335
|
if (!conversation) return null;
|
|
1170
1336
|
const eventLog = parseEventLog(record.event_log, record.acpx_record_id);
|
|
@@ -1177,21 +1343,21 @@ function parseSessionRecord(raw) {
|
|
|
1177
1343
|
agentSessionId: normalizeRuntimeSessionId(record.agent_session_id),
|
|
1178
1344
|
agentCommand: record.agent_command,
|
|
1179
1345
|
cwd: record.cwd,
|
|
1180
|
-
name,
|
|
1346
|
+
name: optionals.name,
|
|
1181
1347
|
createdAt: record.created_at,
|
|
1182
1348
|
lastUsedAt: record.last_used_at,
|
|
1183
1349
|
lastSeq: record.last_seq,
|
|
1184
1350
|
lastRequestId,
|
|
1185
1351
|
eventLog,
|
|
1186
|
-
closed,
|
|
1187
|
-
closedAt,
|
|
1188
|
-
pid,
|
|
1189
|
-
agentStartedAt,
|
|
1190
|
-
lastPromptAt,
|
|
1191
|
-
lastAgentExitCode,
|
|
1192
|
-
lastAgentExitSignal,
|
|
1193
|
-
lastAgentExitAt,
|
|
1194
|
-
lastAgentDisconnectReason,
|
|
1352
|
+
closed: optionals.closed,
|
|
1353
|
+
closedAt: optionals.closedAt,
|
|
1354
|
+
pid: optionals.pid,
|
|
1355
|
+
agentStartedAt: optionals.agentStartedAt,
|
|
1356
|
+
lastPromptAt: optionals.lastPromptAt,
|
|
1357
|
+
lastAgentExitCode: optionals.lastAgentExitCode,
|
|
1358
|
+
lastAgentExitSignal: optionals.lastAgentExitSignal,
|
|
1359
|
+
lastAgentExitAt: optionals.lastAgentExitAt,
|
|
1360
|
+
lastAgentDisconnectReason: optionals.lastAgentDisconnectReason,
|
|
1195
1361
|
protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
|
|
1196
1362
|
agentCapabilities: asRecord$4(record.agent_capabilities),
|
|
1197
1363
|
title: conversation.title,
|
|
@@ -1202,6 +1368,35 @@ function parseSessionRecord(raw) {
|
|
|
1202
1368
|
acpx: parseAcpxState(record.acpx)
|
|
1203
1369
|
};
|
|
1204
1370
|
}
|
|
1371
|
+
function hasValidSessionRecordCore(record) {
|
|
1372
|
+
return hasStringFields(record, [
|
|
1373
|
+
"acpx_record_id",
|
|
1374
|
+
"acp_session_id",
|
|
1375
|
+
"agent_command",
|
|
1376
|
+
"cwd",
|
|
1377
|
+
"created_at",
|
|
1378
|
+
"last_used_at"
|
|
1379
|
+
]) && typeof record.last_seq === "number" && Number.isInteger(record.last_seq) && record.last_seq >= 0;
|
|
1380
|
+
}
|
|
1381
|
+
function validSessionOptionals(options) {
|
|
1382
|
+
if (hasNullOptionalSessionFields(options) || hasInvalidExitStatus(options)) return null;
|
|
1383
|
+
return options;
|
|
1384
|
+
}
|
|
1385
|
+
function hasNullOptionalSessionFields(options) {
|
|
1386
|
+
return [
|
|
1387
|
+
options.name,
|
|
1388
|
+
options.pid,
|
|
1389
|
+
options.closed,
|
|
1390
|
+
options.closedAt,
|
|
1391
|
+
options.agentStartedAt,
|
|
1392
|
+
options.lastPromptAt,
|
|
1393
|
+
options.lastAgentExitAt,
|
|
1394
|
+
options.lastAgentDisconnectReason
|
|
1395
|
+
].some((value) => value === null);
|
|
1396
|
+
}
|
|
1397
|
+
function hasInvalidExitStatus(options) {
|
|
1398
|
+
return typeof options.lastAgentExitCode === "symbol" || typeof options.lastAgentExitSignal === "symbol";
|
|
1399
|
+
}
|
|
1205
1400
|
//#endregion
|
|
1206
1401
|
//#region src/session/persistence/index.ts
|
|
1207
1402
|
const SESSION_INDEX_SCHEMA = "acpx.session-index.v1";
|
|
@@ -1212,7 +1407,7 @@ function asRecord$3(value) {
|
|
|
1212
1407
|
function parseIndexEntry(raw) {
|
|
1213
1408
|
const record = asRecord$3(raw);
|
|
1214
1409
|
if (!record) return;
|
|
1215
|
-
if (
|
|
1410
|
+
if (!hasRequiredIndexEntryFields(record)) return;
|
|
1216
1411
|
if (record.name !== void 0 && typeof record.name !== "string") return;
|
|
1217
1412
|
return {
|
|
1218
1413
|
file: record.file,
|
|
@@ -1225,6 +1420,16 @@ function parseIndexEntry(raw) {
|
|
|
1225
1420
|
lastUsedAt: record.lastUsedAt
|
|
1226
1421
|
};
|
|
1227
1422
|
}
|
|
1423
|
+
function hasRequiredIndexEntryFields(record) {
|
|
1424
|
+
return [
|
|
1425
|
+
"file",
|
|
1426
|
+
"acpxRecordId",
|
|
1427
|
+
"acpSessionId",
|
|
1428
|
+
"agentCommand",
|
|
1429
|
+
"cwd",
|
|
1430
|
+
"lastUsedAt"
|
|
1431
|
+
].every((key) => typeof record[key] === "string") && typeof record.closed === "boolean";
|
|
1432
|
+
}
|
|
1228
1433
|
function sessionIndexPath(sessionDir) {
|
|
1229
1434
|
return path.join(sessionDir, "index.json");
|
|
1230
1435
|
}
|
|
@@ -1436,13 +1641,17 @@ async function findSessionByDirectoryWalk(options) {
|
|
|
1436
1641
|
for (;;) {
|
|
1437
1642
|
const match = sessions.find((session) => matchesSessionEntry(session, current, normalizedName));
|
|
1438
1643
|
if (match) return await loadRecordFromIndexEntry(match);
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
if (parent === current) return;
|
|
1644
|
+
const parent = nextWalkParent(current, walkBoundary, walkRoot);
|
|
1645
|
+
if (!parent) return;
|
|
1442
1646
|
current = parent;
|
|
1443
|
-
if (!isWithinBoundary(walkBoundary, current)) return;
|
|
1444
1647
|
}
|
|
1445
1648
|
}
|
|
1649
|
+
function nextWalkParent(current, walkBoundary, walkRoot) {
|
|
1650
|
+
if (current === walkBoundary || current === walkRoot) return;
|
|
1651
|
+
const parent = path.dirname(current);
|
|
1652
|
+
if (parent === current || !isWithinBoundary(walkBoundary, parent)) return;
|
|
1653
|
+
return parent;
|
|
1654
|
+
}
|
|
1446
1655
|
function closedAtOrLastUsedAt(record) {
|
|
1447
1656
|
return record.closedAt ?? record.lastUsedAt;
|
|
1448
1657
|
}
|
|
@@ -1451,14 +1660,7 @@ function isSessionStreamFile(fileName, safeId) {
|
|
|
1451
1660
|
}
|
|
1452
1661
|
async function pruneSessions(options = {}) {
|
|
1453
1662
|
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
|
-
}
|
|
1663
|
+
const records = await loadPrunableRecords(filterPruneCandidates(await loadSessionIndexEntries(), options.agentCommand), options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0));
|
|
1462
1664
|
if (options.dryRun) return {
|
|
1463
1665
|
pruned: records,
|
|
1464
1666
|
bytesFreed: 0,
|
|
@@ -1470,24 +1672,7 @@ async function pruneSessions(options = {}) {
|
|
|
1470
1672
|
if (options.includeHistory) try {
|
|
1471
1673
|
dirEntries = await fs$1.readdir(sessionDir);
|
|
1472
1674
|
} 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
|
-
}
|
|
1675
|
+
for (const record of records) bytesFreed += await pruneSessionFiles(record, sessionDir, dirEntries, options.includeHistory === true);
|
|
1491
1676
|
await rebuildSessionIndex(sessionDir).catch(() => {});
|
|
1492
1677
|
return {
|
|
1493
1678
|
pruned: records,
|
|
@@ -1495,6 +1680,35 @@ async function pruneSessions(options = {}) {
|
|
|
1495
1680
|
dryRun: false
|
|
1496
1681
|
};
|
|
1497
1682
|
}
|
|
1683
|
+
function filterPruneCandidates(entries, agentCommand) {
|
|
1684
|
+
return entries.filter((entry) => entry.closed && (!agentCommand || entry.agentCommand === agentCommand));
|
|
1685
|
+
}
|
|
1686
|
+
async function loadPrunableRecords(entries, cutoff) {
|
|
1687
|
+
const records = [];
|
|
1688
|
+
const cutoffIso = cutoff?.toISOString();
|
|
1689
|
+
for (const entry of entries) {
|
|
1690
|
+
const record = await loadRecordFromIndexEntry(entry);
|
|
1691
|
+
if (record && isBeforeCutoff(record, cutoffIso)) records.push(record);
|
|
1692
|
+
}
|
|
1693
|
+
return records;
|
|
1694
|
+
}
|
|
1695
|
+
function isBeforeCutoff(record, cutoffIso) {
|
|
1696
|
+
return !cutoffIso || closedAtOrLastUsedAt(record) < cutoffIso;
|
|
1697
|
+
}
|
|
1698
|
+
async function pruneSessionFiles(record, sessionDir, dirEntries, includeHistory) {
|
|
1699
|
+
const safeId = encodeURIComponent(record.acpxRecordId);
|
|
1700
|
+
let bytesFreed = await unlinkCountingBytes(path.join(sessionDir, `${safeId}.json`));
|
|
1701
|
+
if (includeHistory) for (const name of dirEntries.filter((entry) => isSessionStreamFile(entry, safeId))) bytesFreed += await unlinkCountingBytes(path.join(sessionDir, name));
|
|
1702
|
+
return bytesFreed;
|
|
1703
|
+
}
|
|
1704
|
+
async function unlinkCountingBytes(filePath) {
|
|
1705
|
+
let bytes = 0;
|
|
1706
|
+
try {
|
|
1707
|
+
bytes = (await fs$1.stat(filePath)).size;
|
|
1708
|
+
} catch {}
|
|
1709
|
+
await fs$1.unlink(filePath).catch(() => void 0);
|
|
1710
|
+
return bytes;
|
|
1711
|
+
}
|
|
1498
1712
|
//#endregion
|
|
1499
1713
|
//#region src/permission-prompt.ts
|
|
1500
1714
|
async function promptForPermission(options) {
|
|
@@ -1692,21 +1906,66 @@ function pickOption(options, kinds) {
|
|
|
1692
1906
|
if (match) return match;
|
|
1693
1907
|
}
|
|
1694
1908
|
}
|
|
1909
|
+
const TOOL_KIND_TITLE_MATCHERS = [
|
|
1910
|
+
{
|
|
1911
|
+
kind: "read",
|
|
1912
|
+
needles: ["read", "cat"]
|
|
1913
|
+
},
|
|
1914
|
+
{
|
|
1915
|
+
kind: "search",
|
|
1916
|
+
needles: [
|
|
1917
|
+
"search",
|
|
1918
|
+
"find",
|
|
1919
|
+
"grep"
|
|
1920
|
+
]
|
|
1921
|
+
},
|
|
1922
|
+
{
|
|
1923
|
+
kind: "edit",
|
|
1924
|
+
needles: [
|
|
1925
|
+
"write",
|
|
1926
|
+
"edit",
|
|
1927
|
+
"patch"
|
|
1928
|
+
]
|
|
1929
|
+
},
|
|
1930
|
+
{
|
|
1931
|
+
kind: "delete",
|
|
1932
|
+
needles: ["delete", "remove"]
|
|
1933
|
+
},
|
|
1934
|
+
{
|
|
1935
|
+
kind: "move",
|
|
1936
|
+
needles: ["move", "rename"]
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
kind: "execute",
|
|
1940
|
+
needles: [
|
|
1941
|
+
"run",
|
|
1942
|
+
"execute",
|
|
1943
|
+
"bash"
|
|
1944
|
+
]
|
|
1945
|
+
},
|
|
1946
|
+
{
|
|
1947
|
+
kind: "fetch",
|
|
1948
|
+
needles: [
|
|
1949
|
+
"fetch",
|
|
1950
|
+
"http",
|
|
1951
|
+
"url"
|
|
1952
|
+
]
|
|
1953
|
+
},
|
|
1954
|
+
{
|
|
1955
|
+
kind: "think",
|
|
1956
|
+
needles: ["think"]
|
|
1957
|
+
}
|
|
1958
|
+
];
|
|
1695
1959
|
function inferToolKind(params) {
|
|
1696
1960
|
if (params.toolCall.kind) return params.toolCall.kind;
|
|
1697
1961
|
const title = params.toolCall.title?.trim().toLowerCase();
|
|
1698
1962
|
if (!title) return;
|
|
1699
1963
|
const head = title.split(":", 1)[0]?.trim();
|
|
1700
1964
|
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";
|
|
1965
|
+
return titleHeadToolKind(head) ?? "other";
|
|
1966
|
+
}
|
|
1967
|
+
function titleHeadToolKind(head) {
|
|
1968
|
+
return TOOL_KIND_TITLE_MATCHERS.find(({ needles }) => needles.some((needle) => head.includes(needle)))?.kind;
|
|
1710
1969
|
}
|
|
1711
1970
|
function isAutoApprovedReadKind(kind) {
|
|
1712
1971
|
return kind === "read" || kind === "search";
|
|
@@ -1801,6 +2060,44 @@ function buildEscalationEvent(params, matchedRule) {
|
|
|
1801
2060
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1802
2061
|
};
|
|
1803
2062
|
}
|
|
2063
|
+
function selectedOrFirst(options, allowOption) {
|
|
2064
|
+
return { response: selected((allowOption ?? options[0]).optionId) };
|
|
2065
|
+
}
|
|
2066
|
+
function selectedOrCancelled(option) {
|
|
2067
|
+
return { response: option ? selected(option.optionId) : cancelled() };
|
|
2068
|
+
}
|
|
2069
|
+
async function resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption) {
|
|
2070
|
+
if (canPromptForPermission$1()) return resolveInteractivePromptResult(params, allowOption, rejectOption);
|
|
2071
|
+
const escalation = buildEscalationEvent(params, policyMatch.matchedRule);
|
|
2072
|
+
return {
|
|
2073
|
+
response: withEscalationMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), escalation),
|
|
2074
|
+
escalation
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
async function resolveInteractivePromptResult(params, allowOption, rejectOption) {
|
|
2078
|
+
const approved = await promptForToolPermission(params);
|
|
2079
|
+
if (approved && allowOption) return { response: selected(allowOption.optionId) };
|
|
2080
|
+
if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
|
|
2081
|
+
return { response: cancelled() };
|
|
2082
|
+
}
|
|
2083
|
+
function resolvePolicyMatch(params, policyMatch, options, allowOption, rejectOption) {
|
|
2084
|
+
if (policyMatch?.action === "approve") return selectedOrFirst(options, allowOption);
|
|
2085
|
+
if (policyMatch?.action === "deny") return selectedOrCancelled(rejectOption);
|
|
2086
|
+
if (policyMatch?.action === "escalate") return resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption);
|
|
2087
|
+
}
|
|
2088
|
+
function resolveModeMatch(options, mode, allowOption, rejectOption) {
|
|
2089
|
+
if (mode === "approve-all") return selectedOrFirst(options, allowOption);
|
|
2090
|
+
if (mode === "deny-all") return selectedOrCancelled(rejectOption);
|
|
2091
|
+
}
|
|
2092
|
+
function resolveNonInteractivePermission(nonInteractivePolicy, rejectOption) {
|
|
2093
|
+
if (nonInteractivePolicy === "fail") throw new PermissionPromptUnavailableError();
|
|
2094
|
+
return selectedOrCancelled(rejectOption);
|
|
2095
|
+
}
|
|
2096
|
+
async function resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption) {
|
|
2097
|
+
if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return { response: selected(allowOption.optionId) };
|
|
2098
|
+
if (!canPromptForPermission$1()) return resolveNonInteractivePermission(nonInteractivePolicy, rejectOption);
|
|
2099
|
+
return resolveInteractivePromptResult(params, allowOption, rejectOption);
|
|
2100
|
+
}
|
|
1804
2101
|
function permissionModeSatisfies(actual, required) {
|
|
1805
2102
|
return PERMISSION_MODE_RANK[actual] >= PERMISSION_MODE_RANK[required];
|
|
1806
2103
|
}
|
|
@@ -1809,46 +2106,11 @@ async function resolvePermissionRequestWithDetails(params, mode, nonInteractiveP
|
|
|
1809
2106
|
if (options.length === 0) return { response: cancelled() };
|
|
1810
2107
|
const allowOption = pickOption(options, ["allow_once", "allow_always"]);
|
|
1811
2108
|
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() };
|
|
2109
|
+
const resolvedByPolicy = await resolvePolicyMatch(params, matchPermissionPolicy(params, policy), options, allowOption, rejectOption);
|
|
2110
|
+
if (resolvedByPolicy) return resolvedByPolicy;
|
|
2111
|
+
const resolvedByMode = resolveModeMatch(options, mode, allowOption, rejectOption);
|
|
2112
|
+
if (resolvedByMode) return resolvedByMode;
|
|
2113
|
+
return resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption);
|
|
1852
2114
|
}
|
|
1853
2115
|
const DECISION_FALLBACK_ORDER = {
|
|
1854
2116
|
allow_once: ["allow_once", "allow_always"],
|
|
@@ -1875,21 +2137,35 @@ function readWindowsEnvValue(env, key) {
|
|
|
1875
2137
|
const matchedKey = Object.keys(env).find((entry) => entry.toUpperCase() === key);
|
|
1876
2138
|
return matchedKey ? env[matchedKey] : void 0;
|
|
1877
2139
|
}
|
|
1878
|
-
function
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
2140
|
+
function windowsExecutableExtensions(env) {
|
|
2141
|
+
return (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
|
|
2142
|
+
}
|
|
2143
|
+
function commandCandidates(command, env) {
|
|
2144
|
+
if (path.extname(command).length > 0) return [command];
|
|
2145
|
+
return windowsExecutableExtensions(env).map((extension) => `${command}${extension}`);
|
|
2146
|
+
}
|
|
2147
|
+
function commandHasPath(command) {
|
|
2148
|
+
return command.includes("/") || command.includes("\\") || path.isAbsolute(command);
|
|
2149
|
+
}
|
|
2150
|
+
function resolveWindowsPathCommand(command, env) {
|
|
2151
|
+
const candidates = commandCandidates(command, env);
|
|
1882
2152
|
const pathValue = readWindowsEnvValue(env, "PATH");
|
|
1883
2153
|
if (!pathValue) return;
|
|
1884
2154
|
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
|
-
}
|
|
2155
|
+
const resolved = findExistingCommandInDirectory(directory, candidates);
|
|
2156
|
+
if (resolved) return resolved;
|
|
1891
2157
|
}
|
|
1892
2158
|
}
|
|
2159
|
+
function findExistingCommandInDirectory(directory, candidates) {
|
|
2160
|
+
const trimmedDirectory = directory.trim();
|
|
2161
|
+
if (trimmedDirectory.length === 0) return;
|
|
2162
|
+
return candidates.map((candidate) => path.join(trimmedDirectory, candidate)).find((resolved) => fs.existsSync(resolved));
|
|
2163
|
+
}
|
|
2164
|
+
function resolveWindowsCommand(command, env = process.env) {
|
|
2165
|
+
const candidates = commandCandidates(command, env);
|
|
2166
|
+
if (commandHasPath(command)) return candidates.find((candidate) => fs.existsSync(candidate));
|
|
2167
|
+
return resolveWindowsPathCommand(command, env);
|
|
2168
|
+
}
|
|
1893
2169
|
function shouldUseWindowsBatchShell(command, platform = process.platform, env = process.env) {
|
|
1894
2170
|
if (platform !== "win32") return false;
|
|
1895
2171
|
const resolvedCommand = resolveWindowsCommand(command, env) ?? command;
|
|
@@ -1982,32 +2258,16 @@ function splitCommandLine(value) {
|
|
|
1982
2258
|
let quote = null;
|
|
1983
2259
|
let escaping = false;
|
|
1984
2260
|
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;
|
|
2261
|
+
const next = readCommandLineChar({
|
|
2262
|
+
ch,
|
|
2263
|
+
current,
|
|
2264
|
+
quote,
|
|
2265
|
+
escaping,
|
|
2266
|
+
parts
|
|
2267
|
+
});
|
|
2268
|
+
current = next.current;
|
|
2269
|
+
quote = next.quote;
|
|
2270
|
+
escaping = next.escaping;
|
|
2011
2271
|
}
|
|
2012
2272
|
if (escaping) current += "\\";
|
|
2013
2273
|
if (quote) throw new Error("Invalid --agent command: unterminated quote");
|
|
@@ -2018,6 +2278,59 @@ function splitCommandLine(value) {
|
|
|
2018
2278
|
args: parts.slice(1)
|
|
2019
2279
|
};
|
|
2020
2280
|
}
|
|
2281
|
+
function readCommandLineChar(state) {
|
|
2282
|
+
if (state.escaping) return {
|
|
2283
|
+
current: state.current + state.ch,
|
|
2284
|
+
quote: state.quote,
|
|
2285
|
+
escaping: false
|
|
2286
|
+
};
|
|
2287
|
+
if (state.ch === "\\" && state.quote !== "'") return {
|
|
2288
|
+
current: state.current,
|
|
2289
|
+
quote: state.quote,
|
|
2290
|
+
escaping: true
|
|
2291
|
+
};
|
|
2292
|
+
if (state.quote) return readQuotedCommandLineChar({
|
|
2293
|
+
ch: state.ch,
|
|
2294
|
+
current: state.current,
|
|
2295
|
+
quote: state.quote
|
|
2296
|
+
});
|
|
2297
|
+
return readUnquotedCommandLineChar(state);
|
|
2298
|
+
}
|
|
2299
|
+
function readQuotedCommandLineChar(state) {
|
|
2300
|
+
if (state.ch === state.quote) return {
|
|
2301
|
+
current: state.current,
|
|
2302
|
+
quote: null,
|
|
2303
|
+
escaping: false
|
|
2304
|
+
};
|
|
2305
|
+
return {
|
|
2306
|
+
current: state.current + state.ch,
|
|
2307
|
+
quote: state.quote,
|
|
2308
|
+
escaping: false
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
function readUnquotedCommandLineChar(state) {
|
|
2312
|
+
if (state.ch === "'" || state.ch === "\"") return {
|
|
2313
|
+
current: state.current,
|
|
2314
|
+
quote: state.ch,
|
|
2315
|
+
escaping: false
|
|
2316
|
+
};
|
|
2317
|
+
if (/\s/.test(state.ch)) {
|
|
2318
|
+
flushCommandLinePart(state.parts, state.current);
|
|
2319
|
+
return {
|
|
2320
|
+
current: "",
|
|
2321
|
+
quote: null,
|
|
2322
|
+
escaping: false
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
return {
|
|
2326
|
+
current: state.current + state.ch,
|
|
2327
|
+
quote: null,
|
|
2328
|
+
escaping: false
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
function flushCommandLinePart(parts, current) {
|
|
2332
|
+
if (current.length > 0) parts.push(current);
|
|
2333
|
+
}
|
|
2021
2334
|
function asAbsoluteCwd(cwd) {
|
|
2022
2335
|
return path.resolve(cwd);
|
|
2023
2336
|
}
|
|
@@ -2231,17 +2544,28 @@ async function ensureCopilotAcpSupport(command) {
|
|
|
2231
2544
|
function buildClaudeCodeOptionsMeta(options) {
|
|
2232
2545
|
if (!options) return;
|
|
2233
2546
|
const claudeCodeOptions = {};
|
|
2234
|
-
|
|
2235
|
-
if (Array.isArray(options.allowedTools)) claudeCodeOptions.allowedTools = [...options.allowedTools];
|
|
2236
|
-
if (typeof options.maxTurns === "number") claudeCodeOptions.maxTurns = options.maxTurns;
|
|
2547
|
+
assignClaudeCodeOptions(claudeCodeOptions, options);
|
|
2237
2548
|
const meta = {};
|
|
2238
2549
|
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 };
|
|
2550
|
+
assignClaudeCodeSystemPrompt(meta, options.systemPrompt);
|
|
2242
2551
|
if (Object.keys(meta).length === 0) return;
|
|
2243
2552
|
return meta;
|
|
2244
2553
|
}
|
|
2554
|
+
function assignClaudeCodeOptions(target, options) {
|
|
2555
|
+
if (typeof options.model === "string" && options.model.trim().length > 0) target.model = options.model;
|
|
2556
|
+
if (Array.isArray(options.allowedTools)) target.allowedTools = [...options.allowedTools];
|
|
2557
|
+
if (typeof options.maxTurns === "number") target.maxTurns = options.maxTurns;
|
|
2558
|
+
}
|
|
2559
|
+
function assignClaudeCodeSystemPrompt(target, systemPrompt) {
|
|
2560
|
+
if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
|
|
2561
|
+
target.systemPrompt = systemPrompt;
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
if (isAppendSystemPrompt(systemPrompt)) target.systemPrompt = { append: systemPrompt.append };
|
|
2565
|
+
}
|
|
2566
|
+
function isAppendSystemPrompt(value) {
|
|
2567
|
+
return !!value && typeof value === "object" && typeof value.append === "string" && value.append.length > 0;
|
|
2568
|
+
}
|
|
2245
2569
|
function resolveClaudeCodeExecutable(platform = process.platform, env = process.env) {
|
|
2246
2570
|
if (platform !== "win32") return;
|
|
2247
2571
|
if (readWindowsEnvValue(env, "CLAUDE_CODE_EXECUTABLE")) return;
|
|
@@ -2286,18 +2610,21 @@ function buildAgentEnvironment(authCredentials) {
|
|
|
2286
2610
|
const env = { ...process.env };
|
|
2287
2611
|
promotePrefixedAuthEnvironment(env);
|
|
2288
2612
|
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
|
-
}
|
|
2613
|
+
for (const [methodId, credential] of Object.entries(authCredentials)) assignAuthCredentialEnv(env, methodId, credential);
|
|
2299
2614
|
return env;
|
|
2300
2615
|
}
|
|
2616
|
+
function assignAuthCredentialEnv(env, methodId, credential) {
|
|
2617
|
+
if (typeof credential !== "string" || credential.trim().length === 0) return;
|
|
2618
|
+
if (!methodId.includes("=") && !methodId.includes("\0") && env[methodId] == null) env[methodId] = credential;
|
|
2619
|
+
const normalized = toEnvToken(methodId);
|
|
2620
|
+
if (normalized) {
|
|
2621
|
+
assignIfMissing(env, `${AUTH_ENV_PREFIX}${normalized}`, credential);
|
|
2622
|
+
assignIfMissing(env, normalized, credential);
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
function assignIfMissing(env, key, value) {
|
|
2626
|
+
if (env[key] == null) env[key] = value;
|
|
2627
|
+
}
|
|
2301
2628
|
function resolveConfiguredAuthCredential(methodId, authCredentials) {
|
|
2302
2629
|
const configCredentials = authCredentials ?? {};
|
|
2303
2630
|
return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)];
|
|
@@ -2335,18 +2662,23 @@ function hasResultOrError(value) {
|
|
|
2335
2662
|
if (hasError && !isErrorObject(value.error)) return false;
|
|
2336
2663
|
return true;
|
|
2337
2664
|
}
|
|
2665
|
+
function hasMethod(value) {
|
|
2666
|
+
return typeof value.method === "string" && value.method.length > 0;
|
|
2667
|
+
}
|
|
2668
|
+
function isJsonRpcRequest(value) {
|
|
2669
|
+
return hasMethod(value) && Object.hasOwn(value, "id") && hasValidId(value.id);
|
|
2670
|
+
}
|
|
2671
|
+
function isJsonRpcNotificationRecord(value) {
|
|
2672
|
+
return hasMethod(value) && !Object.hasOwn(value, "id");
|
|
2673
|
+
}
|
|
2674
|
+
function isJsonRpcResponse(value) {
|
|
2675
|
+
if (hasMethod(value) || !Object.hasOwn(value, "id") || !hasValidId(value.id)) return false;
|
|
2676
|
+
return hasResultOrError(value);
|
|
2677
|
+
}
|
|
2338
2678
|
function isAcpJsonRpcMessage(value) {
|
|
2339
2679
|
const record = asRecord$2(value);
|
|
2340
2680
|
if (!record || record.jsonrpc !== "2.0") return false;
|
|
2341
|
-
|
|
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;
|
|
2681
|
+
return isJsonRpcNotificationRecord(record) || isJsonRpcRequest(record) || isJsonRpcResponse(record);
|
|
2350
2682
|
}
|
|
2351
2683
|
function isJsonRpcNotification(message) {
|
|
2352
2684
|
return Object.hasOwn(message, "method") && typeof message.method === "string" && !Object.hasOwn(message, "id");
|
|
@@ -2710,27 +3042,35 @@ var TerminalManager = class {
|
|
|
2710
3042
|
async signalProcess(terminal, signal) {
|
|
2711
3043
|
const pid = terminal.process.pid;
|
|
2712
3044
|
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);
|
|
3045
|
+
await this.signalWindowsProcessGroup(terminal, pid, signal);
|
|
2720
3046
|
return;
|
|
2721
3047
|
}
|
|
2722
3048
|
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);
|
|
3049
|
+
await this.signalPosixProcessGroup(terminal, pid, signal);
|
|
2730
3050
|
return;
|
|
2731
3051
|
}
|
|
2732
3052
|
terminal.process.kill(signal);
|
|
2733
3053
|
}
|
|
3054
|
+
async signalWindowsProcessGroup(terminal, pid, signal) {
|
|
3055
|
+
await this.captureDescendantPids(terminal, pid);
|
|
3056
|
+
if (this.isRunning(terminal)) {
|
|
3057
|
+
await killWindowsProcessTree(pid, signal);
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal);
|
|
3061
|
+
}
|
|
3062
|
+
async signalPosixProcessGroup(terminal, pid, signal) {
|
|
3063
|
+
await this.captureDescendantPids(terminal, pid);
|
|
3064
|
+
if (hasLiveProcessGroup(pid)) {
|
|
3065
|
+
sendSignal(-pid, signal);
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
for (const descendantPid of terminal.descendantPids) sendSignal(descendantPid, signal);
|
|
3069
|
+
}
|
|
3070
|
+
async captureDescendantPids(terminal, pid) {
|
|
3071
|
+
if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
|
|
3072
|
+
for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
|
|
3073
|
+
}
|
|
2734
3074
|
async waitForCleanupAfterSignal(terminal) {
|
|
2735
3075
|
return await Promise.race([this.waitForTerminalAndTrackedDescendants(terminal).then(() => true), waitMs(this.killGraceMs).then(() => false)]);
|
|
2736
3076
|
}
|
|
@@ -2790,16 +3130,7 @@ async function listDescendantPids(rootPid) {
|
|
|
2790
3130
|
return [];
|
|
2791
3131
|
}
|
|
2792
3132
|
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
|
-
}
|
|
3133
|
+
for (const line of output.split("\n")) addProcessListLine(childrenByParent, line);
|
|
2803
3134
|
const descendants = [];
|
|
2804
3135
|
const queue = [...childrenByParent.get(rootPid) ?? []];
|
|
2805
3136
|
for (let index = 0; index < queue.length; index += 1) {
|
|
@@ -2809,6 +3140,24 @@ async function listDescendantPids(rootPid) {
|
|
|
2809
3140
|
}
|
|
2810
3141
|
return descendants;
|
|
2811
3142
|
}
|
|
3143
|
+
function addProcessListLine(childrenByParent, line) {
|
|
3144
|
+
const parsed = parseProcessListLine(line);
|
|
3145
|
+
if (!parsed) return;
|
|
3146
|
+
const children = childrenByParent.get(parsed.parentPid);
|
|
3147
|
+
if (children) children.push(parsed.pid);
|
|
3148
|
+
else childrenByParent.set(parsed.parentPid, [parsed.pid]);
|
|
3149
|
+
}
|
|
3150
|
+
function parseProcessListLine(line) {
|
|
3151
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)$/);
|
|
3152
|
+
if (!match) return;
|
|
3153
|
+
const pid = Number(match[1]);
|
|
3154
|
+
const parentPid = Number(match[2]);
|
|
3155
|
+
if (!Number.isInteger(pid) || !Number.isInteger(parentPid) || pid <= 0 || parentPid <= 0) return;
|
|
3156
|
+
return {
|
|
3157
|
+
pid,
|
|
3158
|
+
parentPid
|
|
3159
|
+
};
|
|
3160
|
+
}
|
|
2812
3161
|
async function runProcessListCommand() {
|
|
2813
3162
|
if (process.platform === "win32") return await runWindowsProcessListCommand();
|
|
2814
3163
|
return await new Promise((resolve, reject) => {
|
|
@@ -2979,6 +3328,20 @@ const DRAIN_POLL_INTERVAL_MS = 20;
|
|
|
2979
3328
|
const AGENT_CLOSE_TERM_GRACE_MS = 1500;
|
|
2980
3329
|
const AGENT_CLOSE_KILL_GRACE_MS = 1e3;
|
|
2981
3330
|
const STARTUP_STDERR_MAX_CHARS = 8192;
|
|
3331
|
+
function toReconnectedSessionResult(response) {
|
|
3332
|
+
return {
|
|
3333
|
+
agentSessionId: extractRuntimeSessionId(response?._meta),
|
|
3334
|
+
configOptions: response?.configOptions ?? void 0,
|
|
3335
|
+
models: response?.models ?? void 0
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
function childProcessIsRunning(agent) {
|
|
3339
|
+
if (!agent) return false;
|
|
3340
|
+
return agent.exitCode == null && agent.signalCode == null && !agent.killed;
|
|
3341
|
+
}
|
|
3342
|
+
function cancelledPermissionResponse() {
|
|
3343
|
+
return { outcome: { outcome: "cancelled" } };
|
|
3344
|
+
}
|
|
2982
3345
|
function shouldSuppressSdkConsoleError(args) {
|
|
2983
3346
|
if (args.length === 0) return false;
|
|
2984
3347
|
return typeof args[0] === "string" && args[0] === "Error handling request";
|
|
@@ -2993,6 +3356,19 @@ function installSdkConsoleErrorSuppression() {
|
|
|
2993
3356
|
console.error = originalConsoleError;
|
|
2994
3357
|
};
|
|
2995
3358
|
}
|
|
3359
|
+
function enqueueNdJsonLine(agentCommand, line, controller) {
|
|
3360
|
+
const trimmedLine = line.trim();
|
|
3361
|
+
if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) return;
|
|
3362
|
+
try {
|
|
3363
|
+
const message = JSON.parse(trimmedLine);
|
|
3364
|
+
controller.enqueue(message);
|
|
3365
|
+
} catch (err) {
|
|
3366
|
+
console.error("Failed to parse JSON message:", trimmedLine, err);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
function enqueueNdJsonLines(agentCommand, lines, controller) {
|
|
3370
|
+
for (const line of lines) enqueueNdJsonLine(agentCommand, line, controller);
|
|
3371
|
+
}
|
|
2996
3372
|
function createNdJsonMessageStream(agentCommand, output, input) {
|
|
2997
3373
|
const textEncoder = new TextEncoder();
|
|
2998
3374
|
const textDecoder = new TextDecoder();
|
|
@@ -3008,16 +3384,7 @@ function createNdJsonMessageStream(agentCommand, output, input) {
|
|
|
3008
3384
|
content += textDecoder.decode(value, { stream: true });
|
|
3009
3385
|
const lines = content.split("\n");
|
|
3010
3386
|
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
|
-
}
|
|
3387
|
+
enqueueNdJsonLines(agentCommand, lines, controller);
|
|
3021
3388
|
}
|
|
3022
3389
|
} finally {
|
|
3023
3390
|
reader.releaseLock();
|
|
@@ -3105,7 +3472,7 @@ var AcpClient = class {
|
|
|
3105
3472
|
}
|
|
3106
3473
|
getAgentLifecycleSnapshot() {
|
|
3107
3474
|
const pid = this.agent?.pid ?? this.lastKnownPid;
|
|
3108
|
-
const running =
|
|
3475
|
+
const running = childProcessIsRunning(this.agent);
|
|
3109
3476
|
return {
|
|
3110
3477
|
pid,
|
|
3111
3478
|
startedAt: this.agentStartedAt,
|
|
@@ -3116,9 +3483,15 @@ var AcpClient = class {
|
|
|
3116
3483
|
supportsLoadSession() {
|
|
3117
3484
|
return Boolean(this.initResult?.agentCapabilities?.loadSession);
|
|
3118
3485
|
}
|
|
3486
|
+
supportsResumeSession() {
|
|
3487
|
+
return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.resume);
|
|
3488
|
+
}
|
|
3119
3489
|
supportsCloseSession() {
|
|
3120
3490
|
return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.close);
|
|
3121
3491
|
}
|
|
3492
|
+
supportsListSessions() {
|
|
3493
|
+
return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.list);
|
|
3494
|
+
}
|
|
3122
3495
|
setEventHandlers(handlers) {
|
|
3123
3496
|
this.eventHandlers = { ...handlers };
|
|
3124
3497
|
}
|
|
@@ -3126,17 +3499,20 @@ var AcpClient = class {
|
|
|
3126
3499
|
this.eventHandlers = {};
|
|
3127
3500
|
}
|
|
3128
3501
|
updateRuntimeOptions(options) {
|
|
3502
|
+
const shouldRefreshPermissionPolicy = options.permissionMode !== void 0 || options.nonInteractivePermissions !== void 0;
|
|
3129
3503
|
if (options.permissionMode) this.options.permissionMode = options.permissionMode;
|
|
3130
3504
|
if (options.nonInteractivePermissions !== void 0) this.options.nonInteractivePermissions = options.nonInteractivePermissions;
|
|
3131
3505
|
if (Object.prototype.hasOwnProperty.call(options, "permissionPolicy")) this.options.permissionPolicy = options.permissionPolicy;
|
|
3132
3506
|
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
|
-
}
|
|
3507
|
+
this.refreshRuntimePermissionPolicy(shouldRefreshPermissionPolicy);
|
|
3137
3508
|
if (options.suppressSdkConsoleErrors !== void 0) this.options.suppressSdkConsoleErrors = options.suppressSdkConsoleErrors;
|
|
3138
3509
|
if (options.verbose !== void 0) this.options.verbose = options.verbose;
|
|
3139
3510
|
}
|
|
3511
|
+
refreshRuntimePermissionPolicy(enabled) {
|
|
3512
|
+
if (!enabled) return;
|
|
3513
|
+
this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
3514
|
+
this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
3515
|
+
}
|
|
3140
3516
|
hasReusableSession(sessionId) {
|
|
3141
3517
|
return this.connection != null && this.agent != null && isChildProcessRunning(this.agent) && this.loadedSessionId === sessionId;
|
|
3142
3518
|
}
|
|
@@ -3148,32 +3524,10 @@ var AcpClient = class {
|
|
|
3148
3524
|
async start() {
|
|
3149
3525
|
if (this.connection && this.agent && isChildProcessRunning(this.agent)) return;
|
|
3150
3526
|
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);
|
|
3527
|
+
const launch = await this.resolveAgentLaunchPlan();
|
|
3528
|
+
this.logAgentLaunch(launch);
|
|
3529
|
+
await this.ensureLaunchSupport(launch);
|
|
3530
|
+
const child = await this.spawnAgentProcess(launch);
|
|
3177
3531
|
this.closing = false;
|
|
3178
3532
|
this.agentStartedAt = isoNow$1();
|
|
3179
3533
|
this.lastAgentExit = void 0;
|
|
@@ -3187,17 +3541,79 @@ var AcpClient = class {
|
|
|
3187
3541
|
});
|
|
3188
3542
|
const input = Writable.toWeb(child.stdin);
|
|
3189
3543
|
const output = Readable.toWeb(child.stdout);
|
|
3190
|
-
const
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3544
|
+
const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output));
|
|
3545
|
+
const connection = this.createConnection(stream);
|
|
3546
|
+
connection.signal.addEventListener("abort", () => {
|
|
3547
|
+
this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
|
|
3548
|
+
}, { once: true });
|
|
3549
|
+
const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
|
|
3550
|
+
await this.initializeAgentConnection({
|
|
3551
|
+
child,
|
|
3552
|
+
connection,
|
|
3553
|
+
startupFailure,
|
|
3554
|
+
startupStderr,
|
|
3555
|
+
launch
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
async resolveAgentLaunchPlan() {
|
|
3559
|
+
const configuredCommand = splitCommandLine(this.options.agentCommand);
|
|
3560
|
+
const resolvedBuiltInLaunch = resolveBuiltInAgentLaunch(this.options.agentCommand);
|
|
3561
|
+
const spawnCommand = resolvedBuiltInLaunch?.command ?? configuredCommand.command;
|
|
3562
|
+
let args = resolvedBuiltInLaunch?.args ?? configuredCommand.args;
|
|
3563
|
+
args = await resolveGeminiCommandArgs(spawnCommand, args);
|
|
3564
|
+
if (isQoderAcpCommand(spawnCommand, args)) args = buildQoderAcpCommandArgs(args, this.options);
|
|
3565
|
+
return {
|
|
3566
|
+
spawnCommand,
|
|
3567
|
+
args,
|
|
3568
|
+
resolvedBuiltInLaunch,
|
|
3569
|
+
geminiAcp: isGeminiAcpCommand(spawnCommand, args),
|
|
3570
|
+
copilotAcp: isCopilotAcpCommand(spawnCommand, args),
|
|
3571
|
+
claudeAcp: isClaudeAcpCommand(spawnCommand, args),
|
|
3572
|
+
spawnOptions: buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials)
|
|
3573
|
+
};
|
|
3574
|
+
}
|
|
3575
|
+
logAgentLaunch(plan) {
|
|
3576
|
+
const launch = plan.resolvedBuiltInLaunch;
|
|
3577
|
+
if (launch?.source === "installed") {
|
|
3578
|
+
this.log(`spawning installed built-in agent ${launch.packageName}${launch.packageVersion ? `@${launch.packageVersion}` : ""} via ${plan.spawnCommand} ${plan.args.join(" ")}`);
|
|
3579
|
+
return;
|
|
3580
|
+
}
|
|
3581
|
+
if (launch?.source === "package-exec") {
|
|
3582
|
+
this.log(`spawning built-in agent ${launch.packageName}@${launch.packageRange} via current Node package exec bridge ${plan.spawnCommand} ${plan.args.join(" ")}`);
|
|
3583
|
+
return;
|
|
3584
|
+
}
|
|
3585
|
+
this.log(`spawning agent: ${plan.spawnCommand} ${plan.args.join(" ")}`);
|
|
3586
|
+
}
|
|
3587
|
+
async ensureLaunchSupport(plan) {
|
|
3588
|
+
if (plan.copilotAcp) await ensureCopilotAcpSupport(plan.spawnCommand);
|
|
3589
|
+
if (!plan.claudeAcp) return;
|
|
3590
|
+
const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env);
|
|
3591
|
+
if (claudeExe) {
|
|
3592
|
+
plan.spawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
|
|
3593
|
+
this.log(`resolved system Claude Code executable: ${claudeExe}`);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
async spawnAgentProcess(plan) {
|
|
3597
|
+
const spawnedChild = spawn(plan.spawnCommand, plan.args, buildSpawnCommandOptions(plan.spawnCommand, plan.spawnOptions));
|
|
3598
|
+
try {
|
|
3599
|
+
await waitForSpawn$1(spawnedChild);
|
|
3600
|
+
} catch (error) {
|
|
3601
|
+
throw new AgentSpawnError(this.options.agentCommand, error);
|
|
3602
|
+
}
|
|
3603
|
+
return requireAgentStdio(spawnedChild);
|
|
3604
|
+
}
|
|
3605
|
+
createConnection(stream) {
|
|
3606
|
+
return new ClientSideConnection(() => ({
|
|
3607
|
+
sessionUpdate: async (params) => {
|
|
3608
|
+
await this.handleSessionUpdate(params);
|
|
3609
|
+
},
|
|
3610
|
+
requestPermission: async (params) => {
|
|
3611
|
+
return this.handlePermissionRequest(params);
|
|
3612
|
+
},
|
|
3613
|
+
readTextFile: async (params) => {
|
|
3614
|
+
return this.handleReadTextFile(params);
|
|
3615
|
+
},
|
|
3616
|
+
writeTextFile: async (params) => {
|
|
3201
3617
|
return this.handleWriteTextFile(params);
|
|
3202
3618
|
},
|
|
3203
3619
|
createTerminal: async (params) => {
|
|
@@ -3215,49 +3631,51 @@ var AcpClient = class {
|
|
|
3215
3631
|
releaseTerminal: async (params) => {
|
|
3216
3632
|
return this.handleReleaseTerminal(params);
|
|
3217
3633
|
}
|
|
3218
|
-
}),
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
}, { once: true });
|
|
3222
|
-
const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
|
|
3634
|
+
}), stream);
|
|
3635
|
+
}
|
|
3636
|
+
async initializeAgentConnection(params) {
|
|
3223
3637
|
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;
|
|
3638
|
+
const initResult = await Promise.race([this.initializeProtocolConnection(params.connection, params.launch.geminiAcp), params.startupFailure.promise]);
|
|
3639
|
+
params.startupFailure.dispose();
|
|
3640
|
+
this.connection = params.connection;
|
|
3641
|
+
this.agent = params.child;
|
|
3246
3642
|
this.initResult = initResult;
|
|
3247
3643
|
this.log(`initialized protocol version ${initResult.protocolVersion}`);
|
|
3248
3644
|
} 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;
|
|
3645
|
+
await this.handleInitializeFailure(params, error);
|
|
3259
3646
|
}
|
|
3260
3647
|
}
|
|
3648
|
+
async initializeProtocolConnection(connection, geminiAcp) {
|
|
3649
|
+
const initializePromise = connection.initialize({
|
|
3650
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
3651
|
+
clientCapabilities: {
|
|
3652
|
+
fs: {
|
|
3653
|
+
readTextFile: true,
|
|
3654
|
+
writeTextFile: true
|
|
3655
|
+
},
|
|
3656
|
+
terminal: this.options.terminal !== false
|
|
3657
|
+
},
|
|
3658
|
+
clientInfo: {
|
|
3659
|
+
name: "acpx",
|
|
3660
|
+
version: "0.1.0"
|
|
3661
|
+
}
|
|
3662
|
+
});
|
|
3663
|
+
const initialized = geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
|
|
3664
|
+
await this.authenticateIfRequired(connection, initialized.authMethods ?? []);
|
|
3665
|
+
return initialized;
|
|
3666
|
+
}
|
|
3667
|
+
async handleInitializeFailure(params, error) {
|
|
3668
|
+
params.startupFailure.dispose();
|
|
3669
|
+
const normalizedError = await this.normalizeInitializeError(error, params.child, params.startupStderr);
|
|
3670
|
+
try {
|
|
3671
|
+
params.child.kill();
|
|
3672
|
+
} catch {}
|
|
3673
|
+
if (params.launch.geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(params.launch.spawnCommand), {
|
|
3674
|
+
cause: error,
|
|
3675
|
+
retryable: true
|
|
3676
|
+
});
|
|
3677
|
+
throw normalizedError;
|
|
3678
|
+
}
|
|
3261
3679
|
createTappedStream(base) {
|
|
3262
3680
|
const onAcpMessage = () => this.eventHandlers.onAcpMessage;
|
|
3263
3681
|
const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage;
|
|
@@ -3330,10 +3748,7 @@ var AcpClient = class {
|
|
|
3330
3748
|
async loadSessionWithOptions(sessionId, cwd = this.options.cwd, options = {}) {
|
|
3331
3749
|
const connection = this.getConnection();
|
|
3332
3750
|
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);
|
|
3751
|
+
const previousSuppression = this.applySessionUpdateSuppression(Boolean(options.suppressReplayUpdates));
|
|
3337
3752
|
let response;
|
|
3338
3753
|
try {
|
|
3339
3754
|
response = await this.runConnectionRequest(() => connection.loadSession({
|
|
@@ -3343,24 +3758,44 @@ var AcpClient = class {
|
|
|
3343
3758
|
}));
|
|
3344
3759
|
await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
|
|
3345
3760
|
} finally {
|
|
3346
|
-
this.
|
|
3347
|
-
this.suppressReplaySessionUpdateMessages = previousReplaySuppression;
|
|
3761
|
+
this.restoreSessionUpdateSuppression(previousSuppression);
|
|
3348
3762
|
}
|
|
3349
3763
|
this.loadedSessionId = sessionId;
|
|
3350
|
-
return
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3764
|
+
return toReconnectedSessionResult(response);
|
|
3765
|
+
}
|
|
3766
|
+
async resumeSession(sessionId, cwd = this.options.cwd) {
|
|
3767
|
+
const connection = this.getConnection();
|
|
3768
|
+
const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
|
|
3769
|
+
const response = await this.runConnectionRequest(() => connection.resumeSession({
|
|
3770
|
+
sessionId,
|
|
3771
|
+
cwd: sessionCwd,
|
|
3772
|
+
mcpServers: this.options.mcpServers ?? []
|
|
3773
|
+
}));
|
|
3774
|
+
this.loadedSessionId = sessionId;
|
|
3775
|
+
return toReconnectedSessionResult(response);
|
|
3776
|
+
}
|
|
3777
|
+
applySessionUpdateSuppression(enabled) {
|
|
3778
|
+
const previous = {
|
|
3779
|
+
suppressSessionUpdates: this.suppressSessionUpdates,
|
|
3780
|
+
suppressReplaySessionUpdateMessages: this.suppressReplaySessionUpdateMessages
|
|
3354
3781
|
};
|
|
3782
|
+
this.suppressSessionUpdates = previous.suppressSessionUpdates || enabled;
|
|
3783
|
+
this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages || enabled;
|
|
3784
|
+
return previous;
|
|
3785
|
+
}
|
|
3786
|
+
restoreSessionUpdateSuppression(previous) {
|
|
3787
|
+
this.suppressSessionUpdates = previous.suppressSessionUpdates;
|
|
3788
|
+
this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages;
|
|
3355
3789
|
}
|
|
3356
3790
|
async prompt(sessionId, prompt) {
|
|
3357
3791
|
const connection = this.getConnection();
|
|
3792
|
+
const normalizedPrompt = this.normalizePromptForAgent(prompt);
|
|
3358
3793
|
const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
|
|
3359
3794
|
let promptPromise;
|
|
3360
3795
|
try {
|
|
3361
3796
|
promptPromise = this.runConnectionRequest(() => connection.prompt({
|
|
3362
3797
|
sessionId,
|
|
3363
|
-
prompt:
|
|
3798
|
+
prompt: normalizedPrompt
|
|
3364
3799
|
}));
|
|
3365
3800
|
} catch (error) {
|
|
3366
3801
|
restoreConsoleError?.();
|
|
@@ -3371,13 +3806,9 @@ var AcpClient = class {
|
|
|
3371
3806
|
promise: promptPromise
|
|
3372
3807
|
};
|
|
3373
3808
|
try {
|
|
3374
|
-
|
|
3375
|
-
const permissionFailure = this.consumePromptPermissionFailure(sessionId);
|
|
3376
|
-
if (permissionFailure) throw permissionFailure;
|
|
3377
|
-
return response;
|
|
3809
|
+
return this.returnPromptResponseOrPermissionFailure(sessionId, await promptPromise);
|
|
3378
3810
|
} catch (error) {
|
|
3379
|
-
|
|
3380
|
-
if (permissionFailure) throw permissionFailure;
|
|
3811
|
+
this.throwPromptPermissionFailureIfPresent(sessionId);
|
|
3381
3812
|
throw error;
|
|
3382
3813
|
} finally {
|
|
3383
3814
|
restoreConsoleError?.();
|
|
@@ -3387,6 +3818,20 @@ var AcpClient = class {
|
|
|
3387
3818
|
this.promptPermissionFailures.delete(sessionId);
|
|
3388
3819
|
}
|
|
3389
3820
|
}
|
|
3821
|
+
normalizePromptForAgent(prompt) {
|
|
3822
|
+
const normalizedPrompt = typeof prompt === "string" ? textPrompt(prompt) : prompt;
|
|
3823
|
+
const unsupportedPromptContent = getUnsupportedPromptContentMessage(normalizedPrompt, this.initResult?.agentCapabilities);
|
|
3824
|
+
if (unsupportedPromptContent) throw new UnsupportedPromptContentError(unsupportedPromptContent);
|
|
3825
|
+
return normalizedPrompt;
|
|
3826
|
+
}
|
|
3827
|
+
returnPromptResponseOrPermissionFailure(sessionId, response) {
|
|
3828
|
+
this.throwPromptPermissionFailureIfPresent(sessionId);
|
|
3829
|
+
return response;
|
|
3830
|
+
}
|
|
3831
|
+
throwPromptPermissionFailureIfPresent(sessionId) {
|
|
3832
|
+
const permissionFailure = this.consumePromptPermissionFailure(sessionId);
|
|
3833
|
+
if (permissionFailure) throw permissionFailure;
|
|
3834
|
+
}
|
|
3390
3835
|
async setSessionMode(sessionId, modeId) {
|
|
3391
3836
|
const connection = this.getConnection();
|
|
3392
3837
|
try {
|
|
@@ -3437,6 +3882,10 @@ var AcpClient = class {
|
|
|
3437
3882
|
await this.runConnectionRequest(() => connection.closeSession({ sessionId }));
|
|
3438
3883
|
if (this.loadedSessionId === sessionId) this.loadedSessionId = void 0;
|
|
3439
3884
|
}
|
|
3885
|
+
async listSessions(params = {}) {
|
|
3886
|
+
const connection = this.getConnection();
|
|
3887
|
+
return await this.runConnectionRequest(() => connection.listSessions(params));
|
|
3888
|
+
}
|
|
3440
3889
|
async requestCancelActivePrompt() {
|
|
3441
3890
|
const active = this.activePrompt;
|
|
3442
3891
|
if (!active) return false;
|
|
@@ -3486,25 +3935,28 @@ var AcpClient = class {
|
|
|
3486
3935
|
}
|
|
3487
3936
|
async terminateAgentProcess(child) {
|
|
3488
3937
|
const stdinCloseGraceMs = resolveAgentCloseAfterStdinEndMs(this.options.agentCommand);
|
|
3489
|
-
|
|
3490
|
-
child.stdin.end();
|
|
3491
|
-
} catch {}
|
|
3938
|
+
this.endAgentStdin(child);
|
|
3492
3939
|
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)) {
|
|
3940
|
+
exited = await this.killAgentIfRunning(child, exited, "SIGTERM", AGENT_CLOSE_TERM_GRACE_MS);
|
|
3941
|
+
if (!exited) {
|
|
3500
3942
|
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);
|
|
3943
|
+
exited = await this.killAgentIfRunning(child, exited, "SIGKILL", AGENT_CLOSE_KILL_GRACE_MS);
|
|
3505
3944
|
}
|
|
3506
3945
|
this.detachAgentHandles(child, !exited);
|
|
3507
3946
|
}
|
|
3947
|
+
endAgentStdin(child) {
|
|
3948
|
+
if (child.stdin.destroyed) return;
|
|
3949
|
+
try {
|
|
3950
|
+
child.stdin.end();
|
|
3951
|
+
} catch {}
|
|
3952
|
+
}
|
|
3953
|
+
async killAgentIfRunning(child, alreadyExited, signal, waitMs) {
|
|
3954
|
+
if (alreadyExited || !isChildProcessRunning(child)) return alreadyExited;
|
|
3955
|
+
try {
|
|
3956
|
+
child.kill(signal);
|
|
3957
|
+
} catch {}
|
|
3958
|
+
return await waitForChildExit(child, waitMs);
|
|
3959
|
+
}
|
|
3508
3960
|
detachAgentHandles(agent, unref) {
|
|
3509
3961
|
const stdin = agent.stdin;
|
|
3510
3962
|
const stdout = agent.stdout;
|
|
@@ -3625,49 +4077,71 @@ var AcpClient = class {
|
|
|
3625
4077
|
this.log(`authenticated with method ${selected.methodId} (${selected.source})`);
|
|
3626
4078
|
}
|
|
3627
4079
|
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
|
-
}
|
|
4080
|
+
if (this.cancellingSessionIds.has(params.sessionId)) return cancelledPermissionResponse();
|
|
4081
|
+
const hostResponse = await this.tryHandlePermissionRequestWithHost(params);
|
|
4082
|
+
if (hostResponse) return hostResponse;
|
|
4083
|
+
const { response, recorded } = await this.resolvePermissionRequestFromMode(params);
|
|
4084
|
+
if (!recorded) {
|
|
4085
|
+
const decision = classifyPermissionDecision(params, response);
|
|
4086
|
+
this.recordPermissionDecision(decision);
|
|
3653
4087
|
}
|
|
3654
|
-
|
|
4088
|
+
return response;
|
|
4089
|
+
}
|
|
4090
|
+
async tryHandlePermissionRequestWithHost(params) {
|
|
4091
|
+
if (!this.options.onPermissionRequest) return;
|
|
4092
|
+
const signal = this.cancellationSignalForSession(params.sessionId);
|
|
3655
4093
|
try {
|
|
3656
|
-
const
|
|
3657
|
-
|
|
3658
|
-
|
|
4094
|
+
const decision = await this.options.onPermissionRequest({
|
|
4095
|
+
sessionId: params.sessionId,
|
|
4096
|
+
raw: params,
|
|
4097
|
+
inferredKind: inferToolKind(params)
|
|
4098
|
+
}, { signal });
|
|
4099
|
+
return this.hostPermissionDecisionResponse(params, signal, decision);
|
|
3659
4100
|
} catch (error) {
|
|
3660
|
-
|
|
3661
|
-
this.notePromptPermissionFailure(params.sessionId, error);
|
|
3662
|
-
this.recordPermissionDecision("cancelled");
|
|
3663
|
-
return { outcome: { outcome: "cancelled" } };
|
|
3664
|
-
}
|
|
3665
|
-
throw error;
|
|
4101
|
+
return this.hostPermissionErrorResponse(params, signal, error);
|
|
3666
4102
|
}
|
|
3667
|
-
|
|
3668
|
-
|
|
4103
|
+
}
|
|
4104
|
+
hostPermissionDecisionResponse(params, signal, decision) {
|
|
4105
|
+
if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
|
|
4106
|
+
this.recordPermissionDecision("cancelled");
|
|
4107
|
+
return cancelledPermissionResponse();
|
|
4108
|
+
}
|
|
4109
|
+
if (!decision) return;
|
|
4110
|
+
const response = decisionToResponse(params, decision);
|
|
4111
|
+
this.recordPermissionDecision(classifyPermissionDecision(params, response));
|
|
3669
4112
|
return response;
|
|
3670
4113
|
}
|
|
4114
|
+
hostPermissionErrorResponse(params, signal, error) {
|
|
4115
|
+
if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
|
|
4116
|
+
this.recordPermissionDecision("cancelled");
|
|
4117
|
+
return cancelledPermissionResponse();
|
|
4118
|
+
}
|
|
4119
|
+
this.log(`onPermissionRequest threw, falling through to mode-based resolver: ${error instanceof Error ? error.message : String(error)}`);
|
|
4120
|
+
}
|
|
4121
|
+
async resolvePermissionRequestFromMode(params) {
|
|
4122
|
+
try {
|
|
4123
|
+
const result = await resolvePermissionRequestWithDetails(params, this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny", this.options.permissionPolicy);
|
|
4124
|
+
this.emitPermissionEscalation(result.escalation);
|
|
4125
|
+
return {
|
|
4126
|
+
response: result.response,
|
|
4127
|
+
recorded: false
|
|
4128
|
+
};
|
|
4129
|
+
} catch (error) {
|
|
4130
|
+
return this.handleModePermissionError(params.sessionId, error);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
emitPermissionEscalation(escalation) {
|
|
4134
|
+
if (escalation) this.eventHandlers.onPermissionEscalation?.(escalation);
|
|
4135
|
+
}
|
|
4136
|
+
handleModePermissionError(sessionId, error) {
|
|
4137
|
+
if (!(error instanceof PermissionPromptUnavailableError)) throw error;
|
|
4138
|
+
this.notePromptPermissionFailure(sessionId, error);
|
|
4139
|
+
this.recordPermissionDecision("cancelled");
|
|
4140
|
+
return {
|
|
4141
|
+
response: cancelledPermissionResponse(),
|
|
4142
|
+
recorded: true
|
|
4143
|
+
};
|
|
4144
|
+
}
|
|
3671
4145
|
attachAgentLifecycleObservers(child) {
|
|
3672
4146
|
child.once("exit", (exitCode, signal) => {
|
|
3673
4147
|
this.recordAgentExit("process_exit", exitCode, signal);
|
|
@@ -3837,25 +4311,54 @@ var AcpClient = class {
|
|
|
3837
4311
|
}
|
|
3838
4312
|
};
|
|
3839
4313
|
//#endregion
|
|
4314
|
+
//#region src/runtime/engine/lifecycle.ts
|
|
4315
|
+
function applyLifecycleSnapshotToRecord(record, snapshot) {
|
|
4316
|
+
if (!snapshot) return;
|
|
4317
|
+
record.pid = snapshot.running ? snapshot.pid : void 0;
|
|
4318
|
+
record.agentStartedAt = snapshot.startedAt;
|
|
4319
|
+
if (snapshot.lastExit) {
|
|
4320
|
+
record.lastAgentExitCode = snapshot.lastExit.exitCode;
|
|
4321
|
+
record.lastAgentExitSignal = snapshot.lastExit.signal;
|
|
4322
|
+
record.lastAgentExitAt = snapshot.lastExit.exitedAt;
|
|
4323
|
+
record.lastAgentDisconnectReason = snapshot.lastExit.reason;
|
|
4324
|
+
return;
|
|
4325
|
+
}
|
|
4326
|
+
record.lastAgentExitCode = void 0;
|
|
4327
|
+
record.lastAgentExitSignal = void 0;
|
|
4328
|
+
record.lastAgentExitAt = void 0;
|
|
4329
|
+
record.lastAgentDisconnectReason = void 0;
|
|
4330
|
+
}
|
|
4331
|
+
function reconcileAgentSessionId(record, agentSessionId) {
|
|
4332
|
+
const normalized = normalizeRuntimeSessionId(agentSessionId);
|
|
4333
|
+
if (!normalized) return;
|
|
4334
|
+
record.agentSessionId = normalized;
|
|
4335
|
+
}
|
|
4336
|
+
function sessionHasAgentMessages(recordOrConversation) {
|
|
4337
|
+
return recordOrConversation.messages.some((message) => typeof message === "object" && message !== null && "Agent" in message);
|
|
4338
|
+
}
|
|
4339
|
+
function applyConversation(record, conversation) {
|
|
4340
|
+
record.title = conversation.title;
|
|
4341
|
+
record.updated_at = conversation.updated_at;
|
|
4342
|
+
record.messages = conversation.messages;
|
|
4343
|
+
record.cumulative_token_usage = conversation.cumulative_token_usage;
|
|
4344
|
+
record.request_token_usage = conversation.request_token_usage;
|
|
4345
|
+
}
|
|
4346
|
+
//#endregion
|
|
3840
4347
|
//#region src/runtime/engine/session-options.ts
|
|
3841
4348
|
function mergeSessionOptions(preferred, fallback) {
|
|
3842
4349
|
const merged = { ...fallback };
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
4350
|
+
assignDefinedOption(merged, "model", preferred?.model);
|
|
4351
|
+
assignDefinedOption(merged, "allowedTools", preferred?.allowedTools);
|
|
4352
|
+
assignDefinedOption(merged, "maxTurns", preferred?.maxTurns);
|
|
4353
|
+
assignDefinedOption(merged, "systemPrompt", preferred?.systemPrompt);
|
|
3847
4354
|
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
3848
4355
|
}
|
|
4356
|
+
function assignDefinedOption(target, key, value) {
|
|
4357
|
+
if (value !== void 0) target[key] = value;
|
|
4358
|
+
}
|
|
3849
4359
|
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) {
|
|
4360
|
+
const next = options === void 0 ? void 0 : persistedSessionOptions(options);
|
|
4361
|
+
if (next !== void 0) {
|
|
3859
4362
|
record.acpx = {
|
|
3860
4363
|
...record.acpx,
|
|
3861
4364
|
session_options: next
|
|
@@ -3869,14 +4372,49 @@ function sessionOptionsFromRecord(record) {
|
|
|
3869
4372
|
const stored = record.acpx?.session_options;
|
|
3870
4373
|
if (!stored) return;
|
|
3871
4374
|
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 };
|
|
4375
|
+
assignStoredOption(sessionOptions, "model", nonEmptyString(stored.model));
|
|
4376
|
+
assignStoredOption(sessionOptions, "allowedTools", storedAllowedTools(stored.allowed_tools));
|
|
4377
|
+
assignStoredOption(sessionOptions, "maxTurns", storedMaxTurns(stored.max_turns));
|
|
4378
|
+
assignStoredOption(sessionOptions, "systemPrompt", storedSystemPromptOption(stored.system_prompt));
|
|
3878
4379
|
return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
|
|
3879
4380
|
}
|
|
4381
|
+
function persistedSessionOptions(options) {
|
|
4382
|
+
const next = {
|
|
4383
|
+
model: nonEmptyString(options.model),
|
|
4384
|
+
allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
|
|
4385
|
+
max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
|
|
4386
|
+
system_prompt: normalizeSystemPromptOption(options.systemPrompt)
|
|
4387
|
+
};
|
|
4388
|
+
return hasPersistedSessionOptions(next) ? next : void 0;
|
|
4389
|
+
}
|
|
4390
|
+
function hasPersistedSessionOptions(options) {
|
|
4391
|
+
return options.model !== void 0 || options.allowed_tools !== void 0 || options.max_turns !== void 0 || options.system_prompt !== void 0;
|
|
4392
|
+
}
|
|
4393
|
+
function normalizeSystemPromptOption(value) {
|
|
4394
|
+
const prompt = nonEmptyString(value);
|
|
4395
|
+
if (prompt !== void 0) return prompt;
|
|
4396
|
+
const append = appendedSystemPrompt(value);
|
|
4397
|
+
return append === void 0 ? void 0 : { append };
|
|
4398
|
+
}
|
|
4399
|
+
function appendedSystemPrompt(value) {
|
|
4400
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
4401
|
+
return nonEmptyString(value.append);
|
|
4402
|
+
}
|
|
4403
|
+
function assignStoredOption(target, key, value) {
|
|
4404
|
+
assignDefinedOption(target, key, value);
|
|
4405
|
+
}
|
|
4406
|
+
function storedAllowedTools(value) {
|
|
4407
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : void 0;
|
|
4408
|
+
}
|
|
4409
|
+
function storedMaxTurns(value) {
|
|
4410
|
+
return typeof value === "number" ? value : void 0;
|
|
4411
|
+
}
|
|
4412
|
+
function storedSystemPromptOption(value) {
|
|
4413
|
+
return normalizeSystemPromptOption(value);
|
|
4414
|
+
}
|
|
4415
|
+
function nonEmptyString(value) {
|
|
4416
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
4417
|
+
}
|
|
3880
4418
|
//#endregion
|
|
3881
4419
|
//#region src/session/conversation-model.ts
|
|
3882
4420
|
const MAX_RUNTIME_MESSAGES = 200;
|
|
@@ -3903,13 +4441,17 @@ function normalizeAgentName(value) {
|
|
|
3903
4441
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
3904
4442
|
}
|
|
3905
4443
|
function extractText(content) {
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
return content.
|
|
4444
|
+
switch (content.type) {
|
|
4445
|
+
case "text": return content.text;
|
|
4446
|
+
case "resource_link": return content.title ?? content.name ?? content.uri;
|
|
4447
|
+
case "resource": return extractResourceText(content);
|
|
4448
|
+
case "audio": return `[audio] ${content.mimeType}`;
|
|
4449
|
+
default: return;
|
|
3911
4450
|
}
|
|
3912
4451
|
}
|
|
4452
|
+
function extractResourceText(content) {
|
|
4453
|
+
return "text" in content.resource && typeof content.resource.text === "string" ? content.resource.text : content.resource.uri;
|
|
4454
|
+
}
|
|
3913
4455
|
function contentToUserContent(content) {
|
|
3914
4456
|
if (content.type === "text") return { Text: content.text };
|
|
3915
4457
|
if (content.type === "resource_link") {
|
|
@@ -3919,17 +4461,22 @@ function contentToUserContent(content) {
|
|
|
3919
4461
|
content: value
|
|
3920
4462
|
} };
|
|
3921
4463
|
}
|
|
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
|
-
}
|
|
4464
|
+
if (content.type === "resource") return resourceToUserContent(content);
|
|
3929
4465
|
if (content.type === "image") return { Image: {
|
|
3930
4466
|
source: content.data,
|
|
3931
4467
|
size: null
|
|
3932
4468
|
} };
|
|
4469
|
+
if (content.type === "audio") return { Audio: {
|
|
4470
|
+
source: content.data,
|
|
4471
|
+
mime_type: content.mimeType
|
|
4472
|
+
} };
|
|
4473
|
+
}
|
|
4474
|
+
function resourceToUserContent(content) {
|
|
4475
|
+
if ("text" in content.resource && typeof content.resource.text === "string") return { Text: content.resource.text };
|
|
4476
|
+
return { Mention: {
|
|
4477
|
+
uri: content.resource.uri,
|
|
4478
|
+
content: content.resource.uri
|
|
4479
|
+
} };
|
|
3933
4480
|
}
|
|
3934
4481
|
function nextUserMessageId() {
|
|
3935
4482
|
return randomUUID();
|
|
@@ -4031,38 +4578,67 @@ function ensureToolUseContent(agent, toolCallId) {
|
|
|
4031
4578
|
}
|
|
4032
4579
|
function upsertToolResult(agent, toolCallId, patch) {
|
|
4033
4580
|
const existing = agent.tool_results[toolCallId];
|
|
4581
|
+
const fallback = existingToolResultValues(existing);
|
|
4034
4582
|
const next = {
|
|
4035
4583
|
tool_use_id: toolCallId,
|
|
4036
|
-
tool_name: patch.tool_name ??
|
|
4037
|
-
is_error: patch.is_error ??
|
|
4038
|
-
content: patch.content ??
|
|
4039
|
-
output: patch.output ??
|
|
4584
|
+
tool_name: patch.tool_name ?? fallback.tool_name,
|
|
4585
|
+
is_error: patch.is_error ?? fallback.is_error,
|
|
4586
|
+
content: patch.content ?? fallback.content,
|
|
4587
|
+
output: patch.output ?? fallback.output
|
|
4040
4588
|
};
|
|
4041
4589
|
agent.tool_results[toolCallId] = next;
|
|
4042
4590
|
}
|
|
4591
|
+
function existingToolResultValues(existing) {
|
|
4592
|
+
if (existing) return existing;
|
|
4593
|
+
return {
|
|
4594
|
+
tool_use_id: "",
|
|
4595
|
+
tool_name: "tool_call",
|
|
4596
|
+
is_error: false,
|
|
4597
|
+
content: { Text: "" },
|
|
4598
|
+
output: void 0
|
|
4599
|
+
};
|
|
4600
|
+
}
|
|
4043
4601
|
function applyToolCallUpdate(agent, update) {
|
|
4044
4602
|
const tool = ensureToolUseContent(agent, update.toolCallId);
|
|
4603
|
+
applyToolIdentityUpdate(tool, update);
|
|
4604
|
+
applyToolInputUpdate(tool, update);
|
|
4605
|
+
applyToolStatusUpdate(tool, update);
|
|
4606
|
+
applyToolResultUpdate(agent, tool, update);
|
|
4607
|
+
}
|
|
4608
|
+
function applyToolIdentityUpdate(tool, update) {
|
|
4045
4609
|
if (hasOwn(update, "title")) tool.name = normalizeAgentName(update.title) ?? tool.name ?? "tool_call";
|
|
4046
4610
|
if (hasOwn(update, "kind")) {
|
|
4047
4611
|
const kindName = normalizeAgentName(update.kind);
|
|
4048
4612
|
if (!tool.name || tool.name === "tool_call") tool.name = kindName ?? tool.name;
|
|
4049
4613
|
}
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
}
|
|
4614
|
+
}
|
|
4615
|
+
function applyToolInputUpdate(tool, update) {
|
|
4616
|
+
if (!hasOwn(update, "rawInput")) return;
|
|
4617
|
+
const rawInput = deepClone(update.rawInput);
|
|
4618
|
+
tool.input = rawInput ?? {};
|
|
4619
|
+
tool.raw_input = toRawInput(rawInput);
|
|
4620
|
+
}
|
|
4621
|
+
function applyToolStatusUpdate(tool, update) {
|
|
4055
4622
|
if (hasOwn(update, "status")) tool.is_input_complete = statusIndicatesComplete(update.status);
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4623
|
+
}
|
|
4624
|
+
function applyToolResultUpdate(agent, tool, update) {
|
|
4625
|
+
if (!hasToolResultPatch(update)) return;
|
|
4626
|
+
const status = update.status;
|
|
4627
|
+
const output = hasOwn(update, "rawOutput") ? deepClone(update.rawOutput) : void 0;
|
|
4628
|
+
upsertToolResult(agent, update.toolCallId, {
|
|
4629
|
+
tool_name: tool.name,
|
|
4630
|
+
is_error: statusIndicatesError(status),
|
|
4631
|
+
content: output === void 0 ? void 0 : toToolResultContent(output),
|
|
4632
|
+
output
|
|
4633
|
+
});
|
|
4634
|
+
}
|
|
4635
|
+
function hasToolResultPatch(update) {
|
|
4636
|
+
return [
|
|
4637
|
+
"rawOutput",
|
|
4638
|
+
"status",
|
|
4639
|
+
"title",
|
|
4640
|
+
"kind"
|
|
4641
|
+
].some((key) => hasOwn(update, key));
|
|
4066
4642
|
}
|
|
4067
4643
|
function asRecord(value) {
|
|
4068
4644
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
@@ -4093,9 +4669,12 @@ function usageToTokenUsage(update) {
|
|
|
4093
4669
|
"cachedReadTokens"
|
|
4094
4670
|
])
|
|
4095
4671
|
};
|
|
4096
|
-
if (normalized
|
|
4672
|
+
if (!hasTokenUsageValue(normalized)) return;
|
|
4097
4673
|
return normalized;
|
|
4098
4674
|
}
|
|
4675
|
+
function hasTokenUsageValue(usage) {
|
|
4676
|
+
return Object.values(usage).some((value) => value !== void 0);
|
|
4677
|
+
}
|
|
4099
4678
|
function ensureAcpxState$1(state) {
|
|
4100
4679
|
return state ?? {};
|
|
4101
4680
|
}
|
|
@@ -4134,14 +4713,21 @@ function cloneSessionAcpxState(state) {
|
|
|
4134
4713
|
available_models: state.available_models ? [...state.available_models] : void 0,
|
|
4135
4714
|
available_commands: state.available_commands ? [...state.available_commands] : void 0,
|
|
4136
4715
|
config_options: state.config_options ? deepClone(state.config_options) : void 0,
|
|
4137
|
-
session_options: state.session_options
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4716
|
+
session_options: cloneSessionOptions(state.session_options)
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4719
|
+
function cloneSessionOptions(options) {
|
|
4720
|
+
if (!options) return;
|
|
4721
|
+
return {
|
|
4722
|
+
model: options.model,
|
|
4723
|
+
allowed_tools: options.allowed_tools ? [...options.allowed_tools] : void 0,
|
|
4724
|
+
max_turns: options.max_turns,
|
|
4725
|
+
...options.system_prompt !== void 0 ? { system_prompt: cloneSystemPromptOption(options.system_prompt) } : {}
|
|
4143
4726
|
};
|
|
4144
4727
|
}
|
|
4728
|
+
function cloneSystemPromptOption(option) {
|
|
4729
|
+
return typeof option === "string" ? option : { append: option.append };
|
|
4730
|
+
}
|
|
4145
4731
|
function recordPromptSubmission(conversation, prompt, timestamp = isoNow()) {
|
|
4146
4732
|
const userContent = (typeof prompt === "string" ? textPrompt(prompt) : prompt).map((content) => contentToUserContent(content)).filter((content) => content !== void 0);
|
|
4147
4733
|
if (userContent.length === 0) return;
|
|
@@ -4174,57 +4760,70 @@ function hasAgentReplyAfterPrompt(conversation, promptMessageId) {
|
|
|
4174
4760
|
function recordSessionUpdate(conversation, state, notification, timestamp = isoNow()) {
|
|
4175
4761
|
const acpx = ensureAcpxState$1(state);
|
|
4176
4762
|
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
|
-
}
|
|
4763
|
+
applySessionUpdate(conversation, acpx, update);
|
|
4224
4764
|
updateConversationTimestamp(conversation, timestamp);
|
|
4225
4765
|
trimConversationForRuntime(conversation);
|
|
4226
4766
|
return acpx;
|
|
4227
4767
|
}
|
|
4768
|
+
function applySessionUpdate(conversation, acpx, update) {
|
|
4769
|
+
const handler = SESSION_UPDATE_HANDLERS[update.sessionUpdate];
|
|
4770
|
+
handler?.(conversation, acpx, update);
|
|
4771
|
+
}
|
|
4772
|
+
const SESSION_UPDATE_HANDLERS = {
|
|
4773
|
+
user_message_chunk: (conversation, _acpx, update) => {
|
|
4774
|
+
if (update.sessionUpdate === "user_message_chunk") appendUserMessageChunk(conversation, update.content);
|
|
4775
|
+
},
|
|
4776
|
+
agent_message_chunk: (conversation, _acpx, update) => {
|
|
4777
|
+
if (update.sessionUpdate === "agent_message_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentText);
|
|
4778
|
+
},
|
|
4779
|
+
agent_thought_chunk: (conversation, _acpx, update) => {
|
|
4780
|
+
if (update.sessionUpdate === "agent_thought_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentThinking);
|
|
4781
|
+
},
|
|
4782
|
+
tool_call: (conversation, _acpx, update) => {
|
|
4783
|
+
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
|
|
4784
|
+
},
|
|
4785
|
+
tool_call_update: (conversation, _acpx, update) => {
|
|
4786
|
+
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
|
|
4787
|
+
},
|
|
4788
|
+
usage_update: (conversation, _acpx, update) => {
|
|
4789
|
+
if (update.sessionUpdate === "usage_update") applyUsageUpdate(conversation, update);
|
|
4790
|
+
},
|
|
4791
|
+
session_info_update: (conversation, _acpx, update) => {
|
|
4792
|
+
if (update.sessionUpdate === "session_info_update") applySessionInfoUpdate(conversation, update);
|
|
4793
|
+
},
|
|
4794
|
+
available_commands_update: (_conversation, acpx, update) => {
|
|
4795
|
+
if (update.sessionUpdate === "available_commands_update") acpx.available_commands = update.availableCommands.map((entry) => entry.name).filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
4796
|
+
},
|
|
4797
|
+
current_mode_update: (_conversation, acpx, update) => {
|
|
4798
|
+
if (update.sessionUpdate === "current_mode_update") acpx.current_mode_id = update.currentModeId;
|
|
4799
|
+
},
|
|
4800
|
+
config_option_update: (_conversation, acpx, update) => {
|
|
4801
|
+
if (update.sessionUpdate === "config_option_update") acpx.config_options = deepClone(update.configOptions);
|
|
4802
|
+
}
|
|
4803
|
+
};
|
|
4804
|
+
function appendUserMessageChunk(conversation, content) {
|
|
4805
|
+
const userContent = contentToUserContent(content);
|
|
4806
|
+
if (!userContent) return;
|
|
4807
|
+
conversation.messages.push({ User: {
|
|
4808
|
+
id: nextUserMessageId(),
|
|
4809
|
+
content: [userContent]
|
|
4810
|
+
} });
|
|
4811
|
+
}
|
|
4812
|
+
function appendAgentMessageChunk(conversation, content, append) {
|
|
4813
|
+
const text = extractText(content);
|
|
4814
|
+
if (text) append(ensureAgentMessage(conversation), text);
|
|
4815
|
+
}
|
|
4816
|
+
function applyUsageUpdate(conversation, update) {
|
|
4817
|
+
const usage = usageToTokenUsage(update);
|
|
4818
|
+
if (!usage) return;
|
|
4819
|
+
conversation.cumulative_token_usage = usage;
|
|
4820
|
+
const userId = lastUserMessageId(conversation);
|
|
4821
|
+
if (userId) conversation.request_token_usage[userId] = usage;
|
|
4822
|
+
}
|
|
4823
|
+
function applySessionInfoUpdate(conversation, update) {
|
|
4824
|
+
if (hasOwn(update, "title")) conversation.title = update.title ?? null;
|
|
4825
|
+
if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
|
|
4826
|
+
}
|
|
4228
4827
|
function recordClientOperation(conversation, state, operation, timestamp = isoNow()) {
|
|
4229
4828
|
const acpx = ensureAcpxState$1(state);
|
|
4230
4829
|
updateConversationTimestamp(conversation, timestamp);
|
|
@@ -4233,25 +4832,36 @@ function recordClientOperation(conversation, state, operation, timestamp = isoNo
|
|
|
4233
4832
|
}
|
|
4234
4833
|
function trimConversationForRuntime(conversation) {
|
|
4235
4834
|
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
|
-
}
|
|
4835
|
+
for (const message of conversation.messages) trimRuntimeMessage(message);
|
|
4252
4836
|
const requestUsageEntries = Object.entries(conversation.request_token_usage);
|
|
4253
4837
|
if (requestUsageEntries.length > MAX_RUNTIME_REQUEST_TOKEN_USAGE) conversation.request_token_usage = Object.fromEntries(requestUsageEntries.slice(-MAX_RUNTIME_REQUEST_TOKEN_USAGE));
|
|
4254
4838
|
}
|
|
4839
|
+
function trimRuntimeMessage(message) {
|
|
4840
|
+
if (isUserMessage(message)) {
|
|
4841
|
+
trimRuntimeUserMessage(message.User);
|
|
4842
|
+
return;
|
|
4843
|
+
}
|
|
4844
|
+
if (isAgentMessage(message)) trimRuntimeAgentMessage(message.Agent);
|
|
4845
|
+
}
|
|
4846
|
+
function trimRuntimeUserMessage(message) {
|
|
4847
|
+
message.content = message.content.map((content) => {
|
|
4848
|
+
if ("Text" in content) return { Text: trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS) };
|
|
4849
|
+
return content;
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
function trimRuntimeAgentMessage(message) {
|
|
4853
|
+
for (const content of message.content) trimRuntimeAgentContent(content);
|
|
4854
|
+
for (const result of Object.values(message.tool_results)) trimRuntimeToolResult(result);
|
|
4855
|
+
}
|
|
4856
|
+
function trimRuntimeAgentContent(content) {
|
|
4857
|
+
if ("Text" in content) content.Text = trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS);
|
|
4858
|
+
else if ("Thinking" in content) content.Thinking.text = trimRuntimeText(content.Thinking.text, MAX_RUNTIME_THINKING_CHARS);
|
|
4859
|
+
else if ("ToolUse" in content) content.ToolUse.raw_input = trimRuntimeText(content.ToolUse.raw_input, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4860
|
+
}
|
|
4861
|
+
function trimRuntimeToolResult(result) {
|
|
4862
|
+
if ("Text" in result.content) result.content.Text = trimRuntimeText(result.content.Text, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4863
|
+
if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4864
|
+
}
|
|
4255
4865
|
//#endregion
|
|
4256
4866
|
//#region src/session/config-options.ts
|
|
4257
4867
|
function applyConfigOptionsToRecord(record, result) {
|
|
@@ -4373,39 +4983,6 @@ async function applyRequestedModelIfAdvertised(params) {
|
|
|
4373
4983
|
return true;
|
|
4374
4984
|
}
|
|
4375
4985
|
//#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
4986
|
//#region src/runtime/engine/reconnect.ts
|
|
4410
4987
|
function isProcessAlive(pid) {
|
|
4411
4988
|
if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
|
|
@@ -4418,15 +4995,19 @@ function isProcessAlive(pid) {
|
|
|
4418
4995
|
}
|
|
4419
4996
|
const SESSION_LOAD_UNSUPPORTED_CODES = new Set([-32601, -32602]);
|
|
4420
4997
|
function shouldFallbackToNewSession(error, record) {
|
|
4421
|
-
if (error
|
|
4422
|
-
if (isAcpResourceNotFoundError(error)) return true;
|
|
4998
|
+
if (isHardReconnectFailure(error)) return false;
|
|
4423
4999
|
const acp = extractAcpError(error);
|
|
4424
|
-
if (
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
5000
|
+
if (isAcpResourceNotFoundError(error) || isUnsupportedSessionLoadAcpError(acp)) return true;
|
|
5001
|
+
return !sessionHasAgentMessages(record) && isFallbackSafeEmptySessionError(error, acp);
|
|
5002
|
+
}
|
|
5003
|
+
function isHardReconnectFailure(error) {
|
|
5004
|
+
return error instanceof TimeoutError || error instanceof InterruptedError;
|
|
5005
|
+
}
|
|
5006
|
+
function isUnsupportedSessionLoadAcpError(acp) {
|
|
5007
|
+
return !!acp && SESSION_LOAD_UNSUPPORTED_CODES.has(acp.code);
|
|
5008
|
+
}
|
|
5009
|
+
function isFallbackSafeEmptySessionError(error, acp) {
|
|
5010
|
+
return isAcpQueryClosedBeforeResponseError(error) || acp?.code === -32603;
|
|
4430
5011
|
}
|
|
4431
5012
|
function requiresSameSession(resumePolicy) {
|
|
4432
5013
|
return resumePolicy === "same-session-only";
|
|
@@ -4490,11 +5071,7 @@ async function connectAndLoadSession(options) {
|
|
|
4490
5071
|
const desiredModelId = getDesiredModelId(record.acpx);
|
|
4491
5072
|
const desiredConfigOptions = getDesiredConfigOptions(record.acpx);
|
|
4492
5073
|
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
|
-
}
|
|
5074
|
+
logReconnectAttempt(record, storedProcessAlive, Boolean(record.pid) && !storedProcessAlive, options.verbose);
|
|
4498
5075
|
const reusingLoadedSession = client.hasReusableSession(record.acpSessionId);
|
|
4499
5076
|
if (reusingLoadedSession) incrementPerfCounter("runtime.connect_and_load.reused_session");
|
|
4500
5077
|
else await withTimeout(client.start(), options.timeoutMs);
|
|
@@ -4509,82 +5086,35 @@ async function connectAndLoadSession(options) {
|
|
|
4509
5086
|
let createdFreshSession = false;
|
|
4510
5087
|
let pendingAgentSessionId = record.agentSessionId;
|
|
4511
5088
|
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);
|
|
5089
|
+
const loadState = await loadOrCreateRuntimeSession({
|
|
5090
|
+
client,
|
|
5091
|
+
record,
|
|
5092
|
+
reusingLoadedSession,
|
|
5093
|
+
sameSessionOnly,
|
|
5094
|
+
timeoutMs: options.timeoutMs
|
|
5095
|
+
});
|
|
5096
|
+
resumed = loadState.resumed;
|
|
5097
|
+
loadError = loadState.loadError;
|
|
5098
|
+
sessionId = loadState.sessionId;
|
|
5099
|
+
createdFreshSession = loadState.createdFreshSession;
|
|
5100
|
+
pendingAgentSessionId = loadState.pendingAgentSessionId;
|
|
5101
|
+
sessionModels = loadState.sessionModels;
|
|
5102
|
+
await replayFreshSessionPreferences({
|
|
5103
|
+
client,
|
|
5104
|
+
record,
|
|
5105
|
+
createdFreshSession,
|
|
5106
|
+
sessionId,
|
|
5107
|
+
pendingAgentSessionId,
|
|
5108
|
+
originalSessionId,
|
|
5109
|
+
originalAgentSessionId,
|
|
5110
|
+
desiredModeId,
|
|
5111
|
+
desiredModelId,
|
|
5112
|
+
desiredConfigOptions,
|
|
5113
|
+
sessionModels,
|
|
5114
|
+
timeoutMs: options.timeoutMs,
|
|
5115
|
+
verbose: options.verbose
|
|
5116
|
+
});
|
|
5117
|
+
applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId);
|
|
4588
5118
|
options.onSessionIdResolved?.(sessionId);
|
|
4589
5119
|
return {
|
|
4590
5120
|
sessionId,
|
|
@@ -4593,6 +5123,131 @@ async function connectAndLoadSession(options) {
|
|
|
4593
5123
|
loadError
|
|
4594
5124
|
};
|
|
4595
5125
|
}
|
|
5126
|
+
function applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId) {
|
|
5127
|
+
syncAdvertisedModelState(record, sessionModels);
|
|
5128
|
+
if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
|
|
5129
|
+
}
|
|
5130
|
+
function logReconnectAttempt(record, storedProcessAlive, shouldReconnect, verbose) {
|
|
5131
|
+
if (!verbose) return;
|
|
5132
|
+
if (storedProcessAlive) {
|
|
5133
|
+
process.stderr.write(`[acpx] saved session pid ${record.pid} is running; reconnecting to saved ACP session\n`);
|
|
5134
|
+
return;
|
|
5135
|
+
}
|
|
5136
|
+
if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session reconnect\n`);
|
|
5137
|
+
}
|
|
5138
|
+
async function replayFreshSessionPreferences(params) {
|
|
5139
|
+
if (!params.createdFreshSession) return;
|
|
5140
|
+
try {
|
|
5141
|
+
await replayDesiredMode({
|
|
5142
|
+
client: params.client,
|
|
5143
|
+
sessionId: params.sessionId,
|
|
5144
|
+
desiredModeId: params.desiredModeId,
|
|
5145
|
+
previousSessionId: params.originalSessionId,
|
|
5146
|
+
timeoutMs: params.timeoutMs,
|
|
5147
|
+
verbose: params.verbose
|
|
5148
|
+
});
|
|
5149
|
+
await replayDesiredModel({
|
|
5150
|
+
client: params.client,
|
|
5151
|
+
sessionId: params.sessionId,
|
|
5152
|
+
desiredModelId: params.desiredModelId,
|
|
5153
|
+
previousSessionId: params.originalSessionId,
|
|
5154
|
+
record: params.record,
|
|
5155
|
+
models: params.sessionModels,
|
|
5156
|
+
timeoutMs: params.timeoutMs,
|
|
5157
|
+
verbose: params.verbose
|
|
5158
|
+
});
|
|
5159
|
+
await replayDesiredConfigOptions({
|
|
5160
|
+
client: params.client,
|
|
5161
|
+
sessionId: params.sessionId,
|
|
5162
|
+
desiredConfigOptions: params.desiredConfigOptions,
|
|
5163
|
+
previousSessionId: params.originalSessionId,
|
|
5164
|
+
timeoutMs: params.timeoutMs,
|
|
5165
|
+
verbose: params.verbose
|
|
5166
|
+
});
|
|
5167
|
+
} catch (error) {
|
|
5168
|
+
restoreOriginalSessionState({
|
|
5169
|
+
record: params.record,
|
|
5170
|
+
sessionId: params.originalSessionId,
|
|
5171
|
+
agentSessionId: params.originalAgentSessionId
|
|
5172
|
+
});
|
|
5173
|
+
if (params.verbose) process.stderr.write(`[acpx] ${formatErrorMessage(error)}\n`);
|
|
5174
|
+
throw error;
|
|
5175
|
+
}
|
|
5176
|
+
params.record.acpSessionId = params.sessionId;
|
|
5177
|
+
reconcileAgentSessionId(params.record, params.pendingAgentSessionId);
|
|
5178
|
+
}
|
|
5179
|
+
async function loadOrCreateRuntimeSession(params) {
|
|
5180
|
+
if (params.reusingLoadedSession) return {
|
|
5181
|
+
sessionId: params.record.acpSessionId,
|
|
5182
|
+
pendingAgentSessionId: params.record.agentSessionId,
|
|
5183
|
+
sessionModels: void 0,
|
|
5184
|
+
resumed: true,
|
|
5185
|
+
createdFreshSession: false
|
|
5186
|
+
};
|
|
5187
|
+
if (params.client.supportsResumeSession()) return await resumeRuntimeSession(params);
|
|
5188
|
+
if (params.client.supportsLoadSession()) return await loadRuntimeSession(params);
|
|
5189
|
+
if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
|
|
5190
|
+
record: params.record,
|
|
5191
|
+
reason: "agent does not support session/resume or session/load"
|
|
5192
|
+
});
|
|
5193
|
+
return await createFreshRuntimeSession(params.client, params.record, params.timeoutMs);
|
|
5194
|
+
}
|
|
5195
|
+
async function resumeRuntimeSession(params) {
|
|
5196
|
+
try {
|
|
5197
|
+
const resumeResult = await withTimeout(params.client.resumeSession(params.record.acpSessionId, params.record.cwd), params.timeoutMs);
|
|
5198
|
+
reconcileAgentSessionId(params.record, resumeResult.agentSessionId);
|
|
5199
|
+
applyConfigOptionsToRecord(params.record, resumeResult);
|
|
5200
|
+
return {
|
|
5201
|
+
sessionId: params.record.acpSessionId,
|
|
5202
|
+
pendingAgentSessionId: params.record.agentSessionId,
|
|
5203
|
+
sessionModels: resumeResult.models,
|
|
5204
|
+
resumed: true,
|
|
5205
|
+
createdFreshSession: false
|
|
5206
|
+
};
|
|
5207
|
+
} catch (error) {
|
|
5208
|
+
return await recoverRuntimeSessionLoadFailure(params, error);
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
async function loadRuntimeSession(params) {
|
|
5212
|
+
try {
|
|
5213
|
+
const loadResult = await withTimeout(params.client.loadSessionWithOptions(params.record.acpSessionId, params.record.cwd, { suppressReplayUpdates: true }), params.timeoutMs);
|
|
5214
|
+
reconcileAgentSessionId(params.record, loadResult.agentSessionId);
|
|
5215
|
+
applyConfigOptionsToRecord(params.record, loadResult);
|
|
5216
|
+
return {
|
|
5217
|
+
sessionId: params.record.acpSessionId,
|
|
5218
|
+
pendingAgentSessionId: params.record.agentSessionId,
|
|
5219
|
+
sessionModels: loadResult.models,
|
|
5220
|
+
resumed: true,
|
|
5221
|
+
createdFreshSession: false
|
|
5222
|
+
};
|
|
5223
|
+
} catch (error) {
|
|
5224
|
+
return await recoverRuntimeSessionLoadFailure(params, error);
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
async function recoverRuntimeSessionLoadFailure(params, error) {
|
|
5228
|
+
const loadError = formatErrorMessage(error);
|
|
5229
|
+
if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
|
|
5230
|
+
record: params.record,
|
|
5231
|
+
reason: loadError,
|
|
5232
|
+
cause: error
|
|
5233
|
+
});
|
|
5234
|
+
if (!shouldFallbackToNewSession(error, params.record)) throw error;
|
|
5235
|
+
return {
|
|
5236
|
+
...await createFreshRuntimeSession(params.client, params.record, params.timeoutMs),
|
|
5237
|
+
loadError
|
|
5238
|
+
};
|
|
5239
|
+
}
|
|
5240
|
+
async function createFreshRuntimeSession(client, record, timeoutMs) {
|
|
5241
|
+
const createdSession = await withTimeout(client.createSession(record.cwd), timeoutMs);
|
|
5242
|
+
applyConfigOptionsToRecord(record, createdSession);
|
|
5243
|
+
return {
|
|
5244
|
+
sessionId: createdSession.sessionId,
|
|
5245
|
+
pendingAgentSessionId: createdSession.agentSessionId,
|
|
5246
|
+
sessionModels: createdSession.models,
|
|
5247
|
+
resumed: false,
|
|
5248
|
+
createdFreshSession: true
|
|
5249
|
+
};
|
|
5250
|
+
}
|
|
4596
5251
|
//#endregion
|
|
4597
5252
|
//#region src/runtime/engine/connected-session.ts
|
|
4598
5253
|
function createActiveSessionController(params) {
|
|
@@ -4785,6 +5440,6 @@ var LiveSessionCheckpoint = class {
|
|
|
4785
5440
|
}
|
|
4786
5441
|
};
|
|
4787
5442
|
//#endregion
|
|
4788
|
-
export { getPerfMetricsSnapshot as $, parsePromptStopReason as A, EXIT_CODES as At, normalizeName as B,
|
|
5443
|
+
export { getPerfMetricsSnapshot as $, parsePromptStopReason as A, EXIT_CODES as At, normalizeName as B, QueueProtocolError as Bt, applyConversation as C, formatErrorMessage as Ct, extractSessionUpdateNotification as D, isAcpResourceNotFoundError as Dt, AcpClient as E, extractAcpError as Et, findSession as F, PERMISSION_MODES as Ft, DEFAULT_EVENT_SEGMENT_MAX_BYTES as G, resolveSessionRecord as H, findSessionByDirectoryWalk as I, PERMISSION_POLICY_ACTIONS as It, sessionEventActivePath as J, defaultSessionEventLog as K, isoNow$2 as L, SESSION_RECORD_SCHEMA as Lt, DEFAULT_HISTORY_LIMIT as M, OUTPUT_ERROR_CODES as Mt, absolutePath as N, OUTPUT_ERROR_ORIGINS as Nt, isAcpJsonRpcMessage as O, toAcpErrorPayload as Ot, findGitRepositoryRoot as P, OUTPUT_FORMATS as Pt, formatPerfMetric as Q, listSessions as R, AgentSpawnError as Rt, sessionOptionsFromRecord as S, exitCodeForOutputErrorCode as St, reconcileAgentSessionId as T, normalizeOutputError as Tt, writeSessionRecord as U, pruneSessions as V, parseSessionRecord as W, sessionEventSegmentPath as X, sessionEventLockPath as Y, assertPersistedKeyPolicy as Z, recordPromptSubmission as _, withTimeout as _t, applyRequestedModelIfAdvertised as a, startPerfTimer as at, mergeSessionOptions as b, normalizeAgentName$1 as bt, setDesiredConfigOption as c, PromptInputValidationError as ct, syncAdvertisedModelState as d, parsePromptSource as dt, incrementPerfCounter as et, applyConfigOptionsToRecord as f, promptToDisplayText as ft, recordClientOperation as g, withInterrupt as gt, createSessionConversation as h, TimeoutError as ht, connectAndLoadSession as i, setPerfGauge as it, permissionModeSatisfies as j, NON_INTERACTIVE_PERMISSION_POLICIES as jt, parseJsonRpcErrorMessage as k, AUTH_POLICIES as kt, setDesiredModeId as l, isPromptInput as lt, cloneSessionConversation as m, InterruptedError as mt, runPromptTurn as n, recordPerfDuration as nt, assertRequestedModelSupported as o, serializeSessionRecordForDisk as ot, cloneSessionAcpxState as p, textPrompt as pt, sessionBaseDir$1 as q, withConnectedSession as r, resetPerfMetrics as rt, setCurrentModelId as s, normalizeRuntimeSessionId as st, LiveSessionCheckpoint as t, measurePerf 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, listSessionsForAgent as z, QueueConnectionError as zt };
|
|
4789
5444
|
|
|
4790
|
-
//# sourceMappingURL=live-checkpoint-
|
|
5445
|
+
//# sourceMappingURL=live-checkpoint-D5d-K9s1.js.map
|