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