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