@amistio/cli 0.1.23 → 0.1.24
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 +2 -0
- package/dist/index.js +428 -157
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
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
|
|
6
|
-
import
|
|
7
|
-
import
|
|
4
|
+
import { createHash as createHash8, randomUUID } from "node:crypto";
|
|
5
|
+
import { writeFile as writeFile10 } from "node:fs/promises";
|
|
6
|
+
import os8 from "node:os";
|
|
7
|
+
import path16 from "node:path";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
|
|
10
10
|
// ../shared/src/schemas.ts
|
|
@@ -2178,6 +2178,24 @@ async function readGitTopLevel(rootDir) {
|
|
|
2178
2178
|
|
|
2179
2179
|
// src/api-client.ts
|
|
2180
2180
|
import { z as z3 } from "zod";
|
|
2181
|
+
var AmistioApiError = class extends Error {
|
|
2182
|
+
constructor(status, statusText, detail) {
|
|
2183
|
+
super(`Amistio API request failed: ${status} ${statusText}${detail ? ` - ${detail}` : ""}`);
|
|
2184
|
+
this.status = status;
|
|
2185
|
+
this.statusText = statusText;
|
|
2186
|
+
this.detail = detail;
|
|
2187
|
+
this.name = "AmistioApiError";
|
|
2188
|
+
}
|
|
2189
|
+
status;
|
|
2190
|
+
statusText;
|
|
2191
|
+
detail;
|
|
2192
|
+
};
|
|
2193
|
+
function isRetryableApiError(error) {
|
|
2194
|
+
if (error instanceof AmistioApiError) {
|
|
2195
|
+
return error.status === 408 || error.status === 429 || error.status >= 500;
|
|
2196
|
+
}
|
|
2197
|
+
return error instanceof TypeError;
|
|
2198
|
+
}
|
|
2181
2199
|
var ApiClient = class {
|
|
2182
2200
|
constructor(options) {
|
|
2183
2201
|
this.options = options;
|
|
@@ -2519,7 +2537,7 @@ var ApiClient = class {
|
|
|
2519
2537
|
});
|
|
2520
2538
|
if (!response.ok) {
|
|
2521
2539
|
const detail = await response.text().catch(() => "");
|
|
2522
|
-
throw new
|
|
2540
|
+
throw new AmistioApiError(response.status, response.statusText, detail);
|
|
2523
2541
|
}
|
|
2524
2542
|
return schema.parse(await response.json());
|
|
2525
2543
|
}
|
|
@@ -2554,8 +2572,8 @@ var toolSessionMutationSchema = z3.object({
|
|
|
2554
2572
|
});
|
|
2555
2573
|
function resolveApiUrl(apiUrl, urlPath) {
|
|
2556
2574
|
const base = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
|
|
2557
|
-
const
|
|
2558
|
-
return new URL(`${base}${
|
|
2575
|
+
const path17 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
|
|
2576
|
+
return new URL(`${base}${path17}`);
|
|
2559
2577
|
}
|
|
2560
2578
|
|
|
2561
2579
|
// src/orchestrator.ts
|
|
@@ -2619,11 +2637,155 @@ async function writePromptFile(filePath, prompt) {
|
|
|
2619
2637
|
return filePath;
|
|
2620
2638
|
}
|
|
2621
2639
|
|
|
2622
|
-
// src/
|
|
2623
|
-
import {
|
|
2624
|
-
import {
|
|
2640
|
+
// src/result-finalization-outbox.ts
|
|
2641
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2642
|
+
import { mkdir as mkdir5, readdir as readdir2, readFile as readFile3, rename, rm, writeFile as writeFile4 } from "node:fs/promises";
|
|
2625
2643
|
import os2 from "node:os";
|
|
2626
2644
|
import path5 from "node:path";
|
|
2645
|
+
import { z as z4 } from "zod";
|
|
2646
|
+
var brainGenerationResultMutationSchema = z4.discriminatedUnion("status", [
|
|
2647
|
+
z4.object({
|
|
2648
|
+
status: z4.literal("completed"),
|
|
2649
|
+
runnerId: z4.string().min(1),
|
|
2650
|
+
idempotencyKey: z4.string().min(1),
|
|
2651
|
+
artifacts: z4.array(generatedBrainArtifactSchema).min(1),
|
|
2652
|
+
tool: z4.string().min(1).optional(),
|
|
2653
|
+
durationMs: z4.number().int().nonnegative().optional(),
|
|
2654
|
+
sessionPolicy: sessionPolicySchema.optional(),
|
|
2655
|
+
sessionGroupKey: z4.string().min(1).optional(),
|
|
2656
|
+
toolSessionId: z4.string().min(1).optional(),
|
|
2657
|
+
sessionDecision: sessionDecisionSchema.optional(),
|
|
2658
|
+
sessionDecisionReason: z4.string().min(1).optional(),
|
|
2659
|
+
message: z4.string().optional()
|
|
2660
|
+
}),
|
|
2661
|
+
z4.object({
|
|
2662
|
+
status: z4.literal("failed"),
|
|
2663
|
+
runnerId: z4.string().min(1),
|
|
2664
|
+
idempotencyKey: z4.string().min(1),
|
|
2665
|
+
tool: z4.string().min(1).optional(),
|
|
2666
|
+
durationMs: z4.number().int().nonnegative().optional(),
|
|
2667
|
+
sessionPolicy: sessionPolicySchema.optional(),
|
|
2668
|
+
sessionGroupKey: z4.string().min(1).optional(),
|
|
2669
|
+
toolSessionId: z4.string().min(1).optional(),
|
|
2670
|
+
sessionDecision: sessionDecisionSchema.optional(),
|
|
2671
|
+
sessionDecisionReason: z4.string().min(1).optional(),
|
|
2672
|
+
message: z4.string().optional(),
|
|
2673
|
+
error: z4.string().optional()
|
|
2674
|
+
})
|
|
2675
|
+
]);
|
|
2676
|
+
var brainGenerationFinalizationEntrySchema = z4.object({
|
|
2677
|
+
schemaVersion: z4.literal(1),
|
|
2678
|
+
kind: z4.literal("brainGenerationResult"),
|
|
2679
|
+
status: z4.enum(["pending", "terminal"]),
|
|
2680
|
+
accountId: z4.string().min(1),
|
|
2681
|
+
projectId: z4.string().min(1),
|
|
2682
|
+
repositoryLinkId: z4.string().min(1),
|
|
2683
|
+
runnerId: z4.string().min(1),
|
|
2684
|
+
workItemId: z4.string().min(1),
|
|
2685
|
+
workKind: z4.enum(["brainGeneration", "planRevision"]),
|
|
2686
|
+
attempt: z4.number().int().nonnegative(),
|
|
2687
|
+
idempotencyKey: z4.string().min(1),
|
|
2688
|
+
result: brainGenerationResultMutationSchema,
|
|
2689
|
+
retryCount: z4.number().int().nonnegative(),
|
|
2690
|
+
createdAt: z4.string().min(1),
|
|
2691
|
+
updatedAt: z4.string().min(1),
|
|
2692
|
+
lastError: z4.string().optional()
|
|
2693
|
+
});
|
|
2694
|
+
function defaultResultFinalizationOutboxDir() {
|
|
2695
|
+
return path5.join(os2.homedir(), ".config", "amistio", "result-finalizations");
|
|
2696
|
+
}
|
|
2697
|
+
function createBrainGenerationFinalizationEntry(input, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2698
|
+
return brainGenerationFinalizationEntrySchema.parse({
|
|
2699
|
+
schemaVersion: 1,
|
|
2700
|
+
kind: "brainGenerationResult",
|
|
2701
|
+
status: "pending",
|
|
2702
|
+
accountId: input.accountId,
|
|
2703
|
+
projectId: input.projectId,
|
|
2704
|
+
repositoryLinkId: input.repositoryLinkId,
|
|
2705
|
+
runnerId: input.runnerId,
|
|
2706
|
+
workItemId: input.workItemId,
|
|
2707
|
+
workKind: input.workKind,
|
|
2708
|
+
attempt: input.attempt,
|
|
2709
|
+
idempotencyKey: input.idempotencyKey,
|
|
2710
|
+
result: input.result,
|
|
2711
|
+
retryCount: 0,
|
|
2712
|
+
createdAt: now,
|
|
2713
|
+
updatedAt: now
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
async function upsertBrainGenerationFinalizationEntry(entry, outboxDir = defaultResultFinalizationOutboxDir()) {
|
|
2717
|
+
await mkdir5(outboxDir, { recursive: true });
|
|
2718
|
+
const filePath = brainGenerationFinalizationEntryPath(entry, outboxDir);
|
|
2719
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
2720
|
+
await writeFile4(tempPath, `${JSON.stringify(entry, null, 2)}
|
|
2721
|
+
`, "utf8");
|
|
2722
|
+
await rename(tempPath, filePath);
|
|
2723
|
+
}
|
|
2724
|
+
async function listPendingBrainGenerationFinalizations(scope, outboxDir = defaultResultFinalizationOutboxDir()) {
|
|
2725
|
+
const entries = await readdir2(outboxDir, { withFileTypes: true }).catch((error) => {
|
|
2726
|
+
if (error.code === "ENOENT") return [];
|
|
2727
|
+
throw error;
|
|
2728
|
+
});
|
|
2729
|
+
const pending = [];
|
|
2730
|
+
for (const entry of entries) {
|
|
2731
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
2732
|
+
const filePath = path5.join(outboxDir, entry.name);
|
|
2733
|
+
let raw;
|
|
2734
|
+
try {
|
|
2735
|
+
raw = JSON.parse(await readFile3(filePath, "utf8"));
|
|
2736
|
+
} catch {
|
|
2737
|
+
continue;
|
|
2738
|
+
}
|
|
2739
|
+
const parsed = brainGenerationFinalizationEntrySchema.safeParse(raw);
|
|
2740
|
+
if (!parsed.success) continue;
|
|
2741
|
+
const value = parsed.data;
|
|
2742
|
+
if (value.status !== "pending") continue;
|
|
2743
|
+
if (value.accountId !== scope.accountId || value.projectId !== scope.projectId || value.repositoryLinkId !== scope.repositoryLinkId || value.runnerId !== scope.runnerId) continue;
|
|
2744
|
+
pending.push(value);
|
|
2745
|
+
}
|
|
2746
|
+
return pending.sort((first, second) => Date.parse(first.createdAt) - Date.parse(second.createdAt));
|
|
2747
|
+
}
|
|
2748
|
+
async function markBrainGenerationFinalizationRetry(entry, error, outboxDir = defaultResultFinalizationOutboxDir()) {
|
|
2749
|
+
const updated = brainGenerationFinalizationEntrySchema.parse({
|
|
2750
|
+
...entry,
|
|
2751
|
+
status: "pending",
|
|
2752
|
+
retryCount: entry.retryCount + 1,
|
|
2753
|
+
lastError: truncateLocalError(error),
|
|
2754
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2755
|
+
});
|
|
2756
|
+
await upsertBrainGenerationFinalizationEntry(updated, outboxDir);
|
|
2757
|
+
return updated;
|
|
2758
|
+
}
|
|
2759
|
+
async function markBrainGenerationFinalizationTerminal(entry, error, outboxDir = defaultResultFinalizationOutboxDir()) {
|
|
2760
|
+
const updated = brainGenerationFinalizationEntrySchema.parse({
|
|
2761
|
+
...entry,
|
|
2762
|
+
status: "terminal",
|
|
2763
|
+
lastError: truncateLocalError(error),
|
|
2764
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2765
|
+
});
|
|
2766
|
+
await upsertBrainGenerationFinalizationEntry(updated, outboxDir);
|
|
2767
|
+
return updated;
|
|
2768
|
+
}
|
|
2769
|
+
async function deleteBrainGenerationFinalizationEntry(entry, outboxDir = defaultResultFinalizationOutboxDir()) {
|
|
2770
|
+
await rm(brainGenerationFinalizationEntryPath(entry, outboxDir), { force: true });
|
|
2771
|
+
}
|
|
2772
|
+
function brainGenerationFinalizationEntryPath(entry, outboxDir) {
|
|
2773
|
+
return path5.join(outboxDir, `${brainGenerationFinalizationEntryKey(entry)}.json`);
|
|
2774
|
+
}
|
|
2775
|
+
function brainGenerationFinalizationEntryKey(entry) {
|
|
2776
|
+
const hash = createHash2("sha256").update([entry.accountId, entry.projectId, entry.repositoryLinkId, entry.runnerId, entry.workItemId, String(entry.attempt), entry.idempotencyKey].join("\0")).digest("hex").slice(0, 32);
|
|
2777
|
+
return `brain-generation-${hash}`;
|
|
2778
|
+
}
|
|
2779
|
+
function truncateLocalError(error) {
|
|
2780
|
+
const trimmed = error.trim();
|
|
2781
|
+
return trimmed.length > 600 ? `${trimmed.slice(0, 600)}...` : trimmed;
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
// src/local-tool-runner.ts
|
|
2785
|
+
import { spawn } from "node:child_process";
|
|
2786
|
+
import { mkdtemp, readFile as readFile4, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
|
|
2787
|
+
import os3 from "node:os";
|
|
2788
|
+
import path6 from "node:path";
|
|
2627
2789
|
var localToolNames = runnerToolNames;
|
|
2628
2790
|
var allReasoningEfforts = ["auto", "low", "medium", "high", "xhigh"];
|
|
2629
2791
|
var highReasoningEfforts = ["auto", "high", "xhigh"];
|
|
@@ -2830,9 +2992,9 @@ async function detectLocalTools() {
|
|
|
2830
2992
|
);
|
|
2831
2993
|
}
|
|
2832
2994
|
async function runLocalTool(options) {
|
|
2833
|
-
const promptTempDir = await mkdtemp(
|
|
2834
|
-
const promptFilePath =
|
|
2835
|
-
await
|
|
2995
|
+
const promptTempDir = await mkdtemp(path6.join(os3.tmpdir(), "amistio-prompt-"));
|
|
2996
|
+
const promptFilePath = path6.join(promptTempDir, "prompt.md");
|
|
2997
|
+
await writeFile5(promptFilePath, options.prompt, "utf8");
|
|
2836
2998
|
const modelConfig = normalizeModelOptions(options);
|
|
2837
2999
|
try {
|
|
2838
3000
|
const runnerOptions = {
|
|
@@ -2864,11 +3026,11 @@ async function runLocalTool(options) {
|
|
|
2864
3026
|
...result
|
|
2865
3027
|
};
|
|
2866
3028
|
} finally {
|
|
2867
|
-
await
|
|
3029
|
+
await rm2(promptTempDir, { recursive: true, force: true });
|
|
2868
3030
|
}
|
|
2869
3031
|
}
|
|
2870
3032
|
async function createToolRunPreview(options) {
|
|
2871
|
-
const promptFilePath =
|
|
3033
|
+
const promptFilePath = path6.join(os3.tmpdir(), "amistio-generated-prompt.md");
|
|
2872
3034
|
const modelConfig = normalizeModelOptions(options);
|
|
2873
3035
|
const runnerOptions = {
|
|
2874
3036
|
rootDir: options.rootDir,
|
|
@@ -3061,13 +3223,13 @@ async function detectProviderCatalog(adapter) {
|
|
|
3061
3223
|
}
|
|
3062
3224
|
async function loadOpencodeProviderCatalog() {
|
|
3063
3225
|
const configPaths = [
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3226
|
+
path6.join(os3.homedir(), ".config", "opencode", "opencode.json"),
|
|
3227
|
+
path6.join(os3.homedir(), ".config", "opencode", "config.json"),
|
|
3228
|
+
path6.join(process.cwd(), "opencode.json")
|
|
3067
3229
|
];
|
|
3068
3230
|
for (const configPath of configPaths) {
|
|
3069
3231
|
try {
|
|
3070
|
-
const parsed = JSON.parse(await
|
|
3232
|
+
const parsed = JSON.parse(await readFile4(configPath, "utf8"));
|
|
3071
3233
|
const providerValue = isRecord(parsed) ? parsed.provider : void 0;
|
|
3072
3234
|
const catalog = sanitizeProviderCatalog(providerValue);
|
|
3073
3235
|
if (catalog) return catalog;
|
|
@@ -3439,16 +3601,16 @@ function shellQuote(value) {
|
|
|
3439
3601
|
|
|
3440
3602
|
// src/runner-daemon.ts
|
|
3441
3603
|
import { spawn as spawn2 } from "node:child_process";
|
|
3442
|
-
import { createHash as
|
|
3604
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
3443
3605
|
import { openSync } from "node:fs";
|
|
3444
|
-
import { mkdir as
|
|
3445
|
-
import
|
|
3446
|
-
import
|
|
3606
|
+
import { mkdir as mkdir6, readdir as readdir3, readFile as readFile5, writeFile as writeFile6 } from "node:fs/promises";
|
|
3607
|
+
import os4 from "node:os";
|
|
3608
|
+
import path7 from "node:path";
|
|
3447
3609
|
function currentRunnerMode() {
|
|
3448
3610
|
return process.env.AMISTIO_RUNNER_MODE === "background" ? "background" : "foreground";
|
|
3449
3611
|
}
|
|
3450
3612
|
function defaultRunnerMetadataDir() {
|
|
3451
|
-
return
|
|
3613
|
+
return path7.join(os4.homedir(), ".config", "amistio", "runners");
|
|
3452
3614
|
}
|
|
3453
3615
|
function updatedCliRunnerLaunchOptions() {
|
|
3454
3616
|
return { executablePath: "amistio", directExecutable: true };
|
|
@@ -3465,8 +3627,8 @@ async function startRunnerDaemon(input) {
|
|
|
3465
3627
|
if (existing?.status === "running" && isProcessRunning(existing.pid)) {
|
|
3466
3628
|
throw new Error(`Background runner ${existing.runnerId} is already running with PID ${existing.pid}.`);
|
|
3467
3629
|
}
|
|
3468
|
-
await
|
|
3469
|
-
const logPath =
|
|
3630
|
+
await mkdir6(metadataDir, { recursive: true });
|
|
3631
|
+
const logPath = path7.join(metadataDir, `${runnerDaemonKey(input)}.log`);
|
|
3470
3632
|
const logFd = openSync(logPath, "a");
|
|
3471
3633
|
const launch = resolveRunnerDaemonLaunch({
|
|
3472
3634
|
args: input.args,
|
|
@@ -3493,13 +3655,13 @@ async function startRunnerDaemon(input) {
|
|
|
3493
3655
|
projectId: input.projectId,
|
|
3494
3656
|
repositoryLinkId: input.repositoryLinkId,
|
|
3495
3657
|
runnerId: input.runnerId,
|
|
3496
|
-
rootDir:
|
|
3658
|
+
rootDir: path7.resolve(input.rootDir),
|
|
3497
3659
|
apiUrl: input.apiUrl,
|
|
3498
3660
|
pid: child.pid,
|
|
3499
3661
|
status: "running",
|
|
3500
3662
|
startedAt: now,
|
|
3501
3663
|
updatedAt: now,
|
|
3502
|
-
hostname:
|
|
3664
|
+
hostname: os4.hostname(),
|
|
3503
3665
|
logPath
|
|
3504
3666
|
};
|
|
3505
3667
|
await writeRunnerDaemonMetadata(metadata, metadataDir);
|
|
@@ -3507,8 +3669,8 @@ async function startRunnerDaemon(input) {
|
|
|
3507
3669
|
}
|
|
3508
3670
|
async function restartRunnerDaemonProcess(metadata, args, input = {}) {
|
|
3509
3671
|
const metadataDir = input.metadataDir ?? defaultRunnerMetadataDir();
|
|
3510
|
-
await
|
|
3511
|
-
const logPath = metadata.logPath ??
|
|
3672
|
+
await mkdir6(metadataDir, { recursive: true });
|
|
3673
|
+
const logPath = metadata.logPath ?? path7.join(metadataDir, `${runnerDaemonKey(metadata)}.log`);
|
|
3512
3674
|
const logFd = openSync(logPath, "a");
|
|
3513
3675
|
const launch = resolveRunnerDaemonLaunch({
|
|
3514
3676
|
args,
|
|
@@ -3536,7 +3698,7 @@ async function restartRunnerDaemonProcess(metadata, args, input = {}) {
|
|
|
3536
3698
|
status: "running",
|
|
3537
3699
|
startedAt: now,
|
|
3538
3700
|
updatedAt: now,
|
|
3539
|
-
hostname:
|
|
3701
|
+
hostname: os4.hostname(),
|
|
3540
3702
|
logPath
|
|
3541
3703
|
};
|
|
3542
3704
|
await writeRunnerDaemonMetadata(replacement, metadataDir);
|
|
@@ -3545,12 +3707,12 @@ async function restartRunnerDaemonProcess(metadata, args, input = {}) {
|
|
|
3545
3707
|
async function listRunnerDaemonMetadata(input, metadataDir = defaultRunnerMetadataDir()) {
|
|
3546
3708
|
let entries;
|
|
3547
3709
|
try {
|
|
3548
|
-
entries = await
|
|
3710
|
+
entries = await readdir3(metadataDir);
|
|
3549
3711
|
} catch {
|
|
3550
3712
|
return [];
|
|
3551
3713
|
}
|
|
3552
3714
|
const records = await Promise.all(
|
|
3553
|
-
entries.filter((entry) => entry.endsWith(".json")).map(async (entry) => readRunnerDaemonMetadataFile(
|
|
3715
|
+
entries.filter((entry) => entry.endsWith(".json")).map(async (entry) => readRunnerDaemonMetadataFile(path7.join(metadataDir, entry)))
|
|
3554
3716
|
);
|
|
3555
3717
|
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));
|
|
3556
3718
|
}
|
|
@@ -3558,8 +3720,8 @@ async function readRunnerDaemonMetadata(input, metadataDir = defaultRunnerMetada
|
|
|
3558
3720
|
return readRunnerDaemonMetadataFile(runnerDaemonMetadataPath(input, metadataDir));
|
|
3559
3721
|
}
|
|
3560
3722
|
async function writeRunnerDaemonMetadata(metadata, metadataDir = defaultRunnerMetadataDir()) {
|
|
3561
|
-
await
|
|
3562
|
-
await
|
|
3723
|
+
await mkdir6(metadataDir, { recursive: true });
|
|
3724
|
+
await writeFile6(runnerDaemonMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
|
|
3563
3725
|
}
|
|
3564
3726
|
async function markRunnerDaemonStopped(metadata, metadataDir = defaultRunnerMetadataDir()) {
|
|
3565
3727
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3621,14 +3783,14 @@ function runnerDaemonUptime(metadata, now = Date.now()) {
|
|
|
3621
3783
|
return `${seconds}s`;
|
|
3622
3784
|
}
|
|
3623
3785
|
function runnerDaemonMetadataPath(input, metadataDir) {
|
|
3624
|
-
return
|
|
3786
|
+
return path7.join(metadataDir, `${runnerDaemonKey(input)}.json`);
|
|
3625
3787
|
}
|
|
3626
3788
|
function runnerDaemonKey(input) {
|
|
3627
|
-
return
|
|
3789
|
+
return createHash3("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.runnerId}`).digest("hex");
|
|
3628
3790
|
}
|
|
3629
3791
|
async function readRunnerDaemonMetadataFile(filePath) {
|
|
3630
3792
|
try {
|
|
3631
|
-
const parsed = JSON.parse(await
|
|
3793
|
+
const parsed = JSON.parse(await readFile5(filePath, "utf8"));
|
|
3632
3794
|
if (parsed.schemaVersion !== 1 || !parsed.runnerId || !parsed.projectId || !parsed.repositoryLinkId) {
|
|
3633
3795
|
return void 0;
|
|
3634
3796
|
}
|
|
@@ -3640,10 +3802,10 @@ async function readRunnerDaemonMetadataFile(filePath) {
|
|
|
3640
3802
|
|
|
3641
3803
|
// src/runner-service.ts
|
|
3642
3804
|
import { spawn as spawn3 } from "node:child_process";
|
|
3643
|
-
import { createHash as
|
|
3644
|
-
import { mkdir as
|
|
3645
|
-
import
|
|
3646
|
-
import
|
|
3805
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3806
|
+
import { mkdir as mkdir7, readFile as readFile6, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
|
|
3807
|
+
import os5 from "node:os";
|
|
3808
|
+
import path8 from "node:path";
|
|
3647
3809
|
function detectRunnerServicePlatform(platform = process.platform) {
|
|
3648
3810
|
if (platform === "darwin") return "launchd";
|
|
3649
3811
|
if (platform === "linux") return "systemd";
|
|
@@ -3654,19 +3816,19 @@ function createRunnerServiceDescriptor(input) {
|
|
|
3654
3816
|
if (platform === "unsupported") {
|
|
3655
3817
|
throw new Error("Startup services are supported for user-level launchd on macOS and systemd user services on Linux.");
|
|
3656
3818
|
}
|
|
3657
|
-
const homeDir = input.homeDir ??
|
|
3819
|
+
const homeDir = input.homeDir ?? os5.homedir();
|
|
3658
3820
|
const serviceName = runnerServiceName(input);
|
|
3659
3821
|
const serviceFilePath = runnerServiceFilePath(platform, serviceName, homeDir);
|
|
3660
3822
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3661
3823
|
const command = [input.executablePath ?? process.execPath, input.scriptPath ?? process.argv[1], ...input.args];
|
|
3662
|
-
const logPath =
|
|
3824
|
+
const logPath = path8.join(input.metadataDir ?? defaultRunnerMetadataDir(), `${runnerServiceKey(input)}.service.log`);
|
|
3663
3825
|
const metadata = {
|
|
3664
3826
|
schemaVersion: 1,
|
|
3665
3827
|
accountId: input.accountId,
|
|
3666
3828
|
projectId: input.projectId,
|
|
3667
3829
|
repositoryLinkId: input.repositoryLinkId,
|
|
3668
3830
|
runnerId: input.runnerId,
|
|
3669
|
-
rootDir:
|
|
3831
|
+
rootDir: path8.resolve(input.rootDir),
|
|
3670
3832
|
apiUrl: input.apiUrl,
|
|
3671
3833
|
serviceName,
|
|
3672
3834
|
serviceFilePath,
|
|
@@ -3683,9 +3845,9 @@ function createRunnerServiceDescriptor(input) {
|
|
|
3683
3845
|
}
|
|
3684
3846
|
async function installRunnerService(input, options = {}) {
|
|
3685
3847
|
const descriptor = createRunnerServiceDescriptor(input);
|
|
3686
|
-
await
|
|
3687
|
-
await
|
|
3688
|
-
await
|
|
3848
|
+
await mkdir7(path8.dirname(descriptor.metadata.serviceFilePath), { recursive: true });
|
|
3849
|
+
await mkdir7(input.metadataDir ?? defaultRunnerMetadataDir(), { recursive: true });
|
|
3850
|
+
await writeFile7(descriptor.metadata.serviceFilePath, descriptor.content, { encoding: "utf8", mode: 384 });
|
|
3689
3851
|
await writeRunnerServiceMetadata(descriptor.metadata, input.metadataDir);
|
|
3690
3852
|
if (options.activate !== false) {
|
|
3691
3853
|
const activation = await activateRunnerService(descriptor.metadata);
|
|
@@ -3701,13 +3863,13 @@ async function removeRunnerService(input) {
|
|
|
3701
3863
|
return void 0;
|
|
3702
3864
|
}
|
|
3703
3865
|
await deactivateRunnerService(metadata).catch(() => void 0);
|
|
3704
|
-
await
|
|
3705
|
-
await
|
|
3866
|
+
await rm3(metadata.serviceFilePath, { force: true });
|
|
3867
|
+
await rm3(runnerServiceMetadataPath(input, input.metadataDir ?? defaultRunnerMetadataDir()), { force: true });
|
|
3706
3868
|
return { ...metadata, status: "removed", updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3707
3869
|
}
|
|
3708
3870
|
async function readRunnerServiceMetadata(input, metadataDir = defaultRunnerMetadataDir()) {
|
|
3709
3871
|
try {
|
|
3710
|
-
const parsed = JSON.parse(await
|
|
3872
|
+
const parsed = JSON.parse(await readFile6(runnerServiceMetadataPath(input, metadataDir), "utf8"));
|
|
3711
3873
|
if (parsed.schemaVersion !== 1 || !parsed.serviceName || !parsed.serviceFilePath) {
|
|
3712
3874
|
return void 0;
|
|
3713
3875
|
}
|
|
@@ -3717,8 +3879,8 @@ async function readRunnerServiceMetadata(input, metadataDir = defaultRunnerMetad
|
|
|
3717
3879
|
}
|
|
3718
3880
|
}
|
|
3719
3881
|
async function writeRunnerServiceMetadata(metadata, metadataDir = defaultRunnerMetadataDir()) {
|
|
3720
|
-
await
|
|
3721
|
-
await
|
|
3882
|
+
await mkdir7(metadataDir, { recursive: true });
|
|
3883
|
+
await writeFile7(runnerServiceMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
|
|
3722
3884
|
}
|
|
3723
3885
|
async function runnerServiceRuntimeStatus(metadata) {
|
|
3724
3886
|
if (metadata.platform === "launchd") {
|
|
@@ -3801,18 +3963,18 @@ WantedBy=default.target
|
|
|
3801
3963
|
}
|
|
3802
3964
|
function runnerServiceFilePath(platform, serviceName, homeDir) {
|
|
3803
3965
|
if (platform === "launchd") {
|
|
3804
|
-
return
|
|
3966
|
+
return path8.join(homeDir, "Library", "LaunchAgents", `${serviceName}.plist`);
|
|
3805
3967
|
}
|
|
3806
|
-
return
|
|
3968
|
+
return path8.join(homeDir, ".config", "systemd", "user", `${serviceName}.service`);
|
|
3807
3969
|
}
|
|
3808
3970
|
function runnerServiceMetadataPath(input, metadataDir) {
|
|
3809
|
-
return
|
|
3971
|
+
return path8.join(metadataDir, `${runnerServiceKey(input)}.service.json`);
|
|
3810
3972
|
}
|
|
3811
3973
|
function runnerServiceName(input) {
|
|
3812
3974
|
return `com.amistio.runner.${runnerServiceKey(input).slice(0, 20)}`;
|
|
3813
3975
|
}
|
|
3814
3976
|
function runnerServiceKey(input) {
|
|
3815
|
-
return
|
|
3977
|
+
return createHash4("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.runnerId}`).digest("hex");
|
|
3816
3978
|
}
|
|
3817
3979
|
function launchdDomain() {
|
|
3818
3980
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
@@ -3988,9 +4150,9 @@ function tokens(value) {
|
|
|
3988
4150
|
|
|
3989
4151
|
// src/sync.ts
|
|
3990
4152
|
import { execFile as execFile3 } from "node:child_process";
|
|
3991
|
-
import { createHash as
|
|
3992
|
-
import { mkdir as
|
|
3993
|
-
import
|
|
4153
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
4154
|
+
import { mkdir as mkdir8, readdir as readdir4, readFile as readFile7, stat as stat3, writeFile as writeFile8 } from "node:fs/promises";
|
|
4155
|
+
import path9 from "node:path";
|
|
3994
4156
|
import { promisify as promisify3 } from "node:util";
|
|
3995
4157
|
var execFileAsync3 = promisify3(execFile3);
|
|
3996
4158
|
var legacySyncRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
|
|
@@ -4086,7 +4248,7 @@ async function readLocalSyncedDocuments(rootDir) {
|
|
|
4086
4248
|
const documentFiles = await findBrainDocumentFiles(rootDir);
|
|
4087
4249
|
const documents = [];
|
|
4088
4250
|
for (const fullPath of documentFiles) {
|
|
4089
|
-
const raw = await
|
|
4251
|
+
const raw = await readFile7(fullPath, "utf8");
|
|
4090
4252
|
const repoPath = toRepoPath(rootDir, fullPath);
|
|
4091
4253
|
const parsed = parseSyncedDocument(raw, repoPath);
|
|
4092
4254
|
if (!parsed) {
|
|
@@ -4136,8 +4298,8 @@ async function materializeBrainDocuments(rootDir, documents, options = {}) {
|
|
|
4136
4298
|
result.skipped.push(document.repoPath);
|
|
4137
4299
|
continue;
|
|
4138
4300
|
}
|
|
4139
|
-
await
|
|
4140
|
-
await
|
|
4301
|
+
await mkdir8(path9.dirname(fullPath), { recursive: true });
|
|
4302
|
+
await writeFile8(fullPath, createSyncedDocumentContent(document), "utf8");
|
|
4141
4303
|
result.written.push(document.repoPath);
|
|
4142
4304
|
}
|
|
4143
4305
|
return result;
|
|
@@ -4266,7 +4428,7 @@ function parseSyncedHtml(content) {
|
|
|
4266
4428
|
}
|
|
4267
4429
|
async function readExistingSyncedDocument(fullPath) {
|
|
4268
4430
|
try {
|
|
4269
|
-
const raw = await
|
|
4431
|
+
const raw = await readFile7(fullPath, "utf8");
|
|
4270
4432
|
const parsed = parseSyncedDocument(raw, fullPath);
|
|
4271
4433
|
if (!parsed) {
|
|
4272
4434
|
return { exists: true };
|
|
@@ -4291,7 +4453,7 @@ async function readExistingSyncedDocument(fullPath) {
|
|
|
4291
4453
|
async function findBrainDocumentFiles(rootDir) {
|
|
4292
4454
|
const files = [];
|
|
4293
4455
|
for (const syncRoot of [...syncRoots, htmlSyncRoot]) {
|
|
4294
|
-
const fullRoot =
|
|
4456
|
+
const fullRoot = path9.join(rootDir, syncRoot);
|
|
4295
4457
|
if (!await exists2(fullRoot)) {
|
|
4296
4458
|
continue;
|
|
4297
4459
|
}
|
|
@@ -4300,8 +4462,8 @@ async function findBrainDocumentFiles(rootDir) {
|
|
|
4300
4462
|
return files;
|
|
4301
4463
|
}
|
|
4302
4464
|
async function walkBrainDocumentFiles(directory, files) {
|
|
4303
|
-
for (const entry of await
|
|
4304
|
-
const fullPath =
|
|
4465
|
+
for (const entry of await readdir4(directory, { withFileTypes: true })) {
|
|
4466
|
+
const fullPath = path9.join(directory, entry.name);
|
|
4305
4467
|
if (entry.isDirectory()) {
|
|
4306
4468
|
await walkBrainDocumentFiles(fullPath, files);
|
|
4307
4469
|
} else if (entry.isFile() && /\.(md|mdx|html?)$/i.test(entry.name)) {
|
|
@@ -4310,23 +4472,23 @@ async function walkBrainDocumentFiles(directory, files) {
|
|
|
4310
4472
|
}
|
|
4311
4473
|
}
|
|
4312
4474
|
function safeRepoPath(rootDir, repoPath) {
|
|
4313
|
-
if (
|
|
4475
|
+
if (path9.isAbsolute(repoPath)) {
|
|
4314
4476
|
throw new Error(`Refusing to use absolute repo path: ${repoPath}`);
|
|
4315
4477
|
}
|
|
4316
|
-
const normalized =
|
|
4317
|
-
if (normalized === ".." || normalized.startsWith(`..${
|
|
4478
|
+
const normalized = path9.normalize(repoPath);
|
|
4479
|
+
if (normalized === ".." || normalized.startsWith(`..${path9.sep}`)) {
|
|
4318
4480
|
throw new Error(`Refusing to use path outside the repository: ${repoPath}`);
|
|
4319
4481
|
}
|
|
4320
|
-
const root =
|
|
4321
|
-
const fullPath =
|
|
4322
|
-
if (!fullPath.startsWith(`${root}${
|
|
4482
|
+
const root = path9.resolve(rootDir);
|
|
4483
|
+
const fullPath = path9.resolve(root, normalized);
|
|
4484
|
+
if (!fullPath.startsWith(`${root}${path9.sep}`)) {
|
|
4323
4485
|
throw new Error(`Refusing to use path outside the repository: ${repoPath}`);
|
|
4324
4486
|
}
|
|
4325
4487
|
return fullPath;
|
|
4326
4488
|
}
|
|
4327
4489
|
function isControlPlanePath(repoPath) {
|
|
4328
|
-
const normalized =
|
|
4329
|
-
return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${
|
|
4490
|
+
const normalized = path9.normalize(repoPath);
|
|
4491
|
+
return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path9.sep}`)) || normalized === htmlSyncRoot || normalized.startsWith(`${htmlSyncRoot}${path9.sep}`);
|
|
4330
4492
|
}
|
|
4331
4493
|
function canonicalControlPlaneRepoPath(repoPath) {
|
|
4332
4494
|
const normalized = repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -4337,16 +4499,16 @@ function canonicalControlPlaneRepoPath(repoPath) {
|
|
|
4337
4499
|
return normalized;
|
|
4338
4500
|
}
|
|
4339
4501
|
function toRepoPath(rootDir, fullPath) {
|
|
4340
|
-
return
|
|
4502
|
+
return path9.relative(rootDir, fullPath).split(path9.sep).join("/");
|
|
4341
4503
|
}
|
|
4342
4504
|
function inferTitle(content, repoPath) {
|
|
4343
4505
|
const heading = content.split("\n").find((line) => line.startsWith("# "))?.replace(/^#\s+/, "").trim();
|
|
4344
4506
|
if (heading) return heading;
|
|
4345
4507
|
const htmlHeading = content.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
|
|
4346
|
-
return htmlHeading ||
|
|
4508
|
+
return htmlHeading || path9.basename(repoPath, path9.extname(repoPath));
|
|
4347
4509
|
}
|
|
4348
4510
|
async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingDocuments, options) {
|
|
4349
|
-
const root =
|
|
4511
|
+
const root = path9.resolve(rootDir);
|
|
4350
4512
|
const maxBytes = (options.maxFileKb ?? defaultAutoSyncMaxFileKb) * 1024;
|
|
4351
4513
|
const syncedAt = options.syncedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4352
4514
|
const existingById = new Map(existingDocuments.map((document) => [document.documentId, document]));
|
|
@@ -4371,7 +4533,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
|
|
|
4371
4533
|
skipped.push({ repoPath: normalizedRepoPath, reason: "tooLarge" });
|
|
4372
4534
|
continue;
|
|
4373
4535
|
}
|
|
4374
|
-
const content = await
|
|
4536
|
+
const content = await readFile7(fullPath, "utf8").catch(() => void 0);
|
|
4375
4537
|
if (content === void 0) {
|
|
4376
4538
|
skipped.push({ repoPath: normalizedRepoPath, reason: "unreadable" });
|
|
4377
4539
|
continue;
|
|
@@ -4436,7 +4598,7 @@ async function listAutoSyncCandidatePaths(rootDir) {
|
|
|
4436
4598
|
}
|
|
4437
4599
|
const files = [];
|
|
4438
4600
|
for (const syncRoot of [...syncRoots, htmlSyncRoot, ...legacySyncRoots]) {
|
|
4439
|
-
const fullRoot =
|
|
4601
|
+
const fullRoot = path9.join(rootDir, syncRoot);
|
|
4440
4602
|
if (await exists2(fullRoot)) {
|
|
4441
4603
|
await walkAutoSyncFiles(rootDir, fullRoot, files);
|
|
4442
4604
|
}
|
|
@@ -4444,9 +4606,9 @@ async function listAutoSyncCandidatePaths(rootDir) {
|
|
|
4444
4606
|
return uniqueSortedRepoPaths(files);
|
|
4445
4607
|
}
|
|
4446
4608
|
async function walkAutoSyncFiles(rootDir, directory, files) {
|
|
4447
|
-
for (const entry of await
|
|
4448
|
-
const fullPath =
|
|
4449
|
-
const repoPath = normalizeRepoPath3(
|
|
4609
|
+
for (const entry of await readdir4(directory, { withFileTypes: true }).catch(() => [])) {
|
|
4610
|
+
const fullPath = path9.join(directory, entry.name);
|
|
4611
|
+
const repoPath = normalizeRepoPath3(path9.relative(rootDir, fullPath));
|
|
4450
4612
|
if (entry.isDirectory()) {
|
|
4451
4613
|
if (!autoSyncExcludedDirectoryNames.has(entry.name)) {
|
|
4452
4614
|
await walkAutoSyncFiles(rootDir, fullPath, files);
|
|
@@ -4480,7 +4642,7 @@ function legacyDocumentTypeForRepoPath(repoPath) {
|
|
|
4480
4642
|
return root && root in documentTypeByRoot ? documentTypeByRoot[root] : void 0;
|
|
4481
4643
|
}
|
|
4482
4644
|
function stableExternalDocumentId(metadata, repoPath) {
|
|
4483
|
-
return `doc_external_${
|
|
4645
|
+
return `doc_external_${createHash5("sha256").update(`${metadata.amistioAccountId}:${metadata.amistioProjectId}:${metadata.repositoryLinkId}:${repoPath}`).digest("hex").slice(0, 24)}`;
|
|
4484
4646
|
}
|
|
4485
4647
|
function normalizeRepoPath3(repoPath) {
|
|
4486
4648
|
return repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -4508,9 +4670,9 @@ async function exists2(filePath) {
|
|
|
4508
4670
|
}
|
|
4509
4671
|
|
|
4510
4672
|
// src/tool-session-store.ts
|
|
4511
|
-
import { mkdir as
|
|
4512
|
-
import
|
|
4513
|
-
import
|
|
4673
|
+
import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile9 } from "node:fs/promises";
|
|
4674
|
+
import os6 from "node:os";
|
|
4675
|
+
import path10 from "node:path";
|
|
4514
4676
|
var LocalToolSessionStore = class {
|
|
4515
4677
|
constructor(filePath = defaultSessionStorePath()) {
|
|
4516
4678
|
this.filePath = filePath;
|
|
@@ -4524,12 +4686,12 @@ var LocalToolSessionStore = class {
|
|
|
4524
4686
|
async setProviderSessionId(toolSessionId, toolName, providerSessionId) {
|
|
4525
4687
|
const data = await this.read();
|
|
4526
4688
|
data[toolSessionId] = { toolName, providerSessionId, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4527
|
-
await
|
|
4528
|
-
await
|
|
4689
|
+
await mkdir9(path10.dirname(this.filePath), { recursive: true });
|
|
4690
|
+
await writeFile9(this.filePath, JSON.stringify(data, null, 2), "utf8");
|
|
4529
4691
|
}
|
|
4530
4692
|
async read() {
|
|
4531
4693
|
try {
|
|
4532
|
-
return JSON.parse(await
|
|
4694
|
+
return JSON.parse(await readFile8(this.filePath, "utf8"));
|
|
4533
4695
|
} catch {
|
|
4534
4696
|
return {};
|
|
4535
4697
|
}
|
|
@@ -4537,16 +4699,16 @@ var LocalToolSessionStore = class {
|
|
|
4537
4699
|
};
|
|
4538
4700
|
function defaultSessionStorePath() {
|
|
4539
4701
|
if (process.platform === "darwin") {
|
|
4540
|
-
return
|
|
4702
|
+
return path10.join(os6.homedir(), "Library", "Application Support", "Amistio", "tool-sessions.json");
|
|
4541
4703
|
}
|
|
4542
4704
|
if (process.platform === "win32") {
|
|
4543
|
-
return
|
|
4705
|
+
return path10.join(process.env.APPDATA ?? os6.homedir(), "Amistio", "tool-sessions.json");
|
|
4544
4706
|
}
|
|
4545
|
-
return
|
|
4707
|
+
return path10.join(process.env.XDG_STATE_HOME ?? path10.join(os6.homedir(), ".local", "state"), "amistio", "tool-sessions.json");
|
|
4546
4708
|
}
|
|
4547
4709
|
|
|
4548
4710
|
// src/work-runner.ts
|
|
4549
|
-
import
|
|
4711
|
+
import path11 from "node:path";
|
|
4550
4712
|
var generationResultStart = "AMISTIO_BRAIN_GENERATION_RESULT_START";
|
|
4551
4713
|
var generationResultEnd = "AMISTIO_BRAIN_GENERATION_RESULT_END";
|
|
4552
4714
|
var assistantAnswerStart = "AMISTIO_ASSISTANT_ANSWER_START";
|
|
@@ -5430,15 +5592,15 @@ function normalizeProjectContextRepoPath(value, options) {
|
|
|
5430
5592
|
if (!trimmed || /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(trimmed) || /^file:/i.test(trimmed) || /^[A-Za-z]:($|[^\\/])/.test(trimmed)) {
|
|
5431
5593
|
throwUnsafeProjectContextPath();
|
|
5432
5594
|
}
|
|
5433
|
-
const absolute = trimmed.startsWith("/") || trimmed.startsWith("\\\\") ||
|
|
5595
|
+
const absolute = trimmed.startsWith("/") || trimmed.startsWith("\\\\") || path11.isAbsolute(trimmed) || path11.win32.isAbsolute(trimmed);
|
|
5434
5596
|
if (!absolute) {
|
|
5435
5597
|
return normalizeRelativeProjectContextPath(trimmed);
|
|
5436
5598
|
}
|
|
5437
5599
|
if (!options.repositoryRoot) {
|
|
5438
5600
|
throwUnsafeProjectContextPath();
|
|
5439
5601
|
}
|
|
5440
|
-
const useWindowsPathRules =
|
|
5441
|
-
const relativePath = useWindowsPathRules ?
|
|
5602
|
+
const useWindowsPathRules = path11.win32.isAbsolute(trimmed) && !trimmed.startsWith("/");
|
|
5603
|
+
const relativePath = useWindowsPathRules ? path11.win32.relative(path11.win32.resolve(options.repositoryRoot), path11.win32.resolve(trimmed)) : path11.relative(path11.resolve(options.repositoryRoot), path11.resolve(trimmed));
|
|
5442
5604
|
return normalizeRelativeProjectContextPath(relativePath);
|
|
5443
5605
|
}
|
|
5444
5606
|
function normalizeRelativeProjectContextPath(value) {
|
|
@@ -5490,7 +5652,7 @@ function stripJsonFence(value) {
|
|
|
5490
5652
|
}
|
|
5491
5653
|
|
|
5492
5654
|
// src/runner-status.ts
|
|
5493
|
-
import { createHash as
|
|
5655
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
5494
5656
|
var watchStateReminderMs = 60 * 1e3;
|
|
5495
5657
|
function formatWatchStartupContext(input) {
|
|
5496
5658
|
return [
|
|
@@ -5517,20 +5679,20 @@ function watchStateKey(action) {
|
|
|
5517
5679
|
return [action.kind, action.message, action.workItemId, action.documentId, action.runnerId].filter(Boolean).join(":");
|
|
5518
5680
|
}
|
|
5519
5681
|
function stableRunnerId(input) {
|
|
5520
|
-
const digest =
|
|
5682
|
+
const digest = createHash6("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.machineId}`).digest("hex").slice(0, 20);
|
|
5521
5683
|
return `runner_${digest}`;
|
|
5522
5684
|
}
|
|
5523
5685
|
|
|
5524
5686
|
// src/runner-resources.ts
|
|
5525
|
-
import
|
|
5687
|
+
import os7 from "node:os";
|
|
5526
5688
|
var defaultRuntime = {
|
|
5527
5689
|
nowMs: () => Date.now(),
|
|
5528
5690
|
memoryUsage: () => process.memoryUsage(),
|
|
5529
5691
|
uptime: () => process.uptime(),
|
|
5530
5692
|
cpuUsage: () => process.cpuUsage(),
|
|
5531
|
-
totalmem: () =>
|
|
5532
|
-
freemem: () =>
|
|
5533
|
-
loadavg: () =>
|
|
5693
|
+
totalmem: () => os7.totalmem(),
|
|
5694
|
+
freemem: () => os7.freemem(),
|
|
5695
|
+
loadavg: () => os7.loadavg()
|
|
5534
5696
|
};
|
|
5535
5697
|
var previousRunnerResourceSample;
|
|
5536
5698
|
function sampleCurrentRunnerResourceUsage() {
|
|
@@ -5642,9 +5804,9 @@ function roundNumber(value, digits) {
|
|
|
5642
5804
|
|
|
5643
5805
|
// src/importer.ts
|
|
5644
5806
|
import { execFile as execFile4 } from "node:child_process";
|
|
5645
|
-
import { createHash as
|
|
5646
|
-
import { readdir as
|
|
5647
|
-
import
|
|
5807
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
5808
|
+
import { readdir as readdir5, readFile as readFile9, stat as stat4 } from "node:fs/promises";
|
|
5809
|
+
import path12 from "node:path";
|
|
5648
5810
|
import { promisify as promisify4 } from "node:util";
|
|
5649
5811
|
var execFileAsync4 = promisify4(execFile4);
|
|
5650
5812
|
var defaultMaxFileKb = 256;
|
|
@@ -5665,12 +5827,12 @@ var documentFolderByType = {
|
|
|
5665
5827
|
workflow: "docs/workflows"
|
|
5666
5828
|
};
|
|
5667
5829
|
async function inspectLocalRepository(rootDir, defaultBranch) {
|
|
5668
|
-
const requestedRoot =
|
|
5830
|
+
const requestedRoot = path12.resolve(rootDir);
|
|
5669
5831
|
const root = await runGit2(["-C", requestedRoot, "rev-parse", "--show-toplevel"]).catch(() => requestedRoot);
|
|
5670
5832
|
const detectedBranch = await runGit2(["-C", root, "symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => defaultBranch);
|
|
5671
5833
|
const originUrl = await runGit2(["-C", root, "remote", "get-url", "origin"]).catch(() => void 0);
|
|
5672
5834
|
const parsedCloneUrl = originUrl ? parseOptionalOriginCloneUrl(originUrl) : void 0;
|
|
5673
|
-
const repoName = (parsedCloneUrl?.repoName ??
|
|
5835
|
+
const repoName = (parsedCloneUrl?.repoName ?? path12.basename(root)) || "repository";
|
|
5674
5836
|
const fingerprintSeed = parsedCloneUrl ? `origin:${parsedCloneUrl.normalizedKey}` : `repo:${repoName}:${detectedBranch || defaultBranch}`;
|
|
5675
5837
|
return {
|
|
5676
5838
|
rootDir: root,
|
|
@@ -5682,7 +5844,7 @@ async function inspectLocalRepository(rootDir, defaultBranch) {
|
|
|
5682
5844
|
};
|
|
5683
5845
|
}
|
|
5684
5846
|
async function scanLegacyDocuments(options) {
|
|
5685
|
-
const rootDir =
|
|
5847
|
+
const rootDir = path12.resolve(options.rootDir);
|
|
5686
5848
|
const maxBytes = (options.maxFileKb ?? defaultMaxFileKb) * 1024;
|
|
5687
5849
|
const skipped = [];
|
|
5688
5850
|
const candidates = [];
|
|
@@ -5702,7 +5864,7 @@ async function scanLegacyDocuments(options) {
|
|
|
5702
5864
|
skipped.push({ repoPath, reason: "excluded" });
|
|
5703
5865
|
continue;
|
|
5704
5866
|
}
|
|
5705
|
-
const fullPath =
|
|
5867
|
+
const fullPath = path12.join(rootDir, ...repoPath.split("/"));
|
|
5706
5868
|
const fileStat = await stat4(fullPath).catch(() => void 0);
|
|
5707
5869
|
if (!fileStat?.isFile()) {
|
|
5708
5870
|
skipped.push({ repoPath, reason: "unreadable" });
|
|
@@ -5712,7 +5874,7 @@ async function scanLegacyDocuments(options) {
|
|
|
5712
5874
|
skipped.push({ repoPath, reason: "tooLarge" });
|
|
5713
5875
|
continue;
|
|
5714
5876
|
}
|
|
5715
|
-
const content = await
|
|
5877
|
+
const content = await readFile9(fullPath, "utf8").catch(() => void 0);
|
|
5716
5878
|
if (content === void 0) {
|
|
5717
5879
|
skipped.push({ repoPath, reason: "unreadable" });
|
|
5718
5880
|
continue;
|
|
@@ -5802,10 +5964,10 @@ async function listRepositoryPaths(rootDir) {
|
|
|
5802
5964
|
return files;
|
|
5803
5965
|
}
|
|
5804
5966
|
async function walkRepository(rootDir, directory, files) {
|
|
5805
|
-
const entries = await
|
|
5967
|
+
const entries = await readdir5(directory, { withFileTypes: true }).catch(() => []);
|
|
5806
5968
|
for (const entry of entries) {
|
|
5807
|
-
const fullPath =
|
|
5808
|
-
const repoPath = normalizeRepoPath4(
|
|
5969
|
+
const fullPath = path12.join(directory, entry.name);
|
|
5970
|
+
const repoPath = normalizeRepoPath4(path12.relative(rootDir, fullPath));
|
|
5809
5971
|
if (entry.isDirectory()) {
|
|
5810
5972
|
if (!excludedDirectoryNames.has(entry.name)) {
|
|
5811
5973
|
await walkRepository(rootDir, fullPath, files);
|
|
@@ -5873,9 +6035,9 @@ function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
|
|
|
5873
6035
|
usedPaths.add(basePath);
|
|
5874
6036
|
return basePath;
|
|
5875
6037
|
}
|
|
5876
|
-
const extension =
|
|
5877
|
-
const directory =
|
|
5878
|
-
const basename =
|
|
6038
|
+
const extension = path12.posix.extname(basePath) || ".md";
|
|
6039
|
+
const directory = path12.posix.dirname(basePath);
|
|
6040
|
+
const basename = path12.posix.basename(basePath, extension);
|
|
5879
6041
|
const uniquePath = `${directory}/${basename}-${hashText(sourcePath, 8)}${extension}`;
|
|
5880
6042
|
usedPaths.add(uniquePath);
|
|
5881
6043
|
return uniquePath;
|
|
@@ -5948,7 +6110,7 @@ function inferTitle2(content, repoPath) {
|
|
|
5948
6110
|
if (heading) return heading;
|
|
5949
6111
|
const htmlHeading = body.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
|
|
5950
6112
|
if (htmlHeading) return htmlHeading;
|
|
5951
|
-
const basename =
|
|
6113
|
+
const basename = path12.posix.basename(repoPath, path12.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
|
|
5952
6114
|
return titleCase(basename || "Imported Document");
|
|
5953
6115
|
}
|
|
5954
6116
|
function stripFrontmatter(content) {
|
|
@@ -5970,7 +6132,7 @@ function stableImportDocumentId(accountId, projectId, repositoryLinkId, sourcePa
|
|
|
5970
6132
|
return `doc_import_${hashText(`${accountId}\0${projectId}\0${repositoryLinkId}\0${sourcePath}`, 24)}`;
|
|
5971
6133
|
}
|
|
5972
6134
|
function hashText(value, length) {
|
|
5973
|
-
return
|
|
6135
|
+
return createHash7("sha256").update(value).digest("hex").slice(0, length);
|
|
5974
6136
|
}
|
|
5975
6137
|
function normalizeRepoPath4(value) {
|
|
5976
6138
|
return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -5982,7 +6144,7 @@ async function runGit2(args) {
|
|
|
5982
6144
|
|
|
5983
6145
|
// src/runner-actions.ts
|
|
5984
6146
|
import { spawn as spawn4 } from "node:child_process";
|
|
5985
|
-
import
|
|
6147
|
+
import path13 from "node:path";
|
|
5986
6148
|
function buildBackgroundRunnerArgs(options) {
|
|
5987
6149
|
const args = [
|
|
5988
6150
|
"run",
|
|
@@ -5992,7 +6154,7 @@ function buildBackgroundRunnerArgs(options) {
|
|
|
5992
6154
|
"--runner-id",
|
|
5993
6155
|
options.runnerId,
|
|
5994
6156
|
"--root",
|
|
5995
|
-
|
|
6157
|
+
path13.resolve(options.root),
|
|
5996
6158
|
"--session",
|
|
5997
6159
|
options.session,
|
|
5998
6160
|
"--interval-seconds",
|
|
@@ -6109,8 +6271,8 @@ function truncateProcessOutput(value) {
|
|
|
6109
6271
|
|
|
6110
6272
|
// src/git-worktree.ts
|
|
6111
6273
|
import { execFile as execFile5 } from "node:child_process";
|
|
6112
|
-
import { mkdir as
|
|
6113
|
-
import
|
|
6274
|
+
import { mkdir as mkdir10, stat as stat5 } from "node:fs/promises";
|
|
6275
|
+
import path14 from "node:path";
|
|
6114
6276
|
import { promisify as promisify5 } from "node:util";
|
|
6115
6277
|
var execFileAsync5 = promisify5(execFile5);
|
|
6116
6278
|
function needsGitWorktreeIsolation(workItem) {
|
|
@@ -6139,7 +6301,7 @@ async function prepareGitWorktreeIsolation(rootDir, workItem) {
|
|
|
6139
6301
|
await assertExistingWorktree(worktreePath, identity.branch);
|
|
6140
6302
|
return { ...identity, baseRevision, worktreePath };
|
|
6141
6303
|
}
|
|
6142
|
-
await
|
|
6304
|
+
await mkdir10(path14.dirname(worktreePath), { recursive: true });
|
|
6143
6305
|
const branchExists = await gitCommandSucceeds(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${identity.branch}`]);
|
|
6144
6306
|
const worktreeArgs = branchExists ? ["worktree", "add", worktreePath, identity.branch] : ["worktree", "add", "-b", identity.branch, worktreePath, baseRevision];
|
|
6145
6307
|
await gitOutput(repoRoot, worktreeArgs).catch((error) => {
|
|
@@ -6148,9 +6310,9 @@ async function prepareGitWorktreeIsolation(rootDir, workItem) {
|
|
|
6148
6310
|
return { ...identity, baseRevision, worktreePath };
|
|
6149
6311
|
}
|
|
6150
6312
|
function localWorktreePath(repoRoot, worktreeKey) {
|
|
6151
|
-
const repoName =
|
|
6313
|
+
const repoName = path14.basename(repoRoot);
|
|
6152
6314
|
const worktreeSlug = worktreeKey.split("/").filter(Boolean).pop() ?? "work";
|
|
6153
|
-
return
|
|
6315
|
+
return path14.join(path14.dirname(repoRoot), `${repoName}.worktrees`, worktreeSlug);
|
|
6154
6316
|
}
|
|
6155
6317
|
async function assertExistingWorktree(worktreePath, branch) {
|
|
6156
6318
|
await gitOutput(worktreePath, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -6197,7 +6359,7 @@ function errorMessage2(error) {
|
|
|
6197
6359
|
|
|
6198
6360
|
// src/implementation-handoff.ts
|
|
6199
6361
|
import { execFile as execFile6 } from "node:child_process";
|
|
6200
|
-
import
|
|
6362
|
+
import path15 from "node:path";
|
|
6201
6363
|
import { promisify as promisify6 } from "node:util";
|
|
6202
6364
|
var execFileAsync6 = promisify6(execFile6);
|
|
6203
6365
|
async function completeImplementationHandoff(input) {
|
|
@@ -6283,7 +6445,7 @@ async function cleanupWorktree(run, input) {
|
|
|
6283
6445
|
return { status: "failed", message: "Cleanup skipped because the worktree is not clean after PR handoff." };
|
|
6284
6446
|
}
|
|
6285
6447
|
try {
|
|
6286
|
-
await gitOutput2(run, input.primaryRepoRoot ||
|
|
6448
|
+
await gitOutput2(run, input.primaryRepoRoot || path15.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
|
|
6287
6449
|
return { status: "completed" };
|
|
6288
6450
|
} catch (error) {
|
|
6289
6451
|
return { status: "failed", message: `Cleanup failed: ${safeErrorMessage(error)}` };
|
|
@@ -6694,7 +6856,7 @@ work.command("prompt").description("Print or write an approved work prompt witho
|
|
|
6694
6856
|
}
|
|
6695
6857
|
const prompt = await createRunnerWorkPrompt(context.client, context.metadata.amistioProjectId, workItem);
|
|
6696
6858
|
if (options.out) {
|
|
6697
|
-
await
|
|
6859
|
+
await writeFile10(options.out, prompt, "utf8");
|
|
6698
6860
|
console.log(`Wrote work prompt to ${options.out}.`);
|
|
6699
6861
|
} else {
|
|
6700
6862
|
console.log(prompt);
|
|
@@ -6772,7 +6934,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
6772
6934
|
projectId: context.metadata.amistioProjectId,
|
|
6773
6935
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
6774
6936
|
runnerId,
|
|
6775
|
-
rootDir:
|
|
6937
|
+
rootDir: path16.resolve(options.root),
|
|
6776
6938
|
apiUrl: options.apiUrl,
|
|
6777
6939
|
args: buildBackgroundRunnerArgs(resolvedOptions)
|
|
6778
6940
|
});
|
|
@@ -6944,7 +7106,7 @@ runnerService.command("install").description("Install a user-level startup servi
|
|
|
6944
7106
|
projectId: context.metadata.amistioProjectId,
|
|
6945
7107
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
6946
7108
|
runnerId,
|
|
6947
|
-
rootDir:
|
|
7109
|
+
rootDir: path16.resolve(options.root),
|
|
6948
7110
|
apiUrl: options.apiUrl,
|
|
6949
7111
|
args,
|
|
6950
7112
|
platform
|
|
@@ -7015,6 +7177,18 @@ async function runWatchIteration({ command, context, options, runnerId }) {
|
|
|
7015
7177
|
runnerId
|
|
7016
7178
|
});
|
|
7017
7179
|
}
|
|
7180
|
+
if (!options.dryRun) {
|
|
7181
|
+
const replayResult = await replayPendingBrainGenerationFinalizations({
|
|
7182
|
+
accountId: context.metadata.amistioAccountId,
|
|
7183
|
+
apiClient: context.client,
|
|
7184
|
+
projectId: context.metadata.amistioProjectId,
|
|
7185
|
+
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
7186
|
+
runnerId
|
|
7187
|
+
});
|
|
7188
|
+
if (replayResult) {
|
|
7189
|
+
return replayResult;
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7018
7192
|
return await runNextWorkItem({
|
|
7019
7193
|
apiClient: context.client,
|
|
7020
7194
|
projectId: context.metadata.amistioProjectId,
|
|
@@ -7762,6 +7936,58 @@ function runnerCommandLabel(commandKind) {
|
|
|
7762
7936
|
if (commandKind === "restart") return "restart";
|
|
7763
7937
|
return "remove";
|
|
7764
7938
|
}
|
|
7939
|
+
async function replayPendingBrainGenerationFinalizations({ accountId, apiClient, projectId, repositoryLinkId, runnerId }) {
|
|
7940
|
+
const pendingEntries = await listPendingBrainGenerationFinalizations({ accountId, projectId, repositoryLinkId, runnerId });
|
|
7941
|
+
if (!pendingEntries.length) {
|
|
7942
|
+
return void 0;
|
|
7943
|
+
}
|
|
7944
|
+
let completedCount = 0;
|
|
7945
|
+
for (const entry of pendingEntries) {
|
|
7946
|
+
const replay = await submitBrainGenerationFinalizationEntry(apiClient, entry, { recordReplayTelemetry: true });
|
|
7947
|
+
if (replay.status === "completed") {
|
|
7948
|
+
completedCount += 1;
|
|
7949
|
+
continue;
|
|
7950
|
+
}
|
|
7951
|
+
return { status: "failed", exitCode: 1, message: replay.message };
|
|
7952
|
+
}
|
|
7953
|
+
const message = `Replayed ${completedCount} pending brain generation finalization${completedCount === 1 ? "" : "s"}.`;
|
|
7954
|
+
console.log(message);
|
|
7955
|
+
return { status: "completed", exitCode: 0, message };
|
|
7956
|
+
}
|
|
7957
|
+
async function submitBrainGenerationFinalizationEntry(apiClient, entry, options = {}) {
|
|
7958
|
+
let result;
|
|
7959
|
+
try {
|
|
7960
|
+
result = await apiClient.submitBrainGenerationResult(entry.projectId, entry.workItemId, entry.result);
|
|
7961
|
+
} catch (error) {
|
|
7962
|
+
const detail = truncateLogExcerpt(errorMessage3(error));
|
|
7963
|
+
if (isRetryableApiError(error)) {
|
|
7964
|
+
const updated = await markBrainGenerationFinalizationRetry(entry, detail);
|
|
7965
|
+
const message2 = `Pending brain generation finalization ${entry.workItemId} could not be replayed yet (${updated.retryCount} attempt${updated.retryCount === 1 ? "" : "s"}): ${detail}`;
|
|
7966
|
+
console.error(message2);
|
|
7967
|
+
return { status: "failed", message: message2, retryable: true };
|
|
7968
|
+
}
|
|
7969
|
+
await markBrainGenerationFinalizationTerminal(entry, detail);
|
|
7970
|
+
const message = `Pending brain generation finalization ${entry.workItemId} reached a terminal API failure: ${detail}`;
|
|
7971
|
+
console.error(message);
|
|
7972
|
+
return { status: "failed", message, retryable: false };
|
|
7973
|
+
}
|
|
7974
|
+
await deleteBrainGenerationFinalizationEntry(entry).catch((error) => {
|
|
7975
|
+
console.error(`delete pending brain generation finalization ${entry.workItemId} failed: ${errorMessage3(error)}`);
|
|
7976
|
+
});
|
|
7977
|
+
if (options.recordReplayTelemetry) {
|
|
7978
|
+
await recordRunnerMilestone(apiClient, entry.projectId, result.workItem, entry.runnerId, entry.repositoryLinkId, {
|
|
7979
|
+
status: entry.result.status,
|
|
7980
|
+
summary: entry.result.status === "completed" ? "Replayed pending brain generation result finalization." : "Replayed pending brain generation failure finalization.",
|
|
7981
|
+
idempotencyKey: `runner_milestone_generation_replayed_${entry.workItemId}_${result.workItem.idempotencyKey}`,
|
|
7982
|
+
metadata: { artifactCount: result.documents.length, replayedFinalization: true, workKind: entry.workKind }
|
|
7983
|
+
});
|
|
7984
|
+
await apiClient.sendRunnerHeartbeat(entry.projectId, entry.runnerId, entry.repositoryLinkId, "online", {
|
|
7985
|
+
...runnerHeartbeatMetadata(),
|
|
7986
|
+
preferenceMessage: "Pending result finalization replayed."
|
|
7987
|
+
}).catch(() => void 0);
|
|
7988
|
+
}
|
|
7989
|
+
return { status: "completed", workItem: result.workItem, documentCount: result.documents.length };
|
|
7990
|
+
}
|
|
7765
7991
|
async function finalizeBrainGenerationWork({
|
|
7766
7992
|
apiClient,
|
|
7767
7993
|
durationMs,
|
|
@@ -7787,55 +8013,97 @@ ${toolResult.stderr}`);
|
|
|
7787
8013
|
generationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
7788
8014
|
}
|
|
7789
8015
|
const finalStatus = artifacts ? "completed" : "failed";
|
|
7790
|
-
const updatedToolSession = await finalizeToolSession({
|
|
7791
|
-
apiClient,
|
|
7792
|
-
projectId,
|
|
7793
|
-
status: finalStatus,
|
|
7794
|
-
runnerId,
|
|
7795
|
-
workItemId: workItem.workItemId,
|
|
7796
|
-
stdout: toolResult.stdout,
|
|
7797
|
-
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
7798
|
-
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
7799
|
-
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
7800
|
-
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
7801
|
-
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
7802
|
-
});
|
|
7803
8016
|
const sessionTelemetry = {
|
|
7804
8017
|
sessionPolicy: sessionContext.policy,
|
|
7805
8018
|
sessionDecision: sessionContext.decision,
|
|
7806
8019
|
sessionDecisionReason: sessionContext.reason,
|
|
7807
|
-
...
|
|
7808
|
-
...
|
|
8020
|
+
...sessionContext.toolSession ? { toolSessionId: sessionContext.toolSession.toolSessionId } : {},
|
|
8021
|
+
...sessionContext.toolSession?.sessionGroupKey ? { sessionGroupKey: sessionContext.toolSession.sessionGroupKey } : {}
|
|
7809
8022
|
};
|
|
7810
8023
|
if (artifacts) {
|
|
7811
8024
|
const completionMessage = workItem.workKind === "planRevision" ? `${toolName} returned a revised plan for review.` : `${toolName} generated ${artifacts.length} brain artifact${artifacts.length === 1 ? "" : "s"}.`;
|
|
7812
|
-
const
|
|
8025
|
+
const resultMutation = {
|
|
7813
8026
|
status: "completed",
|
|
7814
8027
|
runnerId,
|
|
7815
|
-
idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
|
|
8028
|
+
idempotencyKey: `generation_${workItem.workItemId}_${workItem.attempt}_${randomUUID()}`,
|
|
7816
8029
|
artifacts,
|
|
7817
8030
|
tool: toolName,
|
|
7818
8031
|
durationMs,
|
|
7819
8032
|
...sessionTelemetry,
|
|
7820
8033
|
message: completionMessage
|
|
8034
|
+
};
|
|
8035
|
+
const entry = createBrainGenerationFinalizationEntry({
|
|
8036
|
+
accountId: workItem.accountId,
|
|
8037
|
+
projectId,
|
|
8038
|
+
repositoryLinkId,
|
|
8039
|
+
runnerId,
|
|
8040
|
+
workItemId: workItem.workItemId,
|
|
8041
|
+
workKind: brainGenerationFinalizationWorkKind(workItem.workKind),
|
|
8042
|
+
attempt: workItem.attempt,
|
|
8043
|
+
idempotencyKey: resultMutation.idempotencyKey,
|
|
8044
|
+
result: resultMutation
|
|
7821
8045
|
});
|
|
8046
|
+
await upsertBrainGenerationFinalizationEntry(entry);
|
|
8047
|
+
const replay = await submitBrainGenerationFinalizationEntry(apiClient, entry);
|
|
8048
|
+
if (replay.status === "failed") {
|
|
8049
|
+
if (!replay.retryable) {
|
|
8050
|
+
throw new Error(replay.message);
|
|
8051
|
+
}
|
|
8052
|
+
return { status: "failed", exitCode: 1 };
|
|
8053
|
+
}
|
|
8054
|
+
const toolSessionSettlement = await Promise.allSettled([
|
|
8055
|
+
finalizeToolSession({
|
|
8056
|
+
apiClient,
|
|
8057
|
+
projectId,
|
|
8058
|
+
status: finalStatus,
|
|
8059
|
+
runnerId,
|
|
8060
|
+
workItemId: workItem.workItemId,
|
|
8061
|
+
stdout: toolResult.stdout,
|
|
8062
|
+
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
8063
|
+
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
8064
|
+
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
8065
|
+
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
8066
|
+
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
8067
|
+
})
|
|
8068
|
+
]);
|
|
8069
|
+
logRejectedSettlements("finalize generation tool session", toolSessionSettlement);
|
|
7822
8070
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7823
8071
|
status: "completed",
|
|
7824
8072
|
summary: completionMessage,
|
|
7825
|
-
idempotencyKey: `runner_milestone_generation_completed_${workItem.workItemId}_${
|
|
7826
|
-
metadata: { tool: toolName, durationMs, artifactCount:
|
|
8073
|
+
idempotencyKey: `runner_milestone_generation_completed_${workItem.workItemId}_${entry.idempotencyKey}`,
|
|
8074
|
+
metadata: { tool: toolName, durationMs, artifactCount: replay.documentCount, verificationSummary: "Generated artifacts were accepted by the Amistio API for review." }
|
|
7827
8075
|
});
|
|
7828
|
-
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig))
|
|
7829
|
-
|
|
8076
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)).catch((error) => {
|
|
8077
|
+
console.error(`send generation completion heartbeat failed: ${errorMessage3(error)}`);
|
|
8078
|
+
});
|
|
8079
|
+
console.log(workItem.workKind === "planRevision" ? "Revised plan returned for review." : `Generated ${replay.documentCount} brain artifact${replay.documentCount === 1 ? "" : "s"} for review.`);
|
|
7830
8080
|
return { status: "completed", exitCode: 0 };
|
|
7831
8081
|
}
|
|
8082
|
+
const updatedToolSession = await finalizeToolSession({
|
|
8083
|
+
apiClient,
|
|
8084
|
+
projectId,
|
|
8085
|
+
status: finalStatus,
|
|
8086
|
+
runnerId,
|
|
8087
|
+
workItemId: workItem.workItemId,
|
|
8088
|
+
stdout: toolResult.stdout,
|
|
8089
|
+
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
8090
|
+
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
8091
|
+
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
8092
|
+
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
8093
|
+
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
8094
|
+
});
|
|
8095
|
+
const failedSessionTelemetry = {
|
|
8096
|
+
...sessionTelemetry,
|
|
8097
|
+
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
8098
|
+
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
8099
|
+
};
|
|
7832
8100
|
const failedResult = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
|
|
7833
8101
|
status: "failed",
|
|
7834
8102
|
runnerId,
|
|
7835
8103
|
idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
|
|
7836
8104
|
tool: toolName,
|
|
7837
8105
|
durationMs,
|
|
7838
|
-
...
|
|
8106
|
+
...failedSessionTelemetry,
|
|
7839
8107
|
message: `${toolName} did not produce valid brain artifacts.`,
|
|
7840
8108
|
...generationError ? { error: generationError } : {}
|
|
7841
8109
|
});
|
|
@@ -7849,6 +8117,9 @@ ${toolResult.stderr}`);
|
|
|
7849
8117
|
console.error(generationError ?? "Local runner generation failed.");
|
|
7850
8118
|
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
7851
8119
|
}
|
|
8120
|
+
function brainGenerationFinalizationWorkKind(workKind) {
|
|
8121
|
+
return workKind === "planRevision" ? "planRevision" : "brainGeneration";
|
|
8122
|
+
}
|
|
7852
8123
|
async function finalizeAssistantQuestionWork({
|
|
7853
8124
|
apiClient,
|
|
7854
8125
|
durationMs,
|
|
@@ -8767,10 +9038,10 @@ function parseReasoningEffort(value) {
|
|
|
8767
9038
|
throw new Error(`Expected reasoning effort auto, low, medium, high, or xhigh; received ${value}.`);
|
|
8768
9039
|
}
|
|
8769
9040
|
function inferRepoName(root) {
|
|
8770
|
-
return
|
|
9041
|
+
return path16.basename(path16.resolve(root)) || "repository";
|
|
8771
9042
|
}
|
|
8772
9043
|
function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
|
|
8773
|
-
return
|
|
9044
|
+
return createHash8("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
|
|
8774
9045
|
}
|
|
8775
9046
|
function defaultApiUrl() {
|
|
8776
9047
|
const envApiUrl = process.env[AMISTIO_API_URL_ENV]?.trim();
|
|
@@ -9028,7 +9299,7 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode()) {
|
|
|
9028
9299
|
return {
|
|
9029
9300
|
version: CLI_VERSION,
|
|
9030
9301
|
mode,
|
|
9031
|
-
hostname:
|
|
9302
|
+
hostname: os8.hostname(),
|
|
9032
9303
|
...runnerIsolationCapabilityMetadata(),
|
|
9033
9304
|
resourceUsage: sampleCurrentRunnerResourceUsage(),
|
|
9034
9305
|
...toolConfig?.capabilities ? { capabilities: toolConfig.capabilities } : {},
|
|
@@ -9051,7 +9322,7 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode()) {
|
|
|
9051
9322
|
};
|
|
9052
9323
|
}
|
|
9053
9324
|
function runnerMachineId() {
|
|
9054
|
-
return
|
|
9325
|
+
return createHash8("sha256").update(`${os8.hostname()}:${os8.platform()}:${os8.arch()}`).digest("hex").slice(0, 20);
|
|
9055
9326
|
}
|
|
9056
9327
|
async function delay(milliseconds) {
|
|
9057
9328
|
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|