@amistio/cli 0.1.35 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { createHash as createHash8, randomUUID } from "node:crypto";
5
- import { writeFile as writeFile10 } from "node:fs/promises";
4
+ import { createHash as createHash9, randomUUID as randomUUID2 } from "node:crypto";
5
+ import { writeFile as writeFile11 } from "node:fs/promises";
6
6
  import os8 from "node:os";
7
- import path16 from "node:path";
7
+ import path17 from "node:path";
8
8
  import { Command } from "commander";
9
9
 
10
10
  // ../shared/src/schemas.ts
@@ -484,6 +484,33 @@ var runnerProviderConfigSchema = z.object({
484
484
  models: z.record(runnerProviderModelIdSchema, runnerProviderModelSchema).default({})
485
485
  }).strict();
486
486
  var runnerProviderCatalogSchema = z.record(runnerProviderIdSchema, runnerProviderConfigSchema);
487
+ var runnerProviderClientIdSchema = z.string().trim().min(1).max(120);
488
+ var runnerProviderRouteTypeSchema = z.enum(["directProvider", "agentClient", "localRuntime"]);
489
+ var runnerProviderAuthStateSchema = z.enum(["notConfigured", "pendingUserAction", "authenticated", "expired", "revoked", "mismatch", "unavailable", "unsupported", "unknown"]);
490
+ var runnerProviderAuthStatusSchema = z.object({
491
+ providerId: runnerProviderIdSchema,
492
+ providerClientId: runnerProviderClientIdSchema,
493
+ routeType: runnerProviderRouteTypeSchema,
494
+ status: runnerProviderAuthStateSchema,
495
+ authMethodLabel: z.string().trim().min(1).max(120).optional(),
496
+ accountHost: z.string().trim().min(1).max(200).optional(),
497
+ accountLogin: z.string().trim().min(1).max(120).optional(),
498
+ accountFingerprint: z.string().trim().min(1).max(160).optional(),
499
+ modelCount: z.number().int().nonnegative().optional(),
500
+ checkedAt: isoDateTimeSchema,
501
+ expiresAt: isoDateTimeSchema.optional(),
502
+ message: z.string().max(400).optional(),
503
+ errorCode: z.string().trim().min(1).max(80).optional()
504
+ }).strict();
505
+ var runnerProviderAuthLinkRequestSchema = z.object({
506
+ providerId: runnerProviderIdSchema,
507
+ providerClientId: runnerProviderClientIdSchema,
508
+ routeType: runnerProviderRouteTypeSchema,
509
+ requestedAccountHost: z.string().trim().min(1).max(200).optional(),
510
+ requestedAccountLogin: z.string().trim().min(1).max(120).optional(),
511
+ requestedAccountFingerprint: z.string().trim().min(1).max(160).optional(),
512
+ intentExpiresAt: isoDateTimeSchema.optional()
513
+ }).strict();
487
514
  var runnerToolModelPreferenceSchema = z.object({
488
515
  tool: runnerToolSelectionSchema.optional(),
489
516
  invocationChannel: runnerInvocationChannelSchema.optional(),
@@ -855,6 +882,7 @@ var runnerHeartbeatItemSchema = baseItemSchema.extend({
855
882
  autoSyncPushedCount: z.number().int().nonnegative().optional(),
856
883
  autoSyncSkippedCount: z.number().int().nonnegative().optional(),
857
884
  autoSyncConflictCount: z.number().int().nonnegative().optional(),
885
+ providerAuthStatuses: z.array(runnerProviderAuthStatusSchema).max(20).optional(),
858
886
  lastSeenAt: isoDateTimeSchema
859
887
  });
860
888
  var runnerSettingsItemSchema = baseItemSchema.extend({
@@ -918,7 +946,7 @@ var runnerCredentialItemSchema = baseItemSchema.extend({
918
946
  lastUsedAt: isoDateTimeSchema.optional(),
919
947
  status: z.enum(["active", "revoked"]).default("active")
920
948
  });
921
- var runnerCommandKindSchema = z.enum(["update", "restart", "remove", "implementationHandoffRecovery"]);
949
+ var runnerCommandKindSchema = z.enum(["update", "restart", "remove", "implementationHandoffRecovery", "providerAuthLinkRequested"]);
922
950
  var runnerCommandStatusSchema = z.enum(["pending", "acknowledged", "running", "completed", "failed", "expired", "cancelled"]);
923
951
  var runnerCommandItemSchema = baseItemSchema.extend({
924
952
  type: z.literal("runnerCommand"),
@@ -930,6 +958,8 @@ var runnerCommandItemSchema = baseItemSchema.extend({
930
958
  repositoryLinkId: z.string().min(1),
931
959
  workItemId: z.string().min(1).optional(),
932
960
  handoffRecoveryAction: implementationHandoffRecoveryActionSchema.optional(),
961
+ providerAuthLinkRequest: runnerProviderAuthLinkRequestSchema.optional(),
962
+ providerAuthStatus: runnerProviderAuthStatusSchema.optional(),
933
963
  executionBranch: z.string().min(1).optional(),
934
964
  executionWorktreeKey: z.string().min(1).optional(),
935
965
  requestedByUserId: z.string().min(1),
@@ -3112,8 +3142,8 @@ var toolSessionMutationSchema = z3.object({
3112
3142
  });
3113
3143
  function resolveApiUrl(apiUrl, urlPath) {
3114
3144
  const base = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
3115
- const path17 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
3116
- return new URL(`${base}${path17}`);
3145
+ const path18 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
3146
+ return new URL(`${base}${path18}`);
3117
3147
  }
3118
3148
 
3119
3149
  // src/orchestrator.ts
@@ -3522,13 +3552,887 @@ function truncateLocalError(error) {
3522
3552
  }
3523
3553
 
3524
3554
  // src/local-tool-runner.ts
3525
- import { spawn } from "node:child_process";
3526
- import { mkdtemp, readFile as readFile4, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
3555
+ import { mkdtemp, readFile as readFile5, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
3527
3556
  import os3 from "node:os";
3557
+ import path7 from "node:path";
3558
+
3559
+ // src/bounded-tool-adapter.ts
3560
+ import { constants } from "node:fs";
3561
+ import { access, mkdir as mkdir6, readdir as readdir3, readFile as readFile4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
3528
3562
  import path6 from "node:path";
3563
+
3564
+ // src/host-execution.ts
3565
+ import { spawn } from "node:child_process";
3566
+ import { randomUUID } from "node:crypto";
3567
+ var hostExecutionProtocolVersion = "amistio.hostExecution.v1";
3568
+ var nativeHostHelperEnvironmentAllowlist = [
3569
+ "PATH",
3570
+ "Path",
3571
+ "HOME",
3572
+ "USERPROFILE",
3573
+ "TMPDIR",
3574
+ "TEMP",
3575
+ "TMP",
3576
+ "LANG",
3577
+ "LC_ALL",
3578
+ "CI",
3579
+ "NODE_ENV",
3580
+ "SystemRoot",
3581
+ "WINDIR",
3582
+ "ComSpec",
3583
+ "PATHEXT"
3584
+ ];
3585
+ var defaultNativeHostOutputBudgetBytes = 64 * 1024;
3586
+ var nodeHostExecutionCapabilities = {
3587
+ protocolVersion: hostExecutionProtocolVersion,
3588
+ implementation: "node",
3589
+ processGroups: process.platform === "win32" ? { supported: false, reason: "Windows process-group cleanup needs a native helper or platform-specific implementation." } : { supported: true },
3590
+ signalEscalation: { supported: true },
3591
+ streamCapture: { supported: true },
3592
+ pty: { supported: false, reason: "PTY bridging is reserved for a future native helper." },
3593
+ sandbox: { supported: false, reason: "Sandboxed command execution is reserved for a future native helper." },
3594
+ outputEncoding: "utf8"
3595
+ };
3596
+ var defaultHostExecution = {
3597
+ capabilities: nodeHostExecutionCapabilities,
3598
+ executeCommand: executeNodeCommand,
3599
+ commandExists
3600
+ };
3601
+ var runtimeHostExecutionCacheKey;
3602
+ var runtimeHostExecutionPortPromise;
3603
+ function getRuntimeHostExecutionPort(env = process.env) {
3604
+ const helperConfig = nativeHostHelperConfigFromEnvironment(env);
3605
+ const cacheKey = helperConfig?.command ?? "";
3606
+ if (!runtimeHostExecutionPortPromise || runtimeHostExecutionCacheKey !== cacheKey) {
3607
+ runtimeHostExecutionCacheKey = cacheKey;
3608
+ runtimeHostExecutionPortPromise = createHostExecutionPort(helperConfig ? { nativeHelper: helperConfig } : {});
3609
+ }
3610
+ return runtimeHostExecutionPortPromise;
3611
+ }
3612
+ async function createHostExecutionPort(options = {}) {
3613
+ const fallback = options.fallback ?? defaultHostExecution;
3614
+ const helperConfig = options.nativeHelper ?? nativeHostHelperConfigFromEnvironment();
3615
+ if (!helperConfig) {
3616
+ return fallback;
3617
+ }
3618
+ const discovery = await discoverNativeHostHelper(helperConfig);
3619
+ return createCompositeHostExecutionPort(fallback, discovery);
3620
+ }
3621
+ function nativeHostHelperConfigFromEnvironment(env = process.env) {
3622
+ const command = env.AMISTIO_HOST_HELPER_PATH?.trim();
3623
+ if (!command) {
3624
+ return void 0;
3625
+ }
3626
+ return { command };
3627
+ }
3628
+ async function discoverNativeHostHelper(config) {
3629
+ const normalizedConfig = normalizeNativeHostHelperConfig(config);
3630
+ const result = await executeHelperProcess(normalizedConfig, "--amistio-host-helper-handshake", void 0, normalizedConfig.handshakeTimeoutMs);
3631
+ if (result.status === "spawnFailed") {
3632
+ return { ok: false, failure: { code: "helper_unavailable", message: result.error?.message ?? "Native host helper is unavailable.", stdout: result.stdout, stderr: result.stderr } };
3633
+ }
3634
+ if (result.status === "timedOut") {
3635
+ return { ok: false, failure: { code: "helper_unavailable", message: result.error?.message ?? "Native host helper handshake timed out.", stdout: result.stdout, stderr: result.stderr } };
3636
+ }
3637
+ if (result.exitCode !== 0) {
3638
+ return { ok: false, failure: { code: "helper_failed", message: `Native host helper handshake exited with code ${result.exitCode}.`, stdout: result.stdout, stderr: result.stderr } };
3639
+ }
3640
+ const parsed = parseJson(result.stdout);
3641
+ if (!isNativeHostHelperHandshake(parsed)) {
3642
+ return { ok: false, failure: { code: "protocol_mismatch", message: "Native host helper handshake did not match the Amistio host execution protocol.", stdout: result.stdout, stderr: result.stderr } };
3643
+ }
3644
+ return { ok: true, helper: { config: normalizedConfig, handshake: parsed } };
3645
+ }
3646
+ function createNativeHostCommandRequestEnvelope(request, requestId = randomUUID()) {
3647
+ const env = filterNativeHostHelperEnvironment(request.env ?? process.env);
3648
+ const sandbox = request.sandbox ?? "none";
3649
+ return {
3650
+ protocolVersion: hostExecutionProtocolVersion,
3651
+ requestId,
3652
+ command: request.command,
3653
+ args: [...request.args],
3654
+ cwd: request.cwd,
3655
+ shell: request.shell ?? false,
3656
+ timeoutMs: request.timeoutMs ?? 0,
3657
+ stdin: request.stdin ? "provided" : "none",
3658
+ env,
3659
+ envAllowlist: Object.keys(env).sort(),
3660
+ streamOutput: request.streamOutput ?? false,
3661
+ requirePty: request.requirePty ?? false,
3662
+ sandbox,
3663
+ sandboxPolicy: nativeHostSandboxPolicy(request.cwd, sandbox),
3664
+ outputBudgetBytes: positiveInteger(request.outputBudgetBytes) ?? defaultNativeHostOutputBudgetBytes
3665
+ };
3666
+ }
3667
+ function createCompositeHostExecutionPort(fallback, discovery) {
3668
+ return {
3669
+ capabilities: discovery.ok ? discovery.helper.handshake.capabilities : fallback.capabilities,
3670
+ async executeCommand(request) {
3671
+ if (!discovery.ok) {
3672
+ if (request.requireNativeHelper) {
3673
+ return helperDiscoveryFailureResult(discovery.failure);
3674
+ }
3675
+ return fallback.executeCommand(request);
3676
+ }
3677
+ const helper = discovery.helper;
3678
+ const capabilityFailure = helperCapabilityFailure(request, helper.handshake.capabilities);
3679
+ if (capabilityFailure) {
3680
+ return capabilityFailure;
3681
+ }
3682
+ if (!canDelegateToNativeHelper(request)) {
3683
+ if (request.requireNativeHelper) {
3684
+ return unsupportedResult("helper_failed", "Native host helper delegation is unavailable for requests with stdin payloads.");
3685
+ }
3686
+ return fallback.executeCommand(request);
3687
+ }
3688
+ return executeNativeHelperCommand(helper, request);
3689
+ },
3690
+ commandExists: fallback.commandExists
3691
+ };
3692
+ }
3693
+ function helperCapabilityFailure(request, capabilities) {
3694
+ if (request.requirePty && !capabilities.pty.supported) {
3695
+ return unsupportedResult("pty_unsupported", capabilities.pty.reason ?? "PTY command execution is not supported by the native host helper.");
3696
+ }
3697
+ if (request.sandbox && request.sandbox !== "none" && !capabilities.sandbox.supported) {
3698
+ return unsupportedResult("sandbox_unsupported", capabilities.sandbox.reason ?? `${request.sandbox} sandbox execution is not supported by the native host helper.`);
3699
+ }
3700
+ return void 0;
3701
+ }
3702
+ function canDelegateToNativeHelper(request) {
3703
+ return !request.stdin && !request.shell;
3704
+ }
3705
+ async function executeNativeHelperCommand(helper, request) {
3706
+ const requestId = randomUUID();
3707
+ const envelope = createNativeHostCommandRequestEnvelope(request, requestId);
3708
+ const result = await executeHelperProcess(helper.config, "--amistio-host-helper-execute", JSON.stringify(envelope), request.timeoutMs ?? helper.config.executeTimeoutMs);
3709
+ if (result.status === "spawnFailed") {
3710
+ return { status: "helperUnavailable", exitCode: 1, stdout: result.stdout, stderr: result.stderr, error: { code: "helper_unavailable", message: result.error?.message ?? "Native host helper is unavailable." } };
3711
+ }
3712
+ if (result.status === "timedOut") {
3713
+ return result;
3714
+ }
3715
+ if (result.exitCode !== 0) {
3716
+ return { status: "failed", exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr, error: { code: "helper_failed", message: `Native host helper exited with code ${result.exitCode}.` } };
3717
+ }
3718
+ const parsed = parseJson(result.stdout);
3719
+ if (!isNativeHostCommandResultEnvelope(parsed) || parsed.requestId !== requestId) {
3720
+ return { status: "protocolMismatch", exitCode: 1, stdout: result.stdout, stderr: result.stderr, error: { code: "protocol_mismatch", message: "Native host helper command result did not match the Amistio host execution protocol." } };
3721
+ }
3722
+ return normalizeHostCommandResult(request, {
3723
+ status: parsed.status,
3724
+ exitCode: parsed.exitCode,
3725
+ stdout: parsed.stdout,
3726
+ stderr: parsed.stderr,
3727
+ ...parsed.error ? { error: parsed.error } : {}
3728
+ });
3729
+ }
3730
+ async function executeNodeCommand(request) {
3731
+ if (request.requirePty) {
3732
+ return unsupportedResult("pty_unsupported", "PTY command execution is not supported by the default Node host execution port.");
3733
+ }
3734
+ if (request.sandbox && request.sandbox !== "none") {
3735
+ return unsupportedResult("sandbox_unsupported", `${request.sandbox} sandbox execution is not supported by the default Node host execution port.`);
3736
+ }
3737
+ return new Promise((resolve) => {
3738
+ let child;
3739
+ try {
3740
+ child = spawn(request.command, [...request.args], {
3741
+ cwd: request.cwd,
3742
+ env: request.env ?? process.env,
3743
+ detached: process.platform !== "win32",
3744
+ shell: request.shell ?? false,
3745
+ stdio: ["pipe", "pipe", "pipe"]
3746
+ });
3747
+ } catch (error) {
3748
+ resolve(spawnFailedResult(error));
3749
+ return;
3750
+ }
3751
+ let stdout = "";
3752
+ let stderr = "";
3753
+ let settled = false;
3754
+ let forceKillTimer;
3755
+ let timedOutError;
3756
+ const timeout = request.timeoutMs && request.timeoutMs > 0 ? setTimeout(() => {
3757
+ if (settled) return;
3758
+ const message = request.timeoutMessage ?? `Command timed out after ${request.timeoutMs}ms: ${request.command}`;
3759
+ stderr += `${message}
3760
+ `;
3761
+ timedOutError = { code: "timeout", message };
3762
+ killProcessTree(child, "SIGTERM");
3763
+ forceKillTimer = setTimeout(() => killProcessTree(child, "SIGKILL"), request.killGraceMs ?? 5e3);
3764
+ forceKillTimer.unref?.();
3765
+ }, request.timeoutMs) : void 0;
3766
+ timeout?.unref?.();
3767
+ const resolveOnce = (result) => {
3768
+ if (settled) return;
3769
+ settled = true;
3770
+ if (timeout) clearTimeout(timeout);
3771
+ if (forceKillTimer) clearTimeout(forceKillTimer);
3772
+ resolve(result);
3773
+ };
3774
+ child.on("error", (error) => {
3775
+ if (timedOutError) return;
3776
+ resolveOnce(spawnFailedResult(error, stdout, stderr));
3777
+ });
3778
+ child.stdout.setEncoding("utf8");
3779
+ child.stderr.setEncoding("utf8");
3780
+ child.stdout.on("data", (chunk) => {
3781
+ stdout += chunk;
3782
+ if (request.streamOutput) process.stdout.write(chunk);
3783
+ });
3784
+ child.stderr.on("data", (chunk) => {
3785
+ stderr += chunk;
3786
+ if (request.streamOutput) process.stderr.write(chunk);
3787
+ });
3788
+ child.stdin.on("error", () => void 0);
3789
+ if (request.stdin) {
3790
+ child.stdin.write(request.stdin);
3791
+ }
3792
+ child.stdin.end();
3793
+ child.on("close", (exitCode) => {
3794
+ if (forceKillTimer) clearTimeout(forceKillTimer);
3795
+ if (timedOutError) {
3796
+ resolveOnce({ status: "timedOut", exitCode: 1, stdout, stderr, error: timedOutError });
3797
+ return;
3798
+ }
3799
+ resolveOnce({ status: "completed", exitCode: exitCode ?? 1, stdout, stderr });
3800
+ });
3801
+ });
3802
+ }
3803
+ function unsupportedResult(code, message) {
3804
+ return { status: "unsupported", exitCode: 1, stdout: "", stderr: "", error: { code, message } };
3805
+ }
3806
+ function helperDiscoveryFailureResult(failure) {
3807
+ const status = failure.code === "protocol_mismatch" ? "protocolMismatch" : failure.code === "helper_unavailable" ? "helperUnavailable" : "failed";
3808
+ return { status, exitCode: 1, stdout: failure.stdout ?? "", stderr: failure.stderr ?? "", error: { code: failure.code, message: failure.message } };
3809
+ }
3810
+ function spawnFailedResult(error, stdout = "", stderr = "") {
3811
+ return { status: "spawnFailed", exitCode: 1, stdout, stderr, error: { code: "spawn_failed", message: errorMessage(error) } };
3812
+ }
3813
+ function killProcessTree(child, signal) {
3814
+ if (child.pid && process.platform !== "win32") {
3815
+ try {
3816
+ process.kill(-child.pid, signal);
3817
+ return;
3818
+ } catch {
3819
+ }
3820
+ }
3821
+ child.kill(signal);
3822
+ }
3823
+ async function commandExists(command) {
3824
+ const lookupCommand = process.platform === "win32" ? "where" : "which";
3825
+ return new Promise((resolve) => {
3826
+ const lookup = spawn(lookupCommand, [command], { stdio: "ignore" });
3827
+ lookup.on("error", () => resolve(false));
3828
+ lookup.on("close", (exitCode) => resolve(exitCode === 0));
3829
+ });
3830
+ }
3831
+ function normalizeNativeHostHelperConfig(config) {
3832
+ return {
3833
+ command: config.command,
3834
+ args: config.args ? [...config.args] : [],
3835
+ cwd: config.cwd ?? process.cwd(),
3836
+ env: filterNativeHostHelperEnvironment(config.env ?? process.env),
3837
+ handshakeTimeoutMs: positiveInteger(config.handshakeTimeoutMs) ?? 2e3,
3838
+ executeTimeoutMs: positiveInteger(config.executeTimeoutMs) ?? 3e4
3839
+ };
3840
+ }
3841
+ function executeHelperProcess(config, mode, stdin, timeoutMs) {
3842
+ return new Promise((resolve) => {
3843
+ let child;
3844
+ try {
3845
+ child = spawn(config.command, [...config.args, mode], {
3846
+ cwd: config.cwd,
3847
+ env: config.env,
3848
+ shell: false,
3849
+ stdio: ["pipe", "pipe", "pipe"]
3850
+ });
3851
+ } catch (error) {
3852
+ resolve(spawnFailedResult(error));
3853
+ return;
3854
+ }
3855
+ let stdout = "";
3856
+ let stderr = "";
3857
+ let settled = false;
3858
+ const timeout = timeoutMs > 0 ? setTimeout(() => {
3859
+ if (settled) return;
3860
+ const message = `Native host helper timed out after ${timeoutMs}ms.`;
3861
+ stderr += `${message}
3862
+ `;
3863
+ child.kill("SIGKILL");
3864
+ settled = true;
3865
+ resolve({ status: "timedOut", exitCode: 1, stdout, stderr, error: { code: "timeout", message } });
3866
+ }, timeoutMs) : void 0;
3867
+ timeout?.unref?.();
3868
+ const resolveOnce = (result) => {
3869
+ if (settled) return;
3870
+ settled = true;
3871
+ if (timeout) clearTimeout(timeout);
3872
+ resolve(result);
3873
+ };
3874
+ child.on("error", (error) => resolveOnce(spawnFailedResult(error, stdout, stderr)));
3875
+ child.stdout.setEncoding("utf8");
3876
+ child.stderr.setEncoding("utf8");
3877
+ child.stdout.on("data", (chunk) => {
3878
+ stdout += chunk;
3879
+ });
3880
+ child.stderr.on("data", (chunk) => {
3881
+ stderr += chunk;
3882
+ });
3883
+ child.stdin.on("error", () => void 0);
3884
+ if (stdin) {
3885
+ child.stdin.write(stdin);
3886
+ }
3887
+ child.stdin.end();
3888
+ child.on("close", (exitCode) => {
3889
+ resolveOnce({ status: "completed", exitCode: exitCode ?? 1, stdout, stderr });
3890
+ });
3891
+ });
3892
+ }
3893
+ function filterNativeHostHelperEnvironment(env) {
3894
+ const allowedNames = new Set(nativeHostHelperEnvironmentAllowlist);
3895
+ const filtered = {};
3896
+ for (const [name, value] of Object.entries(env)) {
3897
+ if (!value || !allowedNames.has(name) || isSensitiveEnvironmentName(name)) {
3898
+ continue;
3899
+ }
3900
+ filtered[name] = value;
3901
+ }
3902
+ return filtered;
3903
+ }
3904
+ function nativeHostSandboxPolicy(cwd, mode) {
3905
+ return {
3906
+ mode,
3907
+ cwd,
3908
+ filesystem: mode === "filesystemReadOnly" ? "readOnly" : "default",
3909
+ network: mode === "networkDisabled" ? "disabled" : "default",
3910
+ envPolicy: "allowlist-only"
3911
+ };
3912
+ }
3913
+ function normalizeHostCommandResult(request, result) {
3914
+ const outputBudgetBytes = positiveInteger(request.outputBudgetBytes) ?? defaultNativeHostOutputBudgetBytes;
3915
+ const normalize = request.requirePty ? normalizePtyTranscript : (value) => value;
3916
+ return {
3917
+ ...result,
3918
+ stdout: capUtf8Text(normalize(result.stdout), outputBudgetBytes),
3919
+ stderr: capUtf8Text(normalize(result.stderr), outputBudgetBytes)
3920
+ };
3921
+ }
3922
+ function normalizePtyTranscript(value) {
3923
+ return value.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
3924
+ }
3925
+ function capUtf8Text(value, budgetBytes) {
3926
+ if (Buffer.byteLength(value, "utf8") <= budgetBytes) {
3927
+ return value;
3928
+ }
3929
+ let capped = Buffer.from(value, "utf8").subarray(0, budgetBytes).toString("utf8");
3930
+ while (Buffer.byteLength(capped, "utf8") > budgetBytes && capped.length > 0) {
3931
+ capped = capped.slice(0, -1);
3932
+ }
3933
+ return capped;
3934
+ }
3935
+ function isSensitiveEnvironmentName(name) {
3936
+ return /(?:TOKEN|SECRET|PASSWORD|PASS|KEY|CREDENTIAL|AUTH|OAUTH|COOKIE|SESSION|PRIVATE|CERT|SSH)/i.test(name);
3937
+ }
3938
+ function isNativeHostHelperHandshake(value) {
3939
+ if (!isRecord(value) || value.protocolVersion !== hostExecutionProtocolVersion) return false;
3940
+ if (!isHostExecutionCapabilities(value.capabilities)) return false;
3941
+ if (!isRecord(value.secretBoundary)) return false;
3942
+ return value.secretBoundary.credentialPolicy === "never-send-credentials" && value.secretBoundary.environmentPolicy === "allowlist-only" && Array.isArray(value.secretBoundary.forbiddenInputs) && value.secretBoundary.forbiddenInputs.length > 0 && value.secretBoundary.forbiddenInputs.every((item) => typeof item === "string" && item.trim().length > 0);
3943
+ }
3944
+ function isHostExecutionCapabilities(value) {
3945
+ if (!isRecord(value)) return false;
3946
+ return value.protocolVersion === hostExecutionProtocolVersion && (value.implementation === "node" || value.implementation === "nativeHelper") && isCapabilitySupport(value.processGroups) && isCapabilitySupport(value.signalEscalation) && isCapabilitySupport(value.streamCapture) && isCapabilitySupport(value.pty) && isCapabilitySupport(value.sandbox) && value.outputEncoding === "utf8";
3947
+ }
3948
+ function isCapabilitySupport(value) {
3949
+ return isRecord(value) && typeof value.supported === "boolean" && (value.reason === void 0 || typeof value.reason === "string");
3950
+ }
3951
+ function isNativeHostCommandResultEnvelope(value) {
3952
+ if (!isRecord(value) || value.protocolVersion !== hostExecutionProtocolVersion) return false;
3953
+ return typeof value.requestId === "string" && isHostCommandStatus(value.status) && typeof value.exitCode === "number" && typeof value.stdout === "string" && typeof value.stderr === "string" && (value.error === void 0 || isHostCommandError(value.error));
3954
+ }
3955
+ function isHostCommandStatus(value) {
3956
+ return value === "completed" || value === "timedOut" || value === "spawnFailed" || value === "permissionDenied" || value === "helperUnavailable" || value === "protocolMismatch" || value === "killed" || value === "unsupported" || value === "failed";
3957
+ }
3958
+ function isHostCommandError(value) {
3959
+ return isRecord(value) && isHostCommandErrorCode(value.code) && typeof value.message === "string";
3960
+ }
3961
+ function isHostCommandErrorCode(value) {
3962
+ return value === "timeout" || value === "spawn_failed" || value === "permission_denied" || value === "helper_unavailable" || value === "protocol_mismatch" || value === "helper_failed" || value === "killed" || value === "pty_unsupported" || value === "sandbox_unsupported";
3963
+ }
3964
+ function isRecord(value) {
3965
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3966
+ }
3967
+ function parseJson(value) {
3968
+ try {
3969
+ return JSON.parse(value);
3970
+ } catch {
3971
+ return void 0;
3972
+ }
3973
+ }
3974
+ function positiveInteger(value) {
3975
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
3976
+ }
3977
+ function errorMessage(error) {
3978
+ return error instanceof Error ? error.message : String(error);
3979
+ }
3980
+
3981
+ // src/bounded-tool-adapter.ts
3982
+ var boundedToolAdapterCatalog = [
3983
+ { id: "filesystem.readFile", kind: "filesystem", displayName: "Read scoped file", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["executionRoot"] },
3984
+ { id: "filesystem.listFiles", kind: "filesystem", displayName: "List scoped files", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["executionRoot"] },
3985
+ { id: "filesystem.searchText", kind: "filesystem", displayName: "Search scoped text", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["executionRoot"] },
3986
+ { id: "filesystem.writeFile", kind: "filesystem", displayName: "Write scoped file", implemented: true, mutating: true, concurrency: "serialized", serializedResourceKinds: ["executionRoot"] },
3987
+ { id: "shell.curatedCommand", kind: "shell", displayName: "Run runner-owned command preset", implemented: true, mutating: false, concurrency: "serialized", serializedResourceKinds: ["executionRoot", "process"] },
3988
+ { id: "git.status", kind: "git", displayName: "Read Git status", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["gitIndex"] },
3989
+ { id: "git.diffNameOnly", kind: "git", displayName: "Read changed Git paths", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["gitIndex"] },
3990
+ { id: "packageManager.detect", kind: "packageManager", displayName: "Detect package manager", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["packageManagerCache"] },
3991
+ { id: "testRunner.profile", kind: "testRunner", displayName: "Run known verification profile", implemented: true, mutating: false, concurrency: "serialized", serializedResourceKinds: ["testRunner", "packageManagerCache"] },
3992
+ { id: "browser.check", kind: "browser", displayName: "Run local browser smoke check", implemented: true, mutating: false, concurrency: "serialized", serializedResourceKinds: ["browserProfile"] },
3993
+ { id: "modelClient.direct", kind: "modelClient", displayName: "Invoke direct model client", implemented: true, mutating: false, concurrency: "serialized", serializedResourceKinds: ["modelProvider"] },
3994
+ { id: "agentClient.compatibilityBridge", kind: "agentClient", displayName: "Invoke optional local AI client", implemented: true, mutating: true, concurrency: "serialized", serializedResourceKinds: ["agentSession", "providerAuth", "executionRoot"] }
3995
+ ];
3996
+ var testRunnerProfileIds = ["verify", "test", "typecheck", "lint", "build"];
3997
+ var packageLockfiles = {
3998
+ pnpm: ["pnpm-lock.yaml"],
3999
+ npm: ["package-lock.json"],
4000
+ yarn: ["yarn.lock"],
4001
+ bun: ["bun.lockb", "bun.lock"]
4002
+ };
4003
+ var ignoredTraversalDirectories = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".turbo", "coverage", ".cache"]);
4004
+ var defaultFilesystemListLimit = 200;
4005
+ var defaultFilesystemSearchLimit = 80;
4006
+ var filesystemSearchMaxFileBytes = 256e3;
4007
+ var defaultCuratedShellCommands = {
4008
+ "git.status.short": {
4009
+ command: "git",
4010
+ args: ["status", "--short"],
4011
+ displayName: "git status --short",
4012
+ mutationPolicy: "readOnly"
4013
+ },
4014
+ "git.diff.nameOnly": {
4015
+ command: "git",
4016
+ args: ["diff", "--name-only"],
4017
+ displayName: "git diff --name-only",
4018
+ mutationPolicy: "readOnly"
4019
+ },
4020
+ "package.detect": {
4021
+ command: process.execPath,
4022
+ args: ["-e", "const fs=require('fs');const files=['pnpm-lock.yaml','package-lock.json','yarn.lock','bun.lockb'];console.log(files.filter((file)=>fs.existsSync(file)).join('\\n'));"],
4023
+ displayName: "detect package manager lockfiles",
4024
+ mutationPolicy: "readOnly"
4025
+ }
4026
+ };
4027
+ function createBoundedToolAdapterPolicy(options) {
4028
+ const executionRoot = path6.resolve(options.executionRoot);
4029
+ return {
4030
+ executionRoot,
4031
+ mutationPolicy: options.mutationPolicy,
4032
+ timeoutMs: positiveInteger2(options.timeoutMs) ?? 3e4,
4033
+ outputBudgetBytes: positiveInteger2(options.outputBudgetBytes) ?? 64e3,
4034
+ allowedWorkKinds: options.allowedWorkKinds ?? ["assistantQuestion", "projectContextRefresh", "implementationVerification", "testQualityScan", "implementationTestGate", "implementation"],
4035
+ redaction: { localPaths: true, secretAssignments: true },
4036
+ concurrency: {
4037
+ mode: options.concurrencyMode ?? "serialized",
4038
+ resourceKey: options.resourceKey ?? executionRoot
4039
+ }
4040
+ };
4041
+ }
4042
+ async function runFilesystemReadTool(options) {
4043
+ const resolved = resolveRelativePath(options.policy, options.relativePath);
4044
+ if (!resolved.ok) {
4045
+ return failedResult("filesystem.readFile", resolved.error, options.policy);
4046
+ }
4047
+ try {
4048
+ const content = await readFile4(resolved.path, "utf8");
4049
+ return completedResult("filesystem.readFile", content, "", options.policy);
4050
+ } catch (error) {
4051
+ return failedResult("filesystem.readFile", { code: "execution_failed", message: `File read failed: ${errorMessage2(error)}` }, options.policy);
4052
+ }
4053
+ }
4054
+ async function runFilesystemWriteTool(options) {
4055
+ const mutation = ensureMutationAllowed(options.policy, "mutating");
4056
+ if (mutation) {
4057
+ return failedResult("filesystem.writeFile", mutation, options.policy);
4058
+ }
4059
+ const resolved = resolveRelativePath(options.policy, options.relativePath);
4060
+ if (!resolved.ok) {
4061
+ return failedResult("filesystem.writeFile", resolved.error, options.policy);
4062
+ }
4063
+ try {
4064
+ await mkdir6(path6.dirname(resolved.path), { recursive: true });
4065
+ await writeFile5(resolved.path, options.content, "utf8");
4066
+ const summary = { relativePath: normalizeRelativePath(options.relativePath), bytes: Buffer.byteLength(options.content, "utf8") };
4067
+ return completedResult("filesystem.writeFile", JSON.stringify(summary, null, 2), "", options.policy);
4068
+ } catch (error) {
4069
+ return failedResult("filesystem.writeFile", { code: "execution_failed", message: `File write failed: ${errorMessage2(error)}` }, options.policy);
4070
+ }
4071
+ }
4072
+ async function runFilesystemListTool(options) {
4073
+ const relativeDirectory = normalizeRelativePath(options.relativeDirectory ?? ".");
4074
+ const resolved = resolveRelativePath(options.policy, relativeDirectory);
4075
+ if (!resolved.ok) {
4076
+ return failedResult("filesystem.listFiles", resolved.error, options.policy);
4077
+ }
4078
+ try {
4079
+ const maxFiles = positiveInteger2(options.maxFiles) ?? defaultFilesystemListLimit;
4080
+ const files = await listFiles(options.policy.executionRoot, resolved.path, maxFiles);
4081
+ const summary = { relativeDirectory, files: files.items, truncated: files.truncated };
4082
+ return completedResult("filesystem.listFiles", JSON.stringify(summary, null, 2), "", options.policy);
4083
+ } catch (error) {
4084
+ return failedResult("filesystem.listFiles", { code: "execution_failed", message: `File list failed: ${errorMessage2(error)}` }, options.policy);
4085
+ }
4086
+ }
4087
+ async function runFilesystemSearchTool(options) {
4088
+ const query = options.query.trim();
4089
+ if (!query) {
4090
+ return failedResult("filesystem.searchText", { code: "command_not_allowed", message: "Search query must not be empty." }, options.policy);
4091
+ }
4092
+ const relativeDirectory = normalizeRelativePath(options.relativeDirectory ?? ".");
4093
+ const resolved = resolveRelativePath(options.policy, relativeDirectory);
4094
+ if (!resolved.ok) {
4095
+ return failedResult("filesystem.searchText", resolved.error, options.policy);
4096
+ }
4097
+ try {
4098
+ const maxMatches = positiveInteger2(options.maxMatches) ?? defaultFilesystemSearchLimit;
4099
+ const matches = await searchText(options.policy, resolved.path, query, maxMatches);
4100
+ const summary = { query, matches: matches.items, truncated: matches.truncated };
4101
+ return completedResult("filesystem.searchText", JSON.stringify(summary, null, 2), "", options.policy);
4102
+ } catch (error) {
4103
+ return failedResult("filesystem.searchText", { code: "execution_failed", message: `Text search failed: ${errorMessage2(error)}` }, options.policy);
4104
+ }
4105
+ }
4106
+ async function runGitStatusTool(options) {
4107
+ return executeCuratedCommand(defaultCuratedShellCommands["git.status.short"], options.policy, "git.status");
4108
+ }
4109
+ async function runGitDiffNameOnlyTool(options) {
4110
+ return executeCuratedCommand(defaultCuratedShellCommands["git.diff.nameOnly"], options.policy, "git.diffNameOnly");
4111
+ }
4112
+ async function runPackageManagerDetectTool(options) {
4113
+ try {
4114
+ const detection = await detectPackageManager(options.policy.executionRoot);
4115
+ return completedResult("packageManager.detect", JSON.stringify(detection, null, 2), "", options.policy);
4116
+ } catch (error) {
4117
+ return failedResult("packageManager.detect", { code: "execution_failed", message: `Package manager detection failed: ${errorMessage2(error)}` }, options.policy);
4118
+ }
4119
+ }
4120
+ async function runTestRunnerProfileTool(options) {
4121
+ const profileId = options.profileId ?? "test";
4122
+ if (!testRunnerProfileIds.includes(profileId)) {
4123
+ return failedResult("testRunner.profile", { code: "command_not_allowed", message: "Verification profile is not allowed by this runner policy." }, options.policy);
4124
+ }
4125
+ let detection;
4126
+ try {
4127
+ detection = await detectPackageManager(options.policy.executionRoot);
4128
+ } catch (error) {
4129
+ return failedResult("testRunner.profile", { code: "execution_failed", message: `Verification profile discovery failed: ${errorMessage2(error)}` }, options.policy);
4130
+ }
4131
+ if (!detection.profiles.includes(profileId)) {
4132
+ return failedResult("testRunner.profile", { code: "command_not_allowed", message: `Verification profile is not defined in local package scripts: ${profileId}.` }, options.policy);
4133
+ }
4134
+ const command = options.commandCatalog?.[profileId] ?? testRunnerProfileCommand(options.packageManager ?? detection.packageManager ?? "npm", profileId);
4135
+ return executeCuratedCommand(command, options.policy, "testRunner.profile");
4136
+ }
4137
+ async function runBrowserCheckTool(options) {
4138
+ const target = parseBrowserCheckTarget(options.url, options.allowedHosts);
4139
+ if (!target.ok) {
4140
+ return failedResult("browser.check", target.error, options.policy);
4141
+ }
4142
+ const abortController = new AbortController();
4143
+ const timeout = setTimeout(() => abortController.abort(), options.policy.timeoutMs);
4144
+ timeout.unref?.();
4145
+ try {
4146
+ const response = await (options.fetchImpl ?? fetch)(target.url, { method: "GET", redirect: "manual", signal: abortController.signal });
4147
+ const body = await response.text();
4148
+ const summary = {
4149
+ url: target.url.toString(),
4150
+ status: response.status,
4151
+ ok: response.ok,
4152
+ ...response.headers.get("content-type") ? { contentType: response.headers.get("content-type") } : {},
4153
+ ...extractHtmlTitle(body) ? { title: extractHtmlTitle(body) } : {},
4154
+ bytes: Buffer.byteLength(body, "utf8")
4155
+ };
4156
+ const stdout = JSON.stringify(summary, null, 2);
4157
+ if (response.ok) {
4158
+ return completedResult("browser.check", stdout, "", options.policy);
4159
+ }
4160
+ return failedResult("browser.check", { code: "execution_failed", message: `Browser check failed with HTTP ${response.status}.` }, options.policy, stdout);
4161
+ } catch (error) {
4162
+ if (abortController.signal.aborted) {
4163
+ return failedResult("browser.check", { code: "timeout", message: `Browser check timed out after ${formatTimeoutDuration(options.policy.timeoutMs)}.` }, options.policy);
4164
+ }
4165
+ return failedResult("browser.check", { code: "execution_failed", message: `Browser check failed: ${errorMessage2(error)}` }, options.policy);
4166
+ } finally {
4167
+ clearTimeout(timeout);
4168
+ }
4169
+ }
4170
+ async function executeCuratedCommand(command, policy, adapterId) {
4171
+ const hostExecution = await getRuntimeHostExecutionPort();
4172
+ const result = await hostExecution.executeCommand({
4173
+ command: command.command,
4174
+ args: command.args,
4175
+ cwd: policy.executionRoot,
4176
+ env: process.env,
4177
+ shell: false,
4178
+ timeoutMs: policy.timeoutMs,
4179
+ timeoutMessage: `Tool adapter timed out after ${formatTimeoutDuration(policy.timeoutMs)}: ${command.displayName}`
4180
+ });
4181
+ if (result.status === "timedOut") {
4182
+ return failedResult(adapterId, { code: "timeout", message: result.error?.message ?? `Tool adapter timed out after ${formatTimeoutDuration(policy.timeoutMs)}: ${command.displayName}` }, policy, result.stdout, result.stderr);
4183
+ }
4184
+ if (result.status === "spawnFailed" || result.status === "unsupported") {
4185
+ return failedResult(adapterId, { code: "execution_failed", message: result.error?.message ?? `Command execution failed: ${command.displayName}` }, policy, result.stdout, result.stderr);
4186
+ }
4187
+ if (result.exitCode === 0) {
4188
+ return completedResult(adapterId, result.stdout, result.stderr, policy);
4189
+ }
4190
+ return failedResult(adapterId, { code: "execution_failed", message: `${command.displayName} exited with code ${result.exitCode}.` }, policy, result.stdout, result.stderr);
4191
+ }
4192
+ async function detectPackageManager(executionRoot) {
4193
+ const packageJson = await readPackageJson(executionRoot);
4194
+ const lockfiles = await detectPackageLockfiles(executionRoot);
4195
+ const scripts = Object.keys(packageJson?.scripts ?? {}).sort();
4196
+ const packageManager = packageManagerFromPackageManagerField(packageJson?.packageManager) ?? packageManagerFromLockfiles(lockfiles);
4197
+ const profiles = testRunnerProfileIds.filter((profileId) => scripts.includes(profileId));
4198
+ return {
4199
+ ...packageManager ? { packageManager } : {},
4200
+ lockfiles,
4201
+ scripts,
4202
+ profiles
4203
+ };
4204
+ }
4205
+ async function listFiles(executionRoot, directoryPath, maxFiles) {
4206
+ const items = [];
4207
+ let truncated = false;
4208
+ const visit = async (currentDirectory) => {
4209
+ if (truncated) return;
4210
+ const entries = await readdir3(currentDirectory, { withFileTypes: true });
4211
+ for (const entry of entries.sort((first, second) => first.name.localeCompare(second.name))) {
4212
+ if (truncated) return;
4213
+ if (entry.name.startsWith(".") && ignoredTraversalDirectories.has(entry.name)) continue;
4214
+ if (ignoredTraversalDirectories.has(entry.name)) continue;
4215
+ const absolutePath = path6.join(currentDirectory, entry.name);
4216
+ if (entry.isDirectory()) {
4217
+ await visit(absolutePath);
4218
+ continue;
4219
+ }
4220
+ if (!entry.isFile()) continue;
4221
+ items.push(normalizeRelativePath(path6.relative(executionRoot, absolutePath)));
4222
+ if (items.length >= maxFiles) {
4223
+ truncated = true;
4224
+ return;
4225
+ }
4226
+ }
4227
+ };
4228
+ await visit(directoryPath);
4229
+ return { items, truncated };
4230
+ }
4231
+ async function searchText(policy, directoryPath, query, maxMatches) {
4232
+ const listed = await listFiles(policy.executionRoot, directoryPath, Math.max(maxMatches * 20, maxMatches));
4233
+ const lowerQuery = query.toLowerCase();
4234
+ const matches = [];
4235
+ let truncated = listed.truncated;
4236
+ for (const relativePath of listed.items) {
4237
+ if (matches.length >= maxMatches) {
4238
+ truncated = true;
4239
+ break;
4240
+ }
4241
+ const absolutePath = path6.join(policy.executionRoot, relativePath);
4242
+ const stats = await stat3(absolutePath);
4243
+ if (!stats.isFile() || stats.size > filesystemSearchMaxFileBytes) continue;
4244
+ const content = await readFile4(absolutePath, "utf8").catch(() => void 0);
4245
+ if (content === void 0 || content.includes("\0")) continue;
4246
+ const lines = content.split(/\r?\n/);
4247
+ for (const [lineIndex, line] of lines.entries()) {
4248
+ if (!line.toLowerCase().includes(lowerQuery)) continue;
4249
+ matches.push({ relativePath, line: lineIndex + 1, text: line.slice(0, 500) });
4250
+ if (matches.length >= maxMatches) {
4251
+ truncated = true;
4252
+ break;
4253
+ }
4254
+ }
4255
+ }
4256
+ return { items: matches, truncated };
4257
+ }
4258
+ async function readPackageJson(executionRoot) {
4259
+ const packageJsonPath = path6.join(executionRoot, "package.json");
4260
+ if (!await fileExists(packageJsonPath)) {
4261
+ return void 0;
4262
+ }
4263
+ const parsed = JSON.parse(await readFile4(packageJsonPath, "utf8"));
4264
+ if (!isRecord2(parsed)) {
4265
+ return void 0;
4266
+ }
4267
+ const scripts = isRecord2(parsed.scripts) ? Object.fromEntries(Object.entries(parsed.scripts).filter((entry) => typeof entry[1] === "string")) : void 0;
4268
+ return {
4269
+ ...typeof parsed.packageManager === "string" ? { packageManager: parsed.packageManager } : {},
4270
+ ...scripts ? { scripts } : {}
4271
+ };
4272
+ }
4273
+ async function detectPackageLockfiles(executionRoot) {
4274
+ const lockfileCandidates = Object.values(packageLockfiles).flat();
4275
+ const presentLockfiles = [];
4276
+ for (const lockfile of lockfileCandidates) {
4277
+ if (await fileExists(path6.join(executionRoot, lockfile))) {
4278
+ presentLockfiles.push(lockfile);
4279
+ }
4280
+ }
4281
+ return presentLockfiles;
4282
+ }
4283
+ function packageManagerFromPackageManagerField(value) {
4284
+ const packageManagerName = value?.split("@")[0]?.trim();
4285
+ return isPackageManagerName(packageManagerName) ? packageManagerName : void 0;
4286
+ }
4287
+ function packageManagerFromLockfiles(lockfiles) {
4288
+ for (const packageManager of ["pnpm", "npm", "yarn", "bun"]) {
4289
+ if (packageLockfiles[packageManager].some((lockfile) => lockfiles.includes(lockfile))) {
4290
+ return packageManager;
4291
+ }
4292
+ }
4293
+ return void 0;
4294
+ }
4295
+ function testRunnerProfileCommand(packageManager, profileId) {
4296
+ if (packageManager === "pnpm") {
4297
+ return { command: "corepack", args: ["pnpm", "--config.verify-deps-before-run=false", "run", profileId], displayName: `pnpm run ${profileId}`, mutationPolicy: "readOnly" };
4298
+ }
4299
+ if (packageManager === "yarn") {
4300
+ return { command: "corepack", args: ["yarn", "run", profileId], displayName: `yarn run ${profileId}`, mutationPolicy: "readOnly" };
4301
+ }
4302
+ if (packageManager === "bun") {
4303
+ return { command: "bun", args: ["run", profileId], displayName: `bun run ${profileId}`, mutationPolicy: "readOnly" };
4304
+ }
4305
+ return { command: "npm", args: ["run", profileId], displayName: `npm run ${profileId}`, mutationPolicy: "readOnly" };
4306
+ }
4307
+ function parseBrowserCheckTarget(value, allowedHosts) {
4308
+ let url;
4309
+ try {
4310
+ url = new URL(value);
4311
+ } catch {
4312
+ return { ok: false, error: { code: "command_not_allowed", message: "Browser check target URL is invalid." } };
4313
+ }
4314
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
4315
+ return { ok: false, error: { code: "command_not_allowed", message: "Browser check target must use http or https." } };
4316
+ }
4317
+ if (url.username || url.password) {
4318
+ return { ok: false, error: { code: "command_not_allowed", message: "Browser check target must not include credentials." } };
4319
+ }
4320
+ const allowedHostSet = new Set((allowedHosts ?? []).map((host2) => host2.toLowerCase()));
4321
+ const hostname = url.hostname.toLowerCase();
4322
+ const host = url.host.toLowerCase();
4323
+ if (!isLoopbackHost(hostname) && !allowedHostSet.has(hostname) && !allowedHostSet.has(host)) {
4324
+ return { ok: false, error: { code: "command_not_allowed", message: "Browser check target must be loopback or explicitly allowed by the runner policy." } };
4325
+ }
4326
+ return { ok: true, url };
4327
+ }
4328
+ function isLoopbackHost(hostname) {
4329
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname.startsWith("127.") || hostname === "::1" || hostname === "[::1]" || hostname === "0.0.0.0";
4330
+ }
4331
+ function extractHtmlTitle(body) {
4332
+ const match = /<title[^>]*>([^<]*)<\/title>/i.exec(body);
4333
+ const title = match?.[1]?.replace(/\s+/g, " ").trim();
4334
+ return title || void 0;
4335
+ }
4336
+ async function fileExists(filePath) {
4337
+ try {
4338
+ await access(filePath, constants.F_OK);
4339
+ return true;
4340
+ } catch {
4341
+ return false;
4342
+ }
4343
+ }
4344
+ function isPackageManagerName(value) {
4345
+ return value === "pnpm" || value === "npm" || value === "yarn" || value === "bun";
4346
+ }
4347
+ function isRecord2(value) {
4348
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4349
+ }
4350
+ function resolveRelativePath(policy, relativePath) {
4351
+ if (path6.isAbsolute(relativePath)) {
4352
+ return { ok: false, error: { code: "out_of_root", message: "Tool adapter rejected absolute local path input." } };
4353
+ }
4354
+ const resolved = path6.resolve(policy.executionRoot, relativePath);
4355
+ if (!isInsideRoot(policy.executionRoot, resolved)) {
4356
+ return { ok: false, error: { code: "out_of_root", message: "Tool adapter path is outside the execution root." } };
4357
+ }
4358
+ return { ok: true, path: resolved };
4359
+ }
4360
+ function normalizeRelativePath(relativePath) {
4361
+ const normalized = relativePath.replaceAll("\\", "/").replace(/^\.\//, "");
4362
+ return normalized || ".";
4363
+ }
4364
+ function ensureMutationAllowed(policy, requiredPolicy) {
4365
+ if (requiredPolicy === "mutating" && policy.mutationPolicy !== "mutating") {
4366
+ return { code: "mutation_denied", message: "Tool adapter mutation is not allowed by the current harness policy." };
4367
+ }
4368
+ return void 0;
4369
+ }
4370
+ function completedResult(adapterId, stdout, stderr, policy) {
4371
+ const output = normalizeOutput(stdout, stderr, policy);
4372
+ return { adapterId, status: "completed", exitCode: 0, stdout: output.stdout, stderr: output.stderr, truncated: output.truncated };
4373
+ }
4374
+ function failedResult(adapterId, error, policy, stdout = "", stderr = "") {
4375
+ const output = normalizeOutput(stdout, stderr, policy);
4376
+ return { adapterId, status: "failed", exitCode: 1, stdout: output.stdout, stderr: output.stderr, truncated: output.truncated, error };
4377
+ }
4378
+ function normalizeOutput(stdout, stderr, policy) {
4379
+ const redactedStdout = redactToolAdapterText(stdout, policy);
4380
+ const redactedStderr = redactToolAdapterText(stderr, policy);
4381
+ const stdoutBudget = Math.floor(policy.outputBudgetBytes / 2);
4382
+ const stderrBudget = policy.outputBudgetBytes - stdoutBudget;
4383
+ const normalizedStdout = truncateToBudget(redactedStdout, stdoutBudget);
4384
+ const normalizedStderr = truncateToBudget(redactedStderr, stderrBudget);
4385
+ return {
4386
+ stdout: normalizedStdout.value,
4387
+ stderr: normalizedStderr.value,
4388
+ truncated: normalizedStdout.truncated || normalizedStderr.truncated
4389
+ };
4390
+ }
4391
+ function redactToolAdapterText(value, policy) {
4392
+ let result = value;
4393
+ if (policy.redaction.localPaths) {
4394
+ result = result.replaceAll(policy.executionRoot, "<execution-root>");
4395
+ }
4396
+ if (policy.redaction.secretAssignments) {
4397
+ result = result.replace(/\b([A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|API_KEY|ACCESS_KEY)[A-Z0-9_]*)=([^\s"'`]+)/gi, "$1=<redacted>");
4398
+ }
4399
+ return result;
4400
+ }
4401
+ function truncateToBudget(value, budgetBytes) {
4402
+ if (budgetBytes <= 0) {
4403
+ return { value: "", truncated: Boolean(value) };
4404
+ }
4405
+ const bytes = Buffer.byteLength(value, "utf8");
4406
+ if (bytes <= budgetBytes) {
4407
+ return { value, truncated: false };
4408
+ }
4409
+ const suffix = "\n[truncated by Amistio tool adapter output budget]";
4410
+ const contentBudget = Math.max(0, budgetBytes - Buffer.byteLength(suffix, "utf8"));
4411
+ return { value: `${Buffer.from(value, "utf8").subarray(0, contentBudget).toString("utf8")}${suffix}`, truncated: true };
4412
+ }
4413
+ function isInsideRoot(root, candidatePath) {
4414
+ const relative = path6.relative(root, candidatePath);
4415
+ return relative === "" || !relative.startsWith("..") && !path6.isAbsolute(relative);
4416
+ }
4417
+ function positiveInteger2(value) {
4418
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
4419
+ }
4420
+ function errorMessage2(error) {
4421
+ return error instanceof Error ? error.message : String(error);
4422
+ }
4423
+ function formatTimeoutDuration(timeoutMs) {
4424
+ if (timeoutMs < 1e3) {
4425
+ return `${timeoutMs}ms`;
4426
+ }
4427
+ const seconds = timeoutMs / 1e3;
4428
+ return Number.isInteger(seconds) ? `${seconds}s` : `${seconds.toFixed(1)}s`;
4429
+ }
4430
+
4431
+ // src/local-tool-runner.ts
3529
4432
  var localToolNames = runnerToolNames;
3530
4433
  var allReasoningEfforts = ["auto", "low", "medium", "high", "xhigh"];
3531
4434
  var highReasoningEfforts = ["auto", "high", "xhigh"];
4435
+ var localToolOutputBudgetBytes = 32 * 1024;
3532
4436
  var builtInProviderCatalogs = {
3533
4437
  opencode: {
3534
4438
  anthropic: {
@@ -3714,7 +4618,7 @@ async function detectLocalTools() {
3714
4618
  return Promise.all(
3715
4619
  localToolAdapters.map(async (adapter) => {
3716
4620
  const sdkAvailable = await isSdkAvailable(adapter);
3717
- const commandAvailable = adapter.executable ? await commandExists(adapter.executable) : false;
4621
+ const commandAvailable = adapter.executable ? await commandExists2(adapter.executable) : false;
3718
4622
  const providerCatalog = await detectRunnerProviderCatalog(adapter);
3719
4623
  return {
3720
4624
  name: adapter.name,
@@ -3732,9 +4636,9 @@ async function detectLocalTools() {
3732
4636
  );
3733
4637
  }
3734
4638
  async function runLocalTool(options) {
3735
- const promptTempDir = await mkdtemp(path6.join(os3.tmpdir(), "amistio-prompt-"));
3736
- const promptFilePath = path6.join(promptTempDir, "prompt.md");
3737
- await writeFile5(promptFilePath, options.prompt, "utf8");
4639
+ const promptTempDir = await mkdtemp(path7.join(os3.tmpdir(), "amistio-prompt-"));
4640
+ const promptFilePath = path7.join(promptTempDir, "prompt.md");
4641
+ await writeFile6(promptFilePath, options.prompt, "utf8");
3738
4642
  const modelConfig = normalizeModelOptions(options);
3739
4643
  try {
3740
4644
  const runnerOptions = {
@@ -3749,14 +4653,14 @@ async function runLocalTool(options) {
3749
4653
  runnerOptions.toolCommand = options.toolCommand;
3750
4654
  }
3751
4655
  const runner2 = await createToolRunner(runnerOptions);
3752
- const result = await executeToolRunner(runner2, {
4656
+ const result = normalizeToolExecutionResult(await executeToolRunner(runner2, {
3753
4657
  rootDir: options.rootDir,
3754
4658
  prompt: options.prompt,
3755
4659
  promptFilePath,
3756
4660
  streamOutput: Boolean(options.streamOutput),
3757
4661
  ...modelConfig,
3758
4662
  ...options.session ? { session: options.session } : {}
3759
- }, options.timeoutMs);
4663
+ }, options.timeoutMs), options.rootDir);
3760
4664
  return {
3761
4665
  toolName: runner2.toolName,
3762
4666
  displayCommand: runner2.kind === "sdk" ? runner2.displayCommand : runner2.invocation.displayCommand,
@@ -3770,7 +4674,7 @@ async function runLocalTool(options) {
3770
4674
  }
3771
4675
  }
3772
4676
  async function createToolRunPreview(options) {
3773
- const promptFilePath = path6.join(os3.tmpdir(), "amistio-generated-prompt.md");
4677
+ const promptFilePath = path7.join(os3.tmpdir(), "amistio-generated-prompt.md");
3774
4678
  const modelConfig = normalizeModelOptions(options);
3775
4679
  const runnerOptions = {
3776
4680
  rootDir: options.rootDir,
@@ -3831,7 +4735,7 @@ async function createToolRunner(options) {
3831
4735
  if (requiresModelSelection && !options.model) {
3832
4736
  throw new Error("Provider-backed model configuration requires --model or --provider with --model-id.");
3833
4737
  }
3834
- const adapter = tool === "auto" ? await selectFirstAvailableAdapter(requiresModelSelection, options.invocationChannel) : await selectRequestedAdapter(tool, options.invocationChannel);
4738
+ const adapter = tool === "auto" ? await selectFirstAvailableAdapter(options, options.invocationChannel) : await selectRequestedAdapter(tool, options.invocationChannel, options);
3835
4739
  if (requiresModelSelection && !adapter.supportsModelSelection) {
3836
4740
  throw new Error(`Model selection is not supported by ${adapter.name}. Remove model configuration or choose a model-aware adapter.`);
3837
4741
  }
@@ -3844,7 +4748,7 @@ async function createToolRunner(options) {
3844
4748
  allowCommandFallback: options.invocationChannel === "auto"
3845
4749
  };
3846
4750
  }
3847
- if (options.invocationChannel !== "sdk" && adapter.buildInvocation && adapter.executable && await commandExists(adapter.executable)) {
4751
+ if (options.invocationChannel !== "sdk" && adapter.buildInvocation && adapter.executable && await commandExists2(adapter.executable)) {
3848
4752
  return {
3849
4753
  toolName: adapter.name,
3850
4754
  kind: "command",
@@ -3866,10 +4770,10 @@ async function executeToolRunner(runner2, input, timeoutMs) {
3866
4770
  try {
3867
4771
  return await withTimeout(runner2.adapter.runWithSdk(input), timeoutMs, runner2.displayCommand);
3868
4772
  } catch (error) {
3869
- if (runner2.allowCommandFallback && runner2.adapter.buildInvocation && runner2.adapter.executable && await commandExists(runner2.adapter.executable)) {
4773
+ if (runner2.allowCommandFallback && runner2.adapter.buildInvocation && runner2.adapter.executable && await commandExists2(runner2.adapter.executable)) {
3870
4774
  const fallback = runner2.adapter.buildInvocation(input);
3871
4775
  const result = await executeToolInvocation(fallback, input.rootDir, input.streamOutput, timeoutMs);
3872
- const sdkFailure = `SDK execution for ${runner2.adapter.name} failed, fell back to ${fallback.displayCommand}: ${errorMessage(error)}`;
4776
+ const sdkFailure = `SDK execution for ${runner2.adapter.name} failed, fell back to ${fallback.displayCommand}: ${errorMessage3(error)}`;
3873
4777
  return {
3874
4778
  ...result,
3875
4779
  stderr: result.stderr ? `${sdkFailure}
@@ -3885,31 +4789,48 @@ function normalizeToolRequest(value) {
3885
4789
  }
3886
4790
  throw new Error(`Unsupported local tool: ${value}. Supported tools: auto, none, ${localToolNames.join(", ")}.`);
3887
4791
  }
3888
- async function selectFirstAvailableAdapter(requiresModelSelection = false, invocationChannel = "auto") {
4792
+ async function selectFirstAvailableAdapter(modelOptions, invocationChannel = "auto") {
4793
+ const requiresModelSelection = hasModelSelection(modelOptions);
4794
+ let modelSelectionFailure;
3889
4795
  for (const adapter of localToolAdapters) {
3890
4796
  if (requiresModelSelection && !adapter.supportsModelSelection) {
3891
4797
  continue;
3892
4798
  }
4799
+ const validationError = requiresModelSelection ? modelSelectionValidationError(adapter, modelOptions) : void 0;
4800
+ if (validationError) {
4801
+ modelSelectionFailure ??= validationError;
4802
+ continue;
4803
+ }
3893
4804
  const sdkAvailable = await isSdkAvailable(adapter);
3894
- const commandAvailable = adapter.executable ? await commandExists(adapter.executable) : false;
4805
+ const commandAvailable = adapter.executable ? await commandExists2(adapter.executable) : false;
3895
4806
  if (supportsRequestedInvocationChannel({ sdkAvailable, commandAvailable }, invocationChannel)) {
3896
4807
  return adapter;
3897
4808
  }
3898
4809
  }
3899
4810
  if (invocationChannel !== "auto") {
4811
+ if (modelSelectionFailure) {
4812
+ throw new Error(`No installed local AI tool supports the selected provider/model for ${invocationChannel} invocation. ${modelSelectionFailure}`);
4813
+ }
3900
4814
  throw new Error(`No installed local AI tool supports ${invocationChannel} invocation${requiresModelSelection ? " with model selection" : ""}. Select Auto or install a compatible tool.`);
3901
4815
  }
4816
+ if (modelSelectionFailure) {
4817
+ throw new Error(`No installed local AI tool supports the selected provider/model. ${modelSelectionFailure}`);
4818
+ }
3902
4819
  throw new Error(
3903
4820
  requiresModelSelection ? "No installed local AI tool supports model selection. Remove --model or choose a model-aware adapter." : `No supported local AI tool was found. Install one of ${localToolNames.join(", ")} or pass --tool-command "your-tool --prompt-file {promptFile}".`
3904
4821
  );
3905
4822
  }
3906
- async function selectRequestedAdapter(tool, invocationChannel = "auto") {
4823
+ async function selectRequestedAdapter(tool, invocationChannel = "auto", modelOptions) {
3907
4824
  const adapter = localToolAdapters.find((candidate) => candidate.name === tool);
3908
4825
  if (!adapter) {
3909
4826
  throw new Error(`Unsupported local tool: ${tool}.`);
3910
4827
  }
4828
+ if (modelOptions) {
4829
+ const validationError = modelSelectionValidationError(adapter, modelOptions);
4830
+ if (validationError) throw new Error(validationError);
4831
+ }
3911
4832
  const sdkAvailable = await isSdkAvailable(adapter);
3912
- const commandAvailable = adapter.executable ? await commandExists(adapter.executable) : false;
4833
+ const commandAvailable = adapter.executable ? await commandExists2(adapter.executable) : false;
3913
4834
  if (!supportsRequestedInvocationChannel({ sdkAvailable, commandAvailable }, invocationChannel)) {
3914
4835
  if (invocationChannel === "sdk") {
3915
4836
  throw new Error(`The ${tool} SDK was not found. Select Auto or Command invocation, install it, or pass --tool-command locally.`);
@@ -3932,11 +4853,71 @@ function supportsRequestedInvocationChannel(capability, invocationChannel) {
3932
4853
  if (invocationChannel === "sdk") return capability.sdkAvailable;
3933
4854
  return capability.commandAvailable;
3934
4855
  }
4856
+ function hasModelSelection(options) {
4857
+ return Boolean(options.model || options.providerId || options.modelId || options.modelVariant || options.reasoningEffort && options.reasoningEffort !== "auto");
4858
+ }
4859
+ function modelSelectionValidationError(adapter, options) {
4860
+ if (!hasModelSelection(options)) return void 0;
4861
+ if (!adapter.supportsModelSelection) {
4862
+ return `Model selection is not supported by ${adapter.name}. Remove model configuration or choose a model-aware adapter.`;
4863
+ }
4864
+ if (!adapter.providerCatalog) return void 0;
4865
+ const selection = resolveModelSelectionParts(options);
4866
+ if (!selection.providerId && !selection.modelId) return void 0;
4867
+ if (selection.providerId) {
4868
+ const provider = adapter.providerCatalog[selection.providerId];
4869
+ if (!provider) return `Provider ${selection.providerId} is not available on ${adapter.name}.`;
4870
+ if (!selection.modelId) return void 0;
4871
+ const model = provider.models[selection.modelId];
4872
+ if (!model) return `Model ${selection.modelId} is not available for provider ${selection.providerId} on ${adapter.name}.`;
4873
+ return modelCapabilityValidationError(adapter, selection.providerId, model, options);
4874
+ }
4875
+ if (selection.modelId) {
4876
+ const match = findCatalogModel(adapter.providerCatalog, selection.modelId);
4877
+ if (!match) return `Model ${selection.modelId} is not available on ${adapter.name}.`;
4878
+ return modelCapabilityValidationError(adapter, match.providerId, match.model, options);
4879
+ }
4880
+ return void 0;
4881
+ }
4882
+ function resolveModelSelectionParts(options) {
4883
+ const providerId = options.providerId?.trim();
4884
+ const modelId = options.modelId?.trim();
4885
+ if (providerId || modelId) {
4886
+ return {
4887
+ ...providerId ? { providerId } : {},
4888
+ ...modelId ? { modelId } : {}
4889
+ };
4890
+ }
4891
+ const model = options.model?.trim();
4892
+ if (!model) return {};
4893
+ const separatorIndex = model.indexOf("/");
4894
+ if (separatorIndex > 0 && separatorIndex < model.length - 1) {
4895
+ return { providerId: model.slice(0, separatorIndex), modelId: model.slice(separatorIndex + 1) };
4896
+ }
4897
+ return { modelId: model };
4898
+ }
4899
+ function findCatalogModel(catalog, modelId) {
4900
+ for (const [providerId, provider] of Object.entries(catalog)) {
4901
+ const model = provider.models[modelId];
4902
+ if (model) return { providerId, model };
4903
+ }
4904
+ return void 0;
4905
+ }
4906
+ function modelCapabilityValidationError(adapter, providerId, model, options) {
4907
+ const modelId = options.modelId ?? model.id;
4908
+ if (options.modelVariant && model.variants && !model.variants[options.modelVariant]) {
4909
+ return `Model variant ${options.modelVariant} is not available for ${providerId}/${modelId} on ${adapter.name}.`;
4910
+ }
4911
+ if (options.reasoningEffort && options.reasoningEffort !== "auto" && model.supportedReasoningEfforts?.length && !model.supportedReasoningEfforts.includes(options.reasoningEffort)) {
4912
+ return `${options.reasoningEffort} reasoning effort is not available for ${providerId}/${modelId} on ${adapter.name}.`;
4913
+ }
4914
+ return void 0;
4915
+ }
3935
4916
  async function isSdkAvailable(adapter) {
3936
4917
  if (!adapter.sdkPackageName || !adapter.runWithSdk) {
3937
4918
  return false;
3938
4919
  }
3939
- if (adapter.sdkRequiresExecutable && adapter.executable && !await commandExists(adapter.executable)) {
4920
+ if (adapter.sdkRequiresExecutable && adapter.executable && !await commandExists2(adapter.executable)) {
3940
4921
  return false;
3941
4922
  }
3942
4923
  return packageAvailable(adapter.sdkPackageName);
@@ -3949,30 +4930,26 @@ async function packageAvailable(packageName) {
3949
4930
  return false;
3950
4931
  }
3951
4932
  }
3952
- async function commandExists(command) {
3953
- const lookupCommand = process.platform === "win32" ? "where" : "which";
3954
- return new Promise((resolve) => {
3955
- const lookup = spawn(lookupCommand, [command], { stdio: "ignore" });
3956
- lookup.on("error", () => resolve(false));
3957
- lookup.on("close", (exitCode) => resolve(exitCode === 0));
3958
- });
3959
- }
4933
+ async function commandExists2(command) {
4934
+ const hostExecution = await getRuntimeHostExecutionPort();
4935
+ return hostExecution.commandExists(command);
4936
+ }
3960
4937
  async function detectRunnerProviderCatalog(adapter) {
3961
4938
  const localOpencodeConfigCatalog = adapter.name === "opencode" ? await loadLocalOpencodeProviderConfigCatalog() : void 0;
3962
4939
  return mergeProviderCatalogs(adapter.providerCatalog, localOpencodeConfigCatalog);
3963
4940
  }
3964
4941
  function localOpencodeProviderConfigPaths() {
3965
4942
  return [
3966
- path6.join(os3.homedir(), ".config", "opencode", "opencode.json"),
3967
- path6.join(os3.homedir(), ".config", "opencode", "config.json"),
3968
- path6.join(process.cwd(), "opencode.json")
4943
+ path7.join(os3.homedir(), ".config", "opencode", "opencode.json"),
4944
+ path7.join(os3.homedir(), ".config", "opencode", "config.json"),
4945
+ path7.join(process.cwd(), "opencode.json")
3969
4946
  ];
3970
4947
  }
3971
4948
  async function loadLocalOpencodeProviderConfigCatalog(configPaths = localOpencodeProviderConfigPaths()) {
3972
4949
  for (const configPath of configPaths) {
3973
4950
  try {
3974
- const parsed = JSON.parse(await readFile4(configPath, "utf8"));
3975
- const providerValue = isRecord(parsed) ? parsed.provider : void 0;
4951
+ const parsed = JSON.parse(await readFile5(configPath, "utf8"));
4952
+ const providerValue = isRecord3(parsed) ? parsed.provider : void 0;
3976
4953
  const catalog = sanitizeProviderCatalog(providerValue);
3977
4954
  if (catalog) return catalog;
3978
4955
  } catch {
@@ -3985,8 +4962,8 @@ function mergeProviderCatalogs(...catalogs) {
3985
4962
  for (const catalog of catalogs) {
3986
4963
  if (!catalog) continue;
3987
4964
  for (const [providerId, provider] of Object.entries(catalog)) {
3988
- const existing = isRecord(merged[providerId]) ? merged[providerId] : void 0;
3989
- const existingModels = existing && isRecord(existing.models) ? existing.models : {};
4965
+ const existing = isRecord3(merged[providerId]) ? merged[providerId] : void 0;
4966
+ const existingModels = existing && isRecord3(existing.models) ? existing.models : {};
3990
4967
  merged[providerId] = {
3991
4968
  ...existing,
3992
4969
  ...provider,
@@ -3999,7 +4976,7 @@ function mergeProviderCatalogs(...catalogs) {
3999
4976
  return parsed.success && Object.keys(parsed.data).length ? parsed.data : void 0;
4000
4977
  }
4001
4978
  function sanitizeProviderCatalog(value) {
4002
- if (!isRecord(value)) return void 0;
4979
+ if (!isRecord3(value)) return void 0;
4003
4980
  const catalog = {};
4004
4981
  for (const [providerId, providerValue] of Object.entries(value)) {
4005
4982
  const provider = sanitizeProviderConfig(providerId, providerValue);
@@ -4009,7 +4986,7 @@ function sanitizeProviderCatalog(value) {
4009
4986
  return parsed.success && Object.keys(parsed.data).length ? parsed.data : void 0;
4010
4987
  }
4011
4988
  function sanitizeProviderConfig(providerId, value) {
4012
- if (!isRecord(value)) return void 0;
4989
+ if (!isRecord3(value)) return void 0;
4013
4990
  const models = sanitizeProviderModels(value.models);
4014
4991
  if (!Object.keys(models).length) return void 0;
4015
4992
  return {
@@ -4020,7 +4997,7 @@ function sanitizeProviderConfig(providerId, value) {
4020
4997
  };
4021
4998
  }
4022
4999
  function sanitizeProviderModels(value) {
4023
- if (!isRecord(value)) return {};
5000
+ if (!isRecord3(value)) return {};
4024
5001
  const models = {};
4025
5002
  for (const [modelId, modelValue] of Object.entries(value)) {
4026
5003
  const model = sanitizeProviderModel(modelId, modelValue);
@@ -4029,7 +5006,7 @@ function sanitizeProviderModels(value) {
4029
5006
  return models;
4030
5007
  }
4031
5008
  function sanitizeProviderModel(modelId, value) {
4032
- if (!isRecord(value)) return void 0;
5009
+ if (!isRecord3(value)) return void 0;
4033
5010
  const reasoning = booleanValue(value.reasoning);
4034
5011
  const releaseDate = stringValue(value.release_date) ?? stringValue(value.releaseDate);
4035
5012
  const toolCall = booleanValue(value.tool_call) ?? booleanValue(value.toolCall);
@@ -4055,7 +5032,7 @@ function sanitizeProviderModel(modelId, value) {
4055
5032
  return model;
4056
5033
  }
4057
5034
  function sanitizeLimit(value) {
4058
- if (!isRecord(value)) return void 0;
5035
+ if (!isRecord3(value)) return void 0;
4059
5036
  const limit = {
4060
5037
  ...numberValue(value.context) !== void 0 ? { context: numberValue(value.context) } : {},
4061
5038
  ...numberValue(value.input) !== void 0 ? { input: numberValue(value.input) } : {},
@@ -4064,7 +5041,7 @@ function sanitizeLimit(value) {
4064
5041
  return Object.keys(limit).length ? limit : void 0;
4065
5042
  }
4066
5043
  function sanitizeModalities(value) {
4067
- if (!isRecord(value)) return void 0;
5044
+ if (!isRecord3(value)) return void 0;
4068
5045
  const modalities = {
4069
5046
  ...stringArrayValue(value.input) ? { input: stringArrayValue(value.input) } : {},
4070
5047
  ...stringArrayValue(value.output) ? { output: stringArrayValue(value.output) } : {}
@@ -4072,10 +5049,10 @@ function sanitizeModalities(value) {
4072
5049
  return Object.keys(modalities).length ? modalities : void 0;
4073
5050
  }
4074
5051
  function sanitizeVariants(value) {
4075
- if (!isRecord(value)) return void 0;
5052
+ if (!isRecord3(value)) return void 0;
4076
5053
  const variants = {};
4077
5054
  for (const [variantId, variantValue] of Object.entries(value)) {
4078
- variants[variantId] = isRecord(variantValue) && booleanValue(variantValue.disabled) !== void 0 ? { disabled: booleanValue(variantValue.disabled) } : {};
5055
+ variants[variantId] = isRecord3(variantValue) && booleanValue(variantValue.disabled) !== void 0 ? { disabled: booleanValue(variantValue.disabled) } : {};
4079
5056
  }
4080
5057
  return Object.keys(variants).length ? variants : void 0;
4081
5058
  }
@@ -4085,7 +5062,7 @@ function sanitizeReasoningEfforts(value) {
4085
5062
  const efforts = values.filter((candidate) => allReasoningEfforts.includes(candidate));
4086
5063
  return efforts.length ? efforts : void 0;
4087
5064
  }
4088
- function isRecord(value) {
5065
+ function isRecord3(value) {
4089
5066
  return typeof value === "object" && value !== null && !Array.isArray(value);
4090
5067
  }
4091
5068
  function stringValue(value) {
@@ -4101,66 +5078,38 @@ function stringArrayValue(value) {
4101
5078
  return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
4102
5079
  }
4103
5080
  async function executeToolInvocation(invocation, rootDir, streamOutput, timeoutMs) {
4104
- return new Promise((resolve, reject) => {
4105
- const child = spawn(invocation.command, invocation.args, {
4106
- cwd: rootDir,
4107
- env: process.env,
4108
- shell: invocation.shell ?? false,
4109
- stdio: ["pipe", "pipe", "pipe"]
4110
- });
4111
- let stdout = "";
4112
- let stderr = "";
4113
- let settled = false;
4114
- let forceKillTimer;
4115
- const timeout = timeoutMs && timeoutMs > 0 ? setTimeout(() => {
4116
- if (settled) return;
4117
- stderr += `${toolTimeoutMessage(invocation.displayCommand, timeoutMs)}
4118
- `;
4119
- child.kill("SIGTERM");
4120
- forceKillTimer = setTimeout(() => child.kill("SIGKILL"), 5e3);
4121
- forceKillTimer.unref?.();
4122
- rejectOnce(new Error(toolTimeoutMessage(invocation.displayCommand, timeoutMs)));
4123
- }, timeoutMs) : void 0;
4124
- timeout?.unref?.();
4125
- const resolveOnce = (value) => {
4126
- if (settled) return;
4127
- settled = true;
4128
- if (timeout) clearTimeout(timeout);
4129
- if (forceKillTimer) clearTimeout(forceKillTimer);
4130
- resolve(value);
4131
- };
4132
- const rejectOnce = (error) => {
4133
- if (settled) return;
4134
- settled = true;
4135
- if (timeout) clearTimeout(timeout);
4136
- if (forceKillTimer) clearTimeout(forceKillTimer);
4137
- reject(error);
4138
- };
4139
- child.on("error", rejectOnce);
4140
- child.stdout.setEncoding("utf8");
4141
- child.stderr.setEncoding("utf8");
4142
- child.stdout.on("data", (chunk) => {
4143
- stdout += chunk;
4144
- if (streamOutput) {
4145
- process.stdout.write(chunk);
4146
- }
4147
- });
4148
- child.stderr.on("data", (chunk) => {
4149
- stderr += chunk;
4150
- if (streamOutput) {
4151
- process.stderr.write(chunk);
4152
- }
4153
- });
4154
- child.stdin.on("error", () => void 0);
4155
- if (invocation.stdin) {
4156
- child.stdin.write(invocation.stdin);
4157
- }
4158
- child.stdin.end();
4159
- child.on("close", (exitCode) => {
4160
- if (forceKillTimer) clearTimeout(forceKillTimer);
4161
- resolveOnce({ exitCode: exitCode ?? 1, stdout, stderr });
4162
- });
5081
+ const hostExecution = await getRuntimeHostExecutionPort();
5082
+ const result = await hostExecution.executeCommand({
5083
+ command: invocation.command,
5084
+ args: invocation.args,
5085
+ cwd: rootDir,
5086
+ env: process.env,
5087
+ shell: invocation.shell ?? false,
5088
+ ...invocation.stdin ? { stdin: invocation.stdin } : {},
5089
+ ...timeoutMs ? { timeoutMs, timeoutMessage: toolTimeoutMessage(invocation.displayCommand, timeoutMs) } : {},
5090
+ streamOutput
4163
5091
  });
5092
+ if (result.status === "timedOut" || result.status === "spawnFailed" || result.status === "unsupported") {
5093
+ throw new Error(result.error?.message ?? `Command execution failed: ${invocation.displayCommand}`);
5094
+ }
5095
+ return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
5096
+ }
5097
+ function normalizeToolExecutionResult(result, rootDir) {
5098
+ return {
5099
+ ...result,
5100
+ stdout: normalizeLocalToolOutput(result.stdout, rootDir),
5101
+ stderr: normalizeLocalToolOutput(result.stderr, rootDir)
5102
+ };
5103
+ }
5104
+ function normalizeLocalToolOutput(value, rootDir) {
5105
+ const redacted = redactToolAdapterText(value, { executionRoot: rootDir, redaction: { localPaths: true, secretAssignments: true } });
5106
+ return truncateLocalToolOutput(redacted, localToolOutputBudgetBytes);
5107
+ }
5108
+ function truncateLocalToolOutput(value, budgetBytes) {
5109
+ if (Buffer.byteLength(value, "utf8") <= budgetBytes) return value;
5110
+ const suffix = "\n[truncated by Amistio local tool output budget]";
5111
+ const contentBudget = Math.max(0, budgetBytes - Buffer.byteLength(suffix, "utf8"));
5112
+ return `${Buffer.from(value, "utf8").subarray(0, contentBudget).toString("utf8")}${suffix}`;
4164
5113
  }
4165
5114
  async function withTimeout(promise, timeoutMs, displayCommand) {
4166
5115
  if (!timeoutMs || timeoutMs <= 0) {
@@ -4182,9 +5131,9 @@ async function withTimeout(promise, timeoutMs, displayCommand) {
4182
5131
  });
4183
5132
  }
4184
5133
  function toolTimeoutMessage(displayCommand, timeoutMs) {
4185
- return `Local tool timed out after ${formatTimeoutDuration(timeoutMs)}: ${displayCommand}`;
5134
+ return `Local tool timed out after ${formatTimeoutDuration2(timeoutMs)}: ${displayCommand}`;
4186
5135
  }
4187
- function formatTimeoutDuration(timeoutMs) {
5136
+ function formatTimeoutDuration2(timeoutMs) {
4188
5137
  if (timeoutMs < 1e3) {
4189
5138
  return `${timeoutMs}ms`;
4190
5139
  }
@@ -4246,7 +5195,7 @@ async function runClaudeSdk(input) {
4246
5195
  }
4247
5196
  }
4248
5197
  })) {
4249
- if (isRecord(message) && message.type === "result") {
5198
+ if (isRecord3(message) && message.type === "result") {
4250
5199
  if (message.subtype === "success" && typeof message.result === "string") {
4251
5200
  stdout += message.result;
4252
5201
  if (input.streamOutput) {
@@ -4332,9 +5281,9 @@ function extractTextParts(parts) {
4332
5281
  if (!Array.isArray(parts)) {
4333
5282
  return "";
4334
5283
  }
4335
- return parts.filter(isRecord).map((part) => part.type === "text" && typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n");
5284
+ return parts.filter(isRecord3).map((part) => part.type === "text" && typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n");
4336
5285
  }
4337
- function errorMessage(error) {
5286
+ function errorMessage3(error) {
4338
5287
  return error instanceof Error ? error.message : String(error);
4339
5288
  }
4340
5289
  function shellQuote(value) {
@@ -4345,14 +5294,14 @@ function shellQuote(value) {
4345
5294
  import { spawn as spawn2 } from "node:child_process";
4346
5295
  import { createHash as createHash3 } from "node:crypto";
4347
5296
  import { openSync } from "node:fs";
4348
- import { mkdir as mkdir6, readdir as readdir3, readFile as readFile5, writeFile as writeFile6 } from "node:fs/promises";
5297
+ import { mkdir as mkdir7, readdir as readdir4, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
4349
5298
  import os4 from "node:os";
4350
- import path7 from "node:path";
5299
+ import path8 from "node:path";
4351
5300
  function currentRunnerMode() {
4352
5301
  return process.env.AMISTIO_RUNNER_MODE === "background" ? "background" : "foreground";
4353
5302
  }
4354
5303
  function defaultRunnerMetadataDir() {
4355
- return path7.join(os4.homedir(), ".config", "amistio", "runners");
5304
+ return path8.join(os4.homedir(), ".config", "amistio", "runners");
4356
5305
  }
4357
5306
  function updatedCliRunnerLaunchOptions() {
4358
5307
  return { executablePath: "amistio", directExecutable: true };
@@ -4369,8 +5318,8 @@ async function startRunnerDaemon(input) {
4369
5318
  if (existing?.status === "running" && isProcessRunning(existing.pid)) {
4370
5319
  throw new Error(`Background runner ${existing.runnerId} is already running with PID ${existing.pid}.`);
4371
5320
  }
4372
- await mkdir6(metadataDir, { recursive: true });
4373
- const logPath = path7.join(metadataDir, `${runnerDaemonKey(input)}.log`);
5321
+ await mkdir7(metadataDir, { recursive: true });
5322
+ const logPath = path8.join(metadataDir, `${runnerDaemonKey(input)}.log`);
4374
5323
  const logFd = openSync(logPath, "a");
4375
5324
  const launch = resolveRunnerDaemonLaunch({
4376
5325
  args: input.args,
@@ -4397,7 +5346,7 @@ async function startRunnerDaemon(input) {
4397
5346
  projectId: input.projectId,
4398
5347
  repositoryLinkId: input.repositoryLinkId,
4399
5348
  runnerId: input.runnerId,
4400
- rootDir: path7.resolve(input.rootDir),
5349
+ rootDir: path8.resolve(input.rootDir),
4401
5350
  apiUrl: input.apiUrl,
4402
5351
  pid: child.pid,
4403
5352
  status: "running",
@@ -4411,8 +5360,8 @@ async function startRunnerDaemon(input) {
4411
5360
  }
4412
5361
  async function restartRunnerDaemonProcess(metadata, args, input = {}) {
4413
5362
  const metadataDir = input.metadataDir ?? defaultRunnerMetadataDir();
4414
- await mkdir6(metadataDir, { recursive: true });
4415
- const logPath = metadata.logPath ?? path7.join(metadataDir, `${runnerDaemonKey(metadata)}.log`);
5363
+ await mkdir7(metadataDir, { recursive: true });
5364
+ const logPath = metadata.logPath ?? path8.join(metadataDir, `${runnerDaemonKey(metadata)}.log`);
4416
5365
  const logFd = openSync(logPath, "a");
4417
5366
  const launch = resolveRunnerDaemonLaunch({
4418
5367
  args,
@@ -4449,12 +5398,12 @@ async function restartRunnerDaemonProcess(metadata, args, input = {}) {
4449
5398
  async function listRunnerDaemonMetadata(input, metadataDir = defaultRunnerMetadataDir()) {
4450
5399
  let entries;
4451
5400
  try {
4452
- entries = await readdir3(metadataDir);
5401
+ entries = await readdir4(metadataDir);
4453
5402
  } catch {
4454
5403
  return [];
4455
5404
  }
4456
5405
  const records = await Promise.all(
4457
- entries.filter((entry) => entry.endsWith(".json")).map(async (entry) => readRunnerDaemonMetadataFile(path7.join(metadataDir, entry)))
5406
+ entries.filter((entry) => entry.endsWith(".json")).map(async (entry) => readRunnerDaemonMetadataFile(path8.join(metadataDir, entry)))
4458
5407
  );
4459
5408
  return records.filter((record) => Boolean(record)).filter((record) => record.accountId === input.accountId && record.projectId === input.projectId && record.repositoryLinkId === input.repositoryLinkId).filter((record) => !input.runnerId || record.runnerId === input.runnerId).sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
4460
5409
  }
@@ -4462,8 +5411,8 @@ async function readRunnerDaemonMetadata(input, metadataDir = defaultRunnerMetada
4462
5411
  return readRunnerDaemonMetadataFile(runnerDaemonMetadataPath(input, metadataDir));
4463
5412
  }
4464
5413
  async function writeRunnerDaemonMetadata(metadata, metadataDir = defaultRunnerMetadataDir()) {
4465
- await mkdir6(metadataDir, { recursive: true });
4466
- await writeFile6(runnerDaemonMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
5414
+ await mkdir7(metadataDir, { recursive: true });
5415
+ await writeFile7(runnerDaemonMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
4467
5416
  }
4468
5417
  async function markRunnerDaemonStopped(metadata, metadataDir = defaultRunnerMetadataDir()) {
4469
5418
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4525,14 +5474,14 @@ function runnerDaemonUptime(metadata, now = Date.now()) {
4525
5474
  return `${seconds}s`;
4526
5475
  }
4527
5476
  function runnerDaemonMetadataPath(input, metadataDir) {
4528
- return path7.join(metadataDir, `${runnerDaemonKey(input)}.json`);
5477
+ return path8.join(metadataDir, `${runnerDaemonKey(input)}.json`);
4529
5478
  }
4530
5479
  function runnerDaemonKey(input) {
4531
5480
  return createHash3("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.runnerId}`).digest("hex");
4532
5481
  }
4533
5482
  async function readRunnerDaemonMetadataFile(filePath) {
4534
5483
  try {
4535
- const parsed = JSON.parse(await readFile5(filePath, "utf8"));
5484
+ const parsed = JSON.parse(await readFile6(filePath, "utf8"));
4536
5485
  if (parsed.schemaVersion !== 1 || !parsed.runnerId || !parsed.projectId || !parsed.repositoryLinkId) {
4537
5486
  return void 0;
4538
5487
  }
@@ -4545,9 +5494,9 @@ async function readRunnerDaemonMetadataFile(filePath) {
4545
5494
  // src/runner-service.ts
4546
5495
  import { spawn as spawn3 } from "node:child_process";
4547
5496
  import { createHash as createHash4 } from "node:crypto";
4548
- import { mkdir as mkdir7, readFile as readFile6, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
5497
+ import { mkdir as mkdir8, readFile as readFile7, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
4549
5498
  import os5 from "node:os";
4550
- import path8 from "node:path";
5499
+ import path9 from "node:path";
4551
5500
  function detectRunnerServicePlatform(platform = process.platform) {
4552
5501
  if (platform === "darwin") return "launchd";
4553
5502
  if (platform === "linux") return "systemd";
@@ -4563,14 +5512,14 @@ function createRunnerServiceDescriptor(input) {
4563
5512
  const serviceFilePath = runnerServiceFilePath(platform, serviceName, homeDir);
4564
5513
  const now = (/* @__PURE__ */ new Date()).toISOString();
4565
5514
  const command = [input.executablePath ?? process.execPath, input.scriptPath ?? process.argv[1], ...input.args];
4566
- const logPath = path8.join(input.metadataDir ?? defaultRunnerMetadataDir(), `${runnerServiceKey(input)}.service.log`);
5515
+ const logPath = path9.join(input.metadataDir ?? defaultRunnerMetadataDir(), `${runnerServiceKey(input)}.service.log`);
4567
5516
  const metadata = {
4568
5517
  schemaVersion: 1,
4569
5518
  accountId: input.accountId,
4570
5519
  projectId: input.projectId,
4571
5520
  repositoryLinkId: input.repositoryLinkId,
4572
5521
  runnerId: input.runnerId,
4573
- rootDir: path8.resolve(input.rootDir),
5522
+ rootDir: path9.resolve(input.rootDir),
4574
5523
  apiUrl: input.apiUrl,
4575
5524
  serviceName,
4576
5525
  serviceFilePath,
@@ -4587,9 +5536,9 @@ function createRunnerServiceDescriptor(input) {
4587
5536
  }
4588
5537
  async function installRunnerService(input, options = {}) {
4589
5538
  const descriptor = createRunnerServiceDescriptor(input);
4590
- await mkdir7(path8.dirname(descriptor.metadata.serviceFilePath), { recursive: true });
4591
- await mkdir7(input.metadataDir ?? defaultRunnerMetadataDir(), { recursive: true });
4592
- await writeFile7(descriptor.metadata.serviceFilePath, descriptor.content, { encoding: "utf8", mode: 384 });
5539
+ await mkdir8(path9.dirname(descriptor.metadata.serviceFilePath), { recursive: true });
5540
+ await mkdir8(input.metadataDir ?? defaultRunnerMetadataDir(), { recursive: true });
5541
+ await writeFile8(descriptor.metadata.serviceFilePath, descriptor.content, { encoding: "utf8", mode: 384 });
4593
5542
  await writeRunnerServiceMetadata(descriptor.metadata, input.metadataDir);
4594
5543
  if (options.activate !== false) {
4595
5544
  const activation = await activateRunnerService(descriptor.metadata);
@@ -4611,7 +5560,7 @@ async function removeRunnerService(input) {
4611
5560
  }
4612
5561
  async function readRunnerServiceMetadata(input, metadataDir = defaultRunnerMetadataDir()) {
4613
5562
  try {
4614
- const parsed = JSON.parse(await readFile6(runnerServiceMetadataPath(input, metadataDir), "utf8"));
5563
+ const parsed = JSON.parse(await readFile7(runnerServiceMetadataPath(input, metadataDir), "utf8"));
4615
5564
  if (parsed.schemaVersion !== 1 || !parsed.serviceName || !parsed.serviceFilePath) {
4616
5565
  return void 0;
4617
5566
  }
@@ -4621,8 +5570,8 @@ async function readRunnerServiceMetadata(input, metadataDir = defaultRunnerMetad
4621
5570
  }
4622
5571
  }
4623
5572
  async function writeRunnerServiceMetadata(metadata, metadataDir = defaultRunnerMetadataDir()) {
4624
- await mkdir7(metadataDir, { recursive: true });
4625
- await writeFile7(runnerServiceMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
5573
+ await mkdir8(metadataDir, { recursive: true });
5574
+ await writeFile8(runnerServiceMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
4626
5575
  }
4627
5576
  async function runnerServiceRuntimeStatus(metadata) {
4628
5577
  if (metadata.platform === "launchd") {
@@ -4705,12 +5654,12 @@ WantedBy=default.target
4705
5654
  }
4706
5655
  function runnerServiceFilePath(platform, serviceName, homeDir) {
4707
5656
  if (platform === "launchd") {
4708
- return path8.join(homeDir, "Library", "LaunchAgents", `${serviceName}.plist`);
5657
+ return path9.join(homeDir, "Library", "LaunchAgents", `${serviceName}.plist`);
4709
5658
  }
4710
- return path8.join(homeDir, ".config", "systemd", "user", `${serviceName}.service`);
5659
+ return path9.join(homeDir, ".config", "systemd", "user", `${serviceName}.service`);
4711
5660
  }
4712
5661
  function runnerServiceMetadataPath(input, metadataDir) {
4713
- return path8.join(metadataDir, `${runnerServiceKey(input)}.service.json`);
5662
+ return path9.join(metadataDir, `${runnerServiceKey(input)}.service.json`);
4714
5663
  }
4715
5664
  function runnerServiceName(input) {
4716
5665
  return `com.amistio.runner.${runnerServiceKey(input).slice(0, 20)}`;
@@ -4769,7 +5718,7 @@ function completedToolSessionClosedReason(session) {
4769
5718
  return "Completed one-shot tool run; this session is not reusable.";
4770
5719
  }
4771
5720
  function staleToolSessionClosedReason(session, now = /* @__PURE__ */ new Date()) {
4772
- if (session.status !== "open") {
5721
+ if (session.status !== "open" && session.status !== "active") {
4773
5722
  return void 0;
4774
5723
  }
4775
5724
  const lastActivityMs = Date.parse(session.lastActivityAt);
@@ -5045,8 +5994,8 @@ function createSmokeSession({ now, status, lastActivityAt }) {
5045
5994
  // src/sync.ts
5046
5995
  import { execFile as execFile3 } from "node:child_process";
5047
5996
  import { createHash as createHash5 } from "node:crypto";
5048
- import { mkdir as mkdir8, readdir as readdir4, readFile as readFile7, stat as stat3, writeFile as writeFile8 } from "node:fs/promises";
5049
- import path9 from "node:path";
5997
+ import { mkdir as mkdir9, readdir as readdir5, readFile as readFile8, stat as stat4, writeFile as writeFile9 } from "node:fs/promises";
5998
+ import path10 from "node:path";
5050
5999
  import { promisify as promisify3 } from "node:util";
5051
6000
  var execFileAsync3 = promisify3(execFile3);
5052
6001
  var legacySyncRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
@@ -5142,7 +6091,7 @@ async function readLocalSyncedDocuments(rootDir) {
5142
6091
  const documentFiles = await findBrainDocumentFiles(rootDir);
5143
6092
  const documents = [];
5144
6093
  for (const fullPath of documentFiles) {
5145
- const raw = await readFile7(fullPath, "utf8");
6094
+ const raw = await readFile8(fullPath, "utf8");
5146
6095
  const repoPath = toRepoPath(rootDir, fullPath);
5147
6096
  const parsed = parseSyncedDocument(raw, repoPath);
5148
6097
  if (!parsed) {
@@ -5192,8 +6141,8 @@ async function materializeBrainDocuments(rootDir, documents, options = {}) {
5192
6141
  result.skipped.push(document.repoPath);
5193
6142
  continue;
5194
6143
  }
5195
- await mkdir8(path9.dirname(fullPath), { recursive: true });
5196
- await writeFile8(fullPath, createSyncedDocumentContent(document), "utf8");
6144
+ await mkdir9(path10.dirname(fullPath), { recursive: true });
6145
+ await writeFile9(fullPath, createSyncedDocumentContent(document), "utf8");
5197
6146
  result.written.push(document.repoPath);
5198
6147
  }
5199
6148
  return result;
@@ -5322,7 +6271,7 @@ function parseSyncedHtml(content) {
5322
6271
  }
5323
6272
  async function readExistingSyncedDocument(fullPath) {
5324
6273
  try {
5325
- const raw = await readFile7(fullPath, "utf8");
6274
+ const raw = await readFile8(fullPath, "utf8");
5326
6275
  const parsed = parseSyncedDocument(raw, fullPath);
5327
6276
  if (!parsed) {
5328
6277
  return { exists: true };
@@ -5347,7 +6296,7 @@ async function readExistingSyncedDocument(fullPath) {
5347
6296
  async function findBrainDocumentFiles(rootDir) {
5348
6297
  const files = [];
5349
6298
  for (const syncRoot of [...syncRoots, htmlSyncRoot]) {
5350
- const fullRoot = path9.join(rootDir, syncRoot);
6299
+ const fullRoot = path10.join(rootDir, syncRoot);
5351
6300
  if (!await exists2(fullRoot)) {
5352
6301
  continue;
5353
6302
  }
@@ -5356,8 +6305,8 @@ async function findBrainDocumentFiles(rootDir) {
5356
6305
  return files;
5357
6306
  }
5358
6307
  async function walkBrainDocumentFiles(directory, files) {
5359
- for (const entry of await readdir4(directory, { withFileTypes: true })) {
5360
- const fullPath = path9.join(directory, entry.name);
6308
+ for (const entry of await readdir5(directory, { withFileTypes: true })) {
6309
+ const fullPath = path10.join(directory, entry.name);
5361
6310
  if (entry.isDirectory()) {
5362
6311
  await walkBrainDocumentFiles(fullPath, files);
5363
6312
  } else if (entry.isFile() && /\.(md|mdx|html?)$/i.test(entry.name)) {
@@ -5366,23 +6315,23 @@ async function walkBrainDocumentFiles(directory, files) {
5366
6315
  }
5367
6316
  }
5368
6317
  function safeRepoPath(rootDir, repoPath) {
5369
- if (path9.isAbsolute(repoPath)) {
6318
+ if (path10.isAbsolute(repoPath)) {
5370
6319
  throw new Error(`Refusing to use absolute repo path: ${repoPath}`);
5371
6320
  }
5372
- const normalized = path9.normalize(repoPath);
5373
- if (normalized === ".." || normalized.startsWith(`..${path9.sep}`)) {
6321
+ const normalized = path10.normalize(repoPath);
6322
+ if (normalized === ".." || normalized.startsWith(`..${path10.sep}`)) {
5374
6323
  throw new Error(`Refusing to use path outside the repository: ${repoPath}`);
5375
6324
  }
5376
- const root = path9.resolve(rootDir);
5377
- const fullPath = path9.resolve(root, normalized);
5378
- if (!fullPath.startsWith(`${root}${path9.sep}`)) {
6325
+ const root = path10.resolve(rootDir);
6326
+ const fullPath = path10.resolve(root, normalized);
6327
+ if (!fullPath.startsWith(`${root}${path10.sep}`)) {
5379
6328
  throw new Error(`Refusing to use path outside the repository: ${repoPath}`);
5380
6329
  }
5381
6330
  return fullPath;
5382
6331
  }
5383
6332
  function isControlPlanePath(repoPath) {
5384
- const normalized = path9.normalize(repoPath);
5385
- return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path9.sep}`)) || normalized === htmlSyncRoot || normalized.startsWith(`${htmlSyncRoot}${path9.sep}`);
6333
+ const normalized = path10.normalize(repoPath);
6334
+ return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path10.sep}`)) || normalized === htmlSyncRoot || normalized.startsWith(`${htmlSyncRoot}${path10.sep}`);
5386
6335
  }
5387
6336
  function canonicalControlPlaneRepoPath(repoPath) {
5388
6337
  const normalized = repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
@@ -5393,16 +6342,16 @@ function canonicalControlPlaneRepoPath(repoPath) {
5393
6342
  return normalized;
5394
6343
  }
5395
6344
  function toRepoPath(rootDir, fullPath) {
5396
- return path9.relative(rootDir, fullPath).split(path9.sep).join("/");
6345
+ return path10.relative(rootDir, fullPath).split(path10.sep).join("/");
5397
6346
  }
5398
6347
  function inferTitle(content, repoPath) {
5399
6348
  const heading = content.split("\n").find((line) => line.startsWith("# "))?.replace(/^#\s+/, "").trim();
5400
6349
  if (heading) return heading;
5401
6350
  const htmlHeading = content.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
5402
- return htmlHeading || path9.basename(repoPath, path9.extname(repoPath));
6351
+ return htmlHeading || path10.basename(repoPath, path10.extname(repoPath));
5403
6352
  }
5404
6353
  async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingDocuments, options) {
5405
- const root = path9.resolve(rootDir);
6354
+ const root = path10.resolve(rootDir);
5406
6355
  const maxBytes = (options.maxFileKb ?? defaultAutoSyncMaxFileKb) * 1024;
5407
6356
  const syncedAt = options.syncedAt ?? (/* @__PURE__ */ new Date()).toISOString();
5408
6357
  const existingById = new Map(existingDocuments.map((document) => [document.documentId, document]));
@@ -5418,7 +6367,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
5418
6367
  continue;
5419
6368
  }
5420
6369
  const fullPath = safeRepoPath(root, normalizedRepoPath);
5421
- const fileStat = await stat3(fullPath).catch(() => void 0);
6370
+ const fileStat = await stat4(fullPath).catch(() => void 0);
5422
6371
  if (!fileStat?.isFile()) {
5423
6372
  skipped.push({ repoPath: normalizedRepoPath, reason: "unreadable" });
5424
6373
  continue;
@@ -5427,7 +6376,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
5427
6376
  skipped.push({ repoPath: normalizedRepoPath, reason: "tooLarge" });
5428
6377
  continue;
5429
6378
  }
5430
- const content = await readFile7(fullPath, "utf8").catch(() => void 0);
6379
+ const content = await readFile8(fullPath, "utf8").catch(() => void 0);
5431
6380
  if (content === void 0) {
5432
6381
  skipped.push({ repoPath: normalizedRepoPath, reason: "unreadable" });
5433
6382
  continue;
@@ -5492,7 +6441,7 @@ async function listAutoSyncCandidatePaths(rootDir) {
5492
6441
  }
5493
6442
  const files = [];
5494
6443
  for (const syncRoot of [...syncRoots, htmlSyncRoot, ...legacySyncRoots]) {
5495
- const fullRoot = path9.join(rootDir, syncRoot);
6444
+ const fullRoot = path10.join(rootDir, syncRoot);
5496
6445
  if (await exists2(fullRoot)) {
5497
6446
  await walkAutoSyncFiles(rootDir, fullRoot, files);
5498
6447
  }
@@ -5500,9 +6449,9 @@ async function listAutoSyncCandidatePaths(rootDir) {
5500
6449
  return uniqueSortedRepoPaths(files);
5501
6450
  }
5502
6451
  async function walkAutoSyncFiles(rootDir, directory, files) {
5503
- for (const entry of await readdir4(directory, { withFileTypes: true }).catch(() => [])) {
5504
- const fullPath = path9.join(directory, entry.name);
5505
- const repoPath = normalizeRepoPath3(path9.relative(rootDir, fullPath));
6452
+ for (const entry of await readdir5(directory, { withFileTypes: true }).catch(() => [])) {
6453
+ const fullPath = path10.join(directory, entry.name);
6454
+ const repoPath = normalizeRepoPath3(path10.relative(rootDir, fullPath));
5506
6455
  if (entry.isDirectory()) {
5507
6456
  if (!autoSyncExcludedDirectoryNames.has(entry.name)) {
5508
6457
  await walkAutoSyncFiles(rootDir, fullPath, files);
@@ -5556,7 +6505,7 @@ function parseFrontmatterFromSyncedDocument(frontmatter) {
5556
6505
  }
5557
6506
  async function exists2(filePath) {
5558
6507
  try {
5559
- await stat3(filePath);
6508
+ await stat4(filePath);
5560
6509
  return true;
5561
6510
  } catch {
5562
6511
  return false;
@@ -5564,9 +6513,9 @@ async function exists2(filePath) {
5564
6513
  }
5565
6514
 
5566
6515
  // src/tool-session-store.ts
5567
- import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile9 } from "node:fs/promises";
6516
+ import { mkdir as mkdir10, readFile as readFile9, writeFile as writeFile10 } from "node:fs/promises";
5568
6517
  import os6 from "node:os";
5569
- import path10 from "node:path";
6518
+ import path11 from "node:path";
5570
6519
  var LocalToolSessionStore = class {
5571
6520
  constructor(filePath = defaultSessionStorePath()) {
5572
6521
  this.filePath = filePath;
@@ -5580,12 +6529,12 @@ var LocalToolSessionStore = class {
5580
6529
  async setProviderSessionId(toolSessionId, toolName, providerSessionId) {
5581
6530
  const data = await this.read();
5582
6531
  data[toolSessionId] = { toolName, providerSessionId, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
5583
- await mkdir9(path10.dirname(this.filePath), { recursive: true });
5584
- await writeFile9(this.filePath, JSON.stringify(data, null, 2), "utf8");
6532
+ await mkdir10(path11.dirname(this.filePath), { recursive: true });
6533
+ await writeFile10(this.filePath, JSON.stringify(data, null, 2), "utf8");
5585
6534
  }
5586
6535
  async read() {
5587
6536
  try {
5588
- return JSON.parse(await readFile8(this.filePath, "utf8"));
6537
+ return JSON.parse(await readFile9(this.filePath, "utf8"));
5589
6538
  } catch {
5590
6539
  return {};
5591
6540
  }
@@ -5593,16 +6542,16 @@ var LocalToolSessionStore = class {
5593
6542
  };
5594
6543
  function defaultSessionStorePath() {
5595
6544
  if (process.platform === "darwin") {
5596
- return path10.join(os6.homedir(), "Library", "Application Support", "Amistio", "tool-sessions.json");
6545
+ return path11.join(os6.homedir(), "Library", "Application Support", "Amistio", "tool-sessions.json");
5597
6546
  }
5598
6547
  if (process.platform === "win32") {
5599
- return path10.join(process.env.APPDATA ?? os6.homedir(), "Amistio", "tool-sessions.json");
6548
+ return path11.join(process.env.APPDATA ?? os6.homedir(), "Amistio", "tool-sessions.json");
5600
6549
  }
5601
- return path10.join(process.env.XDG_STATE_HOME ?? path10.join(os6.homedir(), ".local", "state"), "amistio", "tool-sessions.json");
6550
+ return path11.join(process.env.XDG_STATE_HOME ?? path11.join(os6.homedir(), ".local", "state"), "amistio", "tool-sessions.json");
5602
6551
  }
5603
6552
 
5604
6553
  // src/work-runner.ts
5605
- import path11 from "node:path";
6554
+ import path12 from "node:path";
5606
6555
  var generationResultStart = "AMISTIO_BRAIN_GENERATION_RESULT_START";
5607
6556
  var generationResultEnd = "AMISTIO_BRAIN_GENERATION_RESULT_END";
5608
6557
  var assistantAnswerStart = "AMISTIO_ASSISTANT_ANSWER_START";
@@ -5790,6 +6739,25 @@ var canonicalProjectContextCitationSources = /* @__PURE__ */ new Map([
5790
6739
  ["runnerstate", "runnerState"],
5791
6740
  ["mixed", "mixed"]
5792
6741
  ]);
6742
+ var canonicalProjectContextFreshnessValues = /* @__PURE__ */ new Map([
6743
+ ["fresh", "fresh"],
6744
+ ["current", "fresh"],
6745
+ ["uptodate", "fresh"],
6746
+ ["accurate", "fresh"],
6747
+ ["stale", "stale"],
6748
+ ["outdated", "stale"],
6749
+ ["outofdate", "stale"],
6750
+ ["old", "stale"],
6751
+ ["historical", "stale"],
6752
+ ["partial", "partial"],
6753
+ ["partiallyfresh", "partial"],
6754
+ ["incomplete", "partial"],
6755
+ ["mixed", "partial"],
6756
+ ["missing", "missing"],
6757
+ ["absent", "missing"],
6758
+ ["notfound", "missing"],
6759
+ ["notpresent", "missing"]
6760
+ ]);
5793
6761
  function createImplementationVerificationPrompt(workItem) {
5794
6762
  return [
5795
6763
  "# Amistio Implementation Verification",
@@ -6082,6 +7050,7 @@ function createProjectContextRefreshPrompt(workItem, context) {
6082
7050
  "- Use only these exact singular slice kind values: overview, architecture, domain, data, api, frontend, backend, cli, workflow, operations, security, testing, unknown.",
6083
7051
  "- Capture entities and relations that explain how the app is put together and where future work should look first.",
6084
7052
  "- Prefer summaries, repository-relative paths, short citations, tags, and freshness status over raw source excerpts.",
7053
+ "- Use only these exact freshness values for coverage.status and slices[].freshness: fresh, stale, partial, missing.",
6085
7054
  "- Use only these exact citation source values: projectBrain, localSource, runnerState, mixed. Approved project-brain records use projectBrain, not approvedBrain.",
6086
7055
  "- Mark stale or missing areas explicitly instead of guessing.",
6087
7056
  "- Keep coverage.missingAreas entries concise noun phrases under 160 characters.",
@@ -6828,6 +7797,11 @@ function normalizeProjectContextRefreshEnums(value) {
6828
7797
  return value;
6829
7798
  }
6830
7799
  const normalized = { ...value };
7800
+ if (isObjectRecord(normalized.coverage)) {
7801
+ const normalizedCoverage = { ...normalized.coverage };
7802
+ normalizedCoverage.status = normalizeProjectContextFreshness(normalizedCoverage.status);
7803
+ normalized.coverage = normalizedCoverage;
7804
+ }
6831
7805
  if (Array.isArray(normalized.slices)) {
6832
7806
  normalized.slices = normalized.slices.map((slice) => {
6833
7807
  if (!isObjectRecord(slice)) {
@@ -6835,6 +7809,7 @@ function normalizeProjectContextRefreshEnums(value) {
6835
7809
  }
6836
7810
  const normalizedSlice = { ...slice };
6837
7811
  normalizedSlice.kind = normalizeProjectContextSliceKind(slice.kind);
7812
+ normalizedSlice.freshness = normalizeProjectContextFreshness(slice.freshness);
6838
7813
  return normalizedSlice;
6839
7814
  });
6840
7815
  }
@@ -6945,6 +7920,26 @@ function normalizeProjectContextSliceKind(value) {
6945
7920
  }
6946
7921
  return "unknown";
6947
7922
  }
7923
+ function normalizeProjectContextFreshness(value) {
7924
+ if (typeof value !== "string") {
7925
+ return value;
7926
+ }
7927
+ const trimmed = value.trim();
7928
+ if (!trimmed) {
7929
+ return value;
7930
+ }
7931
+ const direct = canonicalProjectContextFreshnessValues.get(normalizeEnumKey(trimmed));
7932
+ if (direct) {
7933
+ return direct;
7934
+ }
7935
+ for (const segment of trimmed.split(/[\/,|]+/)) {
7936
+ const segmentFreshness = canonicalProjectContextFreshnessValues.get(normalizeEnumKey(segment));
7937
+ if (segmentFreshness) {
7938
+ return segmentFreshness;
7939
+ }
7940
+ }
7941
+ return "partial";
7942
+ }
6948
7943
  function normalizeProjectContextRefreshBoundedText(value) {
6949
7944
  if (!isObjectRecord(value)) {
6950
7945
  return value;
@@ -7039,15 +8034,15 @@ function normalizeProjectContextRepoPath(value, options) {
7039
8034
  if (!trimmed || /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(trimmed) || /^file:/i.test(trimmed) || /^[A-Za-z]:($|[^\\/])/.test(trimmed)) {
7040
8035
  throwUnsafeProjectContextPath();
7041
8036
  }
7042
- const absolute = trimmed.startsWith("/") || trimmed.startsWith("\\\\") || path11.isAbsolute(trimmed) || path11.win32.isAbsolute(trimmed);
8037
+ const absolute = trimmed.startsWith("/") || trimmed.startsWith("\\\\") || path12.isAbsolute(trimmed) || path12.win32.isAbsolute(trimmed);
7043
8038
  if (!absolute) {
7044
8039
  return normalizeRelativeProjectContextPath(trimmed);
7045
8040
  }
7046
8041
  if (!options.repositoryRoot) {
7047
8042
  throwUnsafeProjectContextPath();
7048
8043
  }
7049
- const useWindowsPathRules = path11.win32.isAbsolute(trimmed) && !trimmed.startsWith("/");
7050
- const relativePath = useWindowsPathRules ? path11.win32.relative(path11.win32.resolve(options.repositoryRoot), path11.win32.resolve(trimmed)) : path11.relative(path11.resolve(options.repositoryRoot), path11.resolve(trimmed));
8044
+ const useWindowsPathRules = path12.win32.isAbsolute(trimmed) && !trimmed.startsWith("/");
8045
+ const relativePath = useWindowsPathRules ? path12.win32.relative(path12.win32.resolve(options.repositoryRoot), path12.win32.resolve(trimmed)) : path12.relative(path12.resolve(options.repositoryRoot), path12.resolve(trimmed));
7051
8046
  return normalizeRelativeProjectContextPath(relativePath);
7052
8047
  }
7053
8048
  function normalizeRelativeProjectContextPath(value) {
@@ -7252,8 +8247,8 @@ function roundNumber(value, digits) {
7252
8247
  // src/importer.ts
7253
8248
  import { execFile as execFile4 } from "node:child_process";
7254
8249
  import { createHash as createHash7 } from "node:crypto";
7255
- import { readdir as readdir5, readFile as readFile9, stat as stat4 } from "node:fs/promises";
7256
- import path12 from "node:path";
8250
+ import { readdir as readdir6, readFile as readFile10, stat as stat5 } from "node:fs/promises";
8251
+ import path13 from "node:path";
7257
8252
  import { promisify as promisify4 } from "node:util";
7258
8253
  var execFileAsync4 = promisify4(execFile4);
7259
8254
  var defaultMaxFileKb = 256;
@@ -7274,12 +8269,12 @@ var documentFolderByType = {
7274
8269
  workflow: "docs/workflows"
7275
8270
  };
7276
8271
  async function inspectLocalRepository(rootDir, defaultBranch) {
7277
- const requestedRoot = path12.resolve(rootDir);
8272
+ const requestedRoot = path13.resolve(rootDir);
7278
8273
  const root = await runGit2(["-C", requestedRoot, "rev-parse", "--show-toplevel"]).catch(() => requestedRoot);
7279
8274
  const detectedBranch = await runGit2(["-C", root, "symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => defaultBranch);
7280
8275
  const originUrl = await runGit2(["-C", root, "remote", "get-url", "origin"]).catch(() => void 0);
7281
8276
  const parsedCloneUrl = originUrl ? parseOptionalOriginCloneUrl(originUrl) : void 0;
7282
- const repoName = (parsedCloneUrl?.repoName ?? path12.basename(root)) || "repository";
8277
+ const repoName = (parsedCloneUrl?.repoName ?? path13.basename(root)) || "repository";
7283
8278
  const fingerprintSeed = parsedCloneUrl ? `origin:${parsedCloneUrl.normalizedKey}` : `repo:${repoName}:${detectedBranch || defaultBranch}`;
7284
8279
  return {
7285
8280
  rootDir: root,
@@ -7291,7 +8286,7 @@ async function inspectLocalRepository(rootDir, defaultBranch) {
7291
8286
  };
7292
8287
  }
7293
8288
  async function scanLegacyDocuments(options) {
7294
- const rootDir = path12.resolve(options.rootDir);
8289
+ const rootDir = path13.resolve(options.rootDir);
7295
8290
  const maxBytes = (options.maxFileKb ?? defaultMaxFileKb) * 1024;
7296
8291
  const skipped = [];
7297
8292
  const candidates = [];
@@ -7311,8 +8306,8 @@ async function scanLegacyDocuments(options) {
7311
8306
  skipped.push({ repoPath, reason: "excluded" });
7312
8307
  continue;
7313
8308
  }
7314
- const fullPath = path12.join(rootDir, ...repoPath.split("/"));
7315
- const fileStat = await stat4(fullPath).catch(() => void 0);
8309
+ const fullPath = path13.join(rootDir, ...repoPath.split("/"));
8310
+ const fileStat = await stat5(fullPath).catch(() => void 0);
7316
8311
  if (!fileStat?.isFile()) {
7317
8312
  skipped.push({ repoPath, reason: "unreadable" });
7318
8313
  continue;
@@ -7321,7 +8316,7 @@ async function scanLegacyDocuments(options) {
7321
8316
  skipped.push({ repoPath, reason: "tooLarge" });
7322
8317
  continue;
7323
8318
  }
7324
- const content = await readFile9(fullPath, "utf8").catch(() => void 0);
8319
+ const content = await readFile10(fullPath, "utf8").catch(() => void 0);
7325
8320
  if (content === void 0) {
7326
8321
  skipped.push({ repoPath, reason: "unreadable" });
7327
8322
  continue;
@@ -7411,10 +8406,10 @@ async function listRepositoryPaths(rootDir) {
7411
8406
  return files;
7412
8407
  }
7413
8408
  async function walkRepository(rootDir, directory, files) {
7414
- const entries = await readdir5(directory, { withFileTypes: true }).catch(() => []);
8409
+ const entries = await readdir6(directory, { withFileTypes: true }).catch(() => []);
7415
8410
  for (const entry of entries) {
7416
- const fullPath = path12.join(directory, entry.name);
7417
- const repoPath = normalizeRepoPath4(path12.relative(rootDir, fullPath));
8411
+ const fullPath = path13.join(directory, entry.name);
8412
+ const repoPath = normalizeRepoPath4(path13.relative(rootDir, fullPath));
7418
8413
  if (entry.isDirectory()) {
7419
8414
  if (!excludedDirectoryNames.has(entry.name)) {
7420
8415
  await walkRepository(rootDir, fullPath, files);
@@ -7482,9 +8477,9 @@ function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
7482
8477
  usedPaths.add(basePath);
7483
8478
  return basePath;
7484
8479
  }
7485
- const extension = path12.posix.extname(basePath) || ".md";
7486
- const directory = path12.posix.dirname(basePath);
7487
- const basename = path12.posix.basename(basePath, extension);
8480
+ const extension = path13.posix.extname(basePath) || ".md";
8481
+ const directory = path13.posix.dirname(basePath);
8482
+ const basename = path13.posix.basename(basePath, extension);
7488
8483
  const uniquePath = `${directory}/${basename}-${hashText(sourcePath, 8)}${extension}`;
7489
8484
  usedPaths.add(uniquePath);
7490
8485
  return uniquePath;
@@ -7557,7 +8552,7 @@ function inferTitle2(content, repoPath) {
7557
8552
  if (heading) return heading;
7558
8553
  const htmlHeading = body.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
7559
8554
  if (htmlHeading) return htmlHeading;
7560
- const basename = path12.posix.basename(repoPath, path12.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
8555
+ const basename = path13.posix.basename(repoPath, path13.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
7561
8556
  return titleCase(basename || "Imported Document");
7562
8557
  }
7563
8558
  function stripFrontmatter(content) {
@@ -7591,7 +8586,7 @@ async function runGit2(args) {
7591
8586
 
7592
8587
  // src/runner-actions.ts
7593
8588
  import { spawn as spawn4 } from "node:child_process";
7594
- import path13 from "node:path";
8589
+ import path14 from "node:path";
7595
8590
  function buildBackgroundRunnerArgs(options) {
7596
8591
  const args = [
7597
8592
  "run",
@@ -7601,7 +8596,7 @@ function buildBackgroundRunnerArgs(options) {
7601
8596
  "--runner-id",
7602
8597
  options.runnerId,
7603
8598
  "--root",
7604
- path13.resolve(options.root),
8599
+ path14.resolve(options.root),
7605
8600
  "--session",
7606
8601
  options.session,
7607
8602
  "--interval-seconds",
@@ -7719,8 +8714,8 @@ function truncateProcessOutput(value) {
7719
8714
 
7720
8715
  // src/git-worktree.ts
7721
8716
  import { execFile as execFile5 } from "node:child_process";
7722
- import { copyFile, lstat, mkdir as mkdir10, readdir as readdir6, stat as stat5 } from "node:fs/promises";
7723
- import path14 from "node:path";
8717
+ import { copyFile, lstat, mkdir as mkdir11, readdir as readdir7, stat as stat6 } from "node:fs/promises";
8718
+ import path15 from "node:path";
7724
8719
  import { promisify as promisify5 } from "node:util";
7725
8720
  var execFileAsync5 = promisify5(execFile5);
7726
8721
  var exactLocalEnvironmentFiles = /* @__PURE__ */ new Set([".env", ".env.local", ".env.development", ".env.development.local", ".env.test", ".env.test.local", ".env.production", ".env.production.local"]);
@@ -7741,7 +8736,7 @@ function resolveWorktreeIdentity(workItem) {
7741
8736
  async function prepareGitWorktreeIsolation(rootDir, workItem) {
7742
8737
  const identity = resolveWorktreeIdentity(workItem);
7743
8738
  const repoRoot = await gitOutput(rootDir, ["rev-parse", "--show-toplevel"]).catch((error) => {
7744
- throw new Error(`Git worktree isolation requires a paired Git checkout: ${errorMessage2(error)}`);
8739
+ throw new Error(`Git worktree isolation requires a paired Git checkout: ${errorMessage4(error)}`);
7745
8740
  });
7746
8741
  const currentHead = await gitOutput(repoRoot, ["rev-parse", "HEAD"]);
7747
8742
  await assertBaseRevision(repoRoot, workItem.baseRevision, currentHead);
@@ -7752,11 +8747,11 @@ async function prepareGitWorktreeIsolation(rootDir, workItem) {
7752
8747
  const preparedLocalEnvironmentFileCount2 = await prepareLocalWorktreeEnvironment(repoRoot, worktreePath);
7753
8748
  return { ...identity, baseRevision, worktreePath, ...preparedLocalEnvironmentFileCount2 ? { preparedLocalEnvironmentFileCount: preparedLocalEnvironmentFileCount2 } : {} };
7754
8749
  }
7755
- await mkdir10(path14.dirname(worktreePath), { recursive: true });
8750
+ await mkdir11(path15.dirname(worktreePath), { recursive: true });
7756
8751
  const branchExists = await gitCommandSucceeds(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${identity.branch}`]);
7757
8752
  const worktreeArgs = branchExists ? ["worktree", "add", worktreePath, identity.branch] : ["worktree", "add", "-b", identity.branch, worktreePath, baseRevision];
7758
8753
  await gitOutput(repoRoot, worktreeArgs).catch((error) => {
7759
- throw new Error(`Could not create Git worktree ${identity.worktreeKey} on ${identity.branch}: ${errorMessage2(error)}`);
8754
+ throw new Error(`Could not create Git worktree ${identity.worktreeKey} on ${identity.branch}: ${errorMessage4(error)}`);
7760
8755
  });
7761
8756
  const preparedLocalEnvironmentFileCount = await prepareLocalWorktreeEnvironment(repoRoot, worktreePath);
7762
8757
  return { ...identity, baseRevision, worktreePath, ...preparedLocalEnvironmentFileCount ? { preparedLocalEnvironmentFileCount } : {} };
@@ -7770,9 +8765,9 @@ async function resolveExistingGitWorktreeIsolation(rootDir, workItem) {
7770
8765
  return { ...identity, baseRevision, worktreePath };
7771
8766
  }
7772
8767
  function localWorktreePath(repoRoot, worktreeKey) {
7773
- const repoName = path14.basename(repoRoot);
8768
+ const repoName = path15.basename(repoRoot);
7774
8769
  const worktreeSlug = worktreeKey.split("/").filter(Boolean).pop() ?? "work";
7775
- return path14.join(path14.dirname(repoRoot), `${repoName}.worktrees`, worktreeSlug);
8770
+ return path15.join(path15.dirname(repoRoot), `${repoName}.worktrees`, worktreeSlug);
7776
8771
  }
7777
8772
  async function assertExistingWorktree(worktreePath, branch) {
7778
8773
  await gitOutput(worktreePath, ["rev-parse", "--is-inside-work-tree"]);
@@ -7798,8 +8793,8 @@ async function prepareLocalWorktreeEnvironment(repoRoot, worktreePath) {
7798
8793
  const candidates = await localEnvironmentFileCandidates(repoRoot);
7799
8794
  let preparedCount = 0;
7800
8795
  for (const candidate of candidates) {
7801
- const sourcePath = path14.join(repoRoot, candidate);
7802
- const targetPath = path14.join(worktreePath, candidate);
8796
+ const sourcePath = path15.join(repoRoot, candidate);
8797
+ const targetPath = path15.join(worktreePath, candidate);
7803
8798
  if (await pathExists(targetPath)) {
7804
8799
  continue;
7805
8800
  }
@@ -7820,7 +8815,7 @@ async function prepareLocalWorktreeEnvironment(repoRoot, worktreePath) {
7820
8815
  }
7821
8816
  async function localEnvironmentFileCandidates(repoRoot) {
7822
8817
  const names = new Set(exactLocalEnvironmentFiles);
7823
- for (const entry of await readdir6(repoRoot)) {
8818
+ for (const entry of await readdir7(repoRoot)) {
7824
8819
  if (isAllowedLocalEnvironmentFile(entry)) {
7825
8820
  names.add(entry);
7826
8821
  }
@@ -7830,7 +8825,7 @@ async function localEnvironmentFileCandidates(repoRoot) {
7830
8825
  if (!isRootFileName(name)) {
7831
8826
  continue;
7832
8827
  }
7833
- const source = await lstat(path14.join(repoRoot, name)).catch(() => void 0);
8828
+ const source = await lstat(path15.join(repoRoot, name)).catch(() => void 0);
7834
8829
  if (source?.isFile()) {
7835
8830
  candidates.push(name);
7836
8831
  }
@@ -7841,7 +8836,7 @@ function isAllowedLocalEnvironmentFile(name) {
7841
8836
  return exactLocalEnvironmentFiles.has(name) || localEnvironmentFilePattern.test(name);
7842
8837
  }
7843
8838
  function isRootFileName(name) {
7844
- return name === path14.basename(name) && !name.includes("/") && !name.includes("\\");
8839
+ return name === path15.basename(name) && !name.includes("/") && !name.includes("\\");
7845
8840
  }
7846
8841
  async function gitOutput(cwd, args) {
7847
8842
  const { stdout } = await execFileAsync5("git", args, { cwd, maxBuffer: 1024 * 1024 });
@@ -7851,7 +8846,7 @@ async function gitCommandSucceeds(cwd, args) {
7851
8846
  return execFileAsync5("git", args, { cwd }).then(() => true, () => false);
7852
8847
  }
7853
8848
  async function pathExists(value) {
7854
- return stat5(value).then(() => true, () => false);
8849
+ return stat6(value).then(() => true, () => false);
7855
8850
  }
7856
8851
  function workIsolationSlug(scopeId, title) {
7857
8852
  const scopeSlug = slugify(scopeId);
@@ -7862,7 +8857,7 @@ function workIsolationSlug(scopeId, title) {
7862
8857
  function slugify(value) {
7863
8858
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "work";
7864
8859
  }
7865
- function errorMessage2(error) {
8860
+ function errorMessage4(error) {
7866
8861
  return error instanceof Error ? error.message : String(error);
7867
8862
  }
7868
8863
  function safeFileError(error) {
@@ -7874,7 +8869,7 @@ function safeFileError(error) {
7874
8869
 
7875
8870
  // src/implementation-handoff.ts
7876
8871
  import { execFile as execFile6 } from "node:child_process";
7877
- import path15 from "node:path";
8872
+ import path16 from "node:path";
7878
8873
  import { promisify as promisify6 } from "node:util";
7879
8874
  var execFileAsync6 = promisify6(execFile6);
7880
8875
  async function completeImplementationHandoff(input) {
@@ -8142,7 +9137,7 @@ async function cleanupWorktree(run, input) {
8142
9137
  return { status: "failed", message: "Cleanup skipped because the worktree is not clean after PR handoff." };
8143
9138
  }
8144
9139
  try {
8145
- await gitOutput2(run, input.primaryRepoRoot || path15.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
9140
+ await gitOutput2(run, input.primaryRepoRoot || path16.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
8146
9141
  return { status: "completed" };
8147
9142
  } catch (error) {
8148
9143
  return { status: "failed", message: `Cleanup failed: ${safeErrorMessage(error)}` };
@@ -8254,6 +9249,481 @@ function redactLocalPaths(value) {
8254
9249
  return value.replace(/(^|[\s'"`])(\/{1,2}(?:Users|home|private|tmp|var|Volumes)\/[^\s'"`]+)/g, "$1<local-path>").replace(/(^|[\s'"`])([A-Za-z]:\\[^\s'"`]+)/g, "$1<local-path>");
8255
9250
  }
8256
9251
 
9252
+ // src/direct-model-client.ts
9253
+ var githubModelsProviderId = "github-models";
9254
+ var githubModelsEndpoint = "https://models.github.ai/inference/chat/completions";
9255
+ var githubModelsSupportedModelIds = ["openai/gpt-4.1", "openai/gpt-4o", "openai/gpt-5"];
9256
+ function shouldUseDirectModelClient(options) {
9257
+ if (options.toolCommand) return false;
9258
+ if (options.providerId !== githubModelsProviderId) return false;
9259
+ return options.tool === void 0 || options.tool === "auto" || options.tool === "none";
9260
+ }
9261
+ function createDirectModelClientPreview(options) {
9262
+ const modelId = requireSupportedGithubModelsModelId(options.modelId);
9263
+ return {
9264
+ toolName: "amistio-direct",
9265
+ displayCommand: "GitHub Models Direct API <selected model>",
9266
+ supportsSessionReuse: false,
9267
+ resumabilityScope: "none",
9268
+ model: `${githubModelsProviderId}/${modelId}`,
9269
+ providerId: githubModelsProviderId,
9270
+ modelId,
9271
+ ...options.modelVariant ? { modelVariant: options.modelVariant } : {},
9272
+ ...options.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}
9273
+ };
9274
+ }
9275
+ function resolveDirectModelClientPreference(options) {
9276
+ if (options.tool && options.tool !== "auto" && options.tool !== "none") {
9277
+ return void 0;
9278
+ }
9279
+ const modelFromCombinedPreference = options.model?.startsWith(`${githubModelsProviderId}/`) ? options.model.slice(`${githubModelsProviderId}/`.length) : void 0;
9280
+ const providerId = options.providerId ?? (modelFromCombinedPreference ? githubModelsProviderId : void 0);
9281
+ if (providerId !== githubModelsProviderId) {
9282
+ return void 0;
9283
+ }
9284
+ if (options.requestedInvocationChannel === "command") {
9285
+ return { ready: false, status: "channelUnsupported", message: "GitHub Models direct provider uses the Amistio direct client and does not support command invocation." };
9286
+ }
9287
+ let modelId;
9288
+ try {
9289
+ modelId = requireSupportedGithubModelsModelId(options.modelId ?? modelFromCombinedPreference);
9290
+ } catch (error) {
9291
+ return { ready: false, status: "modelUnsupported", message: errorMessage5(error) };
9292
+ }
9293
+ return {
9294
+ ready: true,
9295
+ config: {
9296
+ model: `${githubModelsProviderId}/${modelId}`,
9297
+ providerId: githubModelsProviderId,
9298
+ modelId,
9299
+ ...options.modelVariant ? { modelVariant: options.modelVariant } : {},
9300
+ ...options.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}
9301
+ }
9302
+ };
9303
+ }
9304
+ async function createGithubModelsChatCompletion(options) {
9305
+ const modelId = requireSupportedGithubModelsModelId(options.modelId);
9306
+ const token = resolveGithubModelsToken(options.env ?? process.env);
9307
+ const requestInit = {
9308
+ method: "POST",
9309
+ headers: {
9310
+ authorization: `Bearer ${token}`,
9311
+ "content-type": "application/json"
9312
+ },
9313
+ body: JSON.stringify({
9314
+ model: modelId,
9315
+ messages: options.messages,
9316
+ ...options.tools?.length ? { tools: options.tools, tool_choice: "auto" } : {},
9317
+ ...options.reasoningEffort && options.reasoningEffort !== "auto" ? { reasoning_effort: options.reasoningEffort } : {}
9318
+ }),
9319
+ ...options.signal ? { signal: options.signal } : {}
9320
+ };
9321
+ const response = await (options.fetchImpl ?? fetch)(githubModelsEndpoint, requestInit);
9322
+ if (!response.ok) {
9323
+ throw new Error(`GitHub Models request failed with HTTP ${response.status}. Check local GitHub Models auth and selected model access.`);
9324
+ }
9325
+ return response.json();
9326
+ }
9327
+ function requireSupportedGithubModelsModelId(modelId) {
9328
+ const trimmed = modelId?.trim();
9329
+ if (!trimmed) {
9330
+ throw new Error("GitHub Models direct provider requires --provider github-models with --model-id.");
9331
+ }
9332
+ if (!githubModelsSupportedModelIds.includes(trimmed)) {
9333
+ throw new Error(`Unsupported GitHub Models direct model: ${trimmed}. Supported models: ${githubModelsSupportedModelIds.join(", ")}.`);
9334
+ }
9335
+ return trimmed;
9336
+ }
9337
+ function resolveGithubModelsToken(env) {
9338
+ const token = env.GITHUB_MODELS_TOKEN?.trim() || env.GITHUB_TOKEN?.trim();
9339
+ if (!token) {
9340
+ throw new Error("GitHub Models direct provider requires GITHUB_MODELS_TOKEN or GITHUB_TOKEN on this runner.");
9341
+ }
9342
+ return token;
9343
+ }
9344
+ function errorMessage5(error) {
9345
+ return error instanceof Error ? error.message : String(error);
9346
+ }
9347
+
9348
+ // src/amistio-direct-agent.ts
9349
+ var defaultMaxToolRounds = 8;
9350
+ async function runAmistioDirectAgent(options) {
9351
+ const preview = createDirectModelClientPreview(options);
9352
+ const messages = [
9353
+ { role: "system", content: directAgentSystemPrompt(options.toolPolicy) },
9354
+ { role: "user", content: options.prompt }
9355
+ ];
9356
+ let tokensIn = 0;
9357
+ let tokensOut = 0;
9358
+ let lastAssistantContent = "";
9359
+ const maxToolRounds = positiveInteger3(options.maxToolRounds) ?? defaultMaxToolRounds;
9360
+ for (let round = 0; round <= maxToolRounds; round += 1) {
9361
+ const response = await createGithubModelsChatCompletion({
9362
+ modelId: preview.modelId,
9363
+ messages,
9364
+ tools: directAgentToolDefinitions,
9365
+ ...options.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {},
9366
+ ...options.env ? { env: options.env } : {},
9367
+ ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},
9368
+ ...options.signal ? { signal: options.signal } : {}
9369
+ });
9370
+ tokensIn += response.usage?.prompt_tokens ?? 0;
9371
+ tokensOut += response.usage?.completion_tokens ?? 0;
9372
+ const message = response.choices?.[0]?.message;
9373
+ const toolCalls = message?.tool_calls ?? [];
9374
+ lastAssistantContent = message?.content ?? lastAssistantContent;
9375
+ if (!toolCalls.length) {
9376
+ if (options.streamOutput && lastAssistantContent) {
9377
+ process.stdout.write(lastAssistantContent);
9378
+ }
9379
+ return {
9380
+ ...preview,
9381
+ exitCode: 0,
9382
+ stdout: lastAssistantContent,
9383
+ stderr: "",
9384
+ ...tokensIn ? { tokensIn } : {},
9385
+ ...tokensOut ? { tokensOut } : {}
9386
+ };
9387
+ }
9388
+ messages.push({ role: "assistant", content: message?.content ?? null, tool_calls: toolCalls });
9389
+ for (const toolCall of toolCalls) {
9390
+ const execution = await executeDirectAgentTool(toolCall, options.toolPolicy);
9391
+ messages.push({ role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(execution) });
9392
+ }
9393
+ }
9394
+ return {
9395
+ ...preview,
9396
+ exitCode: 1,
9397
+ stdout: lastAssistantContent,
9398
+ stderr: `Amistio direct harness stopped after ${maxToolRounds} tool rounds without a final response.`,
9399
+ ...tokensIn ? { tokensIn } : {},
9400
+ ...tokensOut ? { tokensOut } : {}
9401
+ };
9402
+ }
9403
+ async function executeDirectAgentTool(toolCall, policy) {
9404
+ const args = parseToolArguments(toolCall.function.arguments);
9405
+ const toolName = toolCall.function.name;
9406
+ if (!args.ok) {
9407
+ return { toolName, result: toolFailure(toolName, "command_not_allowed", args.message) };
9408
+ }
9409
+ try {
9410
+ switch (toolName) {
9411
+ case "filesystem_read_file":
9412
+ return { toolName, result: await runFilesystemReadTool({ policy, relativePath: stringArg(args.value, "relativePath") }) };
9413
+ case "filesystem_list_files":
9414
+ return { toolName, result: await runFilesystemListTool({ policy, ...optionalStringField(args.value, "relativeDirectory"), ...optionalNumberField(args.value, "maxFiles") }) };
9415
+ case "filesystem_search_text":
9416
+ return { toolName, result: await runFilesystemSearchTool({ policy, query: stringArg(args.value, "query"), ...optionalStringField(args.value, "relativeDirectory"), ...optionalNumberField(args.value, "maxMatches") }) };
9417
+ case "filesystem_write_file":
9418
+ return { toolName, result: await runFilesystemWriteTool({ policy, relativePath: stringArg(args.value, "relativePath"), content: stringArg(args.value, "content") }) };
9419
+ case "git_status":
9420
+ return { toolName, result: await runGitStatusTool({ policy }) };
9421
+ case "git_diff_name_only":
9422
+ return { toolName, result: await runGitDiffNameOnlyTool({ policy }) };
9423
+ case "package_manager_detect":
9424
+ return { toolName, result: await runPackageManagerDetectTool({ policy }) };
9425
+ case "test_runner_profile":
9426
+ return { toolName, result: await runTestRunnerProfileTool({ policy, ...optionalTestProfileField(args.value, "profileId") }) };
9427
+ case "browser_check":
9428
+ return { toolName, result: await runBrowserCheckTool({ policy, url: stringArg(args.value, "url") }) };
9429
+ default:
9430
+ return { toolName, result: toolFailure(toolName, "command_not_allowed", `Unknown Amistio direct harness tool: ${toolName}.`) };
9431
+ }
9432
+ } catch (error) {
9433
+ return { toolName, result: toolFailure(toolName, "command_not_allowed", error instanceof Error ? error.message : String(error)) };
9434
+ }
9435
+ }
9436
+ var directAgentToolDefinitions = [
9437
+ toolDefinition("filesystem_read_file", "Read a UTF-8 file under the prepared execution root.", {
9438
+ type: "object",
9439
+ additionalProperties: false,
9440
+ required: ["relativePath"],
9441
+ properties: { relativePath: { type: "string" } }
9442
+ }),
9443
+ toolDefinition("filesystem_list_files", "List files under a directory within the prepared execution root.", {
9444
+ type: "object",
9445
+ additionalProperties: false,
9446
+ properties: { relativeDirectory: { type: "string" }, maxFiles: { type: "integer", minimum: 1, maximum: 500 } }
9447
+ }),
9448
+ toolDefinition("filesystem_search_text", "Search text under the prepared execution root with bounded output.", {
9449
+ type: "object",
9450
+ additionalProperties: false,
9451
+ required: ["query"],
9452
+ properties: { query: { type: "string" }, relativeDirectory: { type: "string" }, maxMatches: { type: "integer", minimum: 1, maximum: 200 } }
9453
+ }),
9454
+ toolDefinition("filesystem_write_file", "Write a UTF-8 file under the prepared execution root. Allowed only for mutating harness policy.", {
9455
+ type: "object",
9456
+ additionalProperties: false,
9457
+ required: ["relativePath", "content"],
9458
+ properties: { relativePath: { type: "string" }, content: { type: "string" } }
9459
+ }),
9460
+ toolDefinition("git_status", "Read git status --short for the execution root.", { type: "object", additionalProperties: false, properties: {} }),
9461
+ toolDefinition("git_diff_name_only", "Read changed Git paths for the execution root.", { type: "object", additionalProperties: false, properties: {} }),
9462
+ toolDefinition("package_manager_detect", "Detect local package manager lockfiles and declared verification scripts.", { type: "object", additionalProperties: false, properties: {} }),
9463
+ toolDefinition("test_runner_profile", "Run a declared local verification profile such as test, typecheck, lint, build, or verify.", {
9464
+ type: "object",
9465
+ additionalProperties: false,
9466
+ properties: { profileId: { type: "string", enum: ["verify", "test", "typecheck", "lint", "build"] } }
9467
+ }),
9468
+ toolDefinition("browser_check", "Run a loopback-only HTTP browser smoke check.", {
9469
+ type: "object",
9470
+ additionalProperties: false,
9471
+ required: ["url"],
9472
+ properties: { url: { type: "string" } }
9473
+ })
9474
+ ];
9475
+ function directAgentSystemPrompt(policy) {
9476
+ return [
9477
+ "You are the built-in Amistio direct harness running on the user's local runner.",
9478
+ "Use the provided tools to inspect files, make permitted edits, run bounded checks, and then return the final Amistio result requested by the user prompt.",
9479
+ `Mutation policy: ${policy.mutationPolicy}.`,
9480
+ policy.mutationPolicy === "mutating" ? "You may write files only through filesystem_write_file and only with repository-relative paths inside the prepared execution root." : "Do not call filesystem_write_file. This work is read-only.",
9481
+ "Prefer targeted list/search/read operations before edits. Prefer known verification profiles over broad commands. Never ask for arbitrary shell execution, absolute local paths, secrets, provider credentials, or environment dumps.",
9482
+ "Tool outputs are normalized and may be truncated; if more detail is needed, make a narrower tool request.",
9483
+ "When finished, answer with the final task result only. Preserve any structured result contract included in the user prompt."
9484
+ ].join("\n");
9485
+ }
9486
+ function toolDefinition(name, description, parameters) {
9487
+ return { type: "function", function: { name, description, parameters } };
9488
+ }
9489
+ function parseToolArguments(value) {
9490
+ try {
9491
+ const parsed = JSON.parse(value || "{}");
9492
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
9493
+ return { ok: true, value: parsed };
9494
+ }
9495
+ return { ok: false, message: "Tool arguments must be a JSON object." };
9496
+ } catch {
9497
+ return { ok: false, message: "Tool arguments must be valid JSON." };
9498
+ }
9499
+ }
9500
+ function stringArg(args, key) {
9501
+ const value = args[key];
9502
+ if (typeof value !== "string") {
9503
+ throw new Error(`Tool argument ${key} must be a string.`);
9504
+ }
9505
+ return value;
9506
+ }
9507
+ function optionalStringArg(args, key) {
9508
+ const value = args[key];
9509
+ return typeof value === "string" && value.trim() ? value : void 0;
9510
+ }
9511
+ function optionalStringField(args, key) {
9512
+ const value = optionalStringArg(args, key);
9513
+ return value ? { [key]: value } : {};
9514
+ }
9515
+ function optionalNumberArg(args, key) {
9516
+ const value = args[key];
9517
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
9518
+ }
9519
+ function optionalNumberField(args, key) {
9520
+ const value = optionalNumberArg(args, key);
9521
+ return value === void 0 ? {} : { [key]: value };
9522
+ }
9523
+ function optionalTestProfileArg(args, key) {
9524
+ const value = args[key];
9525
+ return value === "verify" || value === "test" || value === "typecheck" || value === "lint" || value === "build" ? value : void 0;
9526
+ }
9527
+ function optionalTestProfileField(args, key) {
9528
+ const value = optionalTestProfileArg(args, key);
9529
+ return value ? { profileId: value } : {};
9530
+ }
9531
+ function toolFailure(toolName, code, message) {
9532
+ return { adapterId: toolName, status: "failed", exitCode: 1, stdout: "", stderr: "", truncated: false, error: { code, message } };
9533
+ }
9534
+ function positiveInteger3(value) {
9535
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
9536
+ }
9537
+
9538
+ // src/harness-adapter.ts
9539
+ var AMISTIO_HARNESS_ID = "amistio";
9540
+ function harnessMutationPolicyForWorkKind(workKind) {
9541
+ return workKind === "implementation" ? "mutating" : "readOnly";
9542
+ }
9543
+ function createHarnessConcurrencyMetadata(executionPolicy, preview) {
9544
+ const serializedResourceKeys = /* @__PURE__ */ new Set([
9545
+ `claim-lane:${executionPolicy.claimLaneId}`,
9546
+ `execution-root:${executionPolicy.executionRoot}`
9547
+ ]);
9548
+ if (executionPolicy.mutationPolicy === "mutating") {
9549
+ serializedResourceKeys.add(`repository:${executionPolicy.executionRoot}`);
9550
+ }
9551
+ if (preview.providerId) {
9552
+ serializedResourceKeys.add(`provider:${preview.providerId}`);
9553
+ }
9554
+ if (preview.toolName && preview.toolName !== "custom") {
9555
+ serializedResourceKeys.add(`client:${preview.toolName}`);
9556
+ }
9557
+ return {
9558
+ claimLaneId: executionPolicy.claimLaneId,
9559
+ supportsConcurrentRuns: true,
9560
+ requiresExclusiveRepository: executionPolicy.mutationPolicy === "mutating",
9561
+ requiresExclusiveProviderAuth: Boolean(preview.providerId && preview.toolName !== "amistio-direct"),
9562
+ serializedResourceKeys: [...serializedResourceKeys]
9563
+ };
9564
+ }
9565
+ var builtinAmistioHarnessAdapter = {
9566
+ id: AMISTIO_HARNESS_ID,
9567
+ displayName: "Amistio",
9568
+ async createRunPreview({ executionPolicy, ...toolOptions }) {
9569
+ const preview = shouldUseDirectModelClient(toolOptions) ? createDirectModelClientPreview(toolOptions) : await createToolRunPreview(toolOptions);
9570
+ const concurrency = createHarnessConcurrencyMetadata(executionPolicy, preview);
9571
+ const toolAdapterPolicy = createBoundedToolAdapterPolicy({
9572
+ executionRoot: executionPolicy.executionRoot,
9573
+ mutationPolicy: executionPolicy.mutationPolicy,
9574
+ concurrencyMode: concurrency.requiresExclusiveRepository ? "serialized" : "parallelSafe",
9575
+ resourceKey: `claim-lane:${executionPolicy.claimLaneId}`
9576
+ });
9577
+ return { harnessId: AMISTIO_HARNESS_ID, displayName: "Amistio", executionPolicy, concurrency, toolAdapterPolicy, availableToolAdapters: boundedToolAdapterCatalog, preview };
9578
+ },
9579
+ async executeRun({ preparedRun, ...toolOptions }) {
9580
+ if (shouldUseDirectModelClient(toolOptions)) {
9581
+ return runAmistioDirectAgent({ ...toolOptions, toolPolicy: preparedRun.toolAdapterPolicy });
9582
+ }
9583
+ return runLocalTool(toolOptions);
9584
+ }
9585
+ };
9586
+
9587
+ // src/host-helper-conformance.ts
9588
+ async function runHostHelperConformance(config, cwd = process.cwd()) {
9589
+ const checks = [];
9590
+ const discovery = await discoverNativeHostHelper(config);
9591
+ checks.push({
9592
+ name: "handshake",
9593
+ passed: discovery.ok,
9594
+ detail: discovery.ok ? `protocol ${discovery.helper.handshake.protocolVersion}` : discovery.failure.message
9595
+ });
9596
+ if (!discovery.ok) {
9597
+ return { passed: false, checks };
9598
+ }
9599
+ const port = await createHostExecutionPort({ nativeHelper: config });
9600
+ const execution = await port.executeCommand({ command: process.execPath, args: ["-e", "console.log('amistio conformance')"], cwd, timeoutMs: 2e3, requireNativeHelper: true });
9601
+ checks.push(commandCheck("command execution", execution, (result) => result.status === "completed" && result.exitCode === 0 && result.stdout.includes("amistio conformance")));
9602
+ const boundedOutput = await port.executeCommand({ command: process.execPath, args: ["-e", "console.log('\u20AC\u20AC\u20AC\u20AC')"], cwd, timeoutMs: 2e3, requireNativeHelper: true, outputBudgetBytes: 7 });
9603
+ checks.push(commandCheck("bounded UTF-8 output", boundedOutput, (result) => result.status === "completed" && Buffer.byteLength(result.stdout, "utf8") <= 7 && !result.stdout.includes("\uFFFD")));
9604
+ if (!discovery.helper.handshake.capabilities.pty.supported) {
9605
+ const pty = await port.executeCommand({ command: process.execPath, args: ["-e", "console.log('pty')"], cwd, timeoutMs: 2e3, requireNativeHelper: true, requirePty: true });
9606
+ checks.push(commandCheck("PTY fail-closed", pty, (result) => result.status === "unsupported" && result.error?.code === "pty_unsupported"));
9607
+ } else {
9608
+ checks.push({ name: "PTY fail-closed", passed: true, detail: "Helper advertises PTY support; platform PTY enforcement must be verified by the helper's own tests." });
9609
+ }
9610
+ if (!discovery.helper.handshake.capabilities.sandbox.supported) {
9611
+ const sandbox = await port.executeCommand({ command: process.execPath, args: ["-e", "console.log('sandbox')"], cwd, timeoutMs: 2e3, requireNativeHelper: true, sandbox: "networkDisabled" });
9612
+ checks.push(commandCheck("sandbox fail-closed", sandbox, (result) => result.status === "unsupported" && result.error?.code === "sandbox_unsupported"));
9613
+ } else {
9614
+ checks.push({ name: "sandbox fail-closed", passed: true, detail: "Helper advertises sandbox support; OS enforcement must be verified by the helper's own platform tests." });
9615
+ }
9616
+ return { passed: checks.every((check) => check.passed), checks };
9617
+ }
9618
+ function commandCheck(name, result, predicate) {
9619
+ return {
9620
+ name,
9621
+ passed: predicate(result),
9622
+ detail: result.error?.message ?? `${result.status} exit ${result.exitCode}`
9623
+ };
9624
+ }
9625
+
9626
+ // src/provider-auth.ts
9627
+ import { createHash as createHash8 } from "node:crypto";
9628
+ var githubCopilotProviderId = "github-copilot";
9629
+ var githubCopilotProviderClientId = "github-copilot-sdk";
9630
+ var githubCopilotRouteType = "agentClient";
9631
+ var githubModelsProviderClientId = "github-models-api";
9632
+ var githubModelsRouteType = "directProvider";
9633
+ async function checkProviderAuthLink(input) {
9634
+ if (isGithubModelsDirectRequest(input.request)) {
9635
+ return checkGithubModelsDirectAuth(input);
9636
+ }
9637
+ if (!isGithubCopilotSdkRequest(input.request)) {
9638
+ return providerAuthResult(input.request, "unsupported", "Provider link target is not supported by this runner.", input.now);
9639
+ }
9640
+ const checkedAt = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
9641
+ let client;
9642
+ try {
9643
+ client = await (input.createCopilotClient ?? createDefaultCopilotClient)({ cwd: input.root, logLevel: "error", useLoggedInUser: true });
9644
+ await client.start();
9645
+ const authStatus = await client.getAuthStatus();
9646
+ const modelCount = authStatus.isAuthenticated ? await listCopilotModelCount(client) : void 0;
9647
+ const status = {
9648
+ providerId: githubCopilotProviderId,
9649
+ providerClientId: githubCopilotProviderClientId,
9650
+ routeType: githubCopilotRouteType,
9651
+ status: authStatus.isAuthenticated ? "authenticated" : "pendingUserAction",
9652
+ authMethodLabel: authStatus.authType ? `GitHub ${authStatus.authType}` : "GitHub Copilot SDK",
9653
+ ...authStatus.host ? { accountHost: authStatus.host } : {},
9654
+ ...authStatus.login ? { accountLogin: authStatus.login } : {},
9655
+ ...authStatus.host && authStatus.login ? { accountFingerprint: accountFingerprint(authStatus.host, authStatus.login) } : {},
9656
+ ...modelCount !== void 0 ? { modelCount } : {},
9657
+ checkedAt,
9658
+ message: authStatus.isAuthenticated ? "GitHub Copilot SDK is authenticated on this runner." : "Complete GitHub Copilot auth locally on the runner machine, then check again."
9659
+ };
9660
+ return { succeeded: true, message: status.message, providerAuthStatus: status };
9661
+ } catch {
9662
+ return providerAuthResult(input.request, "unavailable", "GitHub Copilot SDK auth status is unavailable on this runner.", input.now, "copilot_sdk_unavailable");
9663
+ } finally {
9664
+ await client?.stop().catch(() => void 0);
9665
+ }
9666
+ }
9667
+ function isGithubCopilotSdkRequest(request) {
9668
+ return request.providerId === githubCopilotProviderId && request.providerClientId === githubCopilotProviderClientId && request.routeType === githubCopilotRouteType;
9669
+ }
9670
+ function isGithubModelsDirectRequest(request) {
9671
+ return request.providerId === githubModelsProviderId && request.providerClientId === githubModelsProviderClientId && request.routeType === githubModelsRouteType;
9672
+ }
9673
+ function checkGithubModelsDirectAuth(input) {
9674
+ const checkedAt = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
9675
+ const tokenSource = githubModelsTokenSource(input.env ?? process.env);
9676
+ const message = tokenSource ? `${tokenSource} is configured for GitHub Models direct execution on this runner.` : "Set GITHUB_MODELS_TOKEN or GITHUB_TOKEN on this runner to enable GitHub Models direct execution.";
9677
+ const status = {
9678
+ providerId: githubModelsProviderId,
9679
+ providerClientId: githubModelsProviderClientId,
9680
+ routeType: githubModelsRouteType,
9681
+ status: tokenSource ? "authenticated" : "notConfigured",
9682
+ authMethodLabel: tokenSource ?? "GITHUB_MODELS_TOKEN or GITHUB_TOKEN",
9683
+ ...tokenSource ? { modelCount: githubModelsSupportedModelIds.length } : {},
9684
+ checkedAt,
9685
+ message
9686
+ };
9687
+ return { succeeded: true, message, providerAuthStatus: status };
9688
+ }
9689
+ function githubModelsTokenSource(env) {
9690
+ if (hasEnvValue(env.GITHUB_MODELS_TOKEN)) return "GITHUB_MODELS_TOKEN";
9691
+ if (hasEnvValue(env.GITHUB_TOKEN)) return "GITHUB_TOKEN";
9692
+ return void 0;
9693
+ }
9694
+ function hasEnvValue(value) {
9695
+ return typeof value === "string" && value.trim().length > 0;
9696
+ }
9697
+ async function createDefaultCopilotClient(options) {
9698
+ const { CopilotClient } = await import("@github/copilot-sdk");
9699
+ return new CopilotClient(options);
9700
+ }
9701
+ async function listCopilotModelCount(client) {
9702
+ try {
9703
+ return (await client.listModels()).length;
9704
+ } catch {
9705
+ return void 0;
9706
+ }
9707
+ }
9708
+ function providerAuthResult(request, status, message, now, errorCode) {
9709
+ return {
9710
+ succeeded: status !== "unsupported",
9711
+ message,
9712
+ providerAuthStatus: {
9713
+ providerId: request.providerId,
9714
+ providerClientId: request.providerClientId,
9715
+ routeType: request.routeType,
9716
+ status,
9717
+ checkedAt: now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
9718
+ message,
9719
+ ...errorCode ? { errorCode } : {}
9720
+ }
9721
+ };
9722
+ }
9723
+ function accountFingerprint(host, login) {
9724
+ return `sha256:${createHash8("sha256").update(`${host.toLowerCase()}\0${login.toLowerCase()}`).digest("hex")}`;
9725
+ }
9726
+
8257
9727
  // src/version.ts
8258
9728
  import { readFileSync } from "node:fs";
8259
9729
  function readCliPackageVersion() {
@@ -8388,7 +9858,7 @@ program.command("import").description("Pair an existing checkout and import lega
8388
9858
  });
8389
9859
  program.command("pair").description("Pair this repository with an Amistio web project").requiredOption("--account <accountId>", "Amistio account ID").requiredOption("--project <projectId>", "Amistio project ID").option("--repository-link <repositoryLinkId>", "Existing repository link ID").option("--default-branch <branch>", "Default branch", "main").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--pairing-code <code>", "Short-lived pairing code from the Amistio app").option("--token <token>", "Runner/device credential to store outside the repository").option("--root <path>", "Repository root", defaultRoot).action(async (options, command) => {
8390
9860
  const pairingRoot = await resolvePairingRoot(options.root, { explicitRoot: command.getOptionValueSource("root") === "cli" });
8391
- let repositoryLinkId = options.repositoryLink ?? `repo_${randomUUID()}`;
9861
+ let repositoryLinkId = options.repositoryLink ?? `repo_${randomUUID2()}`;
8392
9862
  let credential = options.token;
8393
9863
  if (options.pairingCode) {
8394
9864
  const pairing = await new ApiClient({
@@ -8574,7 +10044,7 @@ work.command("prompt").description("Print or write an approved work prompt witho
8574
10044
  }
8575
10045
  const prompt = await createRunnerWorkPrompt(context.client, context.metadata.amistioProjectId, workItem);
8576
10046
  if (options.out) {
8577
- await writeFile10(options.out, prompt, "utf8");
10047
+ await writeFile11(options.out, prompt, "utf8");
8578
10048
  console.log(`Wrote work prompt to ${options.out}.`);
8579
10049
  } else {
8580
10050
  console.log(prompt);
@@ -8588,6 +10058,51 @@ program.command("tools").description("List local AI coding tools that the Amisti
8588
10058
  }
8589
10059
  console.log("custom - pass --tool-command to use any other local runner command.");
8590
10060
  });
10061
+ var hostHelper = program.command("host-helper").description("Inspect the optional Amistio host helper used for stronger local execution primitives");
10062
+ hostHelper.command("status").description("Show configured host-helper protocol and capability status").option("--path <path>", "Helper executable path; defaults to AMISTIO_HOST_HELPER_PATH").action(async (options) => {
10063
+ const config = options.path?.trim() ? { command: options.path.trim() } : nativeHostHelperConfigFromEnvironment();
10064
+ console.log(`Protocol: ${hostExecutionProtocolVersion}`);
10065
+ if (!config) {
10066
+ console.log("Helper: not configured");
10067
+ console.log("Default execution: Node host port, no PTY, no sandbox.");
10068
+ console.log("Install @amistio/cli and set AMISTIO_HOST_HELPER_PATH to `amistio-host-helper` only on machines where the reviewed local helper is intended.");
10069
+ return;
10070
+ }
10071
+ console.log(`Helper: ${config.command}`);
10072
+ const discovery = await discoverNativeHostHelper(config);
10073
+ if (!discovery.ok) {
10074
+ console.log(`Status: ${discovery.failure.code}`);
10075
+ console.log(`Reason: ${discovery.failure.message}`);
10076
+ if (discovery.failure.stderr?.trim()) console.log(`stderr: ${truncateLogExcerpt(discovery.failure.stderr)}`);
10077
+ process.exitCode = 1;
10078
+ return;
10079
+ }
10080
+ const { capabilities, secretBoundary } = discovery.helper.handshake;
10081
+ console.log("Status: ready");
10082
+ console.log(`Implementation: ${capabilities.implementation}`);
10083
+ console.log(formatHostHelperCapability("Process groups", capabilities.processGroups));
10084
+ console.log(formatHostHelperCapability("Signal escalation", capabilities.signalEscalation));
10085
+ console.log(formatHostHelperCapability("Stream capture", capabilities.streamCapture));
10086
+ console.log(formatHostHelperCapability("PTY", capabilities.pty));
10087
+ console.log(formatHostHelperCapability("Sandbox", capabilities.sandbox));
10088
+ console.log(`Secret boundary: ${secretBoundary.credentialPolicy}, ${secretBoundary.environmentPolicy}`);
10089
+ });
10090
+ hostHelper.command("conformance").description("Run local compatibility checks against a host helper").option("--path <path>", "Helper executable path; defaults to AMISTIO_HOST_HELPER_PATH").action(async (options) => {
10091
+ const config = options.path?.trim() ? { command: options.path.trim() } : nativeHostHelperConfigFromEnvironment();
10092
+ if (!config) {
10093
+ console.log("No host helper configured. Set AMISTIO_HOST_HELPER_PATH or pass --path.");
10094
+ process.exitCode = 1;
10095
+ return;
10096
+ }
10097
+ const report = await runHostHelperConformance(config);
10098
+ console.log(`Host helper conformance: ${report.passed ? "passed" : "failed"}`);
10099
+ for (const check of report.checks) {
10100
+ console.log(` ${check.passed ? "ok" : "fail"} ${check.name}: ${check.detail}`);
10101
+ }
10102
+ if (!report.passed) {
10103
+ process.exitCode = 1;
10104
+ }
10105
+ });
8591
10106
  program.command("orchestrate").description("Update the Amistio control plane through a user-installed local AI tool").argument("[goal...]", "Goal or next-step instruction for the orchestration pass").option("--root <path>", "Repository root", defaultRoot).option("--tool <name>", "Local tool to use: auto, none, opencode, claude, codex, copilot, gemini, aider, cursor-agent", "auto").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel, "auto").option("--model <model>", "Model to request when the selected local tool supports model selection").option("--provider <providerId>", "Provider id for provider-backed model configuration").option("--model-id <modelId>", "Provider catalog model id to request").option("--model-variant <variant>", "Provider catalog model variant to request").option("--reasoning-effort <effort>", "Reasoning effort: auto, low, medium, high, or xhigh", parseReasoningEffort).option("--tool-command <command>", "Custom local command. Use {promptFile} and {root} placeholders when supported").option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--prompt-out <path>", "Write the generated orchestration prompt to a file before running").option("--dry-run", "Print the generated orchestration prompt without running a tool").option("--no-stream", "Capture local tool output instead of streaming it").action(async (goalParts, options) => {
8592
10107
  const goal = goalParts?.join(" ").trim() || "Review the current repository state and update the Amistio control plane with the next useful orchestration steps.";
8593
10108
  const prompt = await createOrchestrationPrompt({ rootDir: options.root, goal });
@@ -8611,7 +10126,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
8611
10126
  ...options.toolCommand ? { toolCommand: options.toolCommand } : {},
8612
10127
  ...localModelConfig,
8613
10128
  streamOutput: options.stream,
8614
- ...sessionPolicy === "none" ? {} : { session: { toolSessionId: `local_orchestration_${randomUUID()}`, policy: sessionPolicy, decision: localSessionDecision(sessionPolicy) } }
10129
+ ...sessionPolicy === "none" ? {} : { session: { toolSessionId: `local_orchestration_${randomUUID2()}`, policy: sessionPolicy, decision: localSessionDecision(sessionPolicy) } }
8615
10130
  });
8616
10131
  if (!options.stream && result.stdout.trim()) {
8617
10132
  console.log(result.stdout.trim());
@@ -8657,7 +10172,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
8657
10172
  projectId: context.metadata.amistioProjectId,
8658
10173
  repositoryLinkId: context.metadata.repositoryLinkId,
8659
10174
  runnerId,
8660
- rootDir: path16.resolve(options.root),
10175
+ rootDir: path17.resolve(options.root),
8661
10176
  apiUrl: options.apiUrl,
8662
10177
  args: buildBackgroundRunnerArgs(resolvedOptions)
8663
10178
  });
@@ -8848,7 +10363,7 @@ runnerService.command("install").description("Install a user-level startup servi
8848
10363
  projectId: context.metadata.amistioProjectId,
8849
10364
  repositoryLinkId: context.metadata.repositoryLinkId,
8850
10365
  runnerId,
8851
- rootDir: path16.resolve(options.root),
10366
+ rootDir: path17.resolve(options.root),
8852
10367
  apiUrl: options.apiUrl,
8853
10368
  args,
8854
10369
  platform
@@ -8864,7 +10379,7 @@ runnerService.command("install").description("Install a user-level startup servi
8864
10379
  console.log(`Installed startup service ${metadata.serviceName}.`);
8865
10380
  console.log(`Service file: ${metadata.serviceFilePath}`);
8866
10381
  } catch (error) {
8867
- console.error(errorMessage3(error));
10382
+ console.error(errorMessage6(error));
8868
10383
  process.exitCode = 1;
8869
10384
  }
8870
10385
  });
@@ -9007,12 +10522,12 @@ function supportsConcurrentLocalToolExecution(toolConfig) {
9007
10522
  function aggregateRunnerLaneResults(results) {
9008
10523
  const stopResult = results.find((result) => result.stopRunner);
9009
10524
  if (stopResult) return stopResult;
9010
- const failedResult = results.find((result) => result.status === "failed");
9011
- if (failedResult) return failedResult;
10525
+ const failedResult2 = results.find((result) => result.status === "failed");
10526
+ if (failedResult2) return failedResult2;
9012
10527
  const blockedResult = results.find((result) => result.status === "blocked");
9013
10528
  if (blockedResult) return blockedResult;
9014
- const completedResult = results.find((result) => result.status === "completed" || result.status === "preview");
9015
- if (completedResult) return completedResult;
10529
+ const completedResult2 = results.find((result) => result.status === "completed" || result.status === "preview");
10530
+ if (completedResult2) return completedResult2;
9016
10531
  return results.find((result) => result.status === "idle") ?? { status: "idle", exitCode: 0 };
9017
10532
  }
9018
10533
  async function runAutoSyncCycle({ context, maxFileKb, quiet, quietDisabled, root, runnerId }) {
@@ -9064,7 +10579,7 @@ async function runAutoSyncCycle({ context, maxFileKb, quiet, quietDisabled, root
9064
10579
  }
9065
10580
  return { status, message, pushedCount, skippedCount, conflictCount, collection };
9066
10581
  } catch (error) {
9067
- const message = `Auto-sync failed: ${errorMessage3(error)}`;
10582
+ const message = `Auto-sync failed: ${errorMessage6(error)}`;
9068
10583
  await context.client.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "blocked", { ...heartbeatBase, autoSyncStatus: "failed", autoSyncMessage: message, autoSyncLastFailureAt: startedAt }).catch(() => void 0);
9069
10584
  return { status: "failed", message, pushedCount: 0, skippedCount: 0, conflictCount: 0 };
9070
10585
  }
@@ -9167,7 +10682,20 @@ async function runNextWorkItem({
9167
10682
  ...isolationTelemetry.executionBranch ? { currentBranch: isolationTelemetry.executionBranch } : {}
9168
10683
  });
9169
10684
  const resolvedModelConfig = toolConfigModelOptions(toolConfig);
9170
- const preview = await createToolRunPreview({ rootDir: executionRoot, prompt, tool: toolConfig.tool, invocationChannel: toolConfig.requestedInvocationChannel ?? "auto", ...toolCommand ? { toolCommand } : {}, ...resolvedModelConfig });
10685
+ const preparedHarnessRun = await builtinAmistioHarnessAdapter.createRunPreview({
10686
+ rootDir: executionRoot,
10687
+ prompt,
10688
+ tool: toolConfig.tool,
10689
+ invocationChannel: toolConfig.requestedInvocationChannel ?? "auto",
10690
+ ...toolCommand ? { toolCommand } : {},
10691
+ ...resolvedModelConfig,
10692
+ executionPolicy: {
10693
+ executionRoot,
10694
+ mutationPolicy: harnessMutationPolicyForWorkKind(result.workItem.workKind),
10695
+ claimLaneId
10696
+ }
10697
+ });
10698
+ const preview = preparedHarnessRun.preview;
9171
10699
  const sessionContext = await prepareToolSession({
9172
10700
  apiClient,
9173
10701
  projectId,
@@ -9191,7 +10719,15 @@ async function runNextWorkItem({
9191
10719
  status: "running",
9192
10720
  summary: `Local runner started ${preview.toolName} execution.`,
9193
10721
  idempotencyKey: `runner_milestone_started_${result.workItem.workItemId}_${result.workItem.attempt}`,
9194
- metadata: { tool: preview.toolName, invocationChannel: toolConfig.requestedInvocationChannel ?? "auto" }
10722
+ metadata: {
10723
+ tool: preview.toolName,
10724
+ invocationChannel: toolConfig.requestedInvocationChannel ?? "auto",
10725
+ claimLaneId: preparedHarnessRun.concurrency.claimLaneId,
10726
+ harnessSupportsConcurrentRuns: preparedHarnessRun.concurrency.supportsConcurrentRuns,
10727
+ harnessRequiresExclusiveRepository: preparedHarnessRun.concurrency.requiresExclusiveRepository,
10728
+ harnessRequiresExclusiveProviderAuth: preparedHarnessRun.concurrency.requiresExclusiveProviderAuth,
10729
+ harnessSerializedResourceKeys: preparedHarnessRun.concurrency.serializedResourceKeys
10730
+ }
9195
10731
  });
9196
10732
  const startedAt = Date.now();
9197
10733
  const providerSessionStore = new LocalToolSessionStore();
@@ -9199,7 +10735,8 @@ async function runNextWorkItem({
9199
10735
  let toolResult;
9200
10736
  const stopLeaseRenewal = startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerId, toolConfig, workItem: result.workItem, telemetry: isolationTelemetry, heartbeatConcurrency });
9201
10737
  try {
9202
- toolResult = await runLocalTool({
10738
+ toolResult = await builtinAmistioHarnessAdapter.executeRun({
10739
+ preparedRun: preparedHarnessRun,
9203
10740
  rootDir: executionRoot,
9204
10741
  prompt,
9205
10742
  tool: toolConfig.tool,
@@ -9224,7 +10761,7 @@ async function runNextWorkItem({
9224
10761
  const message = `${preview.toolName} failed before returning a result.`;
9225
10762
  const settlements = await Promise.allSettled([
9226
10763
  apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency)),
9227
- markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage3(error)),
10764
+ markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage6(error)),
9228
10765
  apiClient.updateWorkStatus(projectId, result.workItem.workItemId, "failed", `run_failed_${result.workItem.workItemId}_${result.workItem.attempt}_${runnerId}`, runnerId, {
9229
10766
  ...isolationTelemetry,
9230
10767
  tool: preview.toolName,
@@ -9518,7 +11055,7 @@ async function runNextWorkItem({
9518
11055
  projectId,
9519
11056
  result.workItem.workItemId,
9520
11057
  finalStatus,
9521
- `run_${result.workItem.workItemId}_${randomUUID()}`,
11058
+ `run_${result.workItem.workItemId}_${randomUUID2()}`,
9522
11059
  runnerId,
9523
11060
  {
9524
11061
  tool: preview.toolName,
@@ -9608,11 +11145,11 @@ async function prepareWorktreeForClaimedItem({ apiClient, heartbeatConcurrency,
9608
11145
  });
9609
11146
  return { status: "ready", isolation };
9610
11147
  } catch (error) {
9611
- const message = errorMessage3(error);
11148
+ const message = errorMessage6(error);
9612
11149
  const telemetry = workItemIsolationTelemetry(workItem, { ...identity, baseRevision: workItem.baseRevision ?? "unknown", worktreePath: "" });
9613
11150
  const finalAttempt = workItem.attempt >= maxPreflightAttempts;
9614
11151
  const statusMessage = finalAttempt ? `Git worktree preflight failed after ${workItem.attempt}/${maxPreflightAttempts} attempts. ${message}` : `Git worktree preflight attempt ${workItem.attempt}/${maxPreflightAttempts} failed. Requeueing for retry. ${message}`;
9615
- const statusResult = await apiClient.updateWorkStatus(projectId, workItem.workItemId, finalAttempt ? "failed" : "approved", `worktree_${finalAttempt ? "failed" : "retry"}_${workItem.workItemId}_${workItem.attempt}_${randomUUID()}`, runnerId, {
11152
+ const statusResult = await apiClient.updateWorkStatus(projectId, workItem.workItemId, finalAttempt ? "failed" : "approved", `worktree_${finalAttempt ? "failed" : "retry"}_${workItem.workItemId}_${workItem.attempt}_${randomUUID2()}`, runnerId, {
9616
11153
  ...telemetry,
9617
11154
  message: statusMessage,
9618
11155
  ...finalAttempt ? { blockerReason: message } : { releaseClaim: true },
@@ -9680,8 +11217,8 @@ async function recordFinalizationFailure({ apiClient, durationMs, error, isolati
9680
11217
  const message = `${toolName} completed, but Amistio could not finalize the result. ${safeFinalizationFailureSummary(error)}`;
9681
11218
  const settlements = await Promise.allSettled([
9682
11219
  apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)),
9683
- markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage3(error)),
9684
- apiClient.updateWorkStatus(projectId, workItem.workItemId, "failed", `finalize_failed_${workItem.workItemId}_${workItem.attempt}_${randomUUID()}`, runnerId, {
11220
+ markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage6(error)),
11221
+ apiClient.updateWorkStatus(projectId, workItem.workItemId, "failed", `finalize_failed_${workItem.workItemId}_${workItem.attempt}_${randomUUID2()}`, runnerId, {
9685
11222
  ...isolationTelemetry,
9686
11223
  tool: toolName,
9687
11224
  durationMs,
@@ -9799,7 +11336,7 @@ function autopilotWorkMetadata2(workItem) {
9799
11336
  function logRejectedSettlements(action, settlements) {
9800
11337
  for (const settlement of settlements) {
9801
11338
  if (settlement.status === "rejected") {
9802
- console.error(`${action} failed: ${errorMessage3(settlement.reason)}`);
11339
+ console.error(`${action} failed: ${errorMessage6(settlement.reason)}`);
9803
11340
  }
9804
11341
  }
9805
11342
  }
@@ -9812,7 +11349,13 @@ async function runPendingRunnerCommand(apiClient, context, heartbeatMetadata) {
9812
11349
  await updateRunnerCommandStatus(apiClient, context, command, "acknowledged", "Command acknowledged by local runner.");
9813
11350
  await updateRunnerCommandStatus(apiClient, context, command, "running", `Running ${runnerCommandLabel(command.commandKind)} command.`);
9814
11351
  const result = await executeRunnerCommand(apiClient, command, context);
9815
- await updateRunnerCommandStatus(apiClient, context, command, result.succeeded ? "completed" : "failed", result.message, result.error);
11352
+ await updateRunnerCommandStatus(apiClient, context, command, result.succeeded ? "completed" : "failed", result.message, result.error, result.providerAuthStatus);
11353
+ if (result.providerAuthStatus) {
11354
+ await apiClient.sendRunnerHeartbeat(context.projectId, context.runnerId, context.repositoryLinkId, "online", {
11355
+ ...heartbeatMetadata,
11356
+ providerAuthStatuses: mergeProviderAuthStatuses(heartbeatMetadata.providerAuthStatuses, result.providerAuthStatus)
11357
+ }).catch(() => void 0);
11358
+ }
9816
11359
  if (command.commandKind === "remove" && result.succeeded) {
9817
11360
  await new LocalCredentialStore().delete(credentialKey(context.accountId, context.projectId, context.repositoryLinkId));
9818
11361
  }
@@ -9821,14 +11364,15 @@ async function runPendingRunnerCommand(apiClient, context, heartbeatMetadata) {
9821
11364
  }
9822
11365
  return { handled: true, succeeded: result.succeeded, message: result.message, ...result.stopRunner ? { stopRunner: true } : {} };
9823
11366
  }
9824
- async function updateRunnerCommandStatus(apiClient, context, command, status, message, error) {
11367
+ async function updateRunnerCommandStatus(apiClient, context, command, status, message, error, providerAuthStatus) {
9825
11368
  const result = await apiClient.updateRunnerCommand(context.projectId, command.commandId, {
9826
11369
  runnerId: context.runnerId,
9827
11370
  repositoryLinkId: context.repositoryLinkId,
9828
11371
  status,
9829
- idempotencyKey: `runner_command_${command.commandId}_${status}_${randomUUID()}`,
11372
+ idempotencyKey: `runner_command_${command.commandId}_${status}_${randomUUID2()}`,
9830
11373
  message,
9831
- ...error ? { error } : {}
11374
+ ...error ? { error } : {},
11375
+ ...providerAuthStatus ? { providerAuthStatus } : {}
9832
11376
  });
9833
11377
  return result.command;
9834
11378
  }
@@ -9842,11 +11386,21 @@ async function executeRunnerCommand(apiClient, command, context) {
9842
11386
  if (command.commandKind === "implementationHandoffRecovery") {
9843
11387
  return executeImplementationHandoffRecoveryCommand(apiClient, command, context);
9844
11388
  }
11389
+ if (command.commandKind === "providerAuthLinkRequested") {
11390
+ return executeProviderAuthLinkCommand(command, context);
11391
+ }
9845
11392
  return runOfficialCliUpdateWithRuntimeRefresh({
9846
11393
  mode: currentRunnerMode(),
9847
11394
  restartBackgroundRunner: () => restartCurrentRunner(context, { useUpdatedCliExecutable: true })
9848
11395
  });
9849
11396
  }
11397
+ async function executeProviderAuthLinkCommand(command, context) {
11398
+ if (!command.providerAuthLinkRequest) {
11399
+ return { succeeded: false, message: "Provider link command is missing provider auth metadata." };
11400
+ }
11401
+ const result = await checkProviderAuthLink({ request: command.providerAuthLinkRequest, root: context.root });
11402
+ return { succeeded: result.succeeded, message: result.message, providerAuthStatus: result.providerAuthStatus };
11403
+ }
9850
11404
  async function executeImplementationHandoffRecoveryCommand(apiClient, command, context) {
9851
11405
  if (!command.workItemId || !command.handoffRecoveryAction) {
9852
11406
  return { succeeded: false, message: "Handoff recovery command is missing a scoped work item or action." };
@@ -9870,7 +11424,7 @@ async function executeImplementationHandoffRecoveryCommand(apiClient, command, c
9870
11424
  }
9871
11425
  return { succeeded: false, message: "Handoff recovery action is not a runner-local command." };
9872
11426
  } catch (error) {
9873
- return { succeeded: false, message: "Handoff recovery command failed locally.", error: errorMessage3(error) };
11427
+ return { succeeded: false, message: "Handoff recovery command failed locally.", error: errorMessage6(error) };
9874
11428
  }
9875
11429
  }
9876
11430
  async function retryImplementationHandoff(apiClient, context, workItem) {
@@ -9904,7 +11458,7 @@ async function findRecoveryWorkItem(apiClient, projectId, workItemId) {
9904
11458
  return workItems.find((item) => item.workItemId === workItemId);
9905
11459
  }
9906
11460
  async function submitRecoveredHandoff(apiClient, context, workItem, handoff, action) {
9907
- await apiClient.updateWorkStatus(context.projectId, workItem.workItemId, recoveredHandoffWorkStatus(handoff), `handoff_recovery_${workItem.workItemId}_${action}_${randomUUID()}`, context.runnerId, {
11461
+ await apiClient.updateWorkStatus(context.projectId, workItem.workItemId, recoveredHandoffWorkStatus(handoff), `handoff_recovery_${workItem.workItemId}_${action}_${randomUUID2()}`, context.runnerId, {
9908
11462
  implementationHandoff: handoff,
9909
11463
  ...handoff.message ? { message: handoff.message } : {},
9910
11464
  ...workItem.controllingAdrId ? { controllingAdrId: workItem.controllingAdrId } : {},
@@ -9938,15 +11492,20 @@ async function restartCurrentRunner(context, options = {}) {
9938
11492
  const replacement = await restartRunnerDaemonProcess(metadata, context.backgroundArgs, options.useUpdatedCliExecutable ? updatedCliRunnerLaunchOptions() : {});
9939
11493
  return { succeeded: true, stopRunner: true, message: `Replacement background runner started with PID ${replacement.pid}.` };
9940
11494
  } catch (error) {
9941
- return { succeeded: false, message: "Background restart failed.", error: errorMessage3(error) };
11495
+ return { succeeded: false, message: "Background restart failed.", error: errorMessage6(error) };
9942
11496
  }
9943
11497
  }
9944
11498
  function runnerCommandLabel(commandKind) {
9945
11499
  if (commandKind === "update") return "update";
9946
11500
  if (commandKind === "restart") return "restart";
9947
11501
  if (commandKind === "implementationHandoffRecovery") return "handoff recovery";
11502
+ if (commandKind === "providerAuthLinkRequested") return "provider auth link";
9948
11503
  return "remove";
9949
11504
  }
11505
+ function mergeProviderAuthStatuses(existingStatuses, nextStatus) {
11506
+ const statuses = existingStatuses?.filter((status) => status.providerId !== nextStatus.providerId || status.providerClientId !== nextStatus.providerClientId || status.routeType !== nextStatus.routeType) ?? [];
11507
+ return [...statuses, nextStatus].slice(-20);
11508
+ }
9950
11509
  async function replayPendingBrainGenerationFinalizations({ accountId, apiClient, projectId, repositoryLinkId, runnerId }) {
9951
11510
  const pendingEntries = await listPendingBrainGenerationFinalizations({ accountId, projectId, repositoryLinkId, runnerId });
9952
11511
  if (!pendingEntries.length) {
@@ -9992,7 +11551,7 @@ async function submitBrainGenerationFinalizationEntry(apiClient, entry, options
9992
11551
  try {
9993
11552
  result = await apiClient.submitBrainGenerationResult(entry.projectId, entry.workItemId, entry.result);
9994
11553
  } catch (error) {
9995
- const detail = truncateLogExcerpt(errorMessage3(error));
11554
+ const detail = truncateLogExcerpt(errorMessage6(error));
9996
11555
  if (isRetryableApiError(error)) {
9997
11556
  const updated = await markBrainGenerationFinalizationRetry(entry, detail);
9998
11557
  const message2 = `Pending brain generation finalization ${entry.workItemId} could not be replayed yet (${updated.retryCount} attempt${updated.retryCount === 1 ? "" : "s"}): ${detail}`;
@@ -10005,7 +11564,7 @@ async function submitBrainGenerationFinalizationEntry(apiClient, entry, options
10005
11564
  return { status: "failed", message, retryable: false };
10006
11565
  }
10007
11566
  await deleteBrainGenerationFinalizationEntry(entry).catch((error) => {
10008
- console.error(`delete pending brain generation finalization ${entry.workItemId} failed: ${errorMessage3(error)}`);
11567
+ console.error(`delete pending brain generation finalization ${entry.workItemId} failed: ${errorMessage6(error)}`);
10009
11568
  });
10010
11569
  if (options.recordReplayTelemetry) {
10011
11570
  await recordRunnerMilestone(apiClient, entry.projectId, result.workItem, entry.runnerId, entry.repositoryLinkId, {
@@ -10026,7 +11585,7 @@ async function submitDurableResultFinalizationEntry(apiClient, entry, options =
10026
11585
  try {
10027
11586
  response = await submitDurableResultMutation(apiClient, entry);
10028
11587
  } catch (error) {
10029
- const detail = truncateLogExcerpt(errorMessage3(error));
11588
+ const detail = truncateLogExcerpt(errorMessage6(error));
10030
11589
  if (isRetryableApiError(error)) {
10031
11590
  const updated = await markDurableResultFinalizationRetry(entry, detail);
10032
11591
  const message2 = `Pending ${runnerResultFinalizationLabel(entry)} finalization ${entry.workItemId} could not be replayed yet (${updated.retryCount} attempt${updated.retryCount === 1 ? "" : "s"}): ${detail}`;
@@ -10040,7 +11599,7 @@ async function submitDurableResultFinalizationEntry(apiClient, entry, options =
10040
11599
  }
10041
11600
  const workItem = durableResultResponseWorkItem(response);
10042
11601
  await deleteDurableResultFinalizationEntry(entry).catch((error) => {
10043
- console.error(`delete pending ${runnerResultFinalizationLabel(entry)} finalization ${entry.workItemId} failed: ${errorMessage3(error)}`);
11602
+ console.error(`delete pending ${runnerResultFinalizationLabel(entry)} finalization ${entry.workItemId} failed: ${errorMessage6(error)}`);
10044
11603
  });
10045
11604
  if (options.recordReplayTelemetry) {
10046
11605
  await recordRunnerMilestone(apiClient, entry.projectId, workItem, entry.runnerId, entry.repositoryLinkId, {
@@ -10165,7 +11724,7 @@ async function finalizeBrainGenerationWork({
10165
11724
  artifacts = parseBrainGenerationArtifacts(`${toolResult.stdout}
10166
11725
  ${toolResult.stderr}`);
10167
11726
  } catch (error) {
10168
- generationError = errorMessage3(error);
11727
+ generationError = errorMessage6(error);
10169
11728
  }
10170
11729
  } else {
10171
11730
  generationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10183,7 +11742,7 @@ ${toolResult.stderr}`);
10183
11742
  const resultMutation = {
10184
11743
  status: "completed",
10185
11744
  runnerId,
10186
- idempotencyKey: `generation_${workItem.workItemId}_${workItem.attempt}_${randomUUID()}`,
11745
+ idempotencyKey: `generation_${workItem.workItemId}_${workItem.attempt}_${randomUUID2()}`,
10187
11746
  artifacts,
10188
11747
  tool: toolName,
10189
11748
  durationMs,
@@ -10232,7 +11791,7 @@ ${toolResult.stderr}`);
10232
11791
  metadata: { tool: toolName, durationMs, artifactCount: replay.documentCount, verificationSummary: "Generated artifacts were accepted by the Amistio API for review." }
10233
11792
  });
10234
11793
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)).catch((error) => {
10235
- console.error(`send generation completion heartbeat failed: ${errorMessage3(error)}`);
11794
+ console.error(`send generation completion heartbeat failed: ${errorMessage6(error)}`);
10236
11795
  });
10237
11796
  console.log(workItem.workKind === "planRevision" ? "Revised plan returned for review." : `Generated ${replay.documentCount} brain artifact${replay.documentCount === 1 ? "" : "s"} for review.`);
10238
11797
  return { status: "completed", exitCode: 0 };
@@ -10255,10 +11814,10 @@ ${toolResult.stderr}`);
10255
11814
  ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
10256
11815
  ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
10257
11816
  };
10258
- const failedResult = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
11817
+ const failedResult2 = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
10259
11818
  status: "failed",
10260
11819
  runnerId,
10261
- idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
11820
+ idempotencyKey: `generation_${workItem.workItemId}_${randomUUID2()}`,
10262
11821
  tool: toolName,
10263
11822
  durationMs,
10264
11823
  ...failedSessionTelemetry,
@@ -10268,7 +11827,7 @@ ${toolResult.stderr}`);
10268
11827
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10269
11828
  status: "failed",
10270
11829
  summary: generationError ?? `${toolName} did not produce valid brain artifacts.`,
10271
- idempotencyKey: `runner_milestone_generation_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
11830
+ idempotencyKey: `runner_milestone_generation_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10272
11831
  metadata: { tool: toolName, durationMs, verificationSummary: "Generation output could not be parsed into approved artifact JSON." }
10273
11832
  });
10274
11833
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10297,7 +11856,7 @@ async function finalizeAssistantQuestionWork({
10297
11856
  answerResult = parseAssistantAnswerResult(`${toolResult.stdout}
10298
11857
  ${toolResult.stderr}`);
10299
11858
  } catch (error) {
10300
- answerError = errorMessage3(error);
11859
+ answerError = errorMessage6(error);
10301
11860
  }
10302
11861
  } else {
10303
11862
  answerError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10308,7 +11867,7 @@ ${toolResult.stderr}`);
10308
11867
  const resultMutation = {
10309
11868
  status: "completed",
10310
11869
  runnerId,
10311
- idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID()}`,
11870
+ idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID2()}`,
10312
11871
  answer: answerResult.answer,
10313
11872
  sourceBoundary: answerResult.sourceBoundary,
10314
11873
  citations: answerResult.citations,
@@ -10344,14 +11903,14 @@ ${toolResult.stderr}`);
10344
11903
  const failedMutation = {
10345
11904
  status: "failed",
10346
11905
  runnerId,
10347
- idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID()}`,
11906
+ idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID2()}`,
10348
11907
  tool: toolName,
10349
11908
  durationMs,
10350
11909
  ...sessionTelemetry,
10351
11910
  message: `${toolName} did not produce a valid project knowledge answer.`,
10352
11911
  ...answerError ? { error: answerError } : {}
10353
11912
  };
10354
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
11913
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10355
11914
  accountId: workItem.accountId,
10356
11915
  projectId,
10357
11916
  repositoryLinkId,
@@ -10363,12 +11922,12 @@ ${toolResult.stderr}`);
10363
11922
  idempotencyKey: failedMutation.idempotencyKey,
10364
11923
  result: failedMutation
10365
11924
  });
10366
- if (!failedResult) return { status: "failed", exitCode: 1 };
11925
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10367
11926
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10368
11927
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10369
11928
  status: "failed",
10370
11929
  summary: answerError ?? `${toolName} did not produce a valid project knowledge answer.`,
10371
- idempotencyKey: `runner_milestone_assistant_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
11930
+ idempotencyKey: `runner_milestone_assistant_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10372
11931
  metadata: { tool: toolName, durationMs, verificationSummary: "Assistant output did not include a valid structured answer." }
10373
11932
  });
10374
11933
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10395,7 +11954,7 @@ async function finalizeImpactPreviewWork({
10395
11954
  report = parseImpactPreviewResult(`${toolResult.stdout}
10396
11955
  ${toolResult.stderr}`);
10397
11956
  } catch (error) {
10398
- previewError = errorMessage3(error);
11957
+ previewError = errorMessage6(error);
10399
11958
  }
10400
11959
  } else {
10401
11960
  previewError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10407,7 +11966,7 @@ ${toolResult.stderr}`);
10407
11966
  const resultMutation = {
10408
11967
  status: "completed",
10409
11968
  runnerId,
10410
- idempotencyKey: `impact_${workItem.workItemId}_${randomUUID()}`,
11969
+ idempotencyKey: `impact_${workItem.workItemId}_${randomUUID2()}`,
10411
11970
  report: {
10412
11971
  ...report,
10413
11972
  analyzedRepoRevision: report.analyzedRepoRevision ?? metadata?.lastSyncedRevision
@@ -10444,14 +12003,14 @@ ${toolResult.stderr}`);
10444
12003
  const failedMutation = {
10445
12004
  status: "failed",
10446
12005
  runnerId,
10447
- idempotencyKey: `impact_${workItem.workItemId}_${randomUUID()}`,
12006
+ idempotencyKey: `impact_${workItem.workItemId}_${randomUUID2()}`,
10448
12007
  tool: toolName,
10449
12008
  durationMs,
10450
12009
  ...sessionTelemetry,
10451
12010
  message: `${toolName} did not produce a valid impact preview.`,
10452
12011
  ...previewError ? { error: previewError } : {}
10453
12012
  };
10454
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12013
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10455
12014
  accountId: workItem.accountId,
10456
12015
  projectId,
10457
12016
  repositoryLinkId,
@@ -10463,12 +12022,12 @@ ${toolResult.stderr}`);
10463
12022
  idempotencyKey: failedMutation.idempotencyKey,
10464
12023
  result: failedMutation
10465
12024
  });
10466
- if (!failedResult) return { status: "failed", exitCode: 1 };
12025
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10467
12026
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10468
12027
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10469
12028
  status: "failed",
10470
12029
  summary: previewError ?? `${toolName} did not produce a valid impact preview.`,
10471
- idempotencyKey: `runner_milestone_impact_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12030
+ idempotencyKey: `runner_milestone_impact_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10472
12031
  metadata: { tool: toolName, durationMs, verificationSummary: "Impact preview output did not include valid structured JSON." }
10473
12032
  });
10474
12033
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10494,7 +12053,7 @@ async function finalizeIssueDiagnosisWork({
10494
12053
  diagnosis = parseIssueDiagnosisResult(`${toolResult.stdout}
10495
12054
  ${toolResult.stderr}`);
10496
12055
  } catch (error) {
10497
- diagnosisError = errorMessage3(error);
12056
+ diagnosisError = errorMessage6(error);
10498
12057
  }
10499
12058
  } else {
10500
12059
  diagnosisError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10505,7 +12064,7 @@ ${toolResult.stderr}`);
10505
12064
  const resultMutation = {
10506
12065
  status: "completed",
10507
12066
  runnerId,
10508
- idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
12067
+ idempotencyKey: `issue_${workItem.workItemId}_${randomUUID2()}`,
10509
12068
  diagnosis,
10510
12069
  tool: toolName,
10511
12070
  durationMs,
@@ -10539,14 +12098,14 @@ ${toolResult.stderr}`);
10539
12098
  const failedMutation = {
10540
12099
  status: "failed",
10541
12100
  runnerId,
10542
- idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
12101
+ idempotencyKey: `issue_${workItem.workItemId}_${randomUUID2()}`,
10543
12102
  tool: toolName,
10544
12103
  durationMs,
10545
12104
  ...sessionTelemetry,
10546
12105
  message: `${toolName} did not produce a valid issue diagnosis.`,
10547
12106
  ...diagnosisError ? { error: diagnosisError } : {}
10548
12107
  };
10549
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12108
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10550
12109
  accountId: workItem.accountId,
10551
12110
  projectId,
10552
12111
  repositoryLinkId,
@@ -10558,12 +12117,12 @@ ${toolResult.stderr}`);
10558
12117
  idempotencyKey: failedMutation.idempotencyKey,
10559
12118
  result: failedMutation
10560
12119
  });
10561
- if (!failedResult) return { status: "failed", exitCode: 1 };
12120
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10562
12121
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10563
12122
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10564
12123
  status: "failed",
10565
12124
  summary: diagnosisError ?? `${toolName} did not produce a valid issue diagnosis.`,
10566
- idempotencyKey: `runner_milestone_issue_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12125
+ idempotencyKey: `runner_milestone_issue_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10567
12126
  metadata: { tool: toolName, durationMs, verificationSummary: "Issue diagnosis output did not include valid structured JSON." }
10568
12127
  });
10569
12128
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10589,7 +12148,7 @@ async function finalizeSecurityPostureScanWork({
10589
12148
  scanResult = parseSecurityPostureScanResult(`${toolResult.stdout}
10590
12149
  ${toolResult.stderr}`);
10591
12150
  } catch (error) {
10592
- scanError = errorMessage3(error);
12151
+ scanError = errorMessage6(error);
10593
12152
  }
10594
12153
  } else {
10595
12154
  scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10600,7 +12159,7 @@ ${toolResult.stderr}`);
10600
12159
  const resultMutation = {
10601
12160
  status: "completed",
10602
12161
  runnerId,
10603
- idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
12162
+ idempotencyKey: `security_${workItem.workItemId}_${randomUUID2()}`,
10604
12163
  result: scanResult,
10605
12164
  tool: toolName,
10606
12165
  durationMs,
@@ -10634,14 +12193,14 @@ ${toolResult.stderr}`);
10634
12193
  const failedMutation = {
10635
12194
  status: "failed",
10636
12195
  runnerId,
10637
- idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
12196
+ idempotencyKey: `security_${workItem.workItemId}_${randomUUID2()}`,
10638
12197
  tool: toolName,
10639
12198
  durationMs,
10640
12199
  ...sessionTelemetry,
10641
12200
  message: `${toolName} did not produce a valid security posture scan.`,
10642
12201
  ...scanError ? { error: scanError } : {}
10643
12202
  };
10644
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12203
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10645
12204
  accountId: workItem.accountId,
10646
12205
  projectId,
10647
12206
  repositoryLinkId,
@@ -10653,12 +12212,12 @@ ${toolResult.stderr}`);
10653
12212
  idempotencyKey: failedMutation.idempotencyKey,
10654
12213
  result: failedMutation
10655
12214
  });
10656
- if (!failedResult) return { status: "failed", exitCode: 1 };
12215
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10657
12216
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10658
12217
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10659
12218
  status: "failed",
10660
12219
  summary: scanError ?? `${toolName} did not produce a valid security posture scan.`,
10661
- idempotencyKey: `runner_milestone_security_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12220
+ idempotencyKey: `runner_milestone_security_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10662
12221
  metadata: { tool: toolName, durationMs, verificationSummary: "Security posture output did not include valid structured JSON." }
10663
12222
  });
10664
12223
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10684,7 +12243,7 @@ async function finalizeAppEvaluationScanWork({
10684
12243
  scanResult = parseAppEvaluationScanResult(`${toolResult.stdout}
10685
12244
  ${toolResult.stderr}`);
10686
12245
  } catch (error) {
10687
- scanError = errorMessage3(error);
12246
+ scanError = errorMessage6(error);
10688
12247
  }
10689
12248
  } else {
10690
12249
  scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10695,7 +12254,7 @@ ${toolResult.stderr}`);
10695
12254
  const resultMutation = {
10696
12255
  status: "completed",
10697
12256
  runnerId,
10698
- idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
12257
+ idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID2()}`,
10699
12258
  result: scanResult,
10700
12259
  tool: toolName,
10701
12260
  durationMs,
@@ -10729,14 +12288,14 @@ ${toolResult.stderr}`);
10729
12288
  const failedMutation = {
10730
12289
  status: "failed",
10731
12290
  runnerId,
10732
- idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
12291
+ idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID2()}`,
10733
12292
  tool: toolName,
10734
12293
  durationMs,
10735
12294
  ...sessionTelemetry,
10736
12295
  message: `${toolName} did not produce a valid app evaluation scan.`,
10737
12296
  ...scanError ? { error: scanError } : {}
10738
12297
  };
10739
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12298
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10740
12299
  accountId: workItem.accountId,
10741
12300
  projectId,
10742
12301
  repositoryLinkId,
@@ -10748,12 +12307,12 @@ ${toolResult.stderr}`);
10748
12307
  idempotencyKey: failedMutation.idempotencyKey,
10749
12308
  result: failedMutation
10750
12309
  });
10751
- if (!failedResult) return { status: "failed", exitCode: 1 };
12310
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10752
12311
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10753
12312
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10754
12313
  status: "failed",
10755
12314
  summary: scanError ?? `${toolName} did not produce a valid app evaluation scan.`,
10756
- idempotencyKey: `runner_milestone_app_evaluation_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12315
+ idempotencyKey: `runner_milestone_app_evaluation_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10757
12316
  metadata: { tool: toolName, durationMs, verificationSummary: "App evaluation output did not include valid structured JSON." }
10758
12317
  });
10759
12318
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10779,7 +12338,7 @@ async function finalizeBrainConsolidationScanWork({
10779
12338
  scanResult = parseBrainConsolidationScanResult(`${toolResult.stdout}
10780
12339
  ${toolResult.stderr}`);
10781
12340
  } catch (error) {
10782
- scanError = errorMessage3(error);
12341
+ scanError = errorMessage6(error);
10783
12342
  }
10784
12343
  } else {
10785
12344
  scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10790,7 +12349,7 @@ ${toolResult.stderr}`);
10790
12349
  const resultMutation = {
10791
12350
  status: "completed",
10792
12351
  runnerId,
10793
- idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID()}`,
12352
+ idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID2()}`,
10794
12353
  result: scanResult,
10795
12354
  tool: toolName,
10796
12355
  durationMs,
@@ -10824,14 +12383,14 @@ ${toolResult.stderr}`);
10824
12383
  const failedMutation = {
10825
12384
  status: "failed",
10826
12385
  runnerId,
10827
- idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID()}`,
12386
+ idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID2()}`,
10828
12387
  tool: toolName,
10829
12388
  durationMs,
10830
12389
  ...sessionTelemetry,
10831
12390
  message: `${toolName} did not produce a valid semantic brain consolidation scan.`,
10832
12391
  ...scanError ? { error: scanError } : {}
10833
12392
  };
10834
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12393
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10835
12394
  accountId: workItem.accountId,
10836
12395
  projectId,
10837
12396
  repositoryLinkId,
@@ -10843,12 +12402,12 @@ ${toolResult.stderr}`);
10843
12402
  idempotencyKey: failedMutation.idempotencyKey,
10844
12403
  result: failedMutation
10845
12404
  });
10846
- if (!failedResult) return { status: "failed", exitCode: 1 };
12405
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10847
12406
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10848
12407
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10849
12408
  status: "failed",
10850
12409
  summary: scanError ?? `${toolName} did not produce a valid semantic brain consolidation scan.`,
10851
- idempotencyKey: `runner_milestone_brain_consolidation_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12410
+ idempotencyKey: `runner_milestone_brain_consolidation_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10852
12411
  metadata: { tool: toolName, durationMs, verificationSummary: "Brain consolidation output did not include valid structured JSON." }
10853
12412
  });
10854
12413
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10875,7 +12434,7 @@ async function finalizeProjectContextRefreshWork({
10875
12434
  refreshResult = parseProjectContextRefreshResult(`${toolResult.stdout}
10876
12435
  ${toolResult.stderr}`, { repositoryRoot: executionRoot });
10877
12436
  } catch (error) {
10878
- refreshError = errorMessage3(error);
12437
+ refreshError = errorMessage6(error);
10879
12438
  }
10880
12439
  } else {
10881
12440
  refreshError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10886,7 +12445,7 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
10886
12445
  const resultMutation = {
10887
12446
  status: "completed",
10888
12447
  runnerId,
10889
- idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
12448
+ idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID2()}`,
10890
12449
  result: refreshResult,
10891
12450
  tool: toolName,
10892
12451
  durationMs,
@@ -10932,14 +12491,14 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
10932
12491
  const failedMutation = {
10933
12492
  status: "failed",
10934
12493
  runnerId,
10935
- idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
12494
+ idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID2()}`,
10936
12495
  tool: toolName,
10937
12496
  durationMs,
10938
12497
  ...sessionTelemetry,
10939
12498
  message: `${toolName} did not produce a valid project context refresh.`,
10940
12499
  ...refreshError ? { error: refreshError } : {}
10941
12500
  };
10942
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12501
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10943
12502
  accountId: workItem.accountId,
10944
12503
  projectId,
10945
12504
  repositoryLinkId,
@@ -10951,12 +12510,12 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
10951
12510
  idempotencyKey: failedMutation.idempotencyKey,
10952
12511
  result: failedMutation
10953
12512
  });
10954
- if (!failedResult) return { status: "failed", exitCode: 1 };
12513
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10955
12514
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10956
12515
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10957
12516
  status: "failed",
10958
12517
  summary: refreshError ?? `${toolName} did not produce a valid project context refresh.`,
10959
- idempotencyKey: `runner_milestone_project_context_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12518
+ idempotencyKey: `runner_milestone_project_context_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10960
12519
  metadata: { tool: toolName, durationMs, verificationSummary: "Project context output did not include valid structured JSON." }
10961
12520
  });
10962
12521
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10982,7 +12541,7 @@ async function finalizeImplementationVerificationWork({
10982
12541
  verificationResult = parseImplementationVerificationResult(`${toolResult.stdout}
10983
12542
  ${toolResult.stderr}`);
10984
12543
  } catch (error) {
10985
- verificationError = errorMessage3(error);
12544
+ verificationError = errorMessage6(error);
10986
12545
  }
10987
12546
  } else {
10988
12547
  verificationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10993,7 +12552,7 @@ ${toolResult.stderr}`);
10993
12552
  const resultMutation = {
10994
12553
  status: "completed",
10995
12554
  runnerId,
10996
- idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
12555
+ idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID2()}`,
10997
12556
  result: verificationResult,
10998
12557
  tool: toolName,
10999
12558
  durationMs,
@@ -11027,14 +12586,14 @@ ${toolResult.stderr}`);
11027
12586
  const failedMutation = {
11028
12587
  status: "failed",
11029
12588
  runnerId,
11030
- idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
12589
+ idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID2()}`,
11031
12590
  tool: toolName,
11032
12591
  durationMs,
11033
12592
  ...sessionTelemetry,
11034
12593
  message: `${toolName} did not produce valid implementation verification proof.`,
11035
12594
  ...verificationError ? { error: verificationError } : {}
11036
12595
  };
11037
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12596
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
11038
12597
  accountId: workItem.accountId,
11039
12598
  projectId,
11040
12599
  repositoryLinkId,
@@ -11046,12 +12605,12 @@ ${toolResult.stderr}`);
11046
12605
  idempotencyKey: failedMutation.idempotencyKey,
11047
12606
  result: failedMutation
11048
12607
  });
11049
- if (!failedResult) return { status: "failed", exitCode: 1 };
12608
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
11050
12609
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
11051
12610
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
11052
12611
  status: "failed",
11053
12612
  summary: verificationError ?? `${toolName} did not produce valid implementation verification proof.`,
11054
- idempotencyKey: `runner_milestone_implementation_verification_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12613
+ idempotencyKey: `runner_milestone_implementation_verification_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
11055
12614
  metadata: { tool: toolName, durationMs, verificationSummary: "Implementation verification output did not include valid structured JSON." }
11056
12615
  });
11057
12616
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -11077,7 +12636,7 @@ async function finalizeTestQualityScanWork({
11077
12636
  scanResult = parseTestQualityScanResult(`${toolResult.stdout}
11078
12637
  ${toolResult.stderr}`);
11079
12638
  } catch (error) {
11080
- scanError = errorMessage3(error);
12639
+ scanError = errorMessage6(error);
11081
12640
  }
11082
12641
  } else {
11083
12642
  scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -11088,7 +12647,7 @@ ${toolResult.stderr}`);
11088
12647
  const resultMutation = {
11089
12648
  status: "completed",
11090
12649
  runnerId,
11091
- idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID()}`,
12650
+ idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID2()}`,
11092
12651
  result: scanResult,
11093
12652
  tool: toolName,
11094
12653
  durationMs,
@@ -11122,14 +12681,14 @@ ${toolResult.stderr}`);
11122
12681
  const failedMutation = {
11123
12682
  status: "failed",
11124
12683
  runnerId,
11125
- idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID()}`,
12684
+ idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID2()}`,
11126
12685
  tool: toolName,
11127
12686
  durationMs,
11128
12687
  ...sessionTelemetry,
11129
12688
  message: `${toolName} did not produce a valid test quality scan.`,
11130
12689
  ...scanError ? { error: scanError } : {}
11131
12690
  };
11132
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12691
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
11133
12692
  accountId: workItem.accountId,
11134
12693
  projectId,
11135
12694
  repositoryLinkId,
@@ -11141,12 +12700,12 @@ ${toolResult.stderr}`);
11141
12700
  idempotencyKey: failedMutation.idempotencyKey,
11142
12701
  result: failedMutation
11143
12702
  });
11144
- if (!failedResult) return { status: "failed", exitCode: 1 };
12703
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
11145
12704
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
11146
12705
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
11147
12706
  status: "failed",
11148
12707
  summary: scanError ?? `${toolName} did not produce a valid test quality scan.`,
11149
- idempotencyKey: `runner_milestone_test_quality_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12708
+ idempotencyKey: `runner_milestone_test_quality_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
11150
12709
  metadata: { tool: toolName, durationMs, verificationSummary: "Test quality output did not include valid structured JSON." }
11151
12710
  });
11152
12711
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -11172,7 +12731,7 @@ async function finalizeImplementationTestGateWork({
11172
12731
  gateResult = parseImplementationTestGateResult(`${toolResult.stdout}
11173
12732
  ${toolResult.stderr}`);
11174
12733
  } catch (error) {
11175
- gateError = errorMessage3(error);
12734
+ gateError = errorMessage6(error);
11176
12735
  }
11177
12736
  } else {
11178
12737
  gateError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -11183,7 +12742,7 @@ ${toolResult.stderr}`);
11183
12742
  const resultMutation = {
11184
12743
  status: "completed",
11185
12744
  runnerId,
11186
- idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID()}`,
12745
+ idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID2()}`,
11187
12746
  result: gateResult,
11188
12747
  tool: toolName,
11189
12748
  durationMs,
@@ -11217,14 +12776,14 @@ ${toolResult.stderr}`);
11217
12776
  const failedMutation = {
11218
12777
  status: "failed",
11219
12778
  runnerId,
11220
- idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID()}`,
12779
+ idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID2()}`,
11221
12780
  tool: toolName,
11222
12781
  durationMs,
11223
12782
  ...sessionTelemetry,
11224
12783
  message: `${toolName} did not produce a valid implementation test gate result.`,
11225
12784
  ...gateError ? { error: gateError } : {}
11226
12785
  };
11227
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12786
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
11228
12787
  accountId: workItem.accountId,
11229
12788
  projectId,
11230
12789
  repositoryLinkId,
@@ -11236,12 +12795,12 @@ ${toolResult.stderr}`);
11236
12795
  idempotencyKey: failedMutation.idempotencyKey,
11237
12796
  result: failedMutation
11238
12797
  });
11239
- if (!failedResult) return { status: "failed", exitCode: 1 };
12798
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
11240
12799
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
11241
12800
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
11242
12801
  status: "failed",
11243
12802
  summary: gateError ?? `${toolName} did not produce a valid implementation test gate result.`,
11244
- idempotencyKey: `runner_milestone_implementation_test_gate_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12803
+ idempotencyKey: `runner_milestone_implementation_test_gate_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
11245
12804
  metadata: { tool: toolName, durationMs, verificationSummary: "Implementation test gate output did not include valid structured JSON." }
11246
12805
  });
11247
12806
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -11446,7 +13005,7 @@ async function prepareToolSession({
11446
13005
  });
11447
13006
  return { ...selection, toolSession: toolSession2 };
11448
13007
  }
11449
- const toolSessionId = `tool_session_${randomUUID()}`;
13008
+ const toolSessionId = `tool_session_${randomUUID2()}`;
11450
13009
  const { toolSession } = await apiClient.createToolSession(projectId, {
11451
13010
  toolSessionId,
11452
13011
  repositoryLinkId,
@@ -11540,7 +13099,7 @@ function summarizeToolOutput(value) {
11540
13099
  }
11541
13100
  return trimmed.length > 300 ? `${trimmed.slice(0, 300)}...` : trimmed;
11542
13101
  }
11543
- function errorMessage3(error) {
13102
+ function errorMessage6(error) {
11544
13103
  return error instanceof Error ? error.message : String(error);
11545
13104
  }
11546
13105
  function errorDetail(error) {
@@ -11550,6 +13109,9 @@ function truncateLogExcerpt(value) {
11550
13109
  const trimmed = value.trim();
11551
13110
  return trimmed.length > 1200 ? `${trimmed.slice(0, 1200)}...` : trimmed;
11552
13111
  }
13112
+ function formatHostHelperCapability(label, support) {
13113
+ return `${label}: ${support.supported ? "supported" : "unsupported"}${support.reason ? ` (${support.reason})` : ""}`;
13114
+ }
11553
13115
  function collectRepeatedOption(value, previous) {
11554
13116
  return [...previous, value];
11555
13117
  }
@@ -11576,10 +13138,10 @@ function parseReasoningEffort(value) {
11576
13138
  throw new Error(`Expected reasoning effort auto, low, medium, high, or xhigh; received ${value}.`);
11577
13139
  }
11578
13140
  function inferRepoName(root) {
11579
- return path16.basename(path16.resolve(root)) || "repository";
13141
+ return path17.basename(path17.resolve(root)) || "repository";
11580
13142
  }
11581
13143
  function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
11582
- return createHash8("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
13144
+ return createHash9("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
11583
13145
  }
11584
13146
  function defaultApiUrl() {
11585
13147
  const envApiUrl = process.env[AMISTIO_API_URL_ENV]?.trim();
@@ -11614,6 +13176,13 @@ async function resolveRunnerToolConfig({ apiClient, explicitInvocationChannel, e
11614
13176
  }
11615
13177
  if (explicitTool === "none") {
11616
13178
  const modelConfig2 = normalizeModelConfig({ model: explicitModel, providerId: explicitProviderId, modelId: explicitModelId, modelVariant: explicitModelVariant, reasoningEffort: explicitReasoningEffort });
13179
+ const directModelResolution = resolveDirectModelClientPreference({ ...modelConfig2, tool: "none", requestedInvocationChannel: explicitInvocationChannel ?? "auto" });
13180
+ if (directModelResolution) {
13181
+ if (!directModelResolution.ready) {
13182
+ return unavailableToolConfig({ capabilities, source: "cli", status: directModelResolution.status, message: directModelResolution.message, tool: "none", requestedInvocationChannel: explicitInvocationChannel ?? "auto", ...modelConfig2 });
13183
+ }
13184
+ return { ready: true, tool: "none", capabilities, source: "cli", status: "resolved", requestedInvocationChannel: explicitInvocationChannel ?? "auto", effectiveInvocationChannel: "sdk", ...directModelResolution.config, message: "Using Amistio direct model client." };
13185
+ }
11617
13186
  if (hasModelConfig(modelConfig2)) {
11618
13187
  return unavailableToolConfig({ capabilities, source: "cli", status: "modelUnsupported", message: "Model configuration cannot be used with --tool none.", tool: "none", requestedInvocationChannel: explicitInvocationChannel ?? "auto", ...modelConfig2 });
11619
13188
  }
@@ -11638,6 +13207,13 @@ async function resolveRunnerToolConfig({ apiClient, explicitInvocationChannel, e
11638
13207
  }
11639
13208
  function resolveRequestedTool({ capabilities, modelConfig, requestedInvocationChannel, requestedTool, source }) {
11640
13209
  const needsModelSelection = hasModelConfig(modelConfig);
13210
+ const directModelResolution = resolveDirectModelClientPreference({ ...modelConfig, tool: requestedTool, requestedInvocationChannel });
13211
+ if (directModelResolution) {
13212
+ if (!directModelResolution.ready) {
13213
+ return unavailableToolConfig({ capabilities, source, status: directModelResolution.status, requestedTool, requestedInvocationChannel, tool: requestedTool, ...modelConfig, message: directModelResolution.message });
13214
+ }
13215
+ return { ready: true, tool: requestedTool, capabilities, source, status: "resolved", requestedTool, requestedInvocationChannel, effectiveInvocationChannel: "sdk", ...directModelResolution.config, message: "Using Amistio direct model client." };
13216
+ }
11641
13217
  if (requestedTool === "auto") {
11642
13218
  const candidate = capabilities.find((capability2) => capability2.available && capabilitySupportsInvocationChannel(capability2, requestedInvocationChannel) && (!needsModelSelection || capability2.supportsModelSelection));
11643
13219
  if (!candidate) {
@@ -11864,7 +13440,7 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode(), concurr
11864
13440
  };
11865
13441
  }
11866
13442
  function runnerMachineId() {
11867
- return createHash8("sha256").update(`${os8.hostname()}:${os8.platform()}:${os8.arch()}`).digest("hex").slice(0, 20);
13443
+ return createHash9("sha256").update(`${os8.hostname()}:${os8.platform()}:${os8.arch()}`).digest("hex").slice(0, 20);
11868
13444
  }
11869
13445
  async function delay(milliseconds) {
11870
13446
  await new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -11873,4 +13449,3 @@ program.parseAsync().catch((error) => {
11873
13449
  console.error(error instanceof Error ? error.message : String(error));
11874
13450
  process.exitCode = 1;
11875
13451
  });
11876
- //# sourceMappingURL=index.js.map