@amistio/cli 0.1.36 → 0.1.38

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),
@@ -2700,6 +2730,9 @@ var AmistioApiError = class extends Error {
2700
2730
  statusText;
2701
2731
  detail;
2702
2732
  };
2733
+ function isForgottenRunnerApiError(error) {
2734
+ return error instanceof AmistioApiError && error.status === 403 && error.detail.includes("This runner was forgotten");
2735
+ }
2703
2736
  function isRetryableApiError(error) {
2704
2737
  if (error instanceof AmistioApiError) {
2705
2738
  return error.status === 408 || error.status === 429 || error.status >= 500;
@@ -3112,8 +3145,8 @@ var toolSessionMutationSchema = z3.object({
3112
3145
  });
3113
3146
  function resolveApiUrl(apiUrl, urlPath) {
3114
3147
  const base = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
3115
- const path17 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
3116
- return new URL(`${base}${path17}`);
3148
+ const path18 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
3149
+ return new URL(`${base}${path18}`);
3117
3150
  }
3118
3151
 
3119
3152
  // src/orchestrator.ts
@@ -3522,13 +3555,887 @@ function truncateLocalError(error) {
3522
3555
  }
3523
3556
 
3524
3557
  // 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";
3558
+ import { mkdtemp, readFile as readFile5, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
3527
3559
  import os3 from "node:os";
3560
+ import path7 from "node:path";
3561
+
3562
+ // src/bounded-tool-adapter.ts
3563
+ import { constants } from "node:fs";
3564
+ import { access, mkdir as mkdir6, readdir as readdir3, readFile as readFile4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
3528
3565
  import path6 from "node:path";
3566
+
3567
+ // src/host-execution.ts
3568
+ import { spawn } from "node:child_process";
3569
+ import { randomUUID } from "node:crypto";
3570
+ var hostExecutionProtocolVersion = "amistio.hostExecution.v1";
3571
+ var nativeHostHelperEnvironmentAllowlist = [
3572
+ "PATH",
3573
+ "Path",
3574
+ "HOME",
3575
+ "USERPROFILE",
3576
+ "TMPDIR",
3577
+ "TEMP",
3578
+ "TMP",
3579
+ "LANG",
3580
+ "LC_ALL",
3581
+ "CI",
3582
+ "NODE_ENV",
3583
+ "SystemRoot",
3584
+ "WINDIR",
3585
+ "ComSpec",
3586
+ "PATHEXT"
3587
+ ];
3588
+ var defaultNativeHostOutputBudgetBytes = 64 * 1024;
3589
+ var nodeHostExecutionCapabilities = {
3590
+ protocolVersion: hostExecutionProtocolVersion,
3591
+ implementation: "node",
3592
+ processGroups: process.platform === "win32" ? { supported: false, reason: "Windows process-group cleanup needs a native helper or platform-specific implementation." } : { supported: true },
3593
+ signalEscalation: { supported: true },
3594
+ streamCapture: { supported: true },
3595
+ pty: { supported: false, reason: "PTY bridging is reserved for a future native helper." },
3596
+ sandbox: { supported: false, reason: "Sandboxed command execution is reserved for a future native helper." },
3597
+ outputEncoding: "utf8"
3598
+ };
3599
+ var defaultHostExecution = {
3600
+ capabilities: nodeHostExecutionCapabilities,
3601
+ executeCommand: executeNodeCommand,
3602
+ commandExists
3603
+ };
3604
+ var runtimeHostExecutionCacheKey;
3605
+ var runtimeHostExecutionPortPromise;
3606
+ function getRuntimeHostExecutionPort(env = process.env) {
3607
+ const helperConfig = nativeHostHelperConfigFromEnvironment(env);
3608
+ const cacheKey = helperConfig?.command ?? "";
3609
+ if (!runtimeHostExecutionPortPromise || runtimeHostExecutionCacheKey !== cacheKey) {
3610
+ runtimeHostExecutionCacheKey = cacheKey;
3611
+ runtimeHostExecutionPortPromise = createHostExecutionPort(helperConfig ? { nativeHelper: helperConfig } : {});
3612
+ }
3613
+ return runtimeHostExecutionPortPromise;
3614
+ }
3615
+ async function createHostExecutionPort(options = {}) {
3616
+ const fallback = options.fallback ?? defaultHostExecution;
3617
+ const helperConfig = options.nativeHelper ?? nativeHostHelperConfigFromEnvironment();
3618
+ if (!helperConfig) {
3619
+ return fallback;
3620
+ }
3621
+ const discovery = await discoverNativeHostHelper(helperConfig);
3622
+ return createCompositeHostExecutionPort(fallback, discovery);
3623
+ }
3624
+ function nativeHostHelperConfigFromEnvironment(env = process.env) {
3625
+ const command = env.AMISTIO_HOST_HELPER_PATH?.trim();
3626
+ if (!command) {
3627
+ return void 0;
3628
+ }
3629
+ return { command };
3630
+ }
3631
+ async function discoverNativeHostHelper(config) {
3632
+ const normalizedConfig = normalizeNativeHostHelperConfig(config);
3633
+ const result = await executeHelperProcess(normalizedConfig, "--amistio-host-helper-handshake", void 0, normalizedConfig.handshakeTimeoutMs);
3634
+ if (result.status === "spawnFailed") {
3635
+ return { ok: false, failure: { code: "helper_unavailable", message: result.error?.message ?? "Native host helper is unavailable.", stdout: result.stdout, stderr: result.stderr } };
3636
+ }
3637
+ if (result.status === "timedOut") {
3638
+ return { ok: false, failure: { code: "helper_unavailable", message: result.error?.message ?? "Native host helper handshake timed out.", stdout: result.stdout, stderr: result.stderr } };
3639
+ }
3640
+ if (result.exitCode !== 0) {
3641
+ return { ok: false, failure: { code: "helper_failed", message: `Native host helper handshake exited with code ${result.exitCode}.`, stdout: result.stdout, stderr: result.stderr } };
3642
+ }
3643
+ const parsed = parseJson(result.stdout);
3644
+ if (!isNativeHostHelperHandshake(parsed)) {
3645
+ 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 } };
3646
+ }
3647
+ return { ok: true, helper: { config: normalizedConfig, handshake: parsed } };
3648
+ }
3649
+ function createNativeHostCommandRequestEnvelope(request, requestId = randomUUID()) {
3650
+ const env = filterNativeHostHelperEnvironment(request.env ?? process.env);
3651
+ const sandbox = request.sandbox ?? "none";
3652
+ return {
3653
+ protocolVersion: hostExecutionProtocolVersion,
3654
+ requestId,
3655
+ command: request.command,
3656
+ args: [...request.args],
3657
+ cwd: request.cwd,
3658
+ shell: request.shell ?? false,
3659
+ timeoutMs: request.timeoutMs ?? 0,
3660
+ stdin: request.stdin ? "provided" : "none",
3661
+ env,
3662
+ envAllowlist: Object.keys(env).sort(),
3663
+ streamOutput: request.streamOutput ?? false,
3664
+ requirePty: request.requirePty ?? false,
3665
+ sandbox,
3666
+ sandboxPolicy: nativeHostSandboxPolicy(request.cwd, sandbox),
3667
+ outputBudgetBytes: positiveInteger(request.outputBudgetBytes) ?? defaultNativeHostOutputBudgetBytes
3668
+ };
3669
+ }
3670
+ function createCompositeHostExecutionPort(fallback, discovery) {
3671
+ return {
3672
+ capabilities: discovery.ok ? discovery.helper.handshake.capabilities : fallback.capabilities,
3673
+ async executeCommand(request) {
3674
+ if (!discovery.ok) {
3675
+ if (request.requireNativeHelper) {
3676
+ return helperDiscoveryFailureResult(discovery.failure);
3677
+ }
3678
+ return fallback.executeCommand(request);
3679
+ }
3680
+ const helper = discovery.helper;
3681
+ const capabilityFailure = helperCapabilityFailure(request, helper.handshake.capabilities);
3682
+ if (capabilityFailure) {
3683
+ return capabilityFailure;
3684
+ }
3685
+ if (!canDelegateToNativeHelper(request)) {
3686
+ if (request.requireNativeHelper) {
3687
+ return unsupportedResult("helper_failed", "Native host helper delegation is unavailable for requests with stdin payloads.");
3688
+ }
3689
+ return fallback.executeCommand(request);
3690
+ }
3691
+ return executeNativeHelperCommand(helper, request);
3692
+ },
3693
+ commandExists: fallback.commandExists
3694
+ };
3695
+ }
3696
+ function helperCapabilityFailure(request, capabilities) {
3697
+ if (request.requirePty && !capabilities.pty.supported) {
3698
+ return unsupportedResult("pty_unsupported", capabilities.pty.reason ?? "PTY command execution is not supported by the native host helper.");
3699
+ }
3700
+ if (request.sandbox && request.sandbox !== "none" && !capabilities.sandbox.supported) {
3701
+ return unsupportedResult("sandbox_unsupported", capabilities.sandbox.reason ?? `${request.sandbox} sandbox execution is not supported by the native host helper.`);
3702
+ }
3703
+ return void 0;
3704
+ }
3705
+ function canDelegateToNativeHelper(request) {
3706
+ return !request.stdin && !request.shell;
3707
+ }
3708
+ async function executeNativeHelperCommand(helper, request) {
3709
+ const requestId = randomUUID();
3710
+ const envelope = createNativeHostCommandRequestEnvelope(request, requestId);
3711
+ const result = await executeHelperProcess(helper.config, "--amistio-host-helper-execute", JSON.stringify(envelope), request.timeoutMs ?? helper.config.executeTimeoutMs);
3712
+ if (result.status === "spawnFailed") {
3713
+ return { status: "helperUnavailable", exitCode: 1, stdout: result.stdout, stderr: result.stderr, error: { code: "helper_unavailable", message: result.error?.message ?? "Native host helper is unavailable." } };
3714
+ }
3715
+ if (result.status === "timedOut") {
3716
+ return result;
3717
+ }
3718
+ if (result.exitCode !== 0) {
3719
+ 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}.` } };
3720
+ }
3721
+ const parsed = parseJson(result.stdout);
3722
+ if (!isNativeHostCommandResultEnvelope(parsed) || parsed.requestId !== requestId) {
3723
+ 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." } };
3724
+ }
3725
+ return normalizeHostCommandResult(request, {
3726
+ status: parsed.status,
3727
+ exitCode: parsed.exitCode,
3728
+ stdout: parsed.stdout,
3729
+ stderr: parsed.stderr,
3730
+ ...parsed.error ? { error: parsed.error } : {}
3731
+ });
3732
+ }
3733
+ async function executeNodeCommand(request) {
3734
+ if (request.requirePty) {
3735
+ return unsupportedResult("pty_unsupported", "PTY command execution is not supported by the default Node host execution port.");
3736
+ }
3737
+ if (request.sandbox && request.sandbox !== "none") {
3738
+ return unsupportedResult("sandbox_unsupported", `${request.sandbox} sandbox execution is not supported by the default Node host execution port.`);
3739
+ }
3740
+ return new Promise((resolve) => {
3741
+ let child;
3742
+ try {
3743
+ child = spawn(request.command, [...request.args], {
3744
+ cwd: request.cwd,
3745
+ env: request.env ?? process.env,
3746
+ detached: process.platform !== "win32",
3747
+ shell: request.shell ?? false,
3748
+ stdio: ["pipe", "pipe", "pipe"]
3749
+ });
3750
+ } catch (error) {
3751
+ resolve(spawnFailedResult(error));
3752
+ return;
3753
+ }
3754
+ let stdout = "";
3755
+ let stderr = "";
3756
+ let settled = false;
3757
+ let forceKillTimer;
3758
+ let timedOutError;
3759
+ const timeout = request.timeoutMs && request.timeoutMs > 0 ? setTimeout(() => {
3760
+ if (settled) return;
3761
+ const message = request.timeoutMessage ?? `Command timed out after ${request.timeoutMs}ms: ${request.command}`;
3762
+ stderr += `${message}
3763
+ `;
3764
+ timedOutError = { code: "timeout", message };
3765
+ killProcessTree(child, "SIGTERM");
3766
+ forceKillTimer = setTimeout(() => killProcessTree(child, "SIGKILL"), request.killGraceMs ?? 5e3);
3767
+ forceKillTimer.unref?.();
3768
+ }, request.timeoutMs) : void 0;
3769
+ timeout?.unref?.();
3770
+ const resolveOnce = (result) => {
3771
+ if (settled) return;
3772
+ settled = true;
3773
+ if (timeout) clearTimeout(timeout);
3774
+ if (forceKillTimer) clearTimeout(forceKillTimer);
3775
+ resolve(result);
3776
+ };
3777
+ child.on("error", (error) => {
3778
+ if (timedOutError) return;
3779
+ resolveOnce(spawnFailedResult(error, stdout, stderr));
3780
+ });
3781
+ child.stdout.setEncoding("utf8");
3782
+ child.stderr.setEncoding("utf8");
3783
+ child.stdout.on("data", (chunk) => {
3784
+ stdout += chunk;
3785
+ if (request.streamOutput) process.stdout.write(chunk);
3786
+ });
3787
+ child.stderr.on("data", (chunk) => {
3788
+ stderr += chunk;
3789
+ if (request.streamOutput) process.stderr.write(chunk);
3790
+ });
3791
+ child.stdin.on("error", () => void 0);
3792
+ if (request.stdin) {
3793
+ child.stdin.write(request.stdin);
3794
+ }
3795
+ child.stdin.end();
3796
+ child.on("close", (exitCode) => {
3797
+ if (forceKillTimer) clearTimeout(forceKillTimer);
3798
+ if (timedOutError) {
3799
+ resolveOnce({ status: "timedOut", exitCode: 1, stdout, stderr, error: timedOutError });
3800
+ return;
3801
+ }
3802
+ resolveOnce({ status: "completed", exitCode: exitCode ?? 1, stdout, stderr });
3803
+ });
3804
+ });
3805
+ }
3806
+ function unsupportedResult(code, message) {
3807
+ return { status: "unsupported", exitCode: 1, stdout: "", stderr: "", error: { code, message } };
3808
+ }
3809
+ function helperDiscoveryFailureResult(failure) {
3810
+ const status = failure.code === "protocol_mismatch" ? "protocolMismatch" : failure.code === "helper_unavailable" ? "helperUnavailable" : "failed";
3811
+ return { status, exitCode: 1, stdout: failure.stdout ?? "", stderr: failure.stderr ?? "", error: { code: failure.code, message: failure.message } };
3812
+ }
3813
+ function spawnFailedResult(error, stdout = "", stderr = "") {
3814
+ return { status: "spawnFailed", exitCode: 1, stdout, stderr, error: { code: "spawn_failed", message: errorMessage(error) } };
3815
+ }
3816
+ function killProcessTree(child, signal) {
3817
+ if (child.pid && process.platform !== "win32") {
3818
+ try {
3819
+ process.kill(-child.pid, signal);
3820
+ return;
3821
+ } catch {
3822
+ }
3823
+ }
3824
+ child.kill(signal);
3825
+ }
3826
+ async function commandExists(command) {
3827
+ const lookupCommand = process.platform === "win32" ? "where" : "which";
3828
+ return new Promise((resolve) => {
3829
+ const lookup = spawn(lookupCommand, [command], { stdio: "ignore" });
3830
+ lookup.on("error", () => resolve(false));
3831
+ lookup.on("close", (exitCode) => resolve(exitCode === 0));
3832
+ });
3833
+ }
3834
+ function normalizeNativeHostHelperConfig(config) {
3835
+ return {
3836
+ command: config.command,
3837
+ args: config.args ? [...config.args] : [],
3838
+ cwd: config.cwd ?? process.cwd(),
3839
+ env: filterNativeHostHelperEnvironment(config.env ?? process.env),
3840
+ handshakeTimeoutMs: positiveInteger(config.handshakeTimeoutMs) ?? 2e3,
3841
+ executeTimeoutMs: positiveInteger(config.executeTimeoutMs) ?? 3e4
3842
+ };
3843
+ }
3844
+ function executeHelperProcess(config, mode, stdin, timeoutMs) {
3845
+ return new Promise((resolve) => {
3846
+ let child;
3847
+ try {
3848
+ child = spawn(config.command, [...config.args, mode], {
3849
+ cwd: config.cwd,
3850
+ env: config.env,
3851
+ shell: false,
3852
+ stdio: ["pipe", "pipe", "pipe"]
3853
+ });
3854
+ } catch (error) {
3855
+ resolve(spawnFailedResult(error));
3856
+ return;
3857
+ }
3858
+ let stdout = "";
3859
+ let stderr = "";
3860
+ let settled = false;
3861
+ const timeout = timeoutMs > 0 ? setTimeout(() => {
3862
+ if (settled) return;
3863
+ const message = `Native host helper timed out after ${timeoutMs}ms.`;
3864
+ stderr += `${message}
3865
+ `;
3866
+ child.kill("SIGKILL");
3867
+ settled = true;
3868
+ resolve({ status: "timedOut", exitCode: 1, stdout, stderr, error: { code: "timeout", message } });
3869
+ }, timeoutMs) : void 0;
3870
+ timeout?.unref?.();
3871
+ const resolveOnce = (result) => {
3872
+ if (settled) return;
3873
+ settled = true;
3874
+ if (timeout) clearTimeout(timeout);
3875
+ resolve(result);
3876
+ };
3877
+ child.on("error", (error) => resolveOnce(spawnFailedResult(error, stdout, stderr)));
3878
+ child.stdout.setEncoding("utf8");
3879
+ child.stderr.setEncoding("utf8");
3880
+ child.stdout.on("data", (chunk) => {
3881
+ stdout += chunk;
3882
+ });
3883
+ child.stderr.on("data", (chunk) => {
3884
+ stderr += chunk;
3885
+ });
3886
+ child.stdin.on("error", () => void 0);
3887
+ if (stdin) {
3888
+ child.stdin.write(stdin);
3889
+ }
3890
+ child.stdin.end();
3891
+ child.on("close", (exitCode) => {
3892
+ resolveOnce({ status: "completed", exitCode: exitCode ?? 1, stdout, stderr });
3893
+ });
3894
+ });
3895
+ }
3896
+ function filterNativeHostHelperEnvironment(env) {
3897
+ const allowedNames = new Set(nativeHostHelperEnvironmentAllowlist);
3898
+ const filtered = {};
3899
+ for (const [name, value] of Object.entries(env)) {
3900
+ if (!value || !allowedNames.has(name) || isSensitiveEnvironmentName(name)) {
3901
+ continue;
3902
+ }
3903
+ filtered[name] = value;
3904
+ }
3905
+ return filtered;
3906
+ }
3907
+ function nativeHostSandboxPolicy(cwd, mode) {
3908
+ return {
3909
+ mode,
3910
+ cwd,
3911
+ filesystem: mode === "filesystemReadOnly" ? "readOnly" : "default",
3912
+ network: mode === "networkDisabled" ? "disabled" : "default",
3913
+ envPolicy: "allowlist-only"
3914
+ };
3915
+ }
3916
+ function normalizeHostCommandResult(request, result) {
3917
+ const outputBudgetBytes = positiveInteger(request.outputBudgetBytes) ?? defaultNativeHostOutputBudgetBytes;
3918
+ const normalize = request.requirePty ? normalizePtyTranscript : (value) => value;
3919
+ return {
3920
+ ...result,
3921
+ stdout: capUtf8Text(normalize(result.stdout), outputBudgetBytes),
3922
+ stderr: capUtf8Text(normalize(result.stderr), outputBudgetBytes)
3923
+ };
3924
+ }
3925
+ function normalizePtyTranscript(value) {
3926
+ 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, "");
3927
+ }
3928
+ function capUtf8Text(value, budgetBytes) {
3929
+ if (Buffer.byteLength(value, "utf8") <= budgetBytes) {
3930
+ return value;
3931
+ }
3932
+ let capped = Buffer.from(value, "utf8").subarray(0, budgetBytes).toString("utf8");
3933
+ while (Buffer.byteLength(capped, "utf8") > budgetBytes && capped.length > 0) {
3934
+ capped = capped.slice(0, -1);
3935
+ }
3936
+ return capped;
3937
+ }
3938
+ function isSensitiveEnvironmentName(name) {
3939
+ return /(?:TOKEN|SECRET|PASSWORD|PASS|KEY|CREDENTIAL|AUTH|OAUTH|COOKIE|SESSION|PRIVATE|CERT|SSH)/i.test(name);
3940
+ }
3941
+ function isNativeHostHelperHandshake(value) {
3942
+ if (!isRecord(value) || value.protocolVersion !== hostExecutionProtocolVersion) return false;
3943
+ if (!isHostExecutionCapabilities(value.capabilities)) return false;
3944
+ if (!isRecord(value.secretBoundary)) return false;
3945
+ 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);
3946
+ }
3947
+ function isHostExecutionCapabilities(value) {
3948
+ if (!isRecord(value)) return false;
3949
+ 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";
3950
+ }
3951
+ function isCapabilitySupport(value) {
3952
+ return isRecord(value) && typeof value.supported === "boolean" && (value.reason === void 0 || typeof value.reason === "string");
3953
+ }
3954
+ function isNativeHostCommandResultEnvelope(value) {
3955
+ if (!isRecord(value) || value.protocolVersion !== hostExecutionProtocolVersion) return false;
3956
+ 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));
3957
+ }
3958
+ function isHostCommandStatus(value) {
3959
+ return value === "completed" || value === "timedOut" || value === "spawnFailed" || value === "permissionDenied" || value === "helperUnavailable" || value === "protocolMismatch" || value === "killed" || value === "unsupported" || value === "failed";
3960
+ }
3961
+ function isHostCommandError(value) {
3962
+ return isRecord(value) && isHostCommandErrorCode(value.code) && typeof value.message === "string";
3963
+ }
3964
+ function isHostCommandErrorCode(value) {
3965
+ 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";
3966
+ }
3967
+ function isRecord(value) {
3968
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3969
+ }
3970
+ function parseJson(value) {
3971
+ try {
3972
+ return JSON.parse(value);
3973
+ } catch {
3974
+ return void 0;
3975
+ }
3976
+ }
3977
+ function positiveInteger(value) {
3978
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
3979
+ }
3980
+ function errorMessage(error) {
3981
+ return error instanceof Error ? error.message : String(error);
3982
+ }
3983
+
3984
+ // src/bounded-tool-adapter.ts
3985
+ var boundedToolAdapterCatalog = [
3986
+ { id: "filesystem.readFile", kind: "filesystem", displayName: "Read scoped file", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["executionRoot"] },
3987
+ { id: "filesystem.listFiles", kind: "filesystem", displayName: "List scoped files", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["executionRoot"] },
3988
+ { id: "filesystem.searchText", kind: "filesystem", displayName: "Search scoped text", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["executionRoot"] },
3989
+ { id: "filesystem.writeFile", kind: "filesystem", displayName: "Write scoped file", implemented: true, mutating: true, concurrency: "serialized", serializedResourceKinds: ["executionRoot"] },
3990
+ { id: "shell.curatedCommand", kind: "shell", displayName: "Run runner-owned command preset", implemented: true, mutating: false, concurrency: "serialized", serializedResourceKinds: ["executionRoot", "process"] },
3991
+ { id: "git.status", kind: "git", displayName: "Read Git status", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["gitIndex"] },
3992
+ { id: "git.diffNameOnly", kind: "git", displayName: "Read changed Git paths", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["gitIndex"] },
3993
+ { id: "packageManager.detect", kind: "packageManager", displayName: "Detect package manager", implemented: true, mutating: false, concurrency: "parallelSafe", serializedResourceKinds: ["packageManagerCache"] },
3994
+ { id: "testRunner.profile", kind: "testRunner", displayName: "Run known verification profile", implemented: true, mutating: false, concurrency: "serialized", serializedResourceKinds: ["testRunner", "packageManagerCache"] },
3995
+ { id: "browser.check", kind: "browser", displayName: "Run local browser smoke check", implemented: true, mutating: false, concurrency: "serialized", serializedResourceKinds: ["browserProfile"] },
3996
+ { id: "modelClient.direct", kind: "modelClient", displayName: "Invoke direct model client", implemented: true, mutating: false, concurrency: "serialized", serializedResourceKinds: ["modelProvider"] },
3997
+ { id: "agentClient.compatibilityBridge", kind: "agentClient", displayName: "Invoke optional local AI client", implemented: true, mutating: true, concurrency: "serialized", serializedResourceKinds: ["agentSession", "providerAuth", "executionRoot"] }
3998
+ ];
3999
+ var testRunnerProfileIds = ["verify", "test", "typecheck", "lint", "build"];
4000
+ var packageLockfiles = {
4001
+ pnpm: ["pnpm-lock.yaml"],
4002
+ npm: ["package-lock.json"],
4003
+ yarn: ["yarn.lock"],
4004
+ bun: ["bun.lockb", "bun.lock"]
4005
+ };
4006
+ var ignoredTraversalDirectories = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".turbo", "coverage", ".cache"]);
4007
+ var defaultFilesystemListLimit = 200;
4008
+ var defaultFilesystemSearchLimit = 80;
4009
+ var filesystemSearchMaxFileBytes = 256e3;
4010
+ var defaultCuratedShellCommands = {
4011
+ "git.status.short": {
4012
+ command: "git",
4013
+ args: ["status", "--short"],
4014
+ displayName: "git status --short",
4015
+ mutationPolicy: "readOnly"
4016
+ },
4017
+ "git.diff.nameOnly": {
4018
+ command: "git",
4019
+ args: ["diff", "--name-only"],
4020
+ displayName: "git diff --name-only",
4021
+ mutationPolicy: "readOnly"
4022
+ },
4023
+ "package.detect": {
4024
+ command: process.execPath,
4025
+ 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'));"],
4026
+ displayName: "detect package manager lockfiles",
4027
+ mutationPolicy: "readOnly"
4028
+ }
4029
+ };
4030
+ function createBoundedToolAdapterPolicy(options) {
4031
+ const executionRoot = path6.resolve(options.executionRoot);
4032
+ return {
4033
+ executionRoot,
4034
+ mutationPolicy: options.mutationPolicy,
4035
+ timeoutMs: positiveInteger2(options.timeoutMs) ?? 3e4,
4036
+ outputBudgetBytes: positiveInteger2(options.outputBudgetBytes) ?? 64e3,
4037
+ allowedWorkKinds: options.allowedWorkKinds ?? ["assistantQuestion", "projectContextRefresh", "implementationVerification", "testQualityScan", "implementationTestGate", "implementation"],
4038
+ redaction: { localPaths: true, secretAssignments: true },
4039
+ concurrency: {
4040
+ mode: options.concurrencyMode ?? "serialized",
4041
+ resourceKey: options.resourceKey ?? executionRoot
4042
+ }
4043
+ };
4044
+ }
4045
+ async function runFilesystemReadTool(options) {
4046
+ const resolved = resolveRelativePath(options.policy, options.relativePath);
4047
+ if (!resolved.ok) {
4048
+ return failedResult("filesystem.readFile", resolved.error, options.policy);
4049
+ }
4050
+ try {
4051
+ const content = await readFile4(resolved.path, "utf8");
4052
+ return completedResult("filesystem.readFile", content, "", options.policy);
4053
+ } catch (error) {
4054
+ return failedResult("filesystem.readFile", { code: "execution_failed", message: `File read failed: ${errorMessage2(error)}` }, options.policy);
4055
+ }
4056
+ }
4057
+ async function runFilesystemWriteTool(options) {
4058
+ const mutation = ensureMutationAllowed(options.policy, "mutating");
4059
+ if (mutation) {
4060
+ return failedResult("filesystem.writeFile", mutation, options.policy);
4061
+ }
4062
+ const resolved = resolveRelativePath(options.policy, options.relativePath);
4063
+ if (!resolved.ok) {
4064
+ return failedResult("filesystem.writeFile", resolved.error, options.policy);
4065
+ }
4066
+ try {
4067
+ await mkdir6(path6.dirname(resolved.path), { recursive: true });
4068
+ await writeFile5(resolved.path, options.content, "utf8");
4069
+ const summary = { relativePath: normalizeRelativePath(options.relativePath), bytes: Buffer.byteLength(options.content, "utf8") };
4070
+ return completedResult("filesystem.writeFile", JSON.stringify(summary, null, 2), "", options.policy);
4071
+ } catch (error) {
4072
+ return failedResult("filesystem.writeFile", { code: "execution_failed", message: `File write failed: ${errorMessage2(error)}` }, options.policy);
4073
+ }
4074
+ }
4075
+ async function runFilesystemListTool(options) {
4076
+ const relativeDirectory = normalizeRelativePath(options.relativeDirectory ?? ".");
4077
+ const resolved = resolveRelativePath(options.policy, relativeDirectory);
4078
+ if (!resolved.ok) {
4079
+ return failedResult("filesystem.listFiles", resolved.error, options.policy);
4080
+ }
4081
+ try {
4082
+ const maxFiles = positiveInteger2(options.maxFiles) ?? defaultFilesystemListLimit;
4083
+ const files = await listFiles(options.policy.executionRoot, resolved.path, maxFiles);
4084
+ const summary = { relativeDirectory, files: files.items, truncated: files.truncated };
4085
+ return completedResult("filesystem.listFiles", JSON.stringify(summary, null, 2), "", options.policy);
4086
+ } catch (error) {
4087
+ return failedResult("filesystem.listFiles", { code: "execution_failed", message: `File list failed: ${errorMessage2(error)}` }, options.policy);
4088
+ }
4089
+ }
4090
+ async function runFilesystemSearchTool(options) {
4091
+ const query = options.query.trim();
4092
+ if (!query) {
4093
+ return failedResult("filesystem.searchText", { code: "command_not_allowed", message: "Search query must not be empty." }, options.policy);
4094
+ }
4095
+ const relativeDirectory = normalizeRelativePath(options.relativeDirectory ?? ".");
4096
+ const resolved = resolveRelativePath(options.policy, relativeDirectory);
4097
+ if (!resolved.ok) {
4098
+ return failedResult("filesystem.searchText", resolved.error, options.policy);
4099
+ }
4100
+ try {
4101
+ const maxMatches = positiveInteger2(options.maxMatches) ?? defaultFilesystemSearchLimit;
4102
+ const matches = await searchText(options.policy, resolved.path, query, maxMatches);
4103
+ const summary = { query, matches: matches.items, truncated: matches.truncated };
4104
+ return completedResult("filesystem.searchText", JSON.stringify(summary, null, 2), "", options.policy);
4105
+ } catch (error) {
4106
+ return failedResult("filesystem.searchText", { code: "execution_failed", message: `Text search failed: ${errorMessage2(error)}` }, options.policy);
4107
+ }
4108
+ }
4109
+ async function runGitStatusTool(options) {
4110
+ return executeCuratedCommand(defaultCuratedShellCommands["git.status.short"], options.policy, "git.status");
4111
+ }
4112
+ async function runGitDiffNameOnlyTool(options) {
4113
+ return executeCuratedCommand(defaultCuratedShellCommands["git.diff.nameOnly"], options.policy, "git.diffNameOnly");
4114
+ }
4115
+ async function runPackageManagerDetectTool(options) {
4116
+ try {
4117
+ const detection = await detectPackageManager(options.policy.executionRoot);
4118
+ return completedResult("packageManager.detect", JSON.stringify(detection, null, 2), "", options.policy);
4119
+ } catch (error) {
4120
+ return failedResult("packageManager.detect", { code: "execution_failed", message: `Package manager detection failed: ${errorMessage2(error)}` }, options.policy);
4121
+ }
4122
+ }
4123
+ async function runTestRunnerProfileTool(options) {
4124
+ const profileId = options.profileId ?? "test";
4125
+ if (!testRunnerProfileIds.includes(profileId)) {
4126
+ return failedResult("testRunner.profile", { code: "command_not_allowed", message: "Verification profile is not allowed by this runner policy." }, options.policy);
4127
+ }
4128
+ let detection;
4129
+ try {
4130
+ detection = await detectPackageManager(options.policy.executionRoot);
4131
+ } catch (error) {
4132
+ return failedResult("testRunner.profile", { code: "execution_failed", message: `Verification profile discovery failed: ${errorMessage2(error)}` }, options.policy);
4133
+ }
4134
+ if (!detection.profiles.includes(profileId)) {
4135
+ return failedResult("testRunner.profile", { code: "command_not_allowed", message: `Verification profile is not defined in local package scripts: ${profileId}.` }, options.policy);
4136
+ }
4137
+ const command = options.commandCatalog?.[profileId] ?? testRunnerProfileCommand(options.packageManager ?? detection.packageManager ?? "npm", profileId);
4138
+ return executeCuratedCommand(command, options.policy, "testRunner.profile");
4139
+ }
4140
+ async function runBrowserCheckTool(options) {
4141
+ const target = parseBrowserCheckTarget(options.url, options.allowedHosts);
4142
+ if (!target.ok) {
4143
+ return failedResult("browser.check", target.error, options.policy);
4144
+ }
4145
+ const abortController = new AbortController();
4146
+ const timeout = setTimeout(() => abortController.abort(), options.policy.timeoutMs);
4147
+ timeout.unref?.();
4148
+ try {
4149
+ const response = await (options.fetchImpl ?? fetch)(target.url, { method: "GET", redirect: "manual", signal: abortController.signal });
4150
+ const body = await response.text();
4151
+ const summary = {
4152
+ url: target.url.toString(),
4153
+ status: response.status,
4154
+ ok: response.ok,
4155
+ ...response.headers.get("content-type") ? { contentType: response.headers.get("content-type") } : {},
4156
+ ...extractHtmlTitle(body) ? { title: extractHtmlTitle(body) } : {},
4157
+ bytes: Buffer.byteLength(body, "utf8")
4158
+ };
4159
+ const stdout = JSON.stringify(summary, null, 2);
4160
+ if (response.ok) {
4161
+ return completedResult("browser.check", stdout, "", options.policy);
4162
+ }
4163
+ return failedResult("browser.check", { code: "execution_failed", message: `Browser check failed with HTTP ${response.status}.` }, options.policy, stdout);
4164
+ } catch (error) {
4165
+ if (abortController.signal.aborted) {
4166
+ return failedResult("browser.check", { code: "timeout", message: `Browser check timed out after ${formatTimeoutDuration(options.policy.timeoutMs)}.` }, options.policy);
4167
+ }
4168
+ return failedResult("browser.check", { code: "execution_failed", message: `Browser check failed: ${errorMessage2(error)}` }, options.policy);
4169
+ } finally {
4170
+ clearTimeout(timeout);
4171
+ }
4172
+ }
4173
+ async function executeCuratedCommand(command, policy, adapterId) {
4174
+ const hostExecution = await getRuntimeHostExecutionPort();
4175
+ const result = await hostExecution.executeCommand({
4176
+ command: command.command,
4177
+ args: command.args,
4178
+ cwd: policy.executionRoot,
4179
+ env: process.env,
4180
+ shell: false,
4181
+ timeoutMs: policy.timeoutMs,
4182
+ timeoutMessage: `Tool adapter timed out after ${formatTimeoutDuration(policy.timeoutMs)}: ${command.displayName}`
4183
+ });
4184
+ if (result.status === "timedOut") {
4185
+ return failedResult(adapterId, { code: "timeout", message: result.error?.message ?? `Tool adapter timed out after ${formatTimeoutDuration(policy.timeoutMs)}: ${command.displayName}` }, policy, result.stdout, result.stderr);
4186
+ }
4187
+ if (result.status === "spawnFailed" || result.status === "unsupported") {
4188
+ return failedResult(adapterId, { code: "execution_failed", message: result.error?.message ?? `Command execution failed: ${command.displayName}` }, policy, result.stdout, result.stderr);
4189
+ }
4190
+ if (result.exitCode === 0) {
4191
+ return completedResult(adapterId, result.stdout, result.stderr, policy);
4192
+ }
4193
+ return failedResult(adapterId, { code: "execution_failed", message: `${command.displayName} exited with code ${result.exitCode}.` }, policy, result.stdout, result.stderr);
4194
+ }
4195
+ async function detectPackageManager(executionRoot) {
4196
+ const packageJson = await readPackageJson(executionRoot);
4197
+ const lockfiles = await detectPackageLockfiles(executionRoot);
4198
+ const scripts = Object.keys(packageJson?.scripts ?? {}).sort();
4199
+ const packageManager = packageManagerFromPackageManagerField(packageJson?.packageManager) ?? packageManagerFromLockfiles(lockfiles);
4200
+ const profiles = testRunnerProfileIds.filter((profileId) => scripts.includes(profileId));
4201
+ return {
4202
+ ...packageManager ? { packageManager } : {},
4203
+ lockfiles,
4204
+ scripts,
4205
+ profiles
4206
+ };
4207
+ }
4208
+ async function listFiles(executionRoot, directoryPath, maxFiles) {
4209
+ const items = [];
4210
+ let truncated = false;
4211
+ const visit = async (currentDirectory) => {
4212
+ if (truncated) return;
4213
+ const entries = await readdir3(currentDirectory, { withFileTypes: true });
4214
+ for (const entry of entries.sort((first, second) => first.name.localeCompare(second.name))) {
4215
+ if (truncated) return;
4216
+ if (entry.name.startsWith(".") && ignoredTraversalDirectories.has(entry.name)) continue;
4217
+ if (ignoredTraversalDirectories.has(entry.name)) continue;
4218
+ const absolutePath = path6.join(currentDirectory, entry.name);
4219
+ if (entry.isDirectory()) {
4220
+ await visit(absolutePath);
4221
+ continue;
4222
+ }
4223
+ if (!entry.isFile()) continue;
4224
+ items.push(normalizeRelativePath(path6.relative(executionRoot, absolutePath)));
4225
+ if (items.length >= maxFiles) {
4226
+ truncated = true;
4227
+ return;
4228
+ }
4229
+ }
4230
+ };
4231
+ await visit(directoryPath);
4232
+ return { items, truncated };
4233
+ }
4234
+ async function searchText(policy, directoryPath, query, maxMatches) {
4235
+ const listed = await listFiles(policy.executionRoot, directoryPath, Math.max(maxMatches * 20, maxMatches));
4236
+ const lowerQuery = query.toLowerCase();
4237
+ const matches = [];
4238
+ let truncated = listed.truncated;
4239
+ for (const relativePath of listed.items) {
4240
+ if (matches.length >= maxMatches) {
4241
+ truncated = true;
4242
+ break;
4243
+ }
4244
+ const absolutePath = path6.join(policy.executionRoot, relativePath);
4245
+ const stats = await stat3(absolutePath);
4246
+ if (!stats.isFile() || stats.size > filesystemSearchMaxFileBytes) continue;
4247
+ const content = await readFile4(absolutePath, "utf8").catch(() => void 0);
4248
+ if (content === void 0 || content.includes("\0")) continue;
4249
+ const lines = content.split(/\r?\n/);
4250
+ for (const [lineIndex, line] of lines.entries()) {
4251
+ if (!line.toLowerCase().includes(lowerQuery)) continue;
4252
+ matches.push({ relativePath, line: lineIndex + 1, text: line.slice(0, 500) });
4253
+ if (matches.length >= maxMatches) {
4254
+ truncated = true;
4255
+ break;
4256
+ }
4257
+ }
4258
+ }
4259
+ return { items: matches, truncated };
4260
+ }
4261
+ async function readPackageJson(executionRoot) {
4262
+ const packageJsonPath = path6.join(executionRoot, "package.json");
4263
+ if (!await fileExists(packageJsonPath)) {
4264
+ return void 0;
4265
+ }
4266
+ const parsed = JSON.parse(await readFile4(packageJsonPath, "utf8"));
4267
+ if (!isRecord2(parsed)) {
4268
+ return void 0;
4269
+ }
4270
+ const scripts = isRecord2(parsed.scripts) ? Object.fromEntries(Object.entries(parsed.scripts).filter((entry) => typeof entry[1] === "string")) : void 0;
4271
+ return {
4272
+ ...typeof parsed.packageManager === "string" ? { packageManager: parsed.packageManager } : {},
4273
+ ...scripts ? { scripts } : {}
4274
+ };
4275
+ }
4276
+ async function detectPackageLockfiles(executionRoot) {
4277
+ const lockfileCandidates = Object.values(packageLockfiles).flat();
4278
+ const presentLockfiles = [];
4279
+ for (const lockfile of lockfileCandidates) {
4280
+ if (await fileExists(path6.join(executionRoot, lockfile))) {
4281
+ presentLockfiles.push(lockfile);
4282
+ }
4283
+ }
4284
+ return presentLockfiles;
4285
+ }
4286
+ function packageManagerFromPackageManagerField(value) {
4287
+ const packageManagerName = value?.split("@")[0]?.trim();
4288
+ return isPackageManagerName(packageManagerName) ? packageManagerName : void 0;
4289
+ }
4290
+ function packageManagerFromLockfiles(lockfiles) {
4291
+ for (const packageManager of ["pnpm", "npm", "yarn", "bun"]) {
4292
+ if (packageLockfiles[packageManager].some((lockfile) => lockfiles.includes(lockfile))) {
4293
+ return packageManager;
4294
+ }
4295
+ }
4296
+ return void 0;
4297
+ }
4298
+ function testRunnerProfileCommand(packageManager, profileId) {
4299
+ if (packageManager === "pnpm") {
4300
+ return { command: "corepack", args: ["pnpm", "--config.verify-deps-before-run=false", "run", profileId], displayName: `pnpm run ${profileId}`, mutationPolicy: "readOnly" };
4301
+ }
4302
+ if (packageManager === "yarn") {
4303
+ return { command: "corepack", args: ["yarn", "run", profileId], displayName: `yarn run ${profileId}`, mutationPolicy: "readOnly" };
4304
+ }
4305
+ if (packageManager === "bun") {
4306
+ return { command: "bun", args: ["run", profileId], displayName: `bun run ${profileId}`, mutationPolicy: "readOnly" };
4307
+ }
4308
+ return { command: "npm", args: ["run", profileId], displayName: `npm run ${profileId}`, mutationPolicy: "readOnly" };
4309
+ }
4310
+ function parseBrowserCheckTarget(value, allowedHosts) {
4311
+ let url;
4312
+ try {
4313
+ url = new URL(value);
4314
+ } catch {
4315
+ return { ok: false, error: { code: "command_not_allowed", message: "Browser check target URL is invalid." } };
4316
+ }
4317
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
4318
+ return { ok: false, error: { code: "command_not_allowed", message: "Browser check target must use http or https." } };
4319
+ }
4320
+ if (url.username || url.password) {
4321
+ return { ok: false, error: { code: "command_not_allowed", message: "Browser check target must not include credentials." } };
4322
+ }
4323
+ const allowedHostSet = new Set((allowedHosts ?? []).map((host2) => host2.toLowerCase()));
4324
+ const hostname = url.hostname.toLowerCase();
4325
+ const host = url.host.toLowerCase();
4326
+ if (!isLoopbackHost(hostname) && !allowedHostSet.has(hostname) && !allowedHostSet.has(host)) {
4327
+ return { ok: false, error: { code: "command_not_allowed", message: "Browser check target must be loopback or explicitly allowed by the runner policy." } };
4328
+ }
4329
+ return { ok: true, url };
4330
+ }
4331
+ function isLoopbackHost(hostname) {
4332
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname.startsWith("127.") || hostname === "::1" || hostname === "[::1]" || hostname === "0.0.0.0";
4333
+ }
4334
+ function extractHtmlTitle(body) {
4335
+ const match = /<title[^>]*>([^<]*)<\/title>/i.exec(body);
4336
+ const title = match?.[1]?.replace(/\s+/g, " ").trim();
4337
+ return title || void 0;
4338
+ }
4339
+ async function fileExists(filePath) {
4340
+ try {
4341
+ await access(filePath, constants.F_OK);
4342
+ return true;
4343
+ } catch {
4344
+ return false;
4345
+ }
4346
+ }
4347
+ function isPackageManagerName(value) {
4348
+ return value === "pnpm" || value === "npm" || value === "yarn" || value === "bun";
4349
+ }
4350
+ function isRecord2(value) {
4351
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4352
+ }
4353
+ function resolveRelativePath(policy, relativePath) {
4354
+ if (path6.isAbsolute(relativePath)) {
4355
+ return { ok: false, error: { code: "out_of_root", message: "Tool adapter rejected absolute local path input." } };
4356
+ }
4357
+ const resolved = path6.resolve(policy.executionRoot, relativePath);
4358
+ if (!isInsideRoot(policy.executionRoot, resolved)) {
4359
+ return { ok: false, error: { code: "out_of_root", message: "Tool adapter path is outside the execution root." } };
4360
+ }
4361
+ return { ok: true, path: resolved };
4362
+ }
4363
+ function normalizeRelativePath(relativePath) {
4364
+ const normalized = relativePath.replaceAll("\\", "/").replace(/^\.\//, "");
4365
+ return normalized || ".";
4366
+ }
4367
+ function ensureMutationAllowed(policy, requiredPolicy) {
4368
+ if (requiredPolicy === "mutating" && policy.mutationPolicy !== "mutating") {
4369
+ return { code: "mutation_denied", message: "Tool adapter mutation is not allowed by the current harness policy." };
4370
+ }
4371
+ return void 0;
4372
+ }
4373
+ function completedResult(adapterId, stdout, stderr, policy) {
4374
+ const output = normalizeOutput(stdout, stderr, policy);
4375
+ return { adapterId, status: "completed", exitCode: 0, stdout: output.stdout, stderr: output.stderr, truncated: output.truncated };
4376
+ }
4377
+ function failedResult(adapterId, error, policy, stdout = "", stderr = "") {
4378
+ const output = normalizeOutput(stdout, stderr, policy);
4379
+ return { adapterId, status: "failed", exitCode: 1, stdout: output.stdout, stderr: output.stderr, truncated: output.truncated, error };
4380
+ }
4381
+ function normalizeOutput(stdout, stderr, policy) {
4382
+ const redactedStdout = redactToolAdapterText(stdout, policy);
4383
+ const redactedStderr = redactToolAdapterText(stderr, policy);
4384
+ const stdoutBudget = Math.floor(policy.outputBudgetBytes / 2);
4385
+ const stderrBudget = policy.outputBudgetBytes - stdoutBudget;
4386
+ const normalizedStdout = truncateToBudget(redactedStdout, stdoutBudget);
4387
+ const normalizedStderr = truncateToBudget(redactedStderr, stderrBudget);
4388
+ return {
4389
+ stdout: normalizedStdout.value,
4390
+ stderr: normalizedStderr.value,
4391
+ truncated: normalizedStdout.truncated || normalizedStderr.truncated
4392
+ };
4393
+ }
4394
+ function redactToolAdapterText(value, policy) {
4395
+ let result = value;
4396
+ if (policy.redaction.localPaths) {
4397
+ result = result.replaceAll(policy.executionRoot, "<execution-root>");
4398
+ }
4399
+ if (policy.redaction.secretAssignments) {
4400
+ result = result.replace(/\b([A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|API_KEY|ACCESS_KEY)[A-Z0-9_]*)=([^\s"'`]+)/gi, "$1=<redacted>");
4401
+ }
4402
+ return result;
4403
+ }
4404
+ function truncateToBudget(value, budgetBytes) {
4405
+ if (budgetBytes <= 0) {
4406
+ return { value: "", truncated: Boolean(value) };
4407
+ }
4408
+ const bytes = Buffer.byteLength(value, "utf8");
4409
+ if (bytes <= budgetBytes) {
4410
+ return { value, truncated: false };
4411
+ }
4412
+ const suffix = "\n[truncated by Amistio tool adapter output budget]";
4413
+ const contentBudget = Math.max(0, budgetBytes - Buffer.byteLength(suffix, "utf8"));
4414
+ return { value: `${Buffer.from(value, "utf8").subarray(0, contentBudget).toString("utf8")}${suffix}`, truncated: true };
4415
+ }
4416
+ function isInsideRoot(root, candidatePath) {
4417
+ const relative = path6.relative(root, candidatePath);
4418
+ return relative === "" || !relative.startsWith("..") && !path6.isAbsolute(relative);
4419
+ }
4420
+ function positiveInteger2(value) {
4421
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
4422
+ }
4423
+ function errorMessage2(error) {
4424
+ return error instanceof Error ? error.message : String(error);
4425
+ }
4426
+ function formatTimeoutDuration(timeoutMs) {
4427
+ if (timeoutMs < 1e3) {
4428
+ return `${timeoutMs}ms`;
4429
+ }
4430
+ const seconds = timeoutMs / 1e3;
4431
+ return Number.isInteger(seconds) ? `${seconds}s` : `${seconds.toFixed(1)}s`;
4432
+ }
4433
+
4434
+ // src/local-tool-runner.ts
3529
4435
  var localToolNames = runnerToolNames;
3530
4436
  var allReasoningEfforts = ["auto", "low", "medium", "high", "xhigh"];
3531
4437
  var highReasoningEfforts = ["auto", "high", "xhigh"];
4438
+ var localToolOutputBudgetBytes = 32 * 1024;
3532
4439
  var builtInProviderCatalogs = {
3533
4440
  opencode: {
3534
4441
  anthropic: {
@@ -3714,7 +4621,7 @@ async function detectLocalTools() {
3714
4621
  return Promise.all(
3715
4622
  localToolAdapters.map(async (adapter) => {
3716
4623
  const sdkAvailable = await isSdkAvailable(adapter);
3717
- const commandAvailable = adapter.executable ? await commandExists(adapter.executable) : false;
4624
+ const commandAvailable = adapter.executable ? await commandExists2(adapter.executable) : false;
3718
4625
  const providerCatalog = await detectRunnerProviderCatalog(adapter);
3719
4626
  return {
3720
4627
  name: adapter.name,
@@ -3732,9 +4639,9 @@ async function detectLocalTools() {
3732
4639
  );
3733
4640
  }
3734
4641
  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");
4642
+ const promptTempDir = await mkdtemp(path7.join(os3.tmpdir(), "amistio-prompt-"));
4643
+ const promptFilePath = path7.join(promptTempDir, "prompt.md");
4644
+ await writeFile6(promptFilePath, options.prompt, "utf8");
3738
4645
  const modelConfig = normalizeModelOptions(options);
3739
4646
  try {
3740
4647
  const runnerOptions = {
@@ -3749,14 +4656,14 @@ async function runLocalTool(options) {
3749
4656
  runnerOptions.toolCommand = options.toolCommand;
3750
4657
  }
3751
4658
  const runner2 = await createToolRunner(runnerOptions);
3752
- const result = await executeToolRunner(runner2, {
4659
+ const result = normalizeToolExecutionResult(await executeToolRunner(runner2, {
3753
4660
  rootDir: options.rootDir,
3754
4661
  prompt: options.prompt,
3755
4662
  promptFilePath,
3756
4663
  streamOutput: Boolean(options.streamOutput),
3757
4664
  ...modelConfig,
3758
4665
  ...options.session ? { session: options.session } : {}
3759
- }, options.timeoutMs);
4666
+ }, options.timeoutMs), options.rootDir);
3760
4667
  return {
3761
4668
  toolName: runner2.toolName,
3762
4669
  displayCommand: runner2.kind === "sdk" ? runner2.displayCommand : runner2.invocation.displayCommand,
@@ -3770,7 +4677,7 @@ async function runLocalTool(options) {
3770
4677
  }
3771
4678
  }
3772
4679
  async function createToolRunPreview(options) {
3773
- const promptFilePath = path6.join(os3.tmpdir(), "amistio-generated-prompt.md");
4680
+ const promptFilePath = path7.join(os3.tmpdir(), "amistio-generated-prompt.md");
3774
4681
  const modelConfig = normalizeModelOptions(options);
3775
4682
  const runnerOptions = {
3776
4683
  rootDir: options.rootDir,
@@ -3831,7 +4738,7 @@ async function createToolRunner(options) {
3831
4738
  if (requiresModelSelection && !options.model) {
3832
4739
  throw new Error("Provider-backed model configuration requires --model or --provider with --model-id.");
3833
4740
  }
3834
- const adapter = tool === "auto" ? await selectFirstAvailableAdapter(requiresModelSelection, options.invocationChannel) : await selectRequestedAdapter(tool, options.invocationChannel);
4741
+ const adapter = tool === "auto" ? await selectFirstAvailableAdapter(options, options.invocationChannel) : await selectRequestedAdapter(tool, options.invocationChannel, options);
3835
4742
  if (requiresModelSelection && !adapter.supportsModelSelection) {
3836
4743
  throw new Error(`Model selection is not supported by ${adapter.name}. Remove model configuration or choose a model-aware adapter.`);
3837
4744
  }
@@ -3844,7 +4751,7 @@ async function createToolRunner(options) {
3844
4751
  allowCommandFallback: options.invocationChannel === "auto"
3845
4752
  };
3846
4753
  }
3847
- if (options.invocationChannel !== "sdk" && adapter.buildInvocation && adapter.executable && await commandExists(adapter.executable)) {
4754
+ if (options.invocationChannel !== "sdk" && adapter.buildInvocation && adapter.executable && await commandExists2(adapter.executable)) {
3848
4755
  return {
3849
4756
  toolName: adapter.name,
3850
4757
  kind: "command",
@@ -3866,10 +4773,10 @@ async function executeToolRunner(runner2, input, timeoutMs) {
3866
4773
  try {
3867
4774
  return await withTimeout(runner2.adapter.runWithSdk(input), timeoutMs, runner2.displayCommand);
3868
4775
  } catch (error) {
3869
- if (runner2.allowCommandFallback && runner2.adapter.buildInvocation && runner2.adapter.executable && await commandExists(runner2.adapter.executable)) {
4776
+ if (runner2.allowCommandFallback && runner2.adapter.buildInvocation && runner2.adapter.executable && await commandExists2(runner2.adapter.executable)) {
3870
4777
  const fallback = runner2.adapter.buildInvocation(input);
3871
4778
  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)}`;
4779
+ const sdkFailure = `SDK execution for ${runner2.adapter.name} failed, fell back to ${fallback.displayCommand}: ${errorMessage3(error)}`;
3873
4780
  return {
3874
4781
  ...result,
3875
4782
  stderr: result.stderr ? `${sdkFailure}
@@ -3885,31 +4792,48 @@ function normalizeToolRequest(value) {
3885
4792
  }
3886
4793
  throw new Error(`Unsupported local tool: ${value}. Supported tools: auto, none, ${localToolNames.join(", ")}.`);
3887
4794
  }
3888
- async function selectFirstAvailableAdapter(requiresModelSelection = false, invocationChannel = "auto") {
4795
+ async function selectFirstAvailableAdapter(modelOptions, invocationChannel = "auto") {
4796
+ const requiresModelSelection = hasModelSelection(modelOptions);
4797
+ let modelSelectionFailure;
3889
4798
  for (const adapter of localToolAdapters) {
3890
4799
  if (requiresModelSelection && !adapter.supportsModelSelection) {
3891
4800
  continue;
3892
4801
  }
4802
+ const validationError = requiresModelSelection ? modelSelectionValidationError(adapter, modelOptions) : void 0;
4803
+ if (validationError) {
4804
+ modelSelectionFailure ??= validationError;
4805
+ continue;
4806
+ }
3893
4807
  const sdkAvailable = await isSdkAvailable(adapter);
3894
- const commandAvailable = adapter.executable ? await commandExists(adapter.executable) : false;
4808
+ const commandAvailable = adapter.executable ? await commandExists2(adapter.executable) : false;
3895
4809
  if (supportsRequestedInvocationChannel({ sdkAvailable, commandAvailable }, invocationChannel)) {
3896
4810
  return adapter;
3897
4811
  }
3898
4812
  }
3899
4813
  if (invocationChannel !== "auto") {
4814
+ if (modelSelectionFailure) {
4815
+ throw new Error(`No installed local AI tool supports the selected provider/model for ${invocationChannel} invocation. ${modelSelectionFailure}`);
4816
+ }
3900
4817
  throw new Error(`No installed local AI tool supports ${invocationChannel} invocation${requiresModelSelection ? " with model selection" : ""}. Select Auto or install a compatible tool.`);
3901
4818
  }
4819
+ if (modelSelectionFailure) {
4820
+ throw new Error(`No installed local AI tool supports the selected provider/model. ${modelSelectionFailure}`);
4821
+ }
3902
4822
  throw new Error(
3903
4823
  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
4824
  );
3905
4825
  }
3906
- async function selectRequestedAdapter(tool, invocationChannel = "auto") {
4826
+ async function selectRequestedAdapter(tool, invocationChannel = "auto", modelOptions) {
3907
4827
  const adapter = localToolAdapters.find((candidate) => candidate.name === tool);
3908
4828
  if (!adapter) {
3909
4829
  throw new Error(`Unsupported local tool: ${tool}.`);
3910
4830
  }
4831
+ if (modelOptions) {
4832
+ const validationError = modelSelectionValidationError(adapter, modelOptions);
4833
+ if (validationError) throw new Error(validationError);
4834
+ }
3911
4835
  const sdkAvailable = await isSdkAvailable(adapter);
3912
- const commandAvailable = adapter.executable ? await commandExists(adapter.executable) : false;
4836
+ const commandAvailable = adapter.executable ? await commandExists2(adapter.executable) : false;
3913
4837
  if (!supportsRequestedInvocationChannel({ sdkAvailable, commandAvailable }, invocationChannel)) {
3914
4838
  if (invocationChannel === "sdk") {
3915
4839
  throw new Error(`The ${tool} SDK was not found. Select Auto or Command invocation, install it, or pass --tool-command locally.`);
@@ -3932,11 +4856,71 @@ function supportsRequestedInvocationChannel(capability, invocationChannel) {
3932
4856
  if (invocationChannel === "sdk") return capability.sdkAvailable;
3933
4857
  return capability.commandAvailable;
3934
4858
  }
4859
+ function hasModelSelection(options) {
4860
+ return Boolean(options.model || options.providerId || options.modelId || options.modelVariant || options.reasoningEffort && options.reasoningEffort !== "auto");
4861
+ }
4862
+ function modelSelectionValidationError(adapter, options) {
4863
+ if (!hasModelSelection(options)) return void 0;
4864
+ if (!adapter.supportsModelSelection) {
4865
+ return `Model selection is not supported by ${adapter.name}. Remove model configuration or choose a model-aware adapter.`;
4866
+ }
4867
+ if (!adapter.providerCatalog) return void 0;
4868
+ const selection = resolveModelSelectionParts(options);
4869
+ if (!selection.providerId && !selection.modelId) return void 0;
4870
+ if (selection.providerId) {
4871
+ const provider = adapter.providerCatalog[selection.providerId];
4872
+ if (!provider) return `Provider ${selection.providerId} is not available on ${adapter.name}.`;
4873
+ if (!selection.modelId) return void 0;
4874
+ const model = provider.models[selection.modelId];
4875
+ if (!model) return `Model ${selection.modelId} is not available for provider ${selection.providerId} on ${adapter.name}.`;
4876
+ return modelCapabilityValidationError(adapter, selection.providerId, model, options);
4877
+ }
4878
+ if (selection.modelId) {
4879
+ const match = findCatalogModel(adapter.providerCatalog, selection.modelId);
4880
+ if (!match) return `Model ${selection.modelId} is not available on ${adapter.name}.`;
4881
+ return modelCapabilityValidationError(adapter, match.providerId, match.model, options);
4882
+ }
4883
+ return void 0;
4884
+ }
4885
+ function resolveModelSelectionParts(options) {
4886
+ const providerId = options.providerId?.trim();
4887
+ const modelId = options.modelId?.trim();
4888
+ if (providerId || modelId) {
4889
+ return {
4890
+ ...providerId ? { providerId } : {},
4891
+ ...modelId ? { modelId } : {}
4892
+ };
4893
+ }
4894
+ const model = options.model?.trim();
4895
+ if (!model) return {};
4896
+ const separatorIndex = model.indexOf("/");
4897
+ if (separatorIndex > 0 && separatorIndex < model.length - 1) {
4898
+ return { providerId: model.slice(0, separatorIndex), modelId: model.slice(separatorIndex + 1) };
4899
+ }
4900
+ return { modelId: model };
4901
+ }
4902
+ function findCatalogModel(catalog, modelId) {
4903
+ for (const [providerId, provider] of Object.entries(catalog)) {
4904
+ const model = provider.models[modelId];
4905
+ if (model) return { providerId, model };
4906
+ }
4907
+ return void 0;
4908
+ }
4909
+ function modelCapabilityValidationError(adapter, providerId, model, options) {
4910
+ const modelId = options.modelId ?? model.id;
4911
+ if (options.modelVariant && model.variants && !model.variants[options.modelVariant]) {
4912
+ return `Model variant ${options.modelVariant} is not available for ${providerId}/${modelId} on ${adapter.name}.`;
4913
+ }
4914
+ if (options.reasoningEffort && options.reasoningEffort !== "auto" && model.supportedReasoningEfforts?.length && !model.supportedReasoningEfforts.includes(options.reasoningEffort)) {
4915
+ return `${options.reasoningEffort} reasoning effort is not available for ${providerId}/${modelId} on ${adapter.name}.`;
4916
+ }
4917
+ return void 0;
4918
+ }
3935
4919
  async function isSdkAvailable(adapter) {
3936
4920
  if (!adapter.sdkPackageName || !adapter.runWithSdk) {
3937
4921
  return false;
3938
4922
  }
3939
- if (adapter.sdkRequiresExecutable && adapter.executable && !await commandExists(adapter.executable)) {
4923
+ if (adapter.sdkRequiresExecutable && adapter.executable && !await commandExists2(adapter.executable)) {
3940
4924
  return false;
3941
4925
  }
3942
4926
  return packageAvailable(adapter.sdkPackageName);
@@ -3949,13 +4933,9 @@ async function packageAvailable(packageName) {
3949
4933
  return false;
3950
4934
  }
3951
4935
  }
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
- });
4936
+ async function commandExists2(command) {
4937
+ const hostExecution = await getRuntimeHostExecutionPort();
4938
+ return hostExecution.commandExists(command);
3959
4939
  }
3960
4940
  async function detectRunnerProviderCatalog(adapter) {
3961
4941
  const localOpencodeConfigCatalog = adapter.name === "opencode" ? await loadLocalOpencodeProviderConfigCatalog() : void 0;
@@ -3963,16 +4943,16 @@ async function detectRunnerProviderCatalog(adapter) {
3963
4943
  }
3964
4944
  function localOpencodeProviderConfigPaths() {
3965
4945
  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")
4946
+ path7.join(os3.homedir(), ".config", "opencode", "opencode.json"),
4947
+ path7.join(os3.homedir(), ".config", "opencode", "config.json"),
4948
+ path7.join(process.cwd(), "opencode.json")
3969
4949
  ];
3970
4950
  }
3971
4951
  async function loadLocalOpencodeProviderConfigCatalog(configPaths = localOpencodeProviderConfigPaths()) {
3972
4952
  for (const configPath of configPaths) {
3973
4953
  try {
3974
- const parsed = JSON.parse(await readFile4(configPath, "utf8"));
3975
- const providerValue = isRecord(parsed) ? parsed.provider : void 0;
4954
+ const parsed = JSON.parse(await readFile5(configPath, "utf8"));
4955
+ const providerValue = isRecord3(parsed) ? parsed.provider : void 0;
3976
4956
  const catalog = sanitizeProviderCatalog(providerValue);
3977
4957
  if (catalog) return catalog;
3978
4958
  } catch {
@@ -3985,8 +4965,8 @@ function mergeProviderCatalogs(...catalogs) {
3985
4965
  for (const catalog of catalogs) {
3986
4966
  if (!catalog) continue;
3987
4967
  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 : {};
4968
+ const existing = isRecord3(merged[providerId]) ? merged[providerId] : void 0;
4969
+ const existingModels = existing && isRecord3(existing.models) ? existing.models : {};
3990
4970
  merged[providerId] = {
3991
4971
  ...existing,
3992
4972
  ...provider,
@@ -3999,7 +4979,7 @@ function mergeProviderCatalogs(...catalogs) {
3999
4979
  return parsed.success && Object.keys(parsed.data).length ? parsed.data : void 0;
4000
4980
  }
4001
4981
  function sanitizeProviderCatalog(value) {
4002
- if (!isRecord(value)) return void 0;
4982
+ if (!isRecord3(value)) return void 0;
4003
4983
  const catalog = {};
4004
4984
  for (const [providerId, providerValue] of Object.entries(value)) {
4005
4985
  const provider = sanitizeProviderConfig(providerId, providerValue);
@@ -4009,7 +4989,7 @@ function sanitizeProviderCatalog(value) {
4009
4989
  return parsed.success && Object.keys(parsed.data).length ? parsed.data : void 0;
4010
4990
  }
4011
4991
  function sanitizeProviderConfig(providerId, value) {
4012
- if (!isRecord(value)) return void 0;
4992
+ if (!isRecord3(value)) return void 0;
4013
4993
  const models = sanitizeProviderModels(value.models);
4014
4994
  if (!Object.keys(models).length) return void 0;
4015
4995
  return {
@@ -4020,7 +5000,7 @@ function sanitizeProviderConfig(providerId, value) {
4020
5000
  };
4021
5001
  }
4022
5002
  function sanitizeProviderModels(value) {
4023
- if (!isRecord(value)) return {};
5003
+ if (!isRecord3(value)) return {};
4024
5004
  const models = {};
4025
5005
  for (const [modelId, modelValue] of Object.entries(value)) {
4026
5006
  const model = sanitizeProviderModel(modelId, modelValue);
@@ -4029,7 +5009,7 @@ function sanitizeProviderModels(value) {
4029
5009
  return models;
4030
5010
  }
4031
5011
  function sanitizeProviderModel(modelId, value) {
4032
- if (!isRecord(value)) return void 0;
5012
+ if (!isRecord3(value)) return void 0;
4033
5013
  const reasoning = booleanValue(value.reasoning);
4034
5014
  const releaseDate = stringValue(value.release_date) ?? stringValue(value.releaseDate);
4035
5015
  const toolCall = booleanValue(value.tool_call) ?? booleanValue(value.toolCall);
@@ -4055,7 +5035,7 @@ function sanitizeProviderModel(modelId, value) {
4055
5035
  return model;
4056
5036
  }
4057
5037
  function sanitizeLimit(value) {
4058
- if (!isRecord(value)) return void 0;
5038
+ if (!isRecord3(value)) return void 0;
4059
5039
  const limit = {
4060
5040
  ...numberValue(value.context) !== void 0 ? { context: numberValue(value.context) } : {},
4061
5041
  ...numberValue(value.input) !== void 0 ? { input: numberValue(value.input) } : {},
@@ -4064,7 +5044,7 @@ function sanitizeLimit(value) {
4064
5044
  return Object.keys(limit).length ? limit : void 0;
4065
5045
  }
4066
5046
  function sanitizeModalities(value) {
4067
- if (!isRecord(value)) return void 0;
5047
+ if (!isRecord3(value)) return void 0;
4068
5048
  const modalities = {
4069
5049
  ...stringArrayValue(value.input) ? { input: stringArrayValue(value.input) } : {},
4070
5050
  ...stringArrayValue(value.output) ? { output: stringArrayValue(value.output) } : {}
@@ -4072,95 +5052,67 @@ function sanitizeModalities(value) {
4072
5052
  return Object.keys(modalities).length ? modalities : void 0;
4073
5053
  }
4074
5054
  function sanitizeVariants(value) {
4075
- if (!isRecord(value)) return void 0;
5055
+ if (!isRecord3(value)) return void 0;
4076
5056
  const variants = {};
4077
5057
  for (const [variantId, variantValue] of Object.entries(value)) {
4078
- variants[variantId] = isRecord(variantValue) && booleanValue(variantValue.disabled) !== void 0 ? { disabled: booleanValue(variantValue.disabled) } : {};
4079
- }
4080
- return Object.keys(variants).length ? variants : void 0;
4081
- }
4082
- function sanitizeReasoningEfforts(value) {
4083
- const values = stringArrayValue(value);
4084
- if (!values) return void 0;
4085
- const efforts = values.filter((candidate) => allReasoningEfforts.includes(candidate));
4086
- return efforts.length ? efforts : void 0;
4087
- }
4088
- function isRecord(value) {
4089
- return typeof value === "object" && value !== null && !Array.isArray(value);
4090
- }
4091
- function stringValue(value) {
4092
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
4093
- }
4094
- function numberValue(value) {
4095
- return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
4096
- }
4097
- function booleanValue(value) {
4098
- return typeof value === "boolean" ? value : void 0;
4099
- }
4100
- function stringArrayValue(value) {
4101
- return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
4102
- }
4103
- 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
- });
5058
+ variants[variantId] = isRecord3(variantValue) && booleanValue(variantValue.disabled) !== void 0 ? { disabled: booleanValue(variantValue.disabled) } : {};
5059
+ }
5060
+ return Object.keys(variants).length ? variants : void 0;
5061
+ }
5062
+ function sanitizeReasoningEfforts(value) {
5063
+ const values = stringArrayValue(value);
5064
+ if (!values) return void 0;
5065
+ const efforts = values.filter((candidate) => allReasoningEfforts.includes(candidate));
5066
+ return efforts.length ? efforts : void 0;
5067
+ }
5068
+ function isRecord3(value) {
5069
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5070
+ }
5071
+ function stringValue(value) {
5072
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
5073
+ }
5074
+ function numberValue(value) {
5075
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
5076
+ }
5077
+ function booleanValue(value) {
5078
+ return typeof value === "boolean" ? value : void 0;
5079
+ }
5080
+ function stringArrayValue(value) {
5081
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
5082
+ }
5083
+ async function executeToolInvocation(invocation, rootDir, streamOutput, timeoutMs) {
5084
+ const hostExecution = await getRuntimeHostExecutionPort();
5085
+ const result = await hostExecution.executeCommand({
5086
+ command: invocation.command,
5087
+ args: invocation.args,
5088
+ cwd: rootDir,
5089
+ env: process.env,
5090
+ shell: invocation.shell ?? false,
5091
+ ...invocation.stdin ? { stdin: invocation.stdin } : {},
5092
+ ...timeoutMs ? { timeoutMs, timeoutMessage: toolTimeoutMessage(invocation.displayCommand, timeoutMs) } : {},
5093
+ streamOutput
4163
5094
  });
5095
+ if (result.status === "timedOut" || result.status === "spawnFailed" || result.status === "unsupported") {
5096
+ throw new Error(result.error?.message ?? `Command execution failed: ${invocation.displayCommand}`);
5097
+ }
5098
+ return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
5099
+ }
5100
+ function normalizeToolExecutionResult(result, rootDir) {
5101
+ return {
5102
+ ...result,
5103
+ stdout: normalizeLocalToolOutput(result.stdout, rootDir),
5104
+ stderr: normalizeLocalToolOutput(result.stderr, rootDir)
5105
+ };
5106
+ }
5107
+ function normalizeLocalToolOutput(value, rootDir) {
5108
+ const redacted = redactToolAdapterText(value, { executionRoot: rootDir, redaction: { localPaths: true, secretAssignments: true } });
5109
+ return truncateLocalToolOutput(redacted, localToolOutputBudgetBytes);
5110
+ }
5111
+ function truncateLocalToolOutput(value, budgetBytes) {
5112
+ if (Buffer.byteLength(value, "utf8") <= budgetBytes) return value;
5113
+ const suffix = "\n[truncated by Amistio local tool output budget]";
5114
+ const contentBudget = Math.max(0, budgetBytes - Buffer.byteLength(suffix, "utf8"));
5115
+ return `${Buffer.from(value, "utf8").subarray(0, contentBudget).toString("utf8")}${suffix}`;
4164
5116
  }
4165
5117
  async function withTimeout(promise, timeoutMs, displayCommand) {
4166
5118
  if (!timeoutMs || timeoutMs <= 0) {
@@ -4182,9 +5134,9 @@ async function withTimeout(promise, timeoutMs, displayCommand) {
4182
5134
  });
4183
5135
  }
4184
5136
  function toolTimeoutMessage(displayCommand, timeoutMs) {
4185
- return `Local tool timed out after ${formatTimeoutDuration(timeoutMs)}: ${displayCommand}`;
5137
+ return `Local tool timed out after ${formatTimeoutDuration2(timeoutMs)}: ${displayCommand}`;
4186
5138
  }
4187
- function formatTimeoutDuration(timeoutMs) {
5139
+ function formatTimeoutDuration2(timeoutMs) {
4188
5140
  if (timeoutMs < 1e3) {
4189
5141
  return `${timeoutMs}ms`;
4190
5142
  }
@@ -4246,7 +5198,7 @@ async function runClaudeSdk(input) {
4246
5198
  }
4247
5199
  }
4248
5200
  })) {
4249
- if (isRecord(message) && message.type === "result") {
5201
+ if (isRecord3(message) && message.type === "result") {
4250
5202
  if (message.subtype === "success" && typeof message.result === "string") {
4251
5203
  stdout += message.result;
4252
5204
  if (input.streamOutput) {
@@ -4332,9 +5284,9 @@ function extractTextParts(parts) {
4332
5284
  if (!Array.isArray(parts)) {
4333
5285
  return "";
4334
5286
  }
4335
- return parts.filter(isRecord).map((part) => part.type === "text" && typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n");
5287
+ return parts.filter(isRecord3).map((part) => part.type === "text" && typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n");
4336
5288
  }
4337
- function errorMessage(error) {
5289
+ function errorMessage3(error) {
4338
5290
  return error instanceof Error ? error.message : String(error);
4339
5291
  }
4340
5292
  function shellQuote(value) {
@@ -4345,14 +5297,14 @@ function shellQuote(value) {
4345
5297
  import { spawn as spawn2 } from "node:child_process";
4346
5298
  import { createHash as createHash3 } from "node:crypto";
4347
5299
  import { openSync } from "node:fs";
4348
- import { mkdir as mkdir6, readdir as readdir3, readFile as readFile5, writeFile as writeFile6 } from "node:fs/promises";
5300
+ import { mkdir as mkdir7, readdir as readdir4, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
4349
5301
  import os4 from "node:os";
4350
- import path7 from "node:path";
5302
+ import path8 from "node:path";
4351
5303
  function currentRunnerMode() {
4352
5304
  return process.env.AMISTIO_RUNNER_MODE === "background" ? "background" : "foreground";
4353
5305
  }
4354
5306
  function defaultRunnerMetadataDir() {
4355
- return path7.join(os4.homedir(), ".config", "amistio", "runners");
5307
+ return path8.join(os4.homedir(), ".config", "amistio", "runners");
4356
5308
  }
4357
5309
  function updatedCliRunnerLaunchOptions() {
4358
5310
  return { executablePath: "amistio", directExecutable: true };
@@ -4369,8 +5321,8 @@ async function startRunnerDaemon(input) {
4369
5321
  if (existing?.status === "running" && isProcessRunning(existing.pid)) {
4370
5322
  throw new Error(`Background runner ${existing.runnerId} is already running with PID ${existing.pid}.`);
4371
5323
  }
4372
- await mkdir6(metadataDir, { recursive: true });
4373
- const logPath = path7.join(metadataDir, `${runnerDaemonKey(input)}.log`);
5324
+ await mkdir7(metadataDir, { recursive: true });
5325
+ const logPath = path8.join(metadataDir, `${runnerDaemonKey(input)}.log`);
4374
5326
  const logFd = openSync(logPath, "a");
4375
5327
  const launch = resolveRunnerDaemonLaunch({
4376
5328
  args: input.args,
@@ -4397,7 +5349,7 @@ async function startRunnerDaemon(input) {
4397
5349
  projectId: input.projectId,
4398
5350
  repositoryLinkId: input.repositoryLinkId,
4399
5351
  runnerId: input.runnerId,
4400
- rootDir: path7.resolve(input.rootDir),
5352
+ rootDir: path8.resolve(input.rootDir),
4401
5353
  apiUrl: input.apiUrl,
4402
5354
  pid: child.pid,
4403
5355
  status: "running",
@@ -4411,8 +5363,8 @@ async function startRunnerDaemon(input) {
4411
5363
  }
4412
5364
  async function restartRunnerDaemonProcess(metadata, args, input = {}) {
4413
5365
  const metadataDir = input.metadataDir ?? defaultRunnerMetadataDir();
4414
- await mkdir6(metadataDir, { recursive: true });
4415
- const logPath = metadata.logPath ?? path7.join(metadataDir, `${runnerDaemonKey(metadata)}.log`);
5366
+ await mkdir7(metadataDir, { recursive: true });
5367
+ const logPath = metadata.logPath ?? path8.join(metadataDir, `${runnerDaemonKey(metadata)}.log`);
4416
5368
  const logFd = openSync(logPath, "a");
4417
5369
  const launch = resolveRunnerDaemonLaunch({
4418
5370
  args,
@@ -4449,12 +5401,12 @@ async function restartRunnerDaemonProcess(metadata, args, input = {}) {
4449
5401
  async function listRunnerDaemonMetadata(input, metadataDir = defaultRunnerMetadataDir()) {
4450
5402
  let entries;
4451
5403
  try {
4452
- entries = await readdir3(metadataDir);
5404
+ entries = await readdir4(metadataDir);
4453
5405
  } catch {
4454
5406
  return [];
4455
5407
  }
4456
5408
  const records = await Promise.all(
4457
- entries.filter((entry) => entry.endsWith(".json")).map(async (entry) => readRunnerDaemonMetadataFile(path7.join(metadataDir, entry)))
5409
+ entries.filter((entry) => entry.endsWith(".json")).map(async (entry) => readRunnerDaemonMetadataFile(path8.join(metadataDir, entry)))
4458
5410
  );
4459
5411
  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
5412
  }
@@ -4462,8 +5414,8 @@ async function readRunnerDaemonMetadata(input, metadataDir = defaultRunnerMetada
4462
5414
  return readRunnerDaemonMetadataFile(runnerDaemonMetadataPath(input, metadataDir));
4463
5415
  }
4464
5416
  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 });
5417
+ await mkdir7(metadataDir, { recursive: true });
5418
+ await writeFile7(runnerDaemonMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
4467
5419
  }
4468
5420
  async function markRunnerDaemonStopped(metadata, metadataDir = defaultRunnerMetadataDir()) {
4469
5421
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4525,14 +5477,14 @@ function runnerDaemonUptime(metadata, now = Date.now()) {
4525
5477
  return `${seconds}s`;
4526
5478
  }
4527
5479
  function runnerDaemonMetadataPath(input, metadataDir) {
4528
- return path7.join(metadataDir, `${runnerDaemonKey(input)}.json`);
5480
+ return path8.join(metadataDir, `${runnerDaemonKey(input)}.json`);
4529
5481
  }
4530
5482
  function runnerDaemonKey(input) {
4531
5483
  return createHash3("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.runnerId}`).digest("hex");
4532
5484
  }
4533
5485
  async function readRunnerDaemonMetadataFile(filePath) {
4534
5486
  try {
4535
- const parsed = JSON.parse(await readFile5(filePath, "utf8"));
5487
+ const parsed = JSON.parse(await readFile6(filePath, "utf8"));
4536
5488
  if (parsed.schemaVersion !== 1 || !parsed.runnerId || !parsed.projectId || !parsed.repositoryLinkId) {
4537
5489
  return void 0;
4538
5490
  }
@@ -4542,12 +5494,47 @@ async function readRunnerDaemonMetadataFile(filePath) {
4542
5494
  }
4543
5495
  }
4544
5496
 
5497
+ // src/runner-watch-errors.ts
5498
+ var forgottenRunnerWatchMessage = "This runner was forgotten by the server. Start or pair a new runner from the Runner panel; this runner ID cannot heartbeat or claim work anymore.";
5499
+ async function handleRunnerWatchError(options) {
5500
+ const consoleError = options.consoleError ?? console.error;
5501
+ if (isForgottenRunnerApiError(options.error)) {
5502
+ if (options.verbose && options.detail) {
5503
+ consoleError(`${forgottenRunnerWatchMessage}
5504
+ ${options.detail}`);
5505
+ } else {
5506
+ consoleError(forgottenRunnerWatchMessage);
5507
+ }
5508
+ return { status: "failed", exitCode: 1, message: forgottenRunnerWatchMessage, stopRunner: true };
5509
+ }
5510
+ const message = "Runner hit an error while watching and will keep listening.";
5511
+ if (options.verbose) {
5512
+ consoleError(`${message}
5513
+ ${options.detail}`);
5514
+ } else {
5515
+ consoleError(`${message} Run with --verbose for details.`);
5516
+ }
5517
+ const settlements = await Promise.allSettled([
5518
+ options.client.sendRunnerHeartbeat(options.projectId, options.runnerId, options.repositoryLinkId, "blocked", { ...options.heartbeatMetadata, preferenceMessage: message }),
5519
+ options.client.recordRunnerLog(options.projectId, {
5520
+ runnerId: options.runnerId,
5521
+ repositoryLinkId: options.repositoryLinkId,
5522
+ status: "failed",
5523
+ message,
5524
+ error: options.detail,
5525
+ machineId: options.machineId
5526
+ })
5527
+ ]);
5528
+ options.logRejectedSettlements("record watch error", settlements);
5529
+ return { status: "failed", exitCode: 1, message };
5530
+ }
5531
+
4545
5532
  // src/runner-service.ts
4546
5533
  import { spawn as spawn3 } from "node:child_process";
4547
5534
  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";
5535
+ import { mkdir as mkdir8, readFile as readFile7, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
4549
5536
  import os5 from "node:os";
4550
- import path8 from "node:path";
5537
+ import path9 from "node:path";
4551
5538
  function detectRunnerServicePlatform(platform = process.platform) {
4552
5539
  if (platform === "darwin") return "launchd";
4553
5540
  if (platform === "linux") return "systemd";
@@ -4563,14 +5550,14 @@ function createRunnerServiceDescriptor(input) {
4563
5550
  const serviceFilePath = runnerServiceFilePath(platform, serviceName, homeDir);
4564
5551
  const now = (/* @__PURE__ */ new Date()).toISOString();
4565
5552
  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`);
5553
+ const logPath = path9.join(input.metadataDir ?? defaultRunnerMetadataDir(), `${runnerServiceKey(input)}.service.log`);
4567
5554
  const metadata = {
4568
5555
  schemaVersion: 1,
4569
5556
  accountId: input.accountId,
4570
5557
  projectId: input.projectId,
4571
5558
  repositoryLinkId: input.repositoryLinkId,
4572
5559
  runnerId: input.runnerId,
4573
- rootDir: path8.resolve(input.rootDir),
5560
+ rootDir: path9.resolve(input.rootDir),
4574
5561
  apiUrl: input.apiUrl,
4575
5562
  serviceName,
4576
5563
  serviceFilePath,
@@ -4587,9 +5574,9 @@ function createRunnerServiceDescriptor(input) {
4587
5574
  }
4588
5575
  async function installRunnerService(input, options = {}) {
4589
5576
  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 });
5577
+ await mkdir8(path9.dirname(descriptor.metadata.serviceFilePath), { recursive: true });
5578
+ await mkdir8(input.metadataDir ?? defaultRunnerMetadataDir(), { recursive: true });
5579
+ await writeFile8(descriptor.metadata.serviceFilePath, descriptor.content, { encoding: "utf8", mode: 384 });
4593
5580
  await writeRunnerServiceMetadata(descriptor.metadata, input.metadataDir);
4594
5581
  if (options.activate !== false) {
4595
5582
  const activation = await activateRunnerService(descriptor.metadata);
@@ -4611,7 +5598,7 @@ async function removeRunnerService(input) {
4611
5598
  }
4612
5599
  async function readRunnerServiceMetadata(input, metadataDir = defaultRunnerMetadataDir()) {
4613
5600
  try {
4614
- const parsed = JSON.parse(await readFile6(runnerServiceMetadataPath(input, metadataDir), "utf8"));
5601
+ const parsed = JSON.parse(await readFile7(runnerServiceMetadataPath(input, metadataDir), "utf8"));
4615
5602
  if (parsed.schemaVersion !== 1 || !parsed.serviceName || !parsed.serviceFilePath) {
4616
5603
  return void 0;
4617
5604
  }
@@ -4621,8 +5608,8 @@ async function readRunnerServiceMetadata(input, metadataDir = defaultRunnerMetad
4621
5608
  }
4622
5609
  }
4623
5610
  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 });
5611
+ await mkdir8(metadataDir, { recursive: true });
5612
+ await writeFile8(runnerServiceMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
4626
5613
  }
4627
5614
  async function runnerServiceRuntimeStatus(metadata) {
4628
5615
  if (metadata.platform === "launchd") {
@@ -4705,12 +5692,12 @@ WantedBy=default.target
4705
5692
  }
4706
5693
  function runnerServiceFilePath(platform, serviceName, homeDir) {
4707
5694
  if (platform === "launchd") {
4708
- return path8.join(homeDir, "Library", "LaunchAgents", `${serviceName}.plist`);
5695
+ return path9.join(homeDir, "Library", "LaunchAgents", `${serviceName}.plist`);
4709
5696
  }
4710
- return path8.join(homeDir, ".config", "systemd", "user", `${serviceName}.service`);
5697
+ return path9.join(homeDir, ".config", "systemd", "user", `${serviceName}.service`);
4711
5698
  }
4712
5699
  function runnerServiceMetadataPath(input, metadataDir) {
4713
- return path8.join(metadataDir, `${runnerServiceKey(input)}.service.json`);
5700
+ return path9.join(metadataDir, `${runnerServiceKey(input)}.service.json`);
4714
5701
  }
4715
5702
  function runnerServiceName(input) {
4716
5703
  return `com.amistio.runner.${runnerServiceKey(input).slice(0, 20)}`;
@@ -4769,7 +5756,7 @@ function completedToolSessionClosedReason(session) {
4769
5756
  return "Completed one-shot tool run; this session is not reusable.";
4770
5757
  }
4771
5758
  function staleToolSessionClosedReason(session, now = /* @__PURE__ */ new Date()) {
4772
- if (session.status !== "open") {
5759
+ if (session.status !== "open" && session.status !== "active") {
4773
5760
  return void 0;
4774
5761
  }
4775
5762
  const lastActivityMs = Date.parse(session.lastActivityAt);
@@ -5045,8 +6032,8 @@ function createSmokeSession({ now, status, lastActivityAt }) {
5045
6032
  // src/sync.ts
5046
6033
  import { execFile as execFile3 } from "node:child_process";
5047
6034
  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";
6035
+ import { mkdir as mkdir9, readdir as readdir5, readFile as readFile8, stat as stat4, writeFile as writeFile9 } from "node:fs/promises";
6036
+ import path10 from "node:path";
5050
6037
  import { promisify as promisify3 } from "node:util";
5051
6038
  var execFileAsync3 = promisify3(execFile3);
5052
6039
  var legacySyncRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
@@ -5142,7 +6129,7 @@ async function readLocalSyncedDocuments(rootDir) {
5142
6129
  const documentFiles = await findBrainDocumentFiles(rootDir);
5143
6130
  const documents = [];
5144
6131
  for (const fullPath of documentFiles) {
5145
- const raw = await readFile7(fullPath, "utf8");
6132
+ const raw = await readFile8(fullPath, "utf8");
5146
6133
  const repoPath = toRepoPath(rootDir, fullPath);
5147
6134
  const parsed = parseSyncedDocument(raw, repoPath);
5148
6135
  if (!parsed) {
@@ -5192,8 +6179,8 @@ async function materializeBrainDocuments(rootDir, documents, options = {}) {
5192
6179
  result.skipped.push(document.repoPath);
5193
6180
  continue;
5194
6181
  }
5195
- await mkdir8(path9.dirname(fullPath), { recursive: true });
5196
- await writeFile8(fullPath, createSyncedDocumentContent(document), "utf8");
6182
+ await mkdir9(path10.dirname(fullPath), { recursive: true });
6183
+ await writeFile9(fullPath, createSyncedDocumentContent(document), "utf8");
5197
6184
  result.written.push(document.repoPath);
5198
6185
  }
5199
6186
  return result;
@@ -5322,7 +6309,7 @@ function parseSyncedHtml(content) {
5322
6309
  }
5323
6310
  async function readExistingSyncedDocument(fullPath) {
5324
6311
  try {
5325
- const raw = await readFile7(fullPath, "utf8");
6312
+ const raw = await readFile8(fullPath, "utf8");
5326
6313
  const parsed = parseSyncedDocument(raw, fullPath);
5327
6314
  if (!parsed) {
5328
6315
  return { exists: true };
@@ -5347,7 +6334,7 @@ async function readExistingSyncedDocument(fullPath) {
5347
6334
  async function findBrainDocumentFiles(rootDir) {
5348
6335
  const files = [];
5349
6336
  for (const syncRoot of [...syncRoots, htmlSyncRoot]) {
5350
- const fullRoot = path9.join(rootDir, syncRoot);
6337
+ const fullRoot = path10.join(rootDir, syncRoot);
5351
6338
  if (!await exists2(fullRoot)) {
5352
6339
  continue;
5353
6340
  }
@@ -5356,8 +6343,8 @@ async function findBrainDocumentFiles(rootDir) {
5356
6343
  return files;
5357
6344
  }
5358
6345
  async function walkBrainDocumentFiles(directory, files) {
5359
- for (const entry of await readdir4(directory, { withFileTypes: true })) {
5360
- const fullPath = path9.join(directory, entry.name);
6346
+ for (const entry of await readdir5(directory, { withFileTypes: true })) {
6347
+ const fullPath = path10.join(directory, entry.name);
5361
6348
  if (entry.isDirectory()) {
5362
6349
  await walkBrainDocumentFiles(fullPath, files);
5363
6350
  } else if (entry.isFile() && /\.(md|mdx|html?)$/i.test(entry.name)) {
@@ -5366,23 +6353,23 @@ async function walkBrainDocumentFiles(directory, files) {
5366
6353
  }
5367
6354
  }
5368
6355
  function safeRepoPath(rootDir, repoPath) {
5369
- if (path9.isAbsolute(repoPath)) {
6356
+ if (path10.isAbsolute(repoPath)) {
5370
6357
  throw new Error(`Refusing to use absolute repo path: ${repoPath}`);
5371
6358
  }
5372
- const normalized = path9.normalize(repoPath);
5373
- if (normalized === ".." || normalized.startsWith(`..${path9.sep}`)) {
6359
+ const normalized = path10.normalize(repoPath);
6360
+ if (normalized === ".." || normalized.startsWith(`..${path10.sep}`)) {
5374
6361
  throw new Error(`Refusing to use path outside the repository: ${repoPath}`);
5375
6362
  }
5376
- const root = path9.resolve(rootDir);
5377
- const fullPath = path9.resolve(root, normalized);
5378
- if (!fullPath.startsWith(`${root}${path9.sep}`)) {
6363
+ const root = path10.resolve(rootDir);
6364
+ const fullPath = path10.resolve(root, normalized);
6365
+ if (!fullPath.startsWith(`${root}${path10.sep}`)) {
5379
6366
  throw new Error(`Refusing to use path outside the repository: ${repoPath}`);
5380
6367
  }
5381
6368
  return fullPath;
5382
6369
  }
5383
6370
  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}`);
6371
+ const normalized = path10.normalize(repoPath);
6372
+ return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path10.sep}`)) || normalized === htmlSyncRoot || normalized.startsWith(`${htmlSyncRoot}${path10.sep}`);
5386
6373
  }
5387
6374
  function canonicalControlPlaneRepoPath(repoPath) {
5388
6375
  const normalized = repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
@@ -5393,16 +6380,16 @@ function canonicalControlPlaneRepoPath(repoPath) {
5393
6380
  return normalized;
5394
6381
  }
5395
6382
  function toRepoPath(rootDir, fullPath) {
5396
- return path9.relative(rootDir, fullPath).split(path9.sep).join("/");
6383
+ return path10.relative(rootDir, fullPath).split(path10.sep).join("/");
5397
6384
  }
5398
6385
  function inferTitle(content, repoPath) {
5399
6386
  const heading = content.split("\n").find((line) => line.startsWith("# "))?.replace(/^#\s+/, "").trim();
5400
6387
  if (heading) return heading;
5401
6388
  const htmlHeading = content.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
5402
- return htmlHeading || path9.basename(repoPath, path9.extname(repoPath));
6389
+ return htmlHeading || path10.basename(repoPath, path10.extname(repoPath));
5403
6390
  }
5404
6391
  async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingDocuments, options) {
5405
- const root = path9.resolve(rootDir);
6392
+ const root = path10.resolve(rootDir);
5406
6393
  const maxBytes = (options.maxFileKb ?? defaultAutoSyncMaxFileKb) * 1024;
5407
6394
  const syncedAt = options.syncedAt ?? (/* @__PURE__ */ new Date()).toISOString();
5408
6395
  const existingById = new Map(existingDocuments.map((document) => [document.documentId, document]));
@@ -5418,7 +6405,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
5418
6405
  continue;
5419
6406
  }
5420
6407
  const fullPath = safeRepoPath(root, normalizedRepoPath);
5421
- const fileStat = await stat3(fullPath).catch(() => void 0);
6408
+ const fileStat = await stat4(fullPath).catch(() => void 0);
5422
6409
  if (!fileStat?.isFile()) {
5423
6410
  skipped.push({ repoPath: normalizedRepoPath, reason: "unreadable" });
5424
6411
  continue;
@@ -5427,7 +6414,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
5427
6414
  skipped.push({ repoPath: normalizedRepoPath, reason: "tooLarge" });
5428
6415
  continue;
5429
6416
  }
5430
- const content = await readFile7(fullPath, "utf8").catch(() => void 0);
6417
+ const content = await readFile8(fullPath, "utf8").catch(() => void 0);
5431
6418
  if (content === void 0) {
5432
6419
  skipped.push({ repoPath: normalizedRepoPath, reason: "unreadable" });
5433
6420
  continue;
@@ -5492,7 +6479,7 @@ async function listAutoSyncCandidatePaths(rootDir) {
5492
6479
  }
5493
6480
  const files = [];
5494
6481
  for (const syncRoot of [...syncRoots, htmlSyncRoot, ...legacySyncRoots]) {
5495
- const fullRoot = path9.join(rootDir, syncRoot);
6482
+ const fullRoot = path10.join(rootDir, syncRoot);
5496
6483
  if (await exists2(fullRoot)) {
5497
6484
  await walkAutoSyncFiles(rootDir, fullRoot, files);
5498
6485
  }
@@ -5500,9 +6487,9 @@ async function listAutoSyncCandidatePaths(rootDir) {
5500
6487
  return uniqueSortedRepoPaths(files);
5501
6488
  }
5502
6489
  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));
6490
+ for (const entry of await readdir5(directory, { withFileTypes: true }).catch(() => [])) {
6491
+ const fullPath = path10.join(directory, entry.name);
6492
+ const repoPath = normalizeRepoPath3(path10.relative(rootDir, fullPath));
5506
6493
  if (entry.isDirectory()) {
5507
6494
  if (!autoSyncExcludedDirectoryNames.has(entry.name)) {
5508
6495
  await walkAutoSyncFiles(rootDir, fullPath, files);
@@ -5556,7 +6543,7 @@ function parseFrontmatterFromSyncedDocument(frontmatter) {
5556
6543
  }
5557
6544
  async function exists2(filePath) {
5558
6545
  try {
5559
- await stat3(filePath);
6546
+ await stat4(filePath);
5560
6547
  return true;
5561
6548
  } catch {
5562
6549
  return false;
@@ -5564,9 +6551,9 @@ async function exists2(filePath) {
5564
6551
  }
5565
6552
 
5566
6553
  // src/tool-session-store.ts
5567
- import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile9 } from "node:fs/promises";
6554
+ import { mkdir as mkdir10, readFile as readFile9, writeFile as writeFile10 } from "node:fs/promises";
5568
6555
  import os6 from "node:os";
5569
- import path10 from "node:path";
6556
+ import path11 from "node:path";
5570
6557
  var LocalToolSessionStore = class {
5571
6558
  constructor(filePath = defaultSessionStorePath()) {
5572
6559
  this.filePath = filePath;
@@ -5580,12 +6567,12 @@ var LocalToolSessionStore = class {
5580
6567
  async setProviderSessionId(toolSessionId, toolName, providerSessionId) {
5581
6568
  const data = await this.read();
5582
6569
  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");
6570
+ await mkdir10(path11.dirname(this.filePath), { recursive: true });
6571
+ await writeFile10(this.filePath, JSON.stringify(data, null, 2), "utf8");
5585
6572
  }
5586
6573
  async read() {
5587
6574
  try {
5588
- return JSON.parse(await readFile8(this.filePath, "utf8"));
6575
+ return JSON.parse(await readFile9(this.filePath, "utf8"));
5589
6576
  } catch {
5590
6577
  return {};
5591
6578
  }
@@ -5593,16 +6580,16 @@ var LocalToolSessionStore = class {
5593
6580
  };
5594
6581
  function defaultSessionStorePath() {
5595
6582
  if (process.platform === "darwin") {
5596
- return path10.join(os6.homedir(), "Library", "Application Support", "Amistio", "tool-sessions.json");
6583
+ return path11.join(os6.homedir(), "Library", "Application Support", "Amistio", "tool-sessions.json");
5597
6584
  }
5598
6585
  if (process.platform === "win32") {
5599
- return path10.join(process.env.APPDATA ?? os6.homedir(), "Amistio", "tool-sessions.json");
6586
+ return path11.join(process.env.APPDATA ?? os6.homedir(), "Amistio", "tool-sessions.json");
5600
6587
  }
5601
- return path10.join(process.env.XDG_STATE_HOME ?? path10.join(os6.homedir(), ".local", "state"), "amistio", "tool-sessions.json");
6588
+ return path11.join(process.env.XDG_STATE_HOME ?? path11.join(os6.homedir(), ".local", "state"), "amistio", "tool-sessions.json");
5602
6589
  }
5603
6590
 
5604
6591
  // src/work-runner.ts
5605
- import path11 from "node:path";
6592
+ import path12 from "node:path";
5606
6593
  var generationResultStart = "AMISTIO_BRAIN_GENERATION_RESULT_START";
5607
6594
  var generationResultEnd = "AMISTIO_BRAIN_GENERATION_RESULT_END";
5608
6595
  var assistantAnswerStart = "AMISTIO_ASSISTANT_ANSWER_START";
@@ -7085,15 +8072,15 @@ function normalizeProjectContextRepoPath(value, options) {
7085
8072
  if (!trimmed || /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(trimmed) || /^file:/i.test(trimmed) || /^[A-Za-z]:($|[^\\/])/.test(trimmed)) {
7086
8073
  throwUnsafeProjectContextPath();
7087
8074
  }
7088
- const absolute = trimmed.startsWith("/") || trimmed.startsWith("\\\\") || path11.isAbsolute(trimmed) || path11.win32.isAbsolute(trimmed);
8075
+ const absolute = trimmed.startsWith("/") || trimmed.startsWith("\\\\") || path12.isAbsolute(trimmed) || path12.win32.isAbsolute(trimmed);
7089
8076
  if (!absolute) {
7090
8077
  return normalizeRelativeProjectContextPath(trimmed);
7091
8078
  }
7092
8079
  if (!options.repositoryRoot) {
7093
8080
  throwUnsafeProjectContextPath();
7094
8081
  }
7095
- const useWindowsPathRules = path11.win32.isAbsolute(trimmed) && !trimmed.startsWith("/");
7096
- const relativePath = useWindowsPathRules ? path11.win32.relative(path11.win32.resolve(options.repositoryRoot), path11.win32.resolve(trimmed)) : path11.relative(path11.resolve(options.repositoryRoot), path11.resolve(trimmed));
8082
+ const useWindowsPathRules = path12.win32.isAbsolute(trimmed) && !trimmed.startsWith("/");
8083
+ const relativePath = useWindowsPathRules ? path12.win32.relative(path12.win32.resolve(options.repositoryRoot), path12.win32.resolve(trimmed)) : path12.relative(path12.resolve(options.repositoryRoot), path12.resolve(trimmed));
7097
8084
  return normalizeRelativeProjectContextPath(relativePath);
7098
8085
  }
7099
8086
  function normalizeRelativeProjectContextPath(value) {
@@ -7298,8 +8285,8 @@ function roundNumber(value, digits) {
7298
8285
  // src/importer.ts
7299
8286
  import { execFile as execFile4 } from "node:child_process";
7300
8287
  import { createHash as createHash7 } from "node:crypto";
7301
- import { readdir as readdir5, readFile as readFile9, stat as stat4 } from "node:fs/promises";
7302
- import path12 from "node:path";
8288
+ import { readdir as readdir6, readFile as readFile10, stat as stat5 } from "node:fs/promises";
8289
+ import path13 from "node:path";
7303
8290
  import { promisify as promisify4 } from "node:util";
7304
8291
  var execFileAsync4 = promisify4(execFile4);
7305
8292
  var defaultMaxFileKb = 256;
@@ -7320,12 +8307,12 @@ var documentFolderByType = {
7320
8307
  workflow: "docs/workflows"
7321
8308
  };
7322
8309
  async function inspectLocalRepository(rootDir, defaultBranch) {
7323
- const requestedRoot = path12.resolve(rootDir);
8310
+ const requestedRoot = path13.resolve(rootDir);
7324
8311
  const root = await runGit2(["-C", requestedRoot, "rev-parse", "--show-toplevel"]).catch(() => requestedRoot);
7325
8312
  const detectedBranch = await runGit2(["-C", root, "symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => defaultBranch);
7326
8313
  const originUrl = await runGit2(["-C", root, "remote", "get-url", "origin"]).catch(() => void 0);
7327
8314
  const parsedCloneUrl = originUrl ? parseOptionalOriginCloneUrl(originUrl) : void 0;
7328
- const repoName = (parsedCloneUrl?.repoName ?? path12.basename(root)) || "repository";
8315
+ const repoName = (parsedCloneUrl?.repoName ?? path13.basename(root)) || "repository";
7329
8316
  const fingerprintSeed = parsedCloneUrl ? `origin:${parsedCloneUrl.normalizedKey}` : `repo:${repoName}:${detectedBranch || defaultBranch}`;
7330
8317
  return {
7331
8318
  rootDir: root,
@@ -7337,7 +8324,7 @@ async function inspectLocalRepository(rootDir, defaultBranch) {
7337
8324
  };
7338
8325
  }
7339
8326
  async function scanLegacyDocuments(options) {
7340
- const rootDir = path12.resolve(options.rootDir);
8327
+ const rootDir = path13.resolve(options.rootDir);
7341
8328
  const maxBytes = (options.maxFileKb ?? defaultMaxFileKb) * 1024;
7342
8329
  const skipped = [];
7343
8330
  const candidates = [];
@@ -7357,8 +8344,8 @@ async function scanLegacyDocuments(options) {
7357
8344
  skipped.push({ repoPath, reason: "excluded" });
7358
8345
  continue;
7359
8346
  }
7360
- const fullPath = path12.join(rootDir, ...repoPath.split("/"));
7361
- const fileStat = await stat4(fullPath).catch(() => void 0);
8347
+ const fullPath = path13.join(rootDir, ...repoPath.split("/"));
8348
+ const fileStat = await stat5(fullPath).catch(() => void 0);
7362
8349
  if (!fileStat?.isFile()) {
7363
8350
  skipped.push({ repoPath, reason: "unreadable" });
7364
8351
  continue;
@@ -7367,7 +8354,7 @@ async function scanLegacyDocuments(options) {
7367
8354
  skipped.push({ repoPath, reason: "tooLarge" });
7368
8355
  continue;
7369
8356
  }
7370
- const content = await readFile9(fullPath, "utf8").catch(() => void 0);
8357
+ const content = await readFile10(fullPath, "utf8").catch(() => void 0);
7371
8358
  if (content === void 0) {
7372
8359
  skipped.push({ repoPath, reason: "unreadable" });
7373
8360
  continue;
@@ -7457,10 +8444,10 @@ async function listRepositoryPaths(rootDir) {
7457
8444
  return files;
7458
8445
  }
7459
8446
  async function walkRepository(rootDir, directory, files) {
7460
- const entries = await readdir5(directory, { withFileTypes: true }).catch(() => []);
8447
+ const entries = await readdir6(directory, { withFileTypes: true }).catch(() => []);
7461
8448
  for (const entry of entries) {
7462
- const fullPath = path12.join(directory, entry.name);
7463
- const repoPath = normalizeRepoPath4(path12.relative(rootDir, fullPath));
8449
+ const fullPath = path13.join(directory, entry.name);
8450
+ const repoPath = normalizeRepoPath4(path13.relative(rootDir, fullPath));
7464
8451
  if (entry.isDirectory()) {
7465
8452
  if (!excludedDirectoryNames.has(entry.name)) {
7466
8453
  await walkRepository(rootDir, fullPath, files);
@@ -7528,9 +8515,9 @@ function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
7528
8515
  usedPaths.add(basePath);
7529
8516
  return basePath;
7530
8517
  }
7531
- const extension = path12.posix.extname(basePath) || ".md";
7532
- const directory = path12.posix.dirname(basePath);
7533
- const basename = path12.posix.basename(basePath, extension);
8518
+ const extension = path13.posix.extname(basePath) || ".md";
8519
+ const directory = path13.posix.dirname(basePath);
8520
+ const basename = path13.posix.basename(basePath, extension);
7534
8521
  const uniquePath = `${directory}/${basename}-${hashText(sourcePath, 8)}${extension}`;
7535
8522
  usedPaths.add(uniquePath);
7536
8523
  return uniquePath;
@@ -7603,7 +8590,7 @@ function inferTitle2(content, repoPath) {
7603
8590
  if (heading) return heading;
7604
8591
  const htmlHeading = body.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
7605
8592
  if (htmlHeading) return htmlHeading;
7606
- const basename = path12.posix.basename(repoPath, path12.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
8593
+ const basename = path13.posix.basename(repoPath, path13.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
7607
8594
  return titleCase(basename || "Imported Document");
7608
8595
  }
7609
8596
  function stripFrontmatter(content) {
@@ -7637,7 +8624,7 @@ async function runGit2(args) {
7637
8624
 
7638
8625
  // src/runner-actions.ts
7639
8626
  import { spawn as spawn4 } from "node:child_process";
7640
- import path13 from "node:path";
8627
+ import path14 from "node:path";
7641
8628
  function buildBackgroundRunnerArgs(options) {
7642
8629
  const args = [
7643
8630
  "run",
@@ -7647,7 +8634,7 @@ function buildBackgroundRunnerArgs(options) {
7647
8634
  "--runner-id",
7648
8635
  options.runnerId,
7649
8636
  "--root",
7650
- path13.resolve(options.root),
8637
+ path14.resolve(options.root),
7651
8638
  "--session",
7652
8639
  options.session,
7653
8640
  "--interval-seconds",
@@ -7765,8 +8752,8 @@ function truncateProcessOutput(value) {
7765
8752
 
7766
8753
  // src/git-worktree.ts
7767
8754
  import { execFile as execFile5 } from "node:child_process";
7768
- import { copyFile, lstat, mkdir as mkdir10, readdir as readdir6, stat as stat5 } from "node:fs/promises";
7769
- import path14 from "node:path";
8755
+ import { copyFile, lstat, mkdir as mkdir11, readdir as readdir7, stat as stat6 } from "node:fs/promises";
8756
+ import path15 from "node:path";
7770
8757
  import { promisify as promisify5 } from "node:util";
7771
8758
  var execFileAsync5 = promisify5(execFile5);
7772
8759
  var exactLocalEnvironmentFiles = /* @__PURE__ */ new Set([".env", ".env.local", ".env.development", ".env.development.local", ".env.test", ".env.test.local", ".env.production", ".env.production.local"]);
@@ -7787,7 +8774,7 @@ function resolveWorktreeIdentity(workItem) {
7787
8774
  async function prepareGitWorktreeIsolation(rootDir, workItem) {
7788
8775
  const identity = resolveWorktreeIdentity(workItem);
7789
8776
  const repoRoot = await gitOutput(rootDir, ["rev-parse", "--show-toplevel"]).catch((error) => {
7790
- throw new Error(`Git worktree isolation requires a paired Git checkout: ${errorMessage2(error)}`);
8777
+ throw new Error(`Git worktree isolation requires a paired Git checkout: ${errorMessage4(error)}`);
7791
8778
  });
7792
8779
  const currentHead = await gitOutput(repoRoot, ["rev-parse", "HEAD"]);
7793
8780
  await assertBaseRevision(repoRoot, workItem.baseRevision, currentHead);
@@ -7798,11 +8785,11 @@ async function prepareGitWorktreeIsolation(rootDir, workItem) {
7798
8785
  const preparedLocalEnvironmentFileCount2 = await prepareLocalWorktreeEnvironment(repoRoot, worktreePath);
7799
8786
  return { ...identity, baseRevision, worktreePath, ...preparedLocalEnvironmentFileCount2 ? { preparedLocalEnvironmentFileCount: preparedLocalEnvironmentFileCount2 } : {} };
7800
8787
  }
7801
- await mkdir10(path14.dirname(worktreePath), { recursive: true });
8788
+ await mkdir11(path15.dirname(worktreePath), { recursive: true });
7802
8789
  const branchExists = await gitCommandSucceeds(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${identity.branch}`]);
7803
8790
  const worktreeArgs = branchExists ? ["worktree", "add", worktreePath, identity.branch] : ["worktree", "add", "-b", identity.branch, worktreePath, baseRevision];
7804
8791
  await gitOutput(repoRoot, worktreeArgs).catch((error) => {
7805
- throw new Error(`Could not create Git worktree ${identity.worktreeKey} on ${identity.branch}: ${errorMessage2(error)}`);
8792
+ throw new Error(`Could not create Git worktree ${identity.worktreeKey} on ${identity.branch}: ${errorMessage4(error)}`);
7806
8793
  });
7807
8794
  const preparedLocalEnvironmentFileCount = await prepareLocalWorktreeEnvironment(repoRoot, worktreePath);
7808
8795
  return { ...identity, baseRevision, worktreePath, ...preparedLocalEnvironmentFileCount ? { preparedLocalEnvironmentFileCount } : {} };
@@ -7816,9 +8803,9 @@ async function resolveExistingGitWorktreeIsolation(rootDir, workItem) {
7816
8803
  return { ...identity, baseRevision, worktreePath };
7817
8804
  }
7818
8805
  function localWorktreePath(repoRoot, worktreeKey) {
7819
- const repoName = path14.basename(repoRoot);
8806
+ const repoName = path15.basename(repoRoot);
7820
8807
  const worktreeSlug = worktreeKey.split("/").filter(Boolean).pop() ?? "work";
7821
- return path14.join(path14.dirname(repoRoot), `${repoName}.worktrees`, worktreeSlug);
8808
+ return path15.join(path15.dirname(repoRoot), `${repoName}.worktrees`, worktreeSlug);
7822
8809
  }
7823
8810
  async function assertExistingWorktree(worktreePath, branch) {
7824
8811
  await gitOutput(worktreePath, ["rev-parse", "--is-inside-work-tree"]);
@@ -7844,8 +8831,8 @@ async function prepareLocalWorktreeEnvironment(repoRoot, worktreePath) {
7844
8831
  const candidates = await localEnvironmentFileCandidates(repoRoot);
7845
8832
  let preparedCount = 0;
7846
8833
  for (const candidate of candidates) {
7847
- const sourcePath = path14.join(repoRoot, candidate);
7848
- const targetPath = path14.join(worktreePath, candidate);
8834
+ const sourcePath = path15.join(repoRoot, candidate);
8835
+ const targetPath = path15.join(worktreePath, candidate);
7849
8836
  if (await pathExists(targetPath)) {
7850
8837
  continue;
7851
8838
  }
@@ -7866,7 +8853,7 @@ async function prepareLocalWorktreeEnvironment(repoRoot, worktreePath) {
7866
8853
  }
7867
8854
  async function localEnvironmentFileCandidates(repoRoot) {
7868
8855
  const names = new Set(exactLocalEnvironmentFiles);
7869
- for (const entry of await readdir6(repoRoot)) {
8856
+ for (const entry of await readdir7(repoRoot)) {
7870
8857
  if (isAllowedLocalEnvironmentFile(entry)) {
7871
8858
  names.add(entry);
7872
8859
  }
@@ -7876,7 +8863,7 @@ async function localEnvironmentFileCandidates(repoRoot) {
7876
8863
  if (!isRootFileName(name)) {
7877
8864
  continue;
7878
8865
  }
7879
- const source = await lstat(path14.join(repoRoot, name)).catch(() => void 0);
8866
+ const source = await lstat(path15.join(repoRoot, name)).catch(() => void 0);
7880
8867
  if (source?.isFile()) {
7881
8868
  candidates.push(name);
7882
8869
  }
@@ -7887,7 +8874,7 @@ function isAllowedLocalEnvironmentFile(name) {
7887
8874
  return exactLocalEnvironmentFiles.has(name) || localEnvironmentFilePattern.test(name);
7888
8875
  }
7889
8876
  function isRootFileName(name) {
7890
- return name === path14.basename(name) && !name.includes("/") && !name.includes("\\");
8877
+ return name === path15.basename(name) && !name.includes("/") && !name.includes("\\");
7891
8878
  }
7892
8879
  async function gitOutput(cwd, args) {
7893
8880
  const { stdout } = await execFileAsync5("git", args, { cwd, maxBuffer: 1024 * 1024 });
@@ -7897,7 +8884,7 @@ async function gitCommandSucceeds(cwd, args) {
7897
8884
  return execFileAsync5("git", args, { cwd }).then(() => true, () => false);
7898
8885
  }
7899
8886
  async function pathExists(value) {
7900
- return stat5(value).then(() => true, () => false);
8887
+ return stat6(value).then(() => true, () => false);
7901
8888
  }
7902
8889
  function workIsolationSlug(scopeId, title) {
7903
8890
  const scopeSlug = slugify(scopeId);
@@ -7908,7 +8895,7 @@ function workIsolationSlug(scopeId, title) {
7908
8895
  function slugify(value) {
7909
8896
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "work";
7910
8897
  }
7911
- function errorMessage2(error) {
8898
+ function errorMessage4(error) {
7912
8899
  return error instanceof Error ? error.message : String(error);
7913
8900
  }
7914
8901
  function safeFileError(error) {
@@ -7920,7 +8907,7 @@ function safeFileError(error) {
7920
8907
 
7921
8908
  // src/implementation-handoff.ts
7922
8909
  import { execFile as execFile6 } from "node:child_process";
7923
- import path15 from "node:path";
8910
+ import path16 from "node:path";
7924
8911
  import { promisify as promisify6 } from "node:util";
7925
8912
  var execFileAsync6 = promisify6(execFile6);
7926
8913
  async function completeImplementationHandoff(input) {
@@ -8188,7 +9175,7 @@ async function cleanupWorktree(run, input) {
8188
9175
  return { status: "failed", message: "Cleanup skipped because the worktree is not clean after PR handoff." };
8189
9176
  }
8190
9177
  try {
8191
- await gitOutput2(run, input.primaryRepoRoot || path15.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
9178
+ await gitOutput2(run, input.primaryRepoRoot || path16.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
8192
9179
  return { status: "completed" };
8193
9180
  } catch (error) {
8194
9181
  return { status: "failed", message: `Cleanup failed: ${safeErrorMessage(error)}` };
@@ -8300,6 +9287,481 @@ function redactLocalPaths(value) {
8300
9287
  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>");
8301
9288
  }
8302
9289
 
9290
+ // src/direct-model-client.ts
9291
+ var githubModelsProviderId = "github-models";
9292
+ var githubModelsEndpoint = "https://models.github.ai/inference/chat/completions";
9293
+ var githubModelsSupportedModelIds = ["openai/gpt-4.1", "openai/gpt-4o", "openai/gpt-5"];
9294
+ function shouldUseDirectModelClient(options) {
9295
+ if (options.toolCommand) return false;
9296
+ if (options.providerId !== githubModelsProviderId) return false;
9297
+ return options.tool === void 0 || options.tool === "auto" || options.tool === "none";
9298
+ }
9299
+ function createDirectModelClientPreview(options) {
9300
+ const modelId = requireSupportedGithubModelsModelId(options.modelId);
9301
+ return {
9302
+ toolName: "amistio-direct",
9303
+ displayCommand: "GitHub Models Direct API <selected model>",
9304
+ supportsSessionReuse: false,
9305
+ resumabilityScope: "none",
9306
+ model: `${githubModelsProviderId}/${modelId}`,
9307
+ providerId: githubModelsProviderId,
9308
+ modelId,
9309
+ ...options.modelVariant ? { modelVariant: options.modelVariant } : {},
9310
+ ...options.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}
9311
+ };
9312
+ }
9313
+ function resolveDirectModelClientPreference(options) {
9314
+ if (options.tool && options.tool !== "auto" && options.tool !== "none") {
9315
+ return void 0;
9316
+ }
9317
+ const modelFromCombinedPreference = options.model?.startsWith(`${githubModelsProviderId}/`) ? options.model.slice(`${githubModelsProviderId}/`.length) : void 0;
9318
+ const providerId = options.providerId ?? (modelFromCombinedPreference ? githubModelsProviderId : void 0);
9319
+ if (providerId !== githubModelsProviderId) {
9320
+ return void 0;
9321
+ }
9322
+ if (options.requestedInvocationChannel === "command") {
9323
+ return { ready: false, status: "channelUnsupported", message: "GitHub Models direct provider uses the Amistio direct client and does not support command invocation." };
9324
+ }
9325
+ let modelId;
9326
+ try {
9327
+ modelId = requireSupportedGithubModelsModelId(options.modelId ?? modelFromCombinedPreference);
9328
+ } catch (error) {
9329
+ return { ready: false, status: "modelUnsupported", message: errorMessage5(error) };
9330
+ }
9331
+ return {
9332
+ ready: true,
9333
+ config: {
9334
+ model: `${githubModelsProviderId}/${modelId}`,
9335
+ providerId: githubModelsProviderId,
9336
+ modelId,
9337
+ ...options.modelVariant ? { modelVariant: options.modelVariant } : {},
9338
+ ...options.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {}
9339
+ }
9340
+ };
9341
+ }
9342
+ async function createGithubModelsChatCompletion(options) {
9343
+ const modelId = requireSupportedGithubModelsModelId(options.modelId);
9344
+ const token = resolveGithubModelsToken(options.env ?? process.env);
9345
+ const requestInit = {
9346
+ method: "POST",
9347
+ headers: {
9348
+ authorization: `Bearer ${token}`,
9349
+ "content-type": "application/json"
9350
+ },
9351
+ body: JSON.stringify({
9352
+ model: modelId,
9353
+ messages: options.messages,
9354
+ ...options.tools?.length ? { tools: options.tools, tool_choice: "auto" } : {},
9355
+ ...options.reasoningEffort && options.reasoningEffort !== "auto" ? { reasoning_effort: options.reasoningEffort } : {}
9356
+ }),
9357
+ ...options.signal ? { signal: options.signal } : {}
9358
+ };
9359
+ const response = await (options.fetchImpl ?? fetch)(githubModelsEndpoint, requestInit);
9360
+ if (!response.ok) {
9361
+ throw new Error(`GitHub Models request failed with HTTP ${response.status}. Check local GitHub Models auth and selected model access.`);
9362
+ }
9363
+ return response.json();
9364
+ }
9365
+ function requireSupportedGithubModelsModelId(modelId) {
9366
+ const trimmed = modelId?.trim();
9367
+ if (!trimmed) {
9368
+ throw new Error("GitHub Models direct provider requires --provider github-models with --model-id.");
9369
+ }
9370
+ if (!githubModelsSupportedModelIds.includes(trimmed)) {
9371
+ throw new Error(`Unsupported GitHub Models direct model: ${trimmed}. Supported models: ${githubModelsSupportedModelIds.join(", ")}.`);
9372
+ }
9373
+ return trimmed;
9374
+ }
9375
+ function resolveGithubModelsToken(env) {
9376
+ const token = env.GITHUB_MODELS_TOKEN?.trim() || env.GITHUB_TOKEN?.trim();
9377
+ if (!token) {
9378
+ throw new Error("GitHub Models direct provider requires GITHUB_MODELS_TOKEN or GITHUB_TOKEN on this runner.");
9379
+ }
9380
+ return token;
9381
+ }
9382
+ function errorMessage5(error) {
9383
+ return error instanceof Error ? error.message : String(error);
9384
+ }
9385
+
9386
+ // src/amistio-direct-agent.ts
9387
+ var defaultMaxToolRounds = 8;
9388
+ async function runAmistioDirectAgent(options) {
9389
+ const preview = createDirectModelClientPreview(options);
9390
+ const messages = [
9391
+ { role: "system", content: directAgentSystemPrompt(options.toolPolicy) },
9392
+ { role: "user", content: options.prompt }
9393
+ ];
9394
+ let tokensIn = 0;
9395
+ let tokensOut = 0;
9396
+ let lastAssistantContent = "";
9397
+ const maxToolRounds = positiveInteger3(options.maxToolRounds) ?? defaultMaxToolRounds;
9398
+ for (let round = 0; round <= maxToolRounds; round += 1) {
9399
+ const response = await createGithubModelsChatCompletion({
9400
+ modelId: preview.modelId,
9401
+ messages,
9402
+ tools: directAgentToolDefinitions,
9403
+ ...options.reasoningEffort ? { reasoningEffort: options.reasoningEffort } : {},
9404
+ ...options.env ? { env: options.env } : {},
9405
+ ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},
9406
+ ...options.signal ? { signal: options.signal } : {}
9407
+ });
9408
+ tokensIn += response.usage?.prompt_tokens ?? 0;
9409
+ tokensOut += response.usage?.completion_tokens ?? 0;
9410
+ const message = response.choices?.[0]?.message;
9411
+ const toolCalls = message?.tool_calls ?? [];
9412
+ lastAssistantContent = message?.content ?? lastAssistantContent;
9413
+ if (!toolCalls.length) {
9414
+ if (options.streamOutput && lastAssistantContent) {
9415
+ process.stdout.write(lastAssistantContent);
9416
+ }
9417
+ return {
9418
+ ...preview,
9419
+ exitCode: 0,
9420
+ stdout: lastAssistantContent,
9421
+ stderr: "",
9422
+ ...tokensIn ? { tokensIn } : {},
9423
+ ...tokensOut ? { tokensOut } : {}
9424
+ };
9425
+ }
9426
+ messages.push({ role: "assistant", content: message?.content ?? null, tool_calls: toolCalls });
9427
+ for (const toolCall of toolCalls) {
9428
+ const execution = await executeDirectAgentTool(toolCall, options.toolPolicy);
9429
+ messages.push({ role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(execution) });
9430
+ }
9431
+ }
9432
+ return {
9433
+ ...preview,
9434
+ exitCode: 1,
9435
+ stdout: lastAssistantContent,
9436
+ stderr: `Amistio direct harness stopped after ${maxToolRounds} tool rounds without a final response.`,
9437
+ ...tokensIn ? { tokensIn } : {},
9438
+ ...tokensOut ? { tokensOut } : {}
9439
+ };
9440
+ }
9441
+ async function executeDirectAgentTool(toolCall, policy) {
9442
+ const args = parseToolArguments(toolCall.function.arguments);
9443
+ const toolName = toolCall.function.name;
9444
+ if (!args.ok) {
9445
+ return { toolName, result: toolFailure(toolName, "command_not_allowed", args.message) };
9446
+ }
9447
+ try {
9448
+ switch (toolName) {
9449
+ case "filesystem_read_file":
9450
+ return { toolName, result: await runFilesystemReadTool({ policy, relativePath: stringArg(args.value, "relativePath") }) };
9451
+ case "filesystem_list_files":
9452
+ return { toolName, result: await runFilesystemListTool({ policy, ...optionalStringField(args.value, "relativeDirectory"), ...optionalNumberField(args.value, "maxFiles") }) };
9453
+ case "filesystem_search_text":
9454
+ return { toolName, result: await runFilesystemSearchTool({ policy, query: stringArg(args.value, "query"), ...optionalStringField(args.value, "relativeDirectory"), ...optionalNumberField(args.value, "maxMatches") }) };
9455
+ case "filesystem_write_file":
9456
+ return { toolName, result: await runFilesystemWriteTool({ policy, relativePath: stringArg(args.value, "relativePath"), content: stringArg(args.value, "content") }) };
9457
+ case "git_status":
9458
+ return { toolName, result: await runGitStatusTool({ policy }) };
9459
+ case "git_diff_name_only":
9460
+ return { toolName, result: await runGitDiffNameOnlyTool({ policy }) };
9461
+ case "package_manager_detect":
9462
+ return { toolName, result: await runPackageManagerDetectTool({ policy }) };
9463
+ case "test_runner_profile":
9464
+ return { toolName, result: await runTestRunnerProfileTool({ policy, ...optionalTestProfileField(args.value, "profileId") }) };
9465
+ case "browser_check":
9466
+ return { toolName, result: await runBrowserCheckTool({ policy, url: stringArg(args.value, "url") }) };
9467
+ default:
9468
+ return { toolName, result: toolFailure(toolName, "command_not_allowed", `Unknown Amistio direct harness tool: ${toolName}.`) };
9469
+ }
9470
+ } catch (error) {
9471
+ return { toolName, result: toolFailure(toolName, "command_not_allowed", error instanceof Error ? error.message : String(error)) };
9472
+ }
9473
+ }
9474
+ var directAgentToolDefinitions = [
9475
+ toolDefinition("filesystem_read_file", "Read a UTF-8 file under the prepared execution root.", {
9476
+ type: "object",
9477
+ additionalProperties: false,
9478
+ required: ["relativePath"],
9479
+ properties: { relativePath: { type: "string" } }
9480
+ }),
9481
+ toolDefinition("filesystem_list_files", "List files under a directory within the prepared execution root.", {
9482
+ type: "object",
9483
+ additionalProperties: false,
9484
+ properties: { relativeDirectory: { type: "string" }, maxFiles: { type: "integer", minimum: 1, maximum: 500 } }
9485
+ }),
9486
+ toolDefinition("filesystem_search_text", "Search text under the prepared execution root with bounded output.", {
9487
+ type: "object",
9488
+ additionalProperties: false,
9489
+ required: ["query"],
9490
+ properties: { query: { type: "string" }, relativeDirectory: { type: "string" }, maxMatches: { type: "integer", minimum: 1, maximum: 200 } }
9491
+ }),
9492
+ toolDefinition("filesystem_write_file", "Write a UTF-8 file under the prepared execution root. Allowed only for mutating harness policy.", {
9493
+ type: "object",
9494
+ additionalProperties: false,
9495
+ required: ["relativePath", "content"],
9496
+ properties: { relativePath: { type: "string" }, content: { type: "string" } }
9497
+ }),
9498
+ toolDefinition("git_status", "Read git status --short for the execution root.", { type: "object", additionalProperties: false, properties: {} }),
9499
+ toolDefinition("git_diff_name_only", "Read changed Git paths for the execution root.", { type: "object", additionalProperties: false, properties: {} }),
9500
+ toolDefinition("package_manager_detect", "Detect local package manager lockfiles and declared verification scripts.", { type: "object", additionalProperties: false, properties: {} }),
9501
+ toolDefinition("test_runner_profile", "Run a declared local verification profile such as test, typecheck, lint, build, or verify.", {
9502
+ type: "object",
9503
+ additionalProperties: false,
9504
+ properties: { profileId: { type: "string", enum: ["verify", "test", "typecheck", "lint", "build"] } }
9505
+ }),
9506
+ toolDefinition("browser_check", "Run a loopback-only HTTP browser smoke check.", {
9507
+ type: "object",
9508
+ additionalProperties: false,
9509
+ required: ["url"],
9510
+ properties: { url: { type: "string" } }
9511
+ })
9512
+ ];
9513
+ function directAgentSystemPrompt(policy) {
9514
+ return [
9515
+ "You are the built-in Amistio direct harness running on the user's local runner.",
9516
+ "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.",
9517
+ `Mutation policy: ${policy.mutationPolicy}.`,
9518
+ 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.",
9519
+ "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.",
9520
+ "Tool outputs are normalized and may be truncated; if more detail is needed, make a narrower tool request.",
9521
+ "When finished, answer with the final task result only. Preserve any structured result contract included in the user prompt."
9522
+ ].join("\n");
9523
+ }
9524
+ function toolDefinition(name, description, parameters) {
9525
+ return { type: "function", function: { name, description, parameters } };
9526
+ }
9527
+ function parseToolArguments(value) {
9528
+ try {
9529
+ const parsed = JSON.parse(value || "{}");
9530
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
9531
+ return { ok: true, value: parsed };
9532
+ }
9533
+ return { ok: false, message: "Tool arguments must be a JSON object." };
9534
+ } catch {
9535
+ return { ok: false, message: "Tool arguments must be valid JSON." };
9536
+ }
9537
+ }
9538
+ function stringArg(args, key) {
9539
+ const value = args[key];
9540
+ if (typeof value !== "string") {
9541
+ throw new Error(`Tool argument ${key} must be a string.`);
9542
+ }
9543
+ return value;
9544
+ }
9545
+ function optionalStringArg(args, key) {
9546
+ const value = args[key];
9547
+ return typeof value === "string" && value.trim() ? value : void 0;
9548
+ }
9549
+ function optionalStringField(args, key) {
9550
+ const value = optionalStringArg(args, key);
9551
+ return value ? { [key]: value } : {};
9552
+ }
9553
+ function optionalNumberArg(args, key) {
9554
+ const value = args[key];
9555
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
9556
+ }
9557
+ function optionalNumberField(args, key) {
9558
+ const value = optionalNumberArg(args, key);
9559
+ return value === void 0 ? {} : { [key]: value };
9560
+ }
9561
+ function optionalTestProfileArg(args, key) {
9562
+ const value = args[key];
9563
+ return value === "verify" || value === "test" || value === "typecheck" || value === "lint" || value === "build" ? value : void 0;
9564
+ }
9565
+ function optionalTestProfileField(args, key) {
9566
+ const value = optionalTestProfileArg(args, key);
9567
+ return value ? { profileId: value } : {};
9568
+ }
9569
+ function toolFailure(toolName, code, message) {
9570
+ return { adapterId: toolName, status: "failed", exitCode: 1, stdout: "", stderr: "", truncated: false, error: { code, message } };
9571
+ }
9572
+ function positiveInteger3(value) {
9573
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
9574
+ }
9575
+
9576
+ // src/harness-adapter.ts
9577
+ var AMISTIO_HARNESS_ID = "amistio";
9578
+ function harnessMutationPolicyForWorkKind(workKind) {
9579
+ return workKind === "implementation" ? "mutating" : "readOnly";
9580
+ }
9581
+ function createHarnessConcurrencyMetadata(executionPolicy, preview) {
9582
+ const serializedResourceKeys = /* @__PURE__ */ new Set([
9583
+ `claim-lane:${executionPolicy.claimLaneId}`,
9584
+ `execution-root:${executionPolicy.executionRoot}`
9585
+ ]);
9586
+ if (executionPolicy.mutationPolicy === "mutating") {
9587
+ serializedResourceKeys.add(`repository:${executionPolicy.executionRoot}`);
9588
+ }
9589
+ if (preview.providerId) {
9590
+ serializedResourceKeys.add(`provider:${preview.providerId}`);
9591
+ }
9592
+ if (preview.toolName && preview.toolName !== "custom") {
9593
+ serializedResourceKeys.add(`client:${preview.toolName}`);
9594
+ }
9595
+ return {
9596
+ claimLaneId: executionPolicy.claimLaneId,
9597
+ supportsConcurrentRuns: true,
9598
+ requiresExclusiveRepository: executionPolicy.mutationPolicy === "mutating",
9599
+ requiresExclusiveProviderAuth: Boolean(preview.providerId && preview.toolName !== "amistio-direct"),
9600
+ serializedResourceKeys: [...serializedResourceKeys]
9601
+ };
9602
+ }
9603
+ var builtinAmistioHarnessAdapter = {
9604
+ id: AMISTIO_HARNESS_ID,
9605
+ displayName: "Amistio",
9606
+ async createRunPreview({ executionPolicy, ...toolOptions }) {
9607
+ const preview = shouldUseDirectModelClient(toolOptions) ? createDirectModelClientPreview(toolOptions) : await createToolRunPreview(toolOptions);
9608
+ const concurrency = createHarnessConcurrencyMetadata(executionPolicy, preview);
9609
+ const toolAdapterPolicy = createBoundedToolAdapterPolicy({
9610
+ executionRoot: executionPolicy.executionRoot,
9611
+ mutationPolicy: executionPolicy.mutationPolicy,
9612
+ concurrencyMode: concurrency.requiresExclusiveRepository ? "serialized" : "parallelSafe",
9613
+ resourceKey: `claim-lane:${executionPolicy.claimLaneId}`
9614
+ });
9615
+ return { harnessId: AMISTIO_HARNESS_ID, displayName: "Amistio", executionPolicy, concurrency, toolAdapterPolicy, availableToolAdapters: boundedToolAdapterCatalog, preview };
9616
+ },
9617
+ async executeRun({ preparedRun, ...toolOptions }) {
9618
+ if (shouldUseDirectModelClient(toolOptions)) {
9619
+ return runAmistioDirectAgent({ ...toolOptions, toolPolicy: preparedRun.toolAdapterPolicy });
9620
+ }
9621
+ return runLocalTool(toolOptions);
9622
+ }
9623
+ };
9624
+
9625
+ // src/host-helper-conformance.ts
9626
+ async function runHostHelperConformance(config, cwd = process.cwd()) {
9627
+ const checks = [];
9628
+ const discovery = await discoverNativeHostHelper(config);
9629
+ checks.push({
9630
+ name: "handshake",
9631
+ passed: discovery.ok,
9632
+ detail: discovery.ok ? `protocol ${discovery.helper.handshake.protocolVersion}` : discovery.failure.message
9633
+ });
9634
+ if (!discovery.ok) {
9635
+ return { passed: false, checks };
9636
+ }
9637
+ const port = await createHostExecutionPort({ nativeHelper: config });
9638
+ const execution = await port.executeCommand({ command: process.execPath, args: ["-e", "console.log('amistio conformance')"], cwd, timeoutMs: 2e3, requireNativeHelper: true });
9639
+ checks.push(commandCheck("command execution", execution, (result) => result.status === "completed" && result.exitCode === 0 && result.stdout.includes("amistio conformance")));
9640
+ const boundedOutput = await port.executeCommand({ command: process.execPath, args: ["-e", "console.log('\u20AC\u20AC\u20AC\u20AC')"], cwd, timeoutMs: 2e3, requireNativeHelper: true, outputBudgetBytes: 7 });
9641
+ checks.push(commandCheck("bounded UTF-8 output", boundedOutput, (result) => result.status === "completed" && Buffer.byteLength(result.stdout, "utf8") <= 7 && !result.stdout.includes("\uFFFD")));
9642
+ if (!discovery.helper.handshake.capabilities.pty.supported) {
9643
+ const pty = await port.executeCommand({ command: process.execPath, args: ["-e", "console.log('pty')"], cwd, timeoutMs: 2e3, requireNativeHelper: true, requirePty: true });
9644
+ checks.push(commandCheck("PTY fail-closed", pty, (result) => result.status === "unsupported" && result.error?.code === "pty_unsupported"));
9645
+ } else {
9646
+ 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." });
9647
+ }
9648
+ if (!discovery.helper.handshake.capabilities.sandbox.supported) {
9649
+ const sandbox = await port.executeCommand({ command: process.execPath, args: ["-e", "console.log('sandbox')"], cwd, timeoutMs: 2e3, requireNativeHelper: true, sandbox: "networkDisabled" });
9650
+ checks.push(commandCheck("sandbox fail-closed", sandbox, (result) => result.status === "unsupported" && result.error?.code === "sandbox_unsupported"));
9651
+ } else {
9652
+ 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." });
9653
+ }
9654
+ return { passed: checks.every((check) => check.passed), checks };
9655
+ }
9656
+ function commandCheck(name, result, predicate) {
9657
+ return {
9658
+ name,
9659
+ passed: predicate(result),
9660
+ detail: result.error?.message ?? `${result.status} exit ${result.exitCode}`
9661
+ };
9662
+ }
9663
+
9664
+ // src/provider-auth.ts
9665
+ import { createHash as createHash8 } from "node:crypto";
9666
+ var githubCopilotProviderId = "github-copilot";
9667
+ var githubCopilotProviderClientId = "github-copilot-sdk";
9668
+ var githubCopilotRouteType = "agentClient";
9669
+ var githubModelsProviderClientId = "github-models-api";
9670
+ var githubModelsRouteType = "directProvider";
9671
+ async function checkProviderAuthLink(input) {
9672
+ if (isGithubModelsDirectRequest(input.request)) {
9673
+ return checkGithubModelsDirectAuth(input);
9674
+ }
9675
+ if (!isGithubCopilotSdkRequest(input.request)) {
9676
+ return providerAuthResult(input.request, "unsupported", "Provider link target is not supported by this runner.", input.now);
9677
+ }
9678
+ const checkedAt = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
9679
+ let client;
9680
+ try {
9681
+ client = await (input.createCopilotClient ?? createDefaultCopilotClient)({ cwd: input.root, logLevel: "error", useLoggedInUser: true });
9682
+ await client.start();
9683
+ const authStatus = await client.getAuthStatus();
9684
+ const modelCount = authStatus.isAuthenticated ? await listCopilotModelCount(client) : void 0;
9685
+ const status = {
9686
+ providerId: githubCopilotProviderId,
9687
+ providerClientId: githubCopilotProviderClientId,
9688
+ routeType: githubCopilotRouteType,
9689
+ status: authStatus.isAuthenticated ? "authenticated" : "pendingUserAction",
9690
+ authMethodLabel: authStatus.authType ? `GitHub ${authStatus.authType}` : "GitHub Copilot SDK",
9691
+ ...authStatus.host ? { accountHost: authStatus.host } : {},
9692
+ ...authStatus.login ? { accountLogin: authStatus.login } : {},
9693
+ ...authStatus.host && authStatus.login ? { accountFingerprint: accountFingerprint(authStatus.host, authStatus.login) } : {},
9694
+ ...modelCount !== void 0 ? { modelCount } : {},
9695
+ checkedAt,
9696
+ message: authStatus.isAuthenticated ? "GitHub Copilot SDK is authenticated on this runner." : "Complete GitHub Copilot auth locally on the runner machine, then check again."
9697
+ };
9698
+ return { succeeded: true, message: status.message, providerAuthStatus: status };
9699
+ } catch {
9700
+ return providerAuthResult(input.request, "unavailable", "GitHub Copilot SDK auth status is unavailable on this runner.", input.now, "copilot_sdk_unavailable");
9701
+ } finally {
9702
+ await client?.stop().catch(() => void 0);
9703
+ }
9704
+ }
9705
+ function isGithubCopilotSdkRequest(request) {
9706
+ return request.providerId === githubCopilotProviderId && request.providerClientId === githubCopilotProviderClientId && request.routeType === githubCopilotRouteType;
9707
+ }
9708
+ function isGithubModelsDirectRequest(request) {
9709
+ return request.providerId === githubModelsProviderId && request.providerClientId === githubModelsProviderClientId && request.routeType === githubModelsRouteType;
9710
+ }
9711
+ function checkGithubModelsDirectAuth(input) {
9712
+ const checkedAt = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
9713
+ const tokenSource = githubModelsTokenSource(input.env ?? process.env);
9714
+ 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.";
9715
+ const status = {
9716
+ providerId: githubModelsProviderId,
9717
+ providerClientId: githubModelsProviderClientId,
9718
+ routeType: githubModelsRouteType,
9719
+ status: tokenSource ? "authenticated" : "notConfigured",
9720
+ authMethodLabel: tokenSource ?? "GITHUB_MODELS_TOKEN or GITHUB_TOKEN",
9721
+ ...tokenSource ? { modelCount: githubModelsSupportedModelIds.length } : {},
9722
+ checkedAt,
9723
+ message
9724
+ };
9725
+ return { succeeded: true, message, providerAuthStatus: status };
9726
+ }
9727
+ function githubModelsTokenSource(env) {
9728
+ if (hasEnvValue(env.GITHUB_MODELS_TOKEN)) return "GITHUB_MODELS_TOKEN";
9729
+ if (hasEnvValue(env.GITHUB_TOKEN)) return "GITHUB_TOKEN";
9730
+ return void 0;
9731
+ }
9732
+ function hasEnvValue(value) {
9733
+ return typeof value === "string" && value.trim().length > 0;
9734
+ }
9735
+ async function createDefaultCopilotClient(options) {
9736
+ const { CopilotClient } = await import("@github/copilot-sdk");
9737
+ return new CopilotClient(options);
9738
+ }
9739
+ async function listCopilotModelCount(client) {
9740
+ try {
9741
+ return (await client.listModels()).length;
9742
+ } catch {
9743
+ return void 0;
9744
+ }
9745
+ }
9746
+ function providerAuthResult(request, status, message, now, errorCode) {
9747
+ return {
9748
+ succeeded: status !== "unsupported",
9749
+ message,
9750
+ providerAuthStatus: {
9751
+ providerId: request.providerId,
9752
+ providerClientId: request.providerClientId,
9753
+ routeType: request.routeType,
9754
+ status,
9755
+ checkedAt: now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
9756
+ message,
9757
+ ...errorCode ? { errorCode } : {}
9758
+ }
9759
+ };
9760
+ }
9761
+ function accountFingerprint(host, login) {
9762
+ return `sha256:${createHash8("sha256").update(`${host.toLowerCase()}\0${login.toLowerCase()}`).digest("hex")}`;
9763
+ }
9764
+
8303
9765
  // src/version.ts
8304
9766
  import { readFileSync } from "node:fs";
8305
9767
  function readCliPackageVersion() {
@@ -8434,7 +9896,7 @@ program.command("import").description("Pair an existing checkout and import lega
8434
9896
  });
8435
9897
  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) => {
8436
9898
  const pairingRoot = await resolvePairingRoot(options.root, { explicitRoot: command.getOptionValueSource("root") === "cli" });
8437
- let repositoryLinkId = options.repositoryLink ?? `repo_${randomUUID()}`;
9899
+ let repositoryLinkId = options.repositoryLink ?? `repo_${randomUUID2()}`;
8438
9900
  let credential = options.token;
8439
9901
  if (options.pairingCode) {
8440
9902
  const pairing = await new ApiClient({
@@ -8620,7 +10082,7 @@ work.command("prompt").description("Print or write an approved work prompt witho
8620
10082
  }
8621
10083
  const prompt = await createRunnerWorkPrompt(context.client, context.metadata.amistioProjectId, workItem);
8622
10084
  if (options.out) {
8623
- await writeFile10(options.out, prompt, "utf8");
10085
+ await writeFile11(options.out, prompt, "utf8");
8624
10086
  console.log(`Wrote work prompt to ${options.out}.`);
8625
10087
  } else {
8626
10088
  console.log(prompt);
@@ -8634,6 +10096,51 @@ program.command("tools").description("List local AI coding tools that the Amisti
8634
10096
  }
8635
10097
  console.log("custom - pass --tool-command to use any other local runner command.");
8636
10098
  });
10099
+ var hostHelper = program.command("host-helper").description("Inspect the optional Amistio host helper used for stronger local execution primitives");
10100
+ 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) => {
10101
+ const config = options.path?.trim() ? { command: options.path.trim() } : nativeHostHelperConfigFromEnvironment();
10102
+ console.log(`Protocol: ${hostExecutionProtocolVersion}`);
10103
+ if (!config) {
10104
+ console.log("Helper: not configured");
10105
+ console.log("Default execution: Node host port, no PTY, no sandbox.");
10106
+ 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.");
10107
+ return;
10108
+ }
10109
+ console.log(`Helper: ${config.command}`);
10110
+ const discovery = await discoverNativeHostHelper(config);
10111
+ if (!discovery.ok) {
10112
+ console.log(`Status: ${discovery.failure.code}`);
10113
+ console.log(`Reason: ${discovery.failure.message}`);
10114
+ if (discovery.failure.stderr?.trim()) console.log(`stderr: ${truncateLogExcerpt(discovery.failure.stderr)}`);
10115
+ process.exitCode = 1;
10116
+ return;
10117
+ }
10118
+ const { capabilities, secretBoundary } = discovery.helper.handshake;
10119
+ console.log("Status: ready");
10120
+ console.log(`Implementation: ${capabilities.implementation}`);
10121
+ console.log(formatHostHelperCapability("Process groups", capabilities.processGroups));
10122
+ console.log(formatHostHelperCapability("Signal escalation", capabilities.signalEscalation));
10123
+ console.log(formatHostHelperCapability("Stream capture", capabilities.streamCapture));
10124
+ console.log(formatHostHelperCapability("PTY", capabilities.pty));
10125
+ console.log(formatHostHelperCapability("Sandbox", capabilities.sandbox));
10126
+ console.log(`Secret boundary: ${secretBoundary.credentialPolicy}, ${secretBoundary.environmentPolicy}`);
10127
+ });
10128
+ 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) => {
10129
+ const config = options.path?.trim() ? { command: options.path.trim() } : nativeHostHelperConfigFromEnvironment();
10130
+ if (!config) {
10131
+ console.log("No host helper configured. Set AMISTIO_HOST_HELPER_PATH or pass --path.");
10132
+ process.exitCode = 1;
10133
+ return;
10134
+ }
10135
+ const report = await runHostHelperConformance(config);
10136
+ console.log(`Host helper conformance: ${report.passed ? "passed" : "failed"}`);
10137
+ for (const check of report.checks) {
10138
+ console.log(` ${check.passed ? "ok" : "fail"} ${check.name}: ${check.detail}`);
10139
+ }
10140
+ if (!report.passed) {
10141
+ process.exitCode = 1;
10142
+ }
10143
+ });
8637
10144
  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) => {
8638
10145
  const goal = goalParts?.join(" ").trim() || "Review the current repository state and update the Amistio control plane with the next useful orchestration steps.";
8639
10146
  const prompt = await createOrchestrationPrompt({ rootDir: options.root, goal });
@@ -8657,7 +10164,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
8657
10164
  ...options.toolCommand ? { toolCommand: options.toolCommand } : {},
8658
10165
  ...localModelConfig,
8659
10166
  streamOutput: options.stream,
8660
- ...sessionPolicy === "none" ? {} : { session: { toolSessionId: `local_orchestration_${randomUUID()}`, policy: sessionPolicy, decision: localSessionDecision(sessionPolicy) } }
10167
+ ...sessionPolicy === "none" ? {} : { session: { toolSessionId: `local_orchestration_${randomUUID2()}`, policy: sessionPolicy, decision: localSessionDecision(sessionPolicy) } }
8661
10168
  });
8662
10169
  if (!options.stream && result.stdout.trim()) {
8663
10170
  console.log(result.stdout.trim());
@@ -8703,7 +10210,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
8703
10210
  projectId: context.metadata.amistioProjectId,
8704
10211
  repositoryLinkId: context.metadata.repositoryLinkId,
8705
10212
  runnerId,
8706
- rootDir: path16.resolve(options.root),
10213
+ rootDir: path17.resolve(options.root),
8707
10214
  apiUrl: options.apiUrl,
8708
10215
  args: buildBackgroundRunnerArgs(resolvedOptions)
8709
10216
  });
@@ -8756,6 +10263,9 @@ program.command("run").description("Claim and run approved Amistio work locally"
8756
10263
  }
8757
10264
  }
8758
10265
  if (result.stopRunner) {
10266
+ if (result.exitCode !== 0) {
10267
+ process.exitCode = result.exitCode;
10268
+ }
8759
10269
  return;
8760
10270
  }
8761
10271
  if (options.maxIterations !== void 0 && iterations >= options.maxIterations) {
@@ -8894,7 +10404,7 @@ runnerService.command("install").description("Install a user-level startup servi
8894
10404
  projectId: context.metadata.amistioProjectId,
8895
10405
  repositoryLinkId: context.metadata.repositoryLinkId,
8896
10406
  runnerId,
8897
- rootDir: path16.resolve(options.root),
10407
+ rootDir: path17.resolve(options.root),
8898
10408
  apiUrl: options.apiUrl,
8899
10409
  args,
8900
10410
  platform
@@ -8910,7 +10420,7 @@ runnerService.command("install").description("Install a user-level startup servi
8910
10420
  console.log(`Installed startup service ${metadata.serviceName}.`);
8911
10421
  console.log(`Service file: ${metadata.serviceFilePath}`);
8912
10422
  } catch (error) {
8913
- console.error(errorMessage3(error));
10423
+ console.error(errorMessage6(error));
8914
10424
  process.exitCode = 1;
8915
10425
  }
8916
10426
  });
@@ -9022,26 +10532,18 @@ async function runWatchIteration({ command, context, options, runnerId }) {
9022
10532
  throw error;
9023
10533
  }
9024
10534
  const detail = truncateLogExcerpt(errorDetail(error));
9025
- const message = "Runner hit an error while watching and will keep listening.";
9026
- if (options.verbose) {
9027
- console.error(`${message}
9028
- ${detail}`);
9029
- } else {
9030
- console.error(`${message} Run with --verbose for details.`);
9031
- }
9032
- const settlements = await Promise.allSettled([
9033
- context.client.sendRunnerHeartbeat(context.metadata.amistioProjectId, runnerId, context.metadata.repositoryLinkId, "blocked", { ...runnerHeartbeatMetadata(), preferenceMessage: message }),
9034
- context.client.recordRunnerLog(context.metadata.amistioProjectId, {
9035
- runnerId,
9036
- repositoryLinkId: context.metadata.repositoryLinkId,
9037
- status: "failed",
9038
- message,
9039
- error: detail,
9040
- machineId: runnerMachineId()
9041
- })
9042
- ]);
9043
- logRejectedSettlements("record watch error", settlements);
9044
- return { status: "failed", exitCode: 1, message };
10535
+ return await handleRunnerWatchError({
10536
+ client: context.client,
10537
+ detail,
10538
+ error,
10539
+ heartbeatMetadata: runnerHeartbeatMetadata(),
10540
+ logRejectedSettlements,
10541
+ machineId: runnerMachineId(),
10542
+ projectId: context.metadata.amistioProjectId,
10543
+ repositoryLinkId: context.metadata.repositoryLinkId,
10544
+ runnerId,
10545
+ ...options.verbose !== void 0 ? { verbose: options.verbose } : {}
10546
+ });
9045
10547
  }
9046
10548
  }
9047
10549
  function claimLaneIds(maxConcurrentWork) {
@@ -9053,12 +10555,12 @@ function supportsConcurrentLocalToolExecution(toolConfig) {
9053
10555
  function aggregateRunnerLaneResults(results) {
9054
10556
  const stopResult = results.find((result) => result.stopRunner);
9055
10557
  if (stopResult) return stopResult;
9056
- const failedResult = results.find((result) => result.status === "failed");
9057
- if (failedResult) return failedResult;
10558
+ const failedResult2 = results.find((result) => result.status === "failed");
10559
+ if (failedResult2) return failedResult2;
9058
10560
  const blockedResult = results.find((result) => result.status === "blocked");
9059
10561
  if (blockedResult) return blockedResult;
9060
- const completedResult = results.find((result) => result.status === "completed" || result.status === "preview");
9061
- if (completedResult) return completedResult;
10562
+ const completedResult2 = results.find((result) => result.status === "completed" || result.status === "preview");
10563
+ if (completedResult2) return completedResult2;
9062
10564
  return results.find((result) => result.status === "idle") ?? { status: "idle", exitCode: 0 };
9063
10565
  }
9064
10566
  async function runAutoSyncCycle({ context, maxFileKb, quiet, quietDisabled, root, runnerId }) {
@@ -9110,7 +10612,7 @@ async function runAutoSyncCycle({ context, maxFileKb, quiet, quietDisabled, root
9110
10612
  }
9111
10613
  return { status, message, pushedCount, skippedCount, conflictCount, collection };
9112
10614
  } catch (error) {
9113
- const message = `Auto-sync failed: ${errorMessage3(error)}`;
10615
+ const message = `Auto-sync failed: ${errorMessage6(error)}`;
9114
10616
  await context.client.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "blocked", { ...heartbeatBase, autoSyncStatus: "failed", autoSyncMessage: message, autoSyncLastFailureAt: startedAt }).catch(() => void 0);
9115
10617
  return { status: "failed", message, pushedCount: 0, skippedCount: 0, conflictCount: 0 };
9116
10618
  }
@@ -9213,7 +10715,20 @@ async function runNextWorkItem({
9213
10715
  ...isolationTelemetry.executionBranch ? { currentBranch: isolationTelemetry.executionBranch } : {}
9214
10716
  });
9215
10717
  const resolvedModelConfig = toolConfigModelOptions(toolConfig);
9216
- const preview = await createToolRunPreview({ rootDir: executionRoot, prompt, tool: toolConfig.tool, invocationChannel: toolConfig.requestedInvocationChannel ?? "auto", ...toolCommand ? { toolCommand } : {}, ...resolvedModelConfig });
10718
+ const preparedHarnessRun = await builtinAmistioHarnessAdapter.createRunPreview({
10719
+ rootDir: executionRoot,
10720
+ prompt,
10721
+ tool: toolConfig.tool,
10722
+ invocationChannel: toolConfig.requestedInvocationChannel ?? "auto",
10723
+ ...toolCommand ? { toolCommand } : {},
10724
+ ...resolvedModelConfig,
10725
+ executionPolicy: {
10726
+ executionRoot,
10727
+ mutationPolicy: harnessMutationPolicyForWorkKind(result.workItem.workKind),
10728
+ claimLaneId
10729
+ }
10730
+ });
10731
+ const preview = preparedHarnessRun.preview;
9217
10732
  const sessionContext = await prepareToolSession({
9218
10733
  apiClient,
9219
10734
  projectId,
@@ -9237,7 +10752,15 @@ async function runNextWorkItem({
9237
10752
  status: "running",
9238
10753
  summary: `Local runner started ${preview.toolName} execution.`,
9239
10754
  idempotencyKey: `runner_milestone_started_${result.workItem.workItemId}_${result.workItem.attempt}`,
9240
- metadata: { tool: preview.toolName, invocationChannel: toolConfig.requestedInvocationChannel ?? "auto" }
10755
+ metadata: {
10756
+ tool: preview.toolName,
10757
+ invocationChannel: toolConfig.requestedInvocationChannel ?? "auto",
10758
+ claimLaneId: preparedHarnessRun.concurrency.claimLaneId,
10759
+ harnessSupportsConcurrentRuns: preparedHarnessRun.concurrency.supportsConcurrentRuns,
10760
+ harnessRequiresExclusiveRepository: preparedHarnessRun.concurrency.requiresExclusiveRepository,
10761
+ harnessRequiresExclusiveProviderAuth: preparedHarnessRun.concurrency.requiresExclusiveProviderAuth,
10762
+ harnessSerializedResourceKeys: preparedHarnessRun.concurrency.serializedResourceKeys
10763
+ }
9241
10764
  });
9242
10765
  const startedAt = Date.now();
9243
10766
  const providerSessionStore = new LocalToolSessionStore();
@@ -9245,7 +10768,8 @@ async function runNextWorkItem({
9245
10768
  let toolResult;
9246
10769
  const stopLeaseRenewal = startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerId, toolConfig, workItem: result.workItem, telemetry: isolationTelemetry, heartbeatConcurrency });
9247
10770
  try {
9248
- toolResult = await runLocalTool({
10771
+ toolResult = await builtinAmistioHarnessAdapter.executeRun({
10772
+ preparedRun: preparedHarnessRun,
9249
10773
  rootDir: executionRoot,
9250
10774
  prompt,
9251
10775
  tool: toolConfig.tool,
@@ -9270,7 +10794,7 @@ async function runNextWorkItem({
9270
10794
  const message = `${preview.toolName} failed before returning a result.`;
9271
10795
  const settlements = await Promise.allSettled([
9272
10796
  apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig, currentRunnerMode(), heartbeatConcurrency)),
9273
- markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage3(error)),
10797
+ markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage6(error)),
9274
10798
  apiClient.updateWorkStatus(projectId, result.workItem.workItemId, "failed", `run_failed_${result.workItem.workItemId}_${result.workItem.attempt}_${runnerId}`, runnerId, {
9275
10799
  ...isolationTelemetry,
9276
10800
  tool: preview.toolName,
@@ -9564,7 +11088,7 @@ async function runNextWorkItem({
9564
11088
  projectId,
9565
11089
  result.workItem.workItemId,
9566
11090
  finalStatus,
9567
- `run_${result.workItem.workItemId}_${randomUUID()}`,
11091
+ `run_${result.workItem.workItemId}_${randomUUID2()}`,
9568
11092
  runnerId,
9569
11093
  {
9570
11094
  tool: preview.toolName,
@@ -9654,11 +11178,11 @@ async function prepareWorktreeForClaimedItem({ apiClient, heartbeatConcurrency,
9654
11178
  });
9655
11179
  return { status: "ready", isolation };
9656
11180
  } catch (error) {
9657
- const message = errorMessage3(error);
11181
+ const message = errorMessage6(error);
9658
11182
  const telemetry = workItemIsolationTelemetry(workItem, { ...identity, baseRevision: workItem.baseRevision ?? "unknown", worktreePath: "" });
9659
11183
  const finalAttempt = workItem.attempt >= maxPreflightAttempts;
9660
11184
  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}`;
9661
- const statusResult = await apiClient.updateWorkStatus(projectId, workItem.workItemId, finalAttempt ? "failed" : "approved", `worktree_${finalAttempt ? "failed" : "retry"}_${workItem.workItemId}_${workItem.attempt}_${randomUUID()}`, runnerId, {
11185
+ const statusResult = await apiClient.updateWorkStatus(projectId, workItem.workItemId, finalAttempt ? "failed" : "approved", `worktree_${finalAttempt ? "failed" : "retry"}_${workItem.workItemId}_${workItem.attempt}_${randomUUID2()}`, runnerId, {
9662
11186
  ...telemetry,
9663
11187
  message: statusMessage,
9664
11188
  ...finalAttempt ? { blockerReason: message } : { releaseClaim: true },
@@ -9726,8 +11250,8 @@ async function recordFinalizationFailure({ apiClient, durationMs, error, isolati
9726
11250
  const message = `${toolName} completed, but Amistio could not finalize the result. ${safeFinalizationFailureSummary(error)}`;
9727
11251
  const settlements = await Promise.allSettled([
9728
11252
  apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)),
9729
- markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage3(error)),
9730
- apiClient.updateWorkStatus(projectId, workItem.workItemId, "failed", `finalize_failed_${workItem.workItemId}_${workItem.attempt}_${randomUUID()}`, runnerId, {
11253
+ markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage6(error)),
11254
+ apiClient.updateWorkStatus(projectId, workItem.workItemId, "failed", `finalize_failed_${workItem.workItemId}_${workItem.attempt}_${randomUUID2()}`, runnerId, {
9731
11255
  ...isolationTelemetry,
9732
11256
  tool: toolName,
9733
11257
  durationMs,
@@ -9845,7 +11369,7 @@ function autopilotWorkMetadata2(workItem) {
9845
11369
  function logRejectedSettlements(action, settlements) {
9846
11370
  for (const settlement of settlements) {
9847
11371
  if (settlement.status === "rejected") {
9848
- console.error(`${action} failed: ${errorMessage3(settlement.reason)}`);
11372
+ console.error(`${action} failed: ${errorMessage6(settlement.reason)}`);
9849
11373
  }
9850
11374
  }
9851
11375
  }
@@ -9858,7 +11382,13 @@ async function runPendingRunnerCommand(apiClient, context, heartbeatMetadata) {
9858
11382
  await updateRunnerCommandStatus(apiClient, context, command, "acknowledged", "Command acknowledged by local runner.");
9859
11383
  await updateRunnerCommandStatus(apiClient, context, command, "running", `Running ${runnerCommandLabel(command.commandKind)} command.`);
9860
11384
  const result = await executeRunnerCommand(apiClient, command, context);
9861
- await updateRunnerCommandStatus(apiClient, context, command, result.succeeded ? "completed" : "failed", result.message, result.error);
11385
+ await updateRunnerCommandStatus(apiClient, context, command, result.succeeded ? "completed" : "failed", result.message, result.error, result.providerAuthStatus);
11386
+ if (result.providerAuthStatus) {
11387
+ await apiClient.sendRunnerHeartbeat(context.projectId, context.runnerId, context.repositoryLinkId, "online", {
11388
+ ...heartbeatMetadata,
11389
+ providerAuthStatuses: mergeProviderAuthStatuses(heartbeatMetadata.providerAuthStatuses, result.providerAuthStatus)
11390
+ }).catch(() => void 0);
11391
+ }
9862
11392
  if (command.commandKind === "remove" && result.succeeded) {
9863
11393
  await new LocalCredentialStore().delete(credentialKey(context.accountId, context.projectId, context.repositoryLinkId));
9864
11394
  }
@@ -9867,14 +11397,15 @@ async function runPendingRunnerCommand(apiClient, context, heartbeatMetadata) {
9867
11397
  }
9868
11398
  return { handled: true, succeeded: result.succeeded, message: result.message, ...result.stopRunner ? { stopRunner: true } : {} };
9869
11399
  }
9870
- async function updateRunnerCommandStatus(apiClient, context, command, status, message, error) {
11400
+ async function updateRunnerCommandStatus(apiClient, context, command, status, message, error, providerAuthStatus) {
9871
11401
  const result = await apiClient.updateRunnerCommand(context.projectId, command.commandId, {
9872
11402
  runnerId: context.runnerId,
9873
11403
  repositoryLinkId: context.repositoryLinkId,
9874
11404
  status,
9875
- idempotencyKey: `runner_command_${command.commandId}_${status}_${randomUUID()}`,
11405
+ idempotencyKey: `runner_command_${command.commandId}_${status}_${randomUUID2()}`,
9876
11406
  message,
9877
- ...error ? { error } : {}
11407
+ ...error ? { error } : {},
11408
+ ...providerAuthStatus ? { providerAuthStatus } : {}
9878
11409
  });
9879
11410
  return result.command;
9880
11411
  }
@@ -9888,11 +11419,21 @@ async function executeRunnerCommand(apiClient, command, context) {
9888
11419
  if (command.commandKind === "implementationHandoffRecovery") {
9889
11420
  return executeImplementationHandoffRecoveryCommand(apiClient, command, context);
9890
11421
  }
11422
+ if (command.commandKind === "providerAuthLinkRequested") {
11423
+ return executeProviderAuthLinkCommand(command, context);
11424
+ }
9891
11425
  return runOfficialCliUpdateWithRuntimeRefresh({
9892
11426
  mode: currentRunnerMode(),
9893
11427
  restartBackgroundRunner: () => restartCurrentRunner(context, { useUpdatedCliExecutable: true })
9894
11428
  });
9895
11429
  }
11430
+ async function executeProviderAuthLinkCommand(command, context) {
11431
+ if (!command.providerAuthLinkRequest) {
11432
+ return { succeeded: false, message: "Provider link command is missing provider auth metadata." };
11433
+ }
11434
+ const result = await checkProviderAuthLink({ request: command.providerAuthLinkRequest, root: context.root });
11435
+ return { succeeded: result.succeeded, message: result.message, providerAuthStatus: result.providerAuthStatus };
11436
+ }
9896
11437
  async function executeImplementationHandoffRecoveryCommand(apiClient, command, context) {
9897
11438
  if (!command.workItemId || !command.handoffRecoveryAction) {
9898
11439
  return { succeeded: false, message: "Handoff recovery command is missing a scoped work item or action." };
@@ -9916,7 +11457,7 @@ async function executeImplementationHandoffRecoveryCommand(apiClient, command, c
9916
11457
  }
9917
11458
  return { succeeded: false, message: "Handoff recovery action is not a runner-local command." };
9918
11459
  } catch (error) {
9919
- return { succeeded: false, message: "Handoff recovery command failed locally.", error: errorMessage3(error) };
11460
+ return { succeeded: false, message: "Handoff recovery command failed locally.", error: errorMessage6(error) };
9920
11461
  }
9921
11462
  }
9922
11463
  async function retryImplementationHandoff(apiClient, context, workItem) {
@@ -9950,7 +11491,7 @@ async function findRecoveryWorkItem(apiClient, projectId, workItemId) {
9950
11491
  return workItems.find((item) => item.workItemId === workItemId);
9951
11492
  }
9952
11493
  async function submitRecoveredHandoff(apiClient, context, workItem, handoff, action) {
9953
- await apiClient.updateWorkStatus(context.projectId, workItem.workItemId, recoveredHandoffWorkStatus(handoff), `handoff_recovery_${workItem.workItemId}_${action}_${randomUUID()}`, context.runnerId, {
11494
+ await apiClient.updateWorkStatus(context.projectId, workItem.workItemId, recoveredHandoffWorkStatus(handoff), `handoff_recovery_${workItem.workItemId}_${action}_${randomUUID2()}`, context.runnerId, {
9954
11495
  implementationHandoff: handoff,
9955
11496
  ...handoff.message ? { message: handoff.message } : {},
9956
11497
  ...workItem.controllingAdrId ? { controllingAdrId: workItem.controllingAdrId } : {},
@@ -9984,15 +11525,20 @@ async function restartCurrentRunner(context, options = {}) {
9984
11525
  const replacement = await restartRunnerDaemonProcess(metadata, context.backgroundArgs, options.useUpdatedCliExecutable ? updatedCliRunnerLaunchOptions() : {});
9985
11526
  return { succeeded: true, stopRunner: true, message: `Replacement background runner started with PID ${replacement.pid}.` };
9986
11527
  } catch (error) {
9987
- return { succeeded: false, message: "Background restart failed.", error: errorMessage3(error) };
11528
+ return { succeeded: false, message: "Background restart failed.", error: errorMessage6(error) };
9988
11529
  }
9989
11530
  }
9990
11531
  function runnerCommandLabel(commandKind) {
9991
11532
  if (commandKind === "update") return "update";
9992
11533
  if (commandKind === "restart") return "restart";
9993
11534
  if (commandKind === "implementationHandoffRecovery") return "handoff recovery";
11535
+ if (commandKind === "providerAuthLinkRequested") return "provider auth link";
9994
11536
  return "remove";
9995
11537
  }
11538
+ function mergeProviderAuthStatuses(existingStatuses, nextStatus) {
11539
+ const statuses = existingStatuses?.filter((status) => status.providerId !== nextStatus.providerId || status.providerClientId !== nextStatus.providerClientId || status.routeType !== nextStatus.routeType) ?? [];
11540
+ return [...statuses, nextStatus].slice(-20);
11541
+ }
9996
11542
  async function replayPendingBrainGenerationFinalizations({ accountId, apiClient, projectId, repositoryLinkId, runnerId }) {
9997
11543
  const pendingEntries = await listPendingBrainGenerationFinalizations({ accountId, projectId, repositoryLinkId, runnerId });
9998
11544
  if (!pendingEntries.length) {
@@ -10038,7 +11584,7 @@ async function submitBrainGenerationFinalizationEntry(apiClient, entry, options
10038
11584
  try {
10039
11585
  result = await apiClient.submitBrainGenerationResult(entry.projectId, entry.workItemId, entry.result);
10040
11586
  } catch (error) {
10041
- const detail = truncateLogExcerpt(errorMessage3(error));
11587
+ const detail = truncateLogExcerpt(errorMessage6(error));
10042
11588
  if (isRetryableApiError(error)) {
10043
11589
  const updated = await markBrainGenerationFinalizationRetry(entry, detail);
10044
11590
  const message2 = `Pending brain generation finalization ${entry.workItemId} could not be replayed yet (${updated.retryCount} attempt${updated.retryCount === 1 ? "" : "s"}): ${detail}`;
@@ -10051,7 +11597,7 @@ async function submitBrainGenerationFinalizationEntry(apiClient, entry, options
10051
11597
  return { status: "failed", message, retryable: false };
10052
11598
  }
10053
11599
  await deleteBrainGenerationFinalizationEntry(entry).catch((error) => {
10054
- console.error(`delete pending brain generation finalization ${entry.workItemId} failed: ${errorMessage3(error)}`);
11600
+ console.error(`delete pending brain generation finalization ${entry.workItemId} failed: ${errorMessage6(error)}`);
10055
11601
  });
10056
11602
  if (options.recordReplayTelemetry) {
10057
11603
  await recordRunnerMilestone(apiClient, entry.projectId, result.workItem, entry.runnerId, entry.repositoryLinkId, {
@@ -10072,7 +11618,7 @@ async function submitDurableResultFinalizationEntry(apiClient, entry, options =
10072
11618
  try {
10073
11619
  response = await submitDurableResultMutation(apiClient, entry);
10074
11620
  } catch (error) {
10075
- const detail = truncateLogExcerpt(errorMessage3(error));
11621
+ const detail = truncateLogExcerpt(errorMessage6(error));
10076
11622
  if (isRetryableApiError(error)) {
10077
11623
  const updated = await markDurableResultFinalizationRetry(entry, detail);
10078
11624
  const message2 = `Pending ${runnerResultFinalizationLabel(entry)} finalization ${entry.workItemId} could not be replayed yet (${updated.retryCount} attempt${updated.retryCount === 1 ? "" : "s"}): ${detail}`;
@@ -10086,7 +11632,7 @@ async function submitDurableResultFinalizationEntry(apiClient, entry, options =
10086
11632
  }
10087
11633
  const workItem = durableResultResponseWorkItem(response);
10088
11634
  await deleteDurableResultFinalizationEntry(entry).catch((error) => {
10089
- console.error(`delete pending ${runnerResultFinalizationLabel(entry)} finalization ${entry.workItemId} failed: ${errorMessage3(error)}`);
11635
+ console.error(`delete pending ${runnerResultFinalizationLabel(entry)} finalization ${entry.workItemId} failed: ${errorMessage6(error)}`);
10090
11636
  });
10091
11637
  if (options.recordReplayTelemetry) {
10092
11638
  await recordRunnerMilestone(apiClient, entry.projectId, workItem, entry.runnerId, entry.repositoryLinkId, {
@@ -10211,7 +11757,7 @@ async function finalizeBrainGenerationWork({
10211
11757
  artifacts = parseBrainGenerationArtifacts(`${toolResult.stdout}
10212
11758
  ${toolResult.stderr}`);
10213
11759
  } catch (error) {
10214
- generationError = errorMessage3(error);
11760
+ generationError = errorMessage6(error);
10215
11761
  }
10216
11762
  } else {
10217
11763
  generationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10229,7 +11775,7 @@ ${toolResult.stderr}`);
10229
11775
  const resultMutation = {
10230
11776
  status: "completed",
10231
11777
  runnerId,
10232
- idempotencyKey: `generation_${workItem.workItemId}_${workItem.attempt}_${randomUUID()}`,
11778
+ idempotencyKey: `generation_${workItem.workItemId}_${workItem.attempt}_${randomUUID2()}`,
10233
11779
  artifacts,
10234
11780
  tool: toolName,
10235
11781
  durationMs,
@@ -10278,7 +11824,7 @@ ${toolResult.stderr}`);
10278
11824
  metadata: { tool: toolName, durationMs, artifactCount: replay.documentCount, verificationSummary: "Generated artifacts were accepted by the Amistio API for review." }
10279
11825
  });
10280
11826
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)).catch((error) => {
10281
- console.error(`send generation completion heartbeat failed: ${errorMessage3(error)}`);
11827
+ console.error(`send generation completion heartbeat failed: ${errorMessage6(error)}`);
10282
11828
  });
10283
11829
  console.log(workItem.workKind === "planRevision" ? "Revised plan returned for review." : `Generated ${replay.documentCount} brain artifact${replay.documentCount === 1 ? "" : "s"} for review.`);
10284
11830
  return { status: "completed", exitCode: 0 };
@@ -10301,10 +11847,10 @@ ${toolResult.stderr}`);
10301
11847
  ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
10302
11848
  ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
10303
11849
  };
10304
- const failedResult = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
11850
+ const failedResult2 = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
10305
11851
  status: "failed",
10306
11852
  runnerId,
10307
- idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
11853
+ idempotencyKey: `generation_${workItem.workItemId}_${randomUUID2()}`,
10308
11854
  tool: toolName,
10309
11855
  durationMs,
10310
11856
  ...failedSessionTelemetry,
@@ -10314,7 +11860,7 @@ ${toolResult.stderr}`);
10314
11860
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10315
11861
  status: "failed",
10316
11862
  summary: generationError ?? `${toolName} did not produce valid brain artifacts.`,
10317
- idempotencyKey: `runner_milestone_generation_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
11863
+ idempotencyKey: `runner_milestone_generation_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10318
11864
  metadata: { tool: toolName, durationMs, verificationSummary: "Generation output could not be parsed into approved artifact JSON." }
10319
11865
  });
10320
11866
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10343,7 +11889,7 @@ async function finalizeAssistantQuestionWork({
10343
11889
  answerResult = parseAssistantAnswerResult(`${toolResult.stdout}
10344
11890
  ${toolResult.stderr}`);
10345
11891
  } catch (error) {
10346
- answerError = errorMessage3(error);
11892
+ answerError = errorMessage6(error);
10347
11893
  }
10348
11894
  } else {
10349
11895
  answerError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10354,7 +11900,7 @@ ${toolResult.stderr}`);
10354
11900
  const resultMutation = {
10355
11901
  status: "completed",
10356
11902
  runnerId,
10357
- idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID()}`,
11903
+ idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID2()}`,
10358
11904
  answer: answerResult.answer,
10359
11905
  sourceBoundary: answerResult.sourceBoundary,
10360
11906
  citations: answerResult.citations,
@@ -10390,14 +11936,14 @@ ${toolResult.stderr}`);
10390
11936
  const failedMutation = {
10391
11937
  status: "failed",
10392
11938
  runnerId,
10393
- idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID()}`,
11939
+ idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID2()}`,
10394
11940
  tool: toolName,
10395
11941
  durationMs,
10396
11942
  ...sessionTelemetry,
10397
11943
  message: `${toolName} did not produce a valid project knowledge answer.`,
10398
11944
  ...answerError ? { error: answerError } : {}
10399
11945
  };
10400
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
11946
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10401
11947
  accountId: workItem.accountId,
10402
11948
  projectId,
10403
11949
  repositoryLinkId,
@@ -10409,12 +11955,12 @@ ${toolResult.stderr}`);
10409
11955
  idempotencyKey: failedMutation.idempotencyKey,
10410
11956
  result: failedMutation
10411
11957
  });
10412
- if (!failedResult) return { status: "failed", exitCode: 1 };
11958
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10413
11959
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10414
11960
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10415
11961
  status: "failed",
10416
11962
  summary: answerError ?? `${toolName} did not produce a valid project knowledge answer.`,
10417
- idempotencyKey: `runner_milestone_assistant_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
11963
+ idempotencyKey: `runner_milestone_assistant_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10418
11964
  metadata: { tool: toolName, durationMs, verificationSummary: "Assistant output did not include a valid structured answer." }
10419
11965
  });
10420
11966
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10441,7 +11987,7 @@ async function finalizeImpactPreviewWork({
10441
11987
  report = parseImpactPreviewResult(`${toolResult.stdout}
10442
11988
  ${toolResult.stderr}`);
10443
11989
  } catch (error) {
10444
- previewError = errorMessage3(error);
11990
+ previewError = errorMessage6(error);
10445
11991
  }
10446
11992
  } else {
10447
11993
  previewError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10453,7 +11999,7 @@ ${toolResult.stderr}`);
10453
11999
  const resultMutation = {
10454
12000
  status: "completed",
10455
12001
  runnerId,
10456
- idempotencyKey: `impact_${workItem.workItemId}_${randomUUID()}`,
12002
+ idempotencyKey: `impact_${workItem.workItemId}_${randomUUID2()}`,
10457
12003
  report: {
10458
12004
  ...report,
10459
12005
  analyzedRepoRevision: report.analyzedRepoRevision ?? metadata?.lastSyncedRevision
@@ -10490,14 +12036,14 @@ ${toolResult.stderr}`);
10490
12036
  const failedMutation = {
10491
12037
  status: "failed",
10492
12038
  runnerId,
10493
- idempotencyKey: `impact_${workItem.workItemId}_${randomUUID()}`,
12039
+ idempotencyKey: `impact_${workItem.workItemId}_${randomUUID2()}`,
10494
12040
  tool: toolName,
10495
12041
  durationMs,
10496
12042
  ...sessionTelemetry,
10497
12043
  message: `${toolName} did not produce a valid impact preview.`,
10498
12044
  ...previewError ? { error: previewError } : {}
10499
12045
  };
10500
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12046
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10501
12047
  accountId: workItem.accountId,
10502
12048
  projectId,
10503
12049
  repositoryLinkId,
@@ -10509,12 +12055,12 @@ ${toolResult.stderr}`);
10509
12055
  idempotencyKey: failedMutation.idempotencyKey,
10510
12056
  result: failedMutation
10511
12057
  });
10512
- if (!failedResult) return { status: "failed", exitCode: 1 };
12058
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10513
12059
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10514
12060
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10515
12061
  status: "failed",
10516
12062
  summary: previewError ?? `${toolName} did not produce a valid impact preview.`,
10517
- idempotencyKey: `runner_milestone_impact_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12063
+ idempotencyKey: `runner_milestone_impact_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10518
12064
  metadata: { tool: toolName, durationMs, verificationSummary: "Impact preview output did not include valid structured JSON." }
10519
12065
  });
10520
12066
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10540,7 +12086,7 @@ async function finalizeIssueDiagnosisWork({
10540
12086
  diagnosis = parseIssueDiagnosisResult(`${toolResult.stdout}
10541
12087
  ${toolResult.stderr}`);
10542
12088
  } catch (error) {
10543
- diagnosisError = errorMessage3(error);
12089
+ diagnosisError = errorMessage6(error);
10544
12090
  }
10545
12091
  } else {
10546
12092
  diagnosisError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10551,7 +12097,7 @@ ${toolResult.stderr}`);
10551
12097
  const resultMutation = {
10552
12098
  status: "completed",
10553
12099
  runnerId,
10554
- idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
12100
+ idempotencyKey: `issue_${workItem.workItemId}_${randomUUID2()}`,
10555
12101
  diagnosis,
10556
12102
  tool: toolName,
10557
12103
  durationMs,
@@ -10585,14 +12131,14 @@ ${toolResult.stderr}`);
10585
12131
  const failedMutation = {
10586
12132
  status: "failed",
10587
12133
  runnerId,
10588
- idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
12134
+ idempotencyKey: `issue_${workItem.workItemId}_${randomUUID2()}`,
10589
12135
  tool: toolName,
10590
12136
  durationMs,
10591
12137
  ...sessionTelemetry,
10592
12138
  message: `${toolName} did not produce a valid issue diagnosis.`,
10593
12139
  ...diagnosisError ? { error: diagnosisError } : {}
10594
12140
  };
10595
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12141
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10596
12142
  accountId: workItem.accountId,
10597
12143
  projectId,
10598
12144
  repositoryLinkId,
@@ -10604,12 +12150,12 @@ ${toolResult.stderr}`);
10604
12150
  idempotencyKey: failedMutation.idempotencyKey,
10605
12151
  result: failedMutation
10606
12152
  });
10607
- if (!failedResult) return { status: "failed", exitCode: 1 };
12153
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10608
12154
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10609
12155
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10610
12156
  status: "failed",
10611
12157
  summary: diagnosisError ?? `${toolName} did not produce a valid issue diagnosis.`,
10612
- idempotencyKey: `runner_milestone_issue_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12158
+ idempotencyKey: `runner_milestone_issue_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10613
12159
  metadata: { tool: toolName, durationMs, verificationSummary: "Issue diagnosis output did not include valid structured JSON." }
10614
12160
  });
10615
12161
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10635,7 +12181,7 @@ async function finalizeSecurityPostureScanWork({
10635
12181
  scanResult = parseSecurityPostureScanResult(`${toolResult.stdout}
10636
12182
  ${toolResult.stderr}`);
10637
12183
  } catch (error) {
10638
- scanError = errorMessage3(error);
12184
+ scanError = errorMessage6(error);
10639
12185
  }
10640
12186
  } else {
10641
12187
  scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10646,7 +12192,7 @@ ${toolResult.stderr}`);
10646
12192
  const resultMutation = {
10647
12193
  status: "completed",
10648
12194
  runnerId,
10649
- idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
12195
+ idempotencyKey: `security_${workItem.workItemId}_${randomUUID2()}`,
10650
12196
  result: scanResult,
10651
12197
  tool: toolName,
10652
12198
  durationMs,
@@ -10680,14 +12226,14 @@ ${toolResult.stderr}`);
10680
12226
  const failedMutation = {
10681
12227
  status: "failed",
10682
12228
  runnerId,
10683
- idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
12229
+ idempotencyKey: `security_${workItem.workItemId}_${randomUUID2()}`,
10684
12230
  tool: toolName,
10685
12231
  durationMs,
10686
12232
  ...sessionTelemetry,
10687
12233
  message: `${toolName} did not produce a valid security posture scan.`,
10688
12234
  ...scanError ? { error: scanError } : {}
10689
12235
  };
10690
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12236
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10691
12237
  accountId: workItem.accountId,
10692
12238
  projectId,
10693
12239
  repositoryLinkId,
@@ -10699,12 +12245,12 @@ ${toolResult.stderr}`);
10699
12245
  idempotencyKey: failedMutation.idempotencyKey,
10700
12246
  result: failedMutation
10701
12247
  });
10702
- if (!failedResult) return { status: "failed", exitCode: 1 };
12248
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10703
12249
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10704
12250
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10705
12251
  status: "failed",
10706
12252
  summary: scanError ?? `${toolName} did not produce a valid security posture scan.`,
10707
- idempotencyKey: `runner_milestone_security_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12253
+ idempotencyKey: `runner_milestone_security_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10708
12254
  metadata: { tool: toolName, durationMs, verificationSummary: "Security posture output did not include valid structured JSON." }
10709
12255
  });
10710
12256
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10730,7 +12276,7 @@ async function finalizeAppEvaluationScanWork({
10730
12276
  scanResult = parseAppEvaluationScanResult(`${toolResult.stdout}
10731
12277
  ${toolResult.stderr}`);
10732
12278
  } catch (error) {
10733
- scanError = errorMessage3(error);
12279
+ scanError = errorMessage6(error);
10734
12280
  }
10735
12281
  } else {
10736
12282
  scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10741,7 +12287,7 @@ ${toolResult.stderr}`);
10741
12287
  const resultMutation = {
10742
12288
  status: "completed",
10743
12289
  runnerId,
10744
- idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
12290
+ idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID2()}`,
10745
12291
  result: scanResult,
10746
12292
  tool: toolName,
10747
12293
  durationMs,
@@ -10775,14 +12321,14 @@ ${toolResult.stderr}`);
10775
12321
  const failedMutation = {
10776
12322
  status: "failed",
10777
12323
  runnerId,
10778
- idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
12324
+ idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID2()}`,
10779
12325
  tool: toolName,
10780
12326
  durationMs,
10781
12327
  ...sessionTelemetry,
10782
12328
  message: `${toolName} did not produce a valid app evaluation scan.`,
10783
12329
  ...scanError ? { error: scanError } : {}
10784
12330
  };
10785
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12331
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10786
12332
  accountId: workItem.accountId,
10787
12333
  projectId,
10788
12334
  repositoryLinkId,
@@ -10794,12 +12340,12 @@ ${toolResult.stderr}`);
10794
12340
  idempotencyKey: failedMutation.idempotencyKey,
10795
12341
  result: failedMutation
10796
12342
  });
10797
- if (!failedResult) return { status: "failed", exitCode: 1 };
12343
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10798
12344
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10799
12345
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10800
12346
  status: "failed",
10801
12347
  summary: scanError ?? `${toolName} did not produce a valid app evaluation scan.`,
10802
- idempotencyKey: `runner_milestone_app_evaluation_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12348
+ idempotencyKey: `runner_milestone_app_evaluation_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10803
12349
  metadata: { tool: toolName, durationMs, verificationSummary: "App evaluation output did not include valid structured JSON." }
10804
12350
  });
10805
12351
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10825,7 +12371,7 @@ async function finalizeBrainConsolidationScanWork({
10825
12371
  scanResult = parseBrainConsolidationScanResult(`${toolResult.stdout}
10826
12372
  ${toolResult.stderr}`);
10827
12373
  } catch (error) {
10828
- scanError = errorMessage3(error);
12374
+ scanError = errorMessage6(error);
10829
12375
  }
10830
12376
  } else {
10831
12377
  scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10836,7 +12382,7 @@ ${toolResult.stderr}`);
10836
12382
  const resultMutation = {
10837
12383
  status: "completed",
10838
12384
  runnerId,
10839
- idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID()}`,
12385
+ idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID2()}`,
10840
12386
  result: scanResult,
10841
12387
  tool: toolName,
10842
12388
  durationMs,
@@ -10870,14 +12416,14 @@ ${toolResult.stderr}`);
10870
12416
  const failedMutation = {
10871
12417
  status: "failed",
10872
12418
  runnerId,
10873
- idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID()}`,
12419
+ idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID2()}`,
10874
12420
  tool: toolName,
10875
12421
  durationMs,
10876
12422
  ...sessionTelemetry,
10877
12423
  message: `${toolName} did not produce a valid semantic brain consolidation scan.`,
10878
12424
  ...scanError ? { error: scanError } : {}
10879
12425
  };
10880
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12426
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10881
12427
  accountId: workItem.accountId,
10882
12428
  projectId,
10883
12429
  repositoryLinkId,
@@ -10889,12 +12435,12 @@ ${toolResult.stderr}`);
10889
12435
  idempotencyKey: failedMutation.idempotencyKey,
10890
12436
  result: failedMutation
10891
12437
  });
10892
- if (!failedResult) return { status: "failed", exitCode: 1 };
12438
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
10893
12439
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
10894
12440
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
10895
12441
  status: "failed",
10896
12442
  summary: scanError ?? `${toolName} did not produce a valid semantic brain consolidation scan.`,
10897
- idempotencyKey: `runner_milestone_brain_consolidation_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12443
+ idempotencyKey: `runner_milestone_brain_consolidation_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
10898
12444
  metadata: { tool: toolName, durationMs, verificationSummary: "Brain consolidation output did not include valid structured JSON." }
10899
12445
  });
10900
12446
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -10921,7 +12467,7 @@ async function finalizeProjectContextRefreshWork({
10921
12467
  refreshResult = parseProjectContextRefreshResult(`${toolResult.stdout}
10922
12468
  ${toolResult.stderr}`, { repositoryRoot: executionRoot });
10923
12469
  } catch (error) {
10924
- refreshError = errorMessage3(error);
12470
+ refreshError = errorMessage6(error);
10925
12471
  }
10926
12472
  } else {
10927
12473
  refreshError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -10932,7 +12478,7 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
10932
12478
  const resultMutation = {
10933
12479
  status: "completed",
10934
12480
  runnerId,
10935
- idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
12481
+ idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID2()}`,
10936
12482
  result: refreshResult,
10937
12483
  tool: toolName,
10938
12484
  durationMs,
@@ -10978,14 +12524,14 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
10978
12524
  const failedMutation = {
10979
12525
  status: "failed",
10980
12526
  runnerId,
10981
- idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
12527
+ idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID2()}`,
10982
12528
  tool: toolName,
10983
12529
  durationMs,
10984
12530
  ...sessionTelemetry,
10985
12531
  message: `${toolName} did not produce a valid project context refresh.`,
10986
12532
  ...refreshError ? { error: refreshError } : {}
10987
12533
  };
10988
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12534
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
10989
12535
  accountId: workItem.accountId,
10990
12536
  projectId,
10991
12537
  repositoryLinkId,
@@ -10997,12 +12543,12 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
10997
12543
  idempotencyKey: failedMutation.idempotencyKey,
10998
12544
  result: failedMutation
10999
12545
  });
11000
- if (!failedResult) return { status: "failed", exitCode: 1 };
12546
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
11001
12547
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
11002
12548
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
11003
12549
  status: "failed",
11004
12550
  summary: refreshError ?? `${toolName} did not produce a valid project context refresh.`,
11005
- idempotencyKey: `runner_milestone_project_context_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12551
+ idempotencyKey: `runner_milestone_project_context_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
11006
12552
  metadata: { tool: toolName, durationMs, verificationSummary: "Project context output did not include valid structured JSON." }
11007
12553
  });
11008
12554
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -11028,7 +12574,7 @@ async function finalizeImplementationVerificationWork({
11028
12574
  verificationResult = parseImplementationVerificationResult(`${toolResult.stdout}
11029
12575
  ${toolResult.stderr}`);
11030
12576
  } catch (error) {
11031
- verificationError = errorMessage3(error);
12577
+ verificationError = errorMessage6(error);
11032
12578
  }
11033
12579
  } else {
11034
12580
  verificationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -11039,7 +12585,7 @@ ${toolResult.stderr}`);
11039
12585
  const resultMutation = {
11040
12586
  status: "completed",
11041
12587
  runnerId,
11042
- idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
12588
+ idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID2()}`,
11043
12589
  result: verificationResult,
11044
12590
  tool: toolName,
11045
12591
  durationMs,
@@ -11073,14 +12619,14 @@ ${toolResult.stderr}`);
11073
12619
  const failedMutation = {
11074
12620
  status: "failed",
11075
12621
  runnerId,
11076
- idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
12622
+ idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID2()}`,
11077
12623
  tool: toolName,
11078
12624
  durationMs,
11079
12625
  ...sessionTelemetry,
11080
12626
  message: `${toolName} did not produce valid implementation verification proof.`,
11081
12627
  ...verificationError ? { error: verificationError } : {}
11082
12628
  };
11083
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12629
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
11084
12630
  accountId: workItem.accountId,
11085
12631
  projectId,
11086
12632
  repositoryLinkId,
@@ -11092,12 +12638,12 @@ ${toolResult.stderr}`);
11092
12638
  idempotencyKey: failedMutation.idempotencyKey,
11093
12639
  result: failedMutation
11094
12640
  });
11095
- if (!failedResult) return { status: "failed", exitCode: 1 };
12641
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
11096
12642
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
11097
12643
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
11098
12644
  status: "failed",
11099
12645
  summary: verificationError ?? `${toolName} did not produce valid implementation verification proof.`,
11100
- idempotencyKey: `runner_milestone_implementation_verification_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12646
+ idempotencyKey: `runner_milestone_implementation_verification_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
11101
12647
  metadata: { tool: toolName, durationMs, verificationSummary: "Implementation verification output did not include valid structured JSON." }
11102
12648
  });
11103
12649
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -11123,7 +12669,7 @@ async function finalizeTestQualityScanWork({
11123
12669
  scanResult = parseTestQualityScanResult(`${toolResult.stdout}
11124
12670
  ${toolResult.stderr}`);
11125
12671
  } catch (error) {
11126
- scanError = errorMessage3(error);
12672
+ scanError = errorMessage6(error);
11127
12673
  }
11128
12674
  } else {
11129
12675
  scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -11134,7 +12680,7 @@ ${toolResult.stderr}`);
11134
12680
  const resultMutation = {
11135
12681
  status: "completed",
11136
12682
  runnerId,
11137
- idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID()}`,
12683
+ idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID2()}`,
11138
12684
  result: scanResult,
11139
12685
  tool: toolName,
11140
12686
  durationMs,
@@ -11168,14 +12714,14 @@ ${toolResult.stderr}`);
11168
12714
  const failedMutation = {
11169
12715
  status: "failed",
11170
12716
  runnerId,
11171
- idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID()}`,
12717
+ idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID2()}`,
11172
12718
  tool: toolName,
11173
12719
  durationMs,
11174
12720
  ...sessionTelemetry,
11175
12721
  message: `${toolName} did not produce a valid test quality scan.`,
11176
12722
  ...scanError ? { error: scanError } : {}
11177
12723
  };
11178
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12724
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
11179
12725
  accountId: workItem.accountId,
11180
12726
  projectId,
11181
12727
  repositoryLinkId,
@@ -11187,12 +12733,12 @@ ${toolResult.stderr}`);
11187
12733
  idempotencyKey: failedMutation.idempotencyKey,
11188
12734
  result: failedMutation
11189
12735
  });
11190
- if (!failedResult) return { status: "failed", exitCode: 1 };
12736
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
11191
12737
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
11192
12738
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
11193
12739
  status: "failed",
11194
12740
  summary: scanError ?? `${toolName} did not produce a valid test quality scan.`,
11195
- idempotencyKey: `runner_milestone_test_quality_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12741
+ idempotencyKey: `runner_milestone_test_quality_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
11196
12742
  metadata: { tool: toolName, durationMs, verificationSummary: "Test quality output did not include valid structured JSON." }
11197
12743
  });
11198
12744
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -11218,7 +12764,7 @@ async function finalizeImplementationTestGateWork({
11218
12764
  gateResult = parseImplementationTestGateResult(`${toolResult.stdout}
11219
12765
  ${toolResult.stderr}`);
11220
12766
  } catch (error) {
11221
- gateError = errorMessage3(error);
12767
+ gateError = errorMessage6(error);
11222
12768
  }
11223
12769
  } else {
11224
12770
  gateError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -11229,7 +12775,7 @@ ${toolResult.stderr}`);
11229
12775
  const resultMutation = {
11230
12776
  status: "completed",
11231
12777
  runnerId,
11232
- idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID()}`,
12778
+ idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID2()}`,
11233
12779
  result: gateResult,
11234
12780
  tool: toolName,
11235
12781
  durationMs,
@@ -11263,14 +12809,14 @@ ${toolResult.stderr}`);
11263
12809
  const failedMutation = {
11264
12810
  status: "failed",
11265
12811
  runnerId,
11266
- idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID()}`,
12812
+ idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID2()}`,
11267
12813
  tool: toolName,
11268
12814
  durationMs,
11269
12815
  ...sessionTelemetry,
11270
12816
  message: `${toolName} did not produce a valid implementation test gate result.`,
11271
12817
  ...gateError ? { error: gateError } : {}
11272
12818
  };
11273
- const failedResult = await submitPrimaryRunnerResult(apiClient, {
12819
+ const failedResult2 = await submitPrimaryRunnerResult(apiClient, {
11274
12820
  accountId: workItem.accountId,
11275
12821
  projectId,
11276
12822
  repositoryLinkId,
@@ -11282,12 +12828,12 @@ ${toolResult.stderr}`);
11282
12828
  idempotencyKey: failedMutation.idempotencyKey,
11283
12829
  result: failedMutation
11284
12830
  });
11285
- if (!failedResult) return { status: "failed", exitCode: 1 };
12831
+ if (!failedResult2) return { status: "failed", exitCode: 1 };
11286
12832
  await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
11287
12833
  await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
11288
12834
  status: "failed",
11289
12835
  summary: gateError ?? `${toolName} did not produce a valid implementation test gate result.`,
11290
- idempotencyKey: `runner_milestone_implementation_test_gate_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
12836
+ idempotencyKey: `runner_milestone_implementation_test_gate_failed_${workItem.workItemId}_${failedResult2.workItem.idempotencyKey}`,
11291
12837
  metadata: { tool: toolName, durationMs, verificationSummary: "Implementation test gate output did not include valid structured JSON." }
11292
12838
  });
11293
12839
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
@@ -11492,7 +13038,7 @@ async function prepareToolSession({
11492
13038
  });
11493
13039
  return { ...selection, toolSession: toolSession2 };
11494
13040
  }
11495
- const toolSessionId = `tool_session_${randomUUID()}`;
13041
+ const toolSessionId = `tool_session_${randomUUID2()}`;
11496
13042
  const { toolSession } = await apiClient.createToolSession(projectId, {
11497
13043
  toolSessionId,
11498
13044
  repositoryLinkId,
@@ -11586,7 +13132,7 @@ function summarizeToolOutput(value) {
11586
13132
  }
11587
13133
  return trimmed.length > 300 ? `${trimmed.slice(0, 300)}...` : trimmed;
11588
13134
  }
11589
- function errorMessage3(error) {
13135
+ function errorMessage6(error) {
11590
13136
  return error instanceof Error ? error.message : String(error);
11591
13137
  }
11592
13138
  function errorDetail(error) {
@@ -11596,6 +13142,9 @@ function truncateLogExcerpt(value) {
11596
13142
  const trimmed = value.trim();
11597
13143
  return trimmed.length > 1200 ? `${trimmed.slice(0, 1200)}...` : trimmed;
11598
13144
  }
13145
+ function formatHostHelperCapability(label, support) {
13146
+ return `${label}: ${support.supported ? "supported" : "unsupported"}${support.reason ? ` (${support.reason})` : ""}`;
13147
+ }
11599
13148
  function collectRepeatedOption(value, previous) {
11600
13149
  return [...previous, value];
11601
13150
  }
@@ -11622,10 +13171,10 @@ function parseReasoningEffort(value) {
11622
13171
  throw new Error(`Expected reasoning effort auto, low, medium, high, or xhigh; received ${value}.`);
11623
13172
  }
11624
13173
  function inferRepoName(root) {
11625
- return path16.basename(path16.resolve(root)) || "repository";
13174
+ return path17.basename(path17.resolve(root)) || "repository";
11626
13175
  }
11627
13176
  function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
11628
- return createHash8("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
13177
+ return createHash9("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
11629
13178
  }
11630
13179
  function defaultApiUrl() {
11631
13180
  const envApiUrl = process.env[AMISTIO_API_URL_ENV]?.trim();
@@ -11660,6 +13209,13 @@ async function resolveRunnerToolConfig({ apiClient, explicitInvocationChannel, e
11660
13209
  }
11661
13210
  if (explicitTool === "none") {
11662
13211
  const modelConfig2 = normalizeModelConfig({ model: explicitModel, providerId: explicitProviderId, modelId: explicitModelId, modelVariant: explicitModelVariant, reasoningEffort: explicitReasoningEffort });
13212
+ const directModelResolution = resolveDirectModelClientPreference({ ...modelConfig2, tool: "none", requestedInvocationChannel: explicitInvocationChannel ?? "auto" });
13213
+ if (directModelResolution) {
13214
+ if (!directModelResolution.ready) {
13215
+ return unavailableToolConfig({ capabilities, source: "cli", status: directModelResolution.status, message: directModelResolution.message, tool: "none", requestedInvocationChannel: explicitInvocationChannel ?? "auto", ...modelConfig2 });
13216
+ }
13217
+ return { ready: true, tool: "none", capabilities, source: "cli", status: "resolved", requestedInvocationChannel: explicitInvocationChannel ?? "auto", effectiveInvocationChannel: "sdk", ...directModelResolution.config, message: "Using Amistio direct model client." };
13218
+ }
11663
13219
  if (hasModelConfig(modelConfig2)) {
11664
13220
  return unavailableToolConfig({ capabilities, source: "cli", status: "modelUnsupported", message: "Model configuration cannot be used with --tool none.", tool: "none", requestedInvocationChannel: explicitInvocationChannel ?? "auto", ...modelConfig2 });
11665
13221
  }
@@ -11684,6 +13240,13 @@ async function resolveRunnerToolConfig({ apiClient, explicitInvocationChannel, e
11684
13240
  }
11685
13241
  function resolveRequestedTool({ capabilities, modelConfig, requestedInvocationChannel, requestedTool, source }) {
11686
13242
  const needsModelSelection = hasModelConfig(modelConfig);
13243
+ const directModelResolution = resolveDirectModelClientPreference({ ...modelConfig, tool: requestedTool, requestedInvocationChannel });
13244
+ if (directModelResolution) {
13245
+ if (!directModelResolution.ready) {
13246
+ return unavailableToolConfig({ capabilities, source, status: directModelResolution.status, requestedTool, requestedInvocationChannel, tool: requestedTool, ...modelConfig, message: directModelResolution.message });
13247
+ }
13248
+ return { ready: true, tool: requestedTool, capabilities, source, status: "resolved", requestedTool, requestedInvocationChannel, effectiveInvocationChannel: "sdk", ...directModelResolution.config, message: "Using Amistio direct model client." };
13249
+ }
11687
13250
  if (requestedTool === "auto") {
11688
13251
  const candidate = capabilities.find((capability2) => capability2.available && capabilitySupportsInvocationChannel(capability2, requestedInvocationChannel) && (!needsModelSelection || capability2.supportsModelSelection));
11689
13252
  if (!candidate) {
@@ -11910,7 +13473,7 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode(), concurr
11910
13473
  };
11911
13474
  }
11912
13475
  function runnerMachineId() {
11913
- return createHash8("sha256").update(`${os8.hostname()}:${os8.platform()}:${os8.arch()}`).digest("hex").slice(0, 20);
13476
+ return createHash9("sha256").update(`${os8.hostname()}:${os8.platform()}:${os8.arch()}`).digest("hex").slice(0, 20);
11914
13477
  }
11915
13478
  async function delay(milliseconds) {
11916
13479
  await new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -11919,4 +13482,3 @@ program.parseAsync().catch((error) => {
11919
13482
  console.error(error instanceof Error ? error.message : String(error));
11920
13483
  process.exitCode = 1;
11921
13484
  });
11922
- //# sourceMappingURL=index.js.map