@amistio/cli 0.1.38 → 0.1.39
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 +9 -3
- package/dist/index.js +312 -183
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ npm install -g @amistio/cli
|
|
|
9
9
|
amistio --help
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
The package install provides the `amistio` command and the optional `amistio-host-helper` executable. Repository cloning, project pairing, credential storage, sync watching, helper activation, and runner execution happen only when the user explicitly runs commands such as `amistio bootstrap`, `amistio import`, `amistio pair`, `amistio sync watch`, `amistio host-helper status`, or `amistio run --watch`. When the app copies a personal project into an organization, the CLI command syntax stays the same; create the org-scoped code and run `amistio import <code>` from the intended local checkout. Import scans repo-local project-brain docs plus recognized repo-local IDE and local AI tool memory or instruction files such as `AGENTS.md`, `.github/copilot-instructions.md`, `.cursor/rules/*.mdc`, `.windsurfrules`, and `memories/*.md`; durable Amistio memory belongs in `docs/memory/`, and global plugin memory outside the repository is not scanned by default.
|
|
12
|
+
The package install provides the `amistio` command and the optional `amistio-host-helper` executable. Repository cloning, project pairing, credential storage, sync watching, harness diagnostics, helper activation, and runner execution happen only when the user explicitly runs commands such as `amistio bootstrap`, `amistio import`, `amistio pair`, `amistio sync watch`, `amistio harness status`, `amistio host-helper status`, or `amistio run --watch`. When the app copies a personal project into an organization, the CLI command syntax stays the same; create the org-scoped code and run `amistio import <code>` from the intended local checkout. Import scans repo-local project-brain docs plus recognized repo-local IDE and local AI tool memory or instruction files such as `AGENTS.md`, `.github/copilot-instructions.md`, `.cursor/rules/*.mdc`, `.windsurfrules`, and `memories/*.md`; durable Amistio memory belongs in `docs/memory/`, and global plugin memory outside the repository is not scanned by default.
|
|
13
13
|
|
|
14
14
|
For enterprise setup, use the web Runner panel as the primary guide. It shows repository, pairing code, GitHub access, AI provider, local runner, and verification readiness with plain next actions; advanced CLI logs remain secondary diagnostics. The panel also shows trust/privacy boundaries, cost forecast and budget posture, runner health, blockers, verification health, and runner-version distribution from safe runner metadata.
|
|
15
15
|
|
|
@@ -38,6 +38,9 @@ Repository autopilot is disabled until the repository link option is enabled in
|
|
|
38
38
|
After pairing, confirm that at least one local AI tool is available:
|
|
39
39
|
|
|
40
40
|
```sh
|
|
41
|
+
amistio harness status
|
|
42
|
+
amistio harness providers
|
|
43
|
+
amistio harness tools
|
|
41
44
|
amistio tools
|
|
42
45
|
amistio host-helper status
|
|
43
46
|
amistio host-helper conformance
|
|
@@ -54,18 +57,21 @@ amistio run --watch --background --tool opencode
|
|
|
54
57
|
AMISTIO_HOST_HELPER_PATH=$(command -v amistio-host-helper) amistio host-helper status
|
|
55
58
|
AMISTIO_HOST_HELPER_PATH=$(command -v amistio-host-helper) amistio host-helper conformance
|
|
56
59
|
amistio runner status
|
|
60
|
+
amistio runner repair
|
|
57
61
|
amistio runner smoke-session-lifecycle
|
|
58
62
|
```
|
|
59
63
|
|
|
60
64
|
Provider-backed model preferences use sanitized catalog fields: `--provider`, `--model-id`, optional `--model-variant`, and `--reasoning-effort` (`auto`, `low`, `medium`, `high`, or `xhigh`). Opencode catalog metadata is synthesized by Amistio or derived from readable local OpenCode JSON config until opencode exposes a native catalog. Provider credentials, API keys, and local secret paths stay in the local tool configuration; they are not stored in Amistio preferences or runner heartbeats.
|
|
61
65
|
|
|
66
|
+
`amistio harness status`, `amistio harness providers`, and `amistio harness tools` are local diagnostics for the built-in Amistio harness. They report the default harness boundary, safe direct-provider readiness, and bounded Amistio-owned tool adapters without uploading provider credentials, environment values, local config paths, or raw tool errors. The web Runner panel remains the primary setup flow.
|
|
67
|
+
|
|
62
68
|
Opencode remains an optional compatibility route. When a provider/model is named for opencode, Amistio validates it against the safe provider catalog before launching the external tool instead of silently falling back. Command-mode local tool runs are bounded by `--tool-timeout-seconds`, wait for process cleanup after timeout, and return redacted/capped stdout and stderr so large external output, local execution-root paths, and secret-looking assignments are not persisted unbounded.
|
|
63
69
|
|
|
64
70
|
When `--tool copilot` uses the GitHub Copilot SDK, Amistio approves read-only permission requests by default and denies mutating, network, MCP, hook, memory, and shell requests. Set `AMISTIO_COPILOT_APPROVE_ALL=1` only on a local machine where broad Copilot SDK approval is intentional.
|
|
65
71
|
|
|
66
72
|
When `--tool codex` uses the Codex SDK, intermediate progress can be quiet until the final response. For live Codex CLI logs, run `amistio run --watch --tool codex --invocation-channel command`.
|
|
67
73
|
|
|
68
|
-
`amistio runner status` reports local background runner state, latest heartbeat, and bounded resource usage when available. Resource usage is latest-sample runner process memory/CPU plus safe aggregate system memory/load signals; it does not include source files, environment variables, command lines, process lists, credentials, or arbitrary local paths.
|
|
74
|
+
`amistio runner status` reports local background runner state, latest heartbeat, and bounded resource usage when available. `amistio runner repair` rotates the local runner ID for the paired checkout after a forgotten-runner tombstone; use `amistio runner repair --clear-credential` only when you need to delete the local credential and re-pair from the Runner panel. Resource usage is latest-sample runner process memory/CPU plus safe aggregate system memory/load signals; it does not include source files, environment variables, command lines, process lists, credentials, or arbitrary local paths.
|
|
69
75
|
|
|
70
76
|
`amistio runner smoke-session-lifecycle` runs a local no-claim smoke for the runner tool-session lifecycle. It does not contact the API, claim production work, inspect source, or mutate local runner state; it verifies that completed one-shot sessions close, active sessions are treated as in use, stale sessions are not selected for reuse, and fresh related reusable sessions can still continue.
|
|
71
77
|
|
|
@@ -83,7 +89,7 @@ Watch mode prints a completed-work success once per work item, keeps fresh compl
|
|
|
83
89
|
|
|
84
90
|
Known validation failures such as `unsafe_context_path` are printed with attention-needed next steps. For project-context refresh path-safety failures, deploy the latest web/API fix, update and restart the runner when applicable, retry the refresh, and capture only bounded non-secret output if it repeats.
|
|
85
91
|
|
|
86
|
-
If watch mode reports that the runner was forgotten by the server,
|
|
92
|
+
If watch mode reports that the runner was forgotten by the server, run `amistio runner repair` from the paired checkout, then start `amistio run --watch` again. The repair command stores a fresh local runner ID because the default ID for a machine/project/repository is stable and can remain tombstoned. Use `--clear-credential` only when the Runner panel tells you to create a fresh pairing code.
|
|
87
93
|
|
|
88
94
|
App-evaluation result finalization rejections print safe validation paths and preserve the local finalization evidence without exposing raw source or secrets. If a structured app-evaluation result is rejected, update and restart the runner, confirm the web/API deployment is current, and retry the evaluation before acting on cleanup or implementation recommendations.
|
|
89
95
|
|
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 createHash9, randomUUID as
|
|
5
|
-
import { writeFile as
|
|
6
|
-
import
|
|
7
|
-
import
|
|
4
|
+
import { createHash as createHash9, randomUUID as randomUUID3 } from "node:crypto";
|
|
5
|
+
import { writeFile as writeFile12 } from "node:fs/promises";
|
|
6
|
+
import os9 from "node:os";
|
|
7
|
+
import path18 from "node:path";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
|
|
10
10
|
// ../shared/src/schemas.ts
|
|
@@ -3145,8 +3145,8 @@ var toolSessionMutationSchema = z3.object({
|
|
|
3145
3145
|
});
|
|
3146
3146
|
function resolveApiUrl(apiUrl, urlPath) {
|
|
3147
3147
|
const base = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
|
|
3148
|
-
const
|
|
3149
|
-
return new URL(`${base}${
|
|
3148
|
+
const path19 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
|
|
3149
|
+
return new URL(`${base}${path19}`);
|
|
3150
3150
|
}
|
|
3151
3151
|
|
|
3152
3152
|
// src/orchestrator.ts
|
|
@@ -5494,6 +5494,102 @@ async function readRunnerDaemonMetadataFile(filePath) {
|
|
|
5494
5494
|
}
|
|
5495
5495
|
}
|
|
5496
5496
|
|
|
5497
|
+
// src/runner-identity-store.ts
|
|
5498
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5499
|
+
import { chmod as chmod2, mkdir as mkdir8, readFile as readFile7, writeFile as writeFile8 } from "node:fs/promises";
|
|
5500
|
+
import os5 from "node:os";
|
|
5501
|
+
import path9 from "node:path";
|
|
5502
|
+
|
|
5503
|
+
// src/runner-status.ts
|
|
5504
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
5505
|
+
var watchStateReminderMs = 60 * 1e3;
|
|
5506
|
+
function formatWatchStartupContext(input) {
|
|
5507
|
+
return [
|
|
5508
|
+
`Runner ${input.runnerId} is watching project ${input.projectId}.`,
|
|
5509
|
+
`Repository link: ${input.repositoryLinkId}`,
|
|
5510
|
+
`API: ${input.apiUrl}`,
|
|
5511
|
+
`Polling interval: ${input.intervalSeconds}s. Press Ctrl+C to stop.`
|
|
5512
|
+
];
|
|
5513
|
+
}
|
|
5514
|
+
function formatWatchIdleLine(action, intervalSeconds) {
|
|
5515
|
+
return `${formatProjectNextAction(action)} Checking again in ${intervalSeconds}s.`;
|
|
5516
|
+
}
|
|
5517
|
+
function shouldPrintWatchState(action, previous, nowMs, reminderMs = watchStateReminderMs) {
|
|
5518
|
+
const key = watchStateKey(action);
|
|
5519
|
+
if (!previous || previous.key !== key) {
|
|
5520
|
+
return true;
|
|
5521
|
+
}
|
|
5522
|
+
if (action.kind === "workCompleted") {
|
|
5523
|
+
return false;
|
|
5524
|
+
}
|
|
5525
|
+
return nowMs - previous.printedAtMs >= reminderMs;
|
|
5526
|
+
}
|
|
5527
|
+
function watchStateKey(action) {
|
|
5528
|
+
return [action.kind, action.message, action.workItemId, action.documentId, action.runnerId].filter(Boolean).join(":");
|
|
5529
|
+
}
|
|
5530
|
+
function stableRunnerId(input) {
|
|
5531
|
+
const digest = createHash4("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.machineId}`).digest("hex").slice(0, 20);
|
|
5532
|
+
return `runner_${digest}`;
|
|
5533
|
+
}
|
|
5534
|
+
|
|
5535
|
+
// src/runner-identity-store.ts
|
|
5536
|
+
var LocalRunnerIdentityStore = class {
|
|
5537
|
+
constructor(filePath = path9.join(os5.homedir(), ".config", "amistio", "runner-identities.json")) {
|
|
5538
|
+
this.filePath = filePath;
|
|
5539
|
+
}
|
|
5540
|
+
filePath;
|
|
5541
|
+
async get(key) {
|
|
5542
|
+
const data = await this.read();
|
|
5543
|
+
return data.runnerIds[key];
|
|
5544
|
+
}
|
|
5545
|
+
async set(key, runnerId) {
|
|
5546
|
+
const data = await this.read();
|
|
5547
|
+
data.runnerIds[key] = runnerId;
|
|
5548
|
+
await this.write(data);
|
|
5549
|
+
}
|
|
5550
|
+
async delete(key) {
|
|
5551
|
+
const data = await this.read();
|
|
5552
|
+
if (!(key in data.runnerIds)) {
|
|
5553
|
+
return;
|
|
5554
|
+
}
|
|
5555
|
+
delete data.runnerIds[key];
|
|
5556
|
+
await this.write(data);
|
|
5557
|
+
}
|
|
5558
|
+
async read() {
|
|
5559
|
+
try {
|
|
5560
|
+
return normalizeStoredRunnerIdentityFile(JSON.parse(await readFile7(this.filePath, "utf8")));
|
|
5561
|
+
} catch {
|
|
5562
|
+
return { runnerIds: {} };
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
async write(data) {
|
|
5566
|
+
await mkdir8(path9.dirname(this.filePath), { recursive: true });
|
|
5567
|
+
await writeFile8(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf8", mode: 384 });
|
|
5568
|
+
await chmod2(this.filePath, 384);
|
|
5569
|
+
}
|
|
5570
|
+
};
|
|
5571
|
+
function runnerIdentityKey(scope) {
|
|
5572
|
+
return `${scope.accountId}:${scope.projectId}:${scope.repositoryLinkId}`;
|
|
5573
|
+
}
|
|
5574
|
+
function createFreshRunnerId() {
|
|
5575
|
+
return `runner_${randomUUID2().replace(/-/g, "").slice(0, 20)}`;
|
|
5576
|
+
}
|
|
5577
|
+
async function resolveLocalRunnerId(scope, store = new LocalRunnerIdentityStore()) {
|
|
5578
|
+
return await store.get(runnerIdentityKey(scope)) ?? stableRunnerId(scope);
|
|
5579
|
+
}
|
|
5580
|
+
function normalizeStoredRunnerIdentityFile(value) {
|
|
5581
|
+
if (!value || typeof value !== "object" || !("runnerIds" in value)) {
|
|
5582
|
+
return { runnerIds: {} };
|
|
5583
|
+
}
|
|
5584
|
+
const runnerIds = value.runnerIds;
|
|
5585
|
+
if (!runnerIds || typeof runnerIds !== "object") {
|
|
5586
|
+
return { runnerIds: {} };
|
|
5587
|
+
}
|
|
5588
|
+
return {
|
|
5589
|
+
runnerIds: Object.fromEntries(Object.entries(runnerIds).filter((entry) => typeof entry[1] === "string"))
|
|
5590
|
+
};
|
|
5591
|
+
}
|
|
5592
|
+
|
|
5497
5593
|
// src/runner-watch-errors.ts
|
|
5498
5594
|
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
5595
|
async function handleRunnerWatchError(options) {
|
|
@@ -5531,10 +5627,10 @@ ${options.detail}`);
|
|
|
5531
5627
|
|
|
5532
5628
|
// src/runner-service.ts
|
|
5533
5629
|
import { spawn as spawn3 } from "node:child_process";
|
|
5534
|
-
import { createHash as
|
|
5535
|
-
import { mkdir as
|
|
5536
|
-
import
|
|
5537
|
-
import
|
|
5630
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
5631
|
+
import { mkdir as mkdir9, readFile as readFile8, rm as rm3, writeFile as writeFile9 } from "node:fs/promises";
|
|
5632
|
+
import os6 from "node:os";
|
|
5633
|
+
import path10 from "node:path";
|
|
5538
5634
|
function detectRunnerServicePlatform(platform = process.platform) {
|
|
5539
5635
|
if (platform === "darwin") return "launchd";
|
|
5540
5636
|
if (platform === "linux") return "systemd";
|
|
@@ -5545,19 +5641,19 @@ function createRunnerServiceDescriptor(input) {
|
|
|
5545
5641
|
if (platform === "unsupported") {
|
|
5546
5642
|
throw new Error("Startup services are supported for user-level launchd on macOS and systemd user services on Linux.");
|
|
5547
5643
|
}
|
|
5548
|
-
const homeDir = input.homeDir ??
|
|
5644
|
+
const homeDir = input.homeDir ?? os6.homedir();
|
|
5549
5645
|
const serviceName = runnerServiceName(input);
|
|
5550
5646
|
const serviceFilePath = runnerServiceFilePath(platform, serviceName, homeDir);
|
|
5551
5647
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5552
5648
|
const command = [input.executablePath ?? process.execPath, input.scriptPath ?? process.argv[1], ...input.args];
|
|
5553
|
-
const logPath =
|
|
5649
|
+
const logPath = path10.join(input.metadataDir ?? defaultRunnerMetadataDir(), `${runnerServiceKey(input)}.service.log`);
|
|
5554
5650
|
const metadata = {
|
|
5555
5651
|
schemaVersion: 1,
|
|
5556
5652
|
accountId: input.accountId,
|
|
5557
5653
|
projectId: input.projectId,
|
|
5558
5654
|
repositoryLinkId: input.repositoryLinkId,
|
|
5559
5655
|
runnerId: input.runnerId,
|
|
5560
|
-
rootDir:
|
|
5656
|
+
rootDir: path10.resolve(input.rootDir),
|
|
5561
5657
|
apiUrl: input.apiUrl,
|
|
5562
5658
|
serviceName,
|
|
5563
5659
|
serviceFilePath,
|
|
@@ -5574,9 +5670,9 @@ function createRunnerServiceDescriptor(input) {
|
|
|
5574
5670
|
}
|
|
5575
5671
|
async function installRunnerService(input, options = {}) {
|
|
5576
5672
|
const descriptor = createRunnerServiceDescriptor(input);
|
|
5577
|
-
await
|
|
5578
|
-
await
|
|
5579
|
-
await
|
|
5673
|
+
await mkdir9(path10.dirname(descriptor.metadata.serviceFilePath), { recursive: true });
|
|
5674
|
+
await mkdir9(input.metadataDir ?? defaultRunnerMetadataDir(), { recursive: true });
|
|
5675
|
+
await writeFile9(descriptor.metadata.serviceFilePath, descriptor.content, { encoding: "utf8", mode: 384 });
|
|
5580
5676
|
await writeRunnerServiceMetadata(descriptor.metadata, input.metadataDir);
|
|
5581
5677
|
if (options.activate !== false) {
|
|
5582
5678
|
const activation = await activateRunnerService(descriptor.metadata);
|
|
@@ -5598,7 +5694,7 @@ async function removeRunnerService(input) {
|
|
|
5598
5694
|
}
|
|
5599
5695
|
async function readRunnerServiceMetadata(input, metadataDir = defaultRunnerMetadataDir()) {
|
|
5600
5696
|
try {
|
|
5601
|
-
const parsed = JSON.parse(await
|
|
5697
|
+
const parsed = JSON.parse(await readFile8(runnerServiceMetadataPath(input, metadataDir), "utf8"));
|
|
5602
5698
|
if (parsed.schemaVersion !== 1 || !parsed.serviceName || !parsed.serviceFilePath) {
|
|
5603
5699
|
return void 0;
|
|
5604
5700
|
}
|
|
@@ -5608,8 +5704,8 @@ async function readRunnerServiceMetadata(input, metadataDir = defaultRunnerMetad
|
|
|
5608
5704
|
}
|
|
5609
5705
|
}
|
|
5610
5706
|
async function writeRunnerServiceMetadata(metadata, metadataDir = defaultRunnerMetadataDir()) {
|
|
5611
|
-
await
|
|
5612
|
-
await
|
|
5707
|
+
await mkdir9(metadataDir, { recursive: true });
|
|
5708
|
+
await writeFile9(runnerServiceMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
|
|
5613
5709
|
}
|
|
5614
5710
|
async function runnerServiceRuntimeStatus(metadata) {
|
|
5615
5711
|
if (metadata.platform === "launchd") {
|
|
@@ -5692,18 +5788,18 @@ WantedBy=default.target
|
|
|
5692
5788
|
}
|
|
5693
5789
|
function runnerServiceFilePath(platform, serviceName, homeDir) {
|
|
5694
5790
|
if (platform === "launchd") {
|
|
5695
|
-
return
|
|
5791
|
+
return path10.join(homeDir, "Library", "LaunchAgents", `${serviceName}.plist`);
|
|
5696
5792
|
}
|
|
5697
|
-
return
|
|
5793
|
+
return path10.join(homeDir, ".config", "systemd", "user", `${serviceName}.service`);
|
|
5698
5794
|
}
|
|
5699
5795
|
function runnerServiceMetadataPath(input, metadataDir) {
|
|
5700
|
-
return
|
|
5796
|
+
return path10.join(metadataDir, `${runnerServiceKey(input)}.service.json`);
|
|
5701
5797
|
}
|
|
5702
5798
|
function runnerServiceName(input) {
|
|
5703
5799
|
return `com.amistio.runner.${runnerServiceKey(input).slice(0, 20)}`;
|
|
5704
5800
|
}
|
|
5705
5801
|
function runnerServiceKey(input) {
|
|
5706
|
-
return
|
|
5802
|
+
return createHash5("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.runnerId}`).digest("hex");
|
|
5707
5803
|
}
|
|
5708
5804
|
function launchdDomain() {
|
|
5709
5805
|
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
@@ -6031,9 +6127,9 @@ function createSmokeSession({ now, status, lastActivityAt }) {
|
|
|
6031
6127
|
|
|
6032
6128
|
// src/sync.ts
|
|
6033
6129
|
import { execFile as execFile3 } from "node:child_process";
|
|
6034
|
-
import { createHash as
|
|
6035
|
-
import { mkdir as
|
|
6036
|
-
import
|
|
6130
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
6131
|
+
import { mkdir as mkdir10, readdir as readdir5, readFile as readFile9, stat as stat4, writeFile as writeFile10 } from "node:fs/promises";
|
|
6132
|
+
import path11 from "node:path";
|
|
6037
6133
|
import { promisify as promisify3 } from "node:util";
|
|
6038
6134
|
var execFileAsync3 = promisify3(execFile3);
|
|
6039
6135
|
var legacySyncRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
|
|
@@ -6129,7 +6225,7 @@ async function readLocalSyncedDocuments(rootDir) {
|
|
|
6129
6225
|
const documentFiles = await findBrainDocumentFiles(rootDir);
|
|
6130
6226
|
const documents = [];
|
|
6131
6227
|
for (const fullPath of documentFiles) {
|
|
6132
|
-
const raw = await
|
|
6228
|
+
const raw = await readFile9(fullPath, "utf8");
|
|
6133
6229
|
const repoPath = toRepoPath(rootDir, fullPath);
|
|
6134
6230
|
const parsed = parseSyncedDocument(raw, repoPath);
|
|
6135
6231
|
if (!parsed) {
|
|
@@ -6179,8 +6275,8 @@ async function materializeBrainDocuments(rootDir, documents, options = {}) {
|
|
|
6179
6275
|
result.skipped.push(document.repoPath);
|
|
6180
6276
|
continue;
|
|
6181
6277
|
}
|
|
6182
|
-
await
|
|
6183
|
-
await
|
|
6278
|
+
await mkdir10(path11.dirname(fullPath), { recursive: true });
|
|
6279
|
+
await writeFile10(fullPath, createSyncedDocumentContent(document), "utf8");
|
|
6184
6280
|
result.written.push(document.repoPath);
|
|
6185
6281
|
}
|
|
6186
6282
|
return result;
|
|
@@ -6309,7 +6405,7 @@ function parseSyncedHtml(content) {
|
|
|
6309
6405
|
}
|
|
6310
6406
|
async function readExistingSyncedDocument(fullPath) {
|
|
6311
6407
|
try {
|
|
6312
|
-
const raw = await
|
|
6408
|
+
const raw = await readFile9(fullPath, "utf8");
|
|
6313
6409
|
const parsed = parseSyncedDocument(raw, fullPath);
|
|
6314
6410
|
if (!parsed) {
|
|
6315
6411
|
return { exists: true };
|
|
@@ -6334,7 +6430,7 @@ async function readExistingSyncedDocument(fullPath) {
|
|
|
6334
6430
|
async function findBrainDocumentFiles(rootDir) {
|
|
6335
6431
|
const files = [];
|
|
6336
6432
|
for (const syncRoot of [...syncRoots, htmlSyncRoot]) {
|
|
6337
|
-
const fullRoot =
|
|
6433
|
+
const fullRoot = path11.join(rootDir, syncRoot);
|
|
6338
6434
|
if (!await exists2(fullRoot)) {
|
|
6339
6435
|
continue;
|
|
6340
6436
|
}
|
|
@@ -6344,7 +6440,7 @@ async function findBrainDocumentFiles(rootDir) {
|
|
|
6344
6440
|
}
|
|
6345
6441
|
async function walkBrainDocumentFiles(directory, files) {
|
|
6346
6442
|
for (const entry of await readdir5(directory, { withFileTypes: true })) {
|
|
6347
|
-
const fullPath =
|
|
6443
|
+
const fullPath = path11.join(directory, entry.name);
|
|
6348
6444
|
if (entry.isDirectory()) {
|
|
6349
6445
|
await walkBrainDocumentFiles(fullPath, files);
|
|
6350
6446
|
} else if (entry.isFile() && /\.(md|mdx|html?)$/i.test(entry.name)) {
|
|
@@ -6353,23 +6449,23 @@ async function walkBrainDocumentFiles(directory, files) {
|
|
|
6353
6449
|
}
|
|
6354
6450
|
}
|
|
6355
6451
|
function safeRepoPath(rootDir, repoPath) {
|
|
6356
|
-
if (
|
|
6452
|
+
if (path11.isAbsolute(repoPath)) {
|
|
6357
6453
|
throw new Error(`Refusing to use absolute repo path: ${repoPath}`);
|
|
6358
6454
|
}
|
|
6359
|
-
const normalized =
|
|
6360
|
-
if (normalized === ".." || normalized.startsWith(`..${
|
|
6455
|
+
const normalized = path11.normalize(repoPath);
|
|
6456
|
+
if (normalized === ".." || normalized.startsWith(`..${path11.sep}`)) {
|
|
6361
6457
|
throw new Error(`Refusing to use path outside the repository: ${repoPath}`);
|
|
6362
6458
|
}
|
|
6363
|
-
const root =
|
|
6364
|
-
const fullPath =
|
|
6365
|
-
if (!fullPath.startsWith(`${root}${
|
|
6459
|
+
const root = path11.resolve(rootDir);
|
|
6460
|
+
const fullPath = path11.resolve(root, normalized);
|
|
6461
|
+
if (!fullPath.startsWith(`${root}${path11.sep}`)) {
|
|
6366
6462
|
throw new Error(`Refusing to use path outside the repository: ${repoPath}`);
|
|
6367
6463
|
}
|
|
6368
6464
|
return fullPath;
|
|
6369
6465
|
}
|
|
6370
6466
|
function isControlPlanePath(repoPath) {
|
|
6371
|
-
const normalized =
|
|
6372
|
-
return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${
|
|
6467
|
+
const normalized = path11.normalize(repoPath);
|
|
6468
|
+
return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path11.sep}`)) || normalized === htmlSyncRoot || normalized.startsWith(`${htmlSyncRoot}${path11.sep}`);
|
|
6373
6469
|
}
|
|
6374
6470
|
function canonicalControlPlaneRepoPath(repoPath) {
|
|
6375
6471
|
const normalized = repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -6380,16 +6476,16 @@ function canonicalControlPlaneRepoPath(repoPath) {
|
|
|
6380
6476
|
return normalized;
|
|
6381
6477
|
}
|
|
6382
6478
|
function toRepoPath(rootDir, fullPath) {
|
|
6383
|
-
return
|
|
6479
|
+
return path11.relative(rootDir, fullPath).split(path11.sep).join("/");
|
|
6384
6480
|
}
|
|
6385
6481
|
function inferTitle(content, repoPath) {
|
|
6386
6482
|
const heading = content.split("\n").find((line) => line.startsWith("# "))?.replace(/^#\s+/, "").trim();
|
|
6387
6483
|
if (heading) return heading;
|
|
6388
6484
|
const htmlHeading = content.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
|
|
6389
|
-
return htmlHeading ||
|
|
6485
|
+
return htmlHeading || path11.basename(repoPath, path11.extname(repoPath));
|
|
6390
6486
|
}
|
|
6391
6487
|
async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingDocuments, options) {
|
|
6392
|
-
const root =
|
|
6488
|
+
const root = path11.resolve(rootDir);
|
|
6393
6489
|
const maxBytes = (options.maxFileKb ?? defaultAutoSyncMaxFileKb) * 1024;
|
|
6394
6490
|
const syncedAt = options.syncedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6395
6491
|
const existingById = new Map(existingDocuments.map((document) => [document.documentId, document]));
|
|
@@ -6414,7 +6510,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
|
|
|
6414
6510
|
skipped.push({ repoPath: normalizedRepoPath, reason: "tooLarge" });
|
|
6415
6511
|
continue;
|
|
6416
6512
|
}
|
|
6417
|
-
const content = await
|
|
6513
|
+
const content = await readFile9(fullPath, "utf8").catch(() => void 0);
|
|
6418
6514
|
if (content === void 0) {
|
|
6419
6515
|
skipped.push({ repoPath: normalizedRepoPath, reason: "unreadable" });
|
|
6420
6516
|
continue;
|
|
@@ -6479,7 +6575,7 @@ async function listAutoSyncCandidatePaths(rootDir) {
|
|
|
6479
6575
|
}
|
|
6480
6576
|
const files = [];
|
|
6481
6577
|
for (const syncRoot of [...syncRoots, htmlSyncRoot, ...legacySyncRoots]) {
|
|
6482
|
-
const fullRoot =
|
|
6578
|
+
const fullRoot = path11.join(rootDir, syncRoot);
|
|
6483
6579
|
if (await exists2(fullRoot)) {
|
|
6484
6580
|
await walkAutoSyncFiles(rootDir, fullRoot, files);
|
|
6485
6581
|
}
|
|
@@ -6488,8 +6584,8 @@ async function listAutoSyncCandidatePaths(rootDir) {
|
|
|
6488
6584
|
}
|
|
6489
6585
|
async function walkAutoSyncFiles(rootDir, directory, files) {
|
|
6490
6586
|
for (const entry of await readdir5(directory, { withFileTypes: true }).catch(() => [])) {
|
|
6491
|
-
const fullPath =
|
|
6492
|
-
const repoPath = normalizeRepoPath3(
|
|
6587
|
+
const fullPath = path11.join(directory, entry.name);
|
|
6588
|
+
const repoPath = normalizeRepoPath3(path11.relative(rootDir, fullPath));
|
|
6493
6589
|
if (entry.isDirectory()) {
|
|
6494
6590
|
if (!autoSyncExcludedDirectoryNames.has(entry.name)) {
|
|
6495
6591
|
await walkAutoSyncFiles(rootDir, fullPath, files);
|
|
@@ -6523,7 +6619,7 @@ function legacyDocumentTypeForRepoPath(repoPath) {
|
|
|
6523
6619
|
return root && root in documentTypeByRoot ? documentTypeByRoot[root] : void 0;
|
|
6524
6620
|
}
|
|
6525
6621
|
function stableExternalDocumentId(metadata, repoPath) {
|
|
6526
|
-
return `doc_external_${
|
|
6622
|
+
return `doc_external_${createHash6("sha256").update(`${metadata.amistioAccountId}:${metadata.amistioProjectId}:${metadata.repositoryLinkId}:${repoPath}`).digest("hex").slice(0, 24)}`;
|
|
6527
6623
|
}
|
|
6528
6624
|
function normalizeRepoPath3(repoPath) {
|
|
6529
6625
|
return repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -6551,9 +6647,9 @@ async function exists2(filePath) {
|
|
|
6551
6647
|
}
|
|
6552
6648
|
|
|
6553
6649
|
// src/tool-session-store.ts
|
|
6554
|
-
import { mkdir as
|
|
6555
|
-
import
|
|
6556
|
-
import
|
|
6650
|
+
import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
|
|
6651
|
+
import os7 from "node:os";
|
|
6652
|
+
import path12 from "node:path";
|
|
6557
6653
|
var LocalToolSessionStore = class {
|
|
6558
6654
|
constructor(filePath = defaultSessionStorePath()) {
|
|
6559
6655
|
this.filePath = filePath;
|
|
@@ -6567,12 +6663,12 @@ var LocalToolSessionStore = class {
|
|
|
6567
6663
|
async setProviderSessionId(toolSessionId, toolName, providerSessionId) {
|
|
6568
6664
|
const data = await this.read();
|
|
6569
6665
|
data[toolSessionId] = { toolName, providerSessionId, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
6570
|
-
await
|
|
6571
|
-
await
|
|
6666
|
+
await mkdir11(path12.dirname(this.filePath), { recursive: true });
|
|
6667
|
+
await writeFile11(this.filePath, JSON.stringify(data, null, 2), "utf8");
|
|
6572
6668
|
}
|
|
6573
6669
|
async read() {
|
|
6574
6670
|
try {
|
|
6575
|
-
return JSON.parse(await
|
|
6671
|
+
return JSON.parse(await readFile10(this.filePath, "utf8"));
|
|
6576
6672
|
} catch {
|
|
6577
6673
|
return {};
|
|
6578
6674
|
}
|
|
@@ -6580,16 +6676,16 @@ var LocalToolSessionStore = class {
|
|
|
6580
6676
|
};
|
|
6581
6677
|
function defaultSessionStorePath() {
|
|
6582
6678
|
if (process.platform === "darwin") {
|
|
6583
|
-
return
|
|
6679
|
+
return path12.join(os7.homedir(), "Library", "Application Support", "Amistio", "tool-sessions.json");
|
|
6584
6680
|
}
|
|
6585
6681
|
if (process.platform === "win32") {
|
|
6586
|
-
return
|
|
6682
|
+
return path12.join(process.env.APPDATA ?? os7.homedir(), "Amistio", "tool-sessions.json");
|
|
6587
6683
|
}
|
|
6588
|
-
return
|
|
6684
|
+
return path12.join(process.env.XDG_STATE_HOME ?? path12.join(os7.homedir(), ".local", "state"), "amistio", "tool-sessions.json");
|
|
6589
6685
|
}
|
|
6590
6686
|
|
|
6591
6687
|
// src/work-runner.ts
|
|
6592
|
-
import
|
|
6688
|
+
import path13 from "node:path";
|
|
6593
6689
|
var generationResultStart = "AMISTIO_BRAIN_GENERATION_RESULT_START";
|
|
6594
6690
|
var generationResultEnd = "AMISTIO_BRAIN_GENERATION_RESULT_END";
|
|
6595
6691
|
var assistantAnswerStart = "AMISTIO_ASSISTANT_ANSWER_START";
|
|
@@ -8072,15 +8168,15 @@ function normalizeProjectContextRepoPath(value, options) {
|
|
|
8072
8168
|
if (!trimmed || /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(trimmed) || /^file:/i.test(trimmed) || /^[A-Za-z]:($|[^\\/])/.test(trimmed)) {
|
|
8073
8169
|
throwUnsafeProjectContextPath();
|
|
8074
8170
|
}
|
|
8075
|
-
const absolute = trimmed.startsWith("/") || trimmed.startsWith("\\\\") ||
|
|
8171
|
+
const absolute = trimmed.startsWith("/") || trimmed.startsWith("\\\\") || path13.isAbsolute(trimmed) || path13.win32.isAbsolute(trimmed);
|
|
8076
8172
|
if (!absolute) {
|
|
8077
8173
|
return normalizeRelativeProjectContextPath(trimmed);
|
|
8078
8174
|
}
|
|
8079
8175
|
if (!options.repositoryRoot) {
|
|
8080
8176
|
throwUnsafeProjectContextPath();
|
|
8081
8177
|
}
|
|
8082
|
-
const useWindowsPathRules =
|
|
8083
|
-
const relativePath = useWindowsPathRules ?
|
|
8178
|
+
const useWindowsPathRules = path13.win32.isAbsolute(trimmed) && !trimmed.startsWith("/");
|
|
8179
|
+
const relativePath = useWindowsPathRules ? path13.win32.relative(path13.win32.resolve(options.repositoryRoot), path13.win32.resolve(trimmed)) : path13.relative(path13.resolve(options.repositoryRoot), path13.resolve(trimmed));
|
|
8084
8180
|
return normalizeRelativeProjectContextPath(relativePath);
|
|
8085
8181
|
}
|
|
8086
8182
|
function normalizeRelativeProjectContextPath(value) {
|
|
@@ -8131,48 +8227,16 @@ function stripJsonFence(value) {
|
|
|
8131
8227
|
return trimmed.replace(/^```(?:json)?\s*/i, "").replace(/```$/i, "").trim();
|
|
8132
8228
|
}
|
|
8133
8229
|
|
|
8134
|
-
// src/runner-status.ts
|
|
8135
|
-
import { createHash as createHash6 } from "node:crypto";
|
|
8136
|
-
var watchStateReminderMs = 60 * 1e3;
|
|
8137
|
-
function formatWatchStartupContext(input) {
|
|
8138
|
-
return [
|
|
8139
|
-
`Runner ${input.runnerId} is watching project ${input.projectId}.`,
|
|
8140
|
-
`Repository link: ${input.repositoryLinkId}`,
|
|
8141
|
-
`API: ${input.apiUrl}`,
|
|
8142
|
-
`Polling interval: ${input.intervalSeconds}s. Press Ctrl+C to stop.`
|
|
8143
|
-
];
|
|
8144
|
-
}
|
|
8145
|
-
function formatWatchIdleLine(action, intervalSeconds) {
|
|
8146
|
-
return `${formatProjectNextAction(action)} Checking again in ${intervalSeconds}s.`;
|
|
8147
|
-
}
|
|
8148
|
-
function shouldPrintWatchState(action, previous, nowMs, reminderMs = watchStateReminderMs) {
|
|
8149
|
-
const key = watchStateKey(action);
|
|
8150
|
-
if (!previous || previous.key !== key) {
|
|
8151
|
-
return true;
|
|
8152
|
-
}
|
|
8153
|
-
if (action.kind === "workCompleted") {
|
|
8154
|
-
return false;
|
|
8155
|
-
}
|
|
8156
|
-
return nowMs - previous.printedAtMs >= reminderMs;
|
|
8157
|
-
}
|
|
8158
|
-
function watchStateKey(action) {
|
|
8159
|
-
return [action.kind, action.message, action.workItemId, action.documentId, action.runnerId].filter(Boolean).join(":");
|
|
8160
|
-
}
|
|
8161
|
-
function stableRunnerId(input) {
|
|
8162
|
-
const digest = createHash6("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.machineId}`).digest("hex").slice(0, 20);
|
|
8163
|
-
return `runner_${digest}`;
|
|
8164
|
-
}
|
|
8165
|
-
|
|
8166
8230
|
// src/runner-resources.ts
|
|
8167
|
-
import
|
|
8231
|
+
import os8 from "node:os";
|
|
8168
8232
|
var defaultRuntime = {
|
|
8169
8233
|
nowMs: () => Date.now(),
|
|
8170
8234
|
memoryUsage: () => process.memoryUsage(),
|
|
8171
8235
|
uptime: () => process.uptime(),
|
|
8172
8236
|
cpuUsage: () => process.cpuUsage(),
|
|
8173
|
-
totalmem: () =>
|
|
8174
|
-
freemem: () =>
|
|
8175
|
-
loadavg: () =>
|
|
8237
|
+
totalmem: () => os8.totalmem(),
|
|
8238
|
+
freemem: () => os8.freemem(),
|
|
8239
|
+
loadavg: () => os8.loadavg()
|
|
8176
8240
|
};
|
|
8177
8241
|
var previousRunnerResourceSample;
|
|
8178
8242
|
function sampleCurrentRunnerResourceUsage() {
|
|
@@ -8285,8 +8349,8 @@ function roundNumber(value, digits) {
|
|
|
8285
8349
|
// src/importer.ts
|
|
8286
8350
|
import { execFile as execFile4 } from "node:child_process";
|
|
8287
8351
|
import { createHash as createHash7 } from "node:crypto";
|
|
8288
|
-
import { readdir as readdir6, readFile as
|
|
8289
|
-
import
|
|
8352
|
+
import { readdir as readdir6, readFile as readFile11, stat as stat5 } from "node:fs/promises";
|
|
8353
|
+
import path14 from "node:path";
|
|
8290
8354
|
import { promisify as promisify4 } from "node:util";
|
|
8291
8355
|
var execFileAsync4 = promisify4(execFile4);
|
|
8292
8356
|
var defaultMaxFileKb = 256;
|
|
@@ -8307,12 +8371,12 @@ var documentFolderByType = {
|
|
|
8307
8371
|
workflow: "docs/workflows"
|
|
8308
8372
|
};
|
|
8309
8373
|
async function inspectLocalRepository(rootDir, defaultBranch) {
|
|
8310
|
-
const requestedRoot =
|
|
8374
|
+
const requestedRoot = path14.resolve(rootDir);
|
|
8311
8375
|
const root = await runGit2(["-C", requestedRoot, "rev-parse", "--show-toplevel"]).catch(() => requestedRoot);
|
|
8312
8376
|
const detectedBranch = await runGit2(["-C", root, "symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => defaultBranch);
|
|
8313
8377
|
const originUrl = await runGit2(["-C", root, "remote", "get-url", "origin"]).catch(() => void 0);
|
|
8314
8378
|
const parsedCloneUrl = originUrl ? parseOptionalOriginCloneUrl(originUrl) : void 0;
|
|
8315
|
-
const repoName = (parsedCloneUrl?.repoName ??
|
|
8379
|
+
const repoName = (parsedCloneUrl?.repoName ?? path14.basename(root)) || "repository";
|
|
8316
8380
|
const fingerprintSeed = parsedCloneUrl ? `origin:${parsedCloneUrl.normalizedKey}` : `repo:${repoName}:${detectedBranch || defaultBranch}`;
|
|
8317
8381
|
return {
|
|
8318
8382
|
rootDir: root,
|
|
@@ -8324,7 +8388,7 @@ async function inspectLocalRepository(rootDir, defaultBranch) {
|
|
|
8324
8388
|
};
|
|
8325
8389
|
}
|
|
8326
8390
|
async function scanLegacyDocuments(options) {
|
|
8327
|
-
const rootDir =
|
|
8391
|
+
const rootDir = path14.resolve(options.rootDir);
|
|
8328
8392
|
const maxBytes = (options.maxFileKb ?? defaultMaxFileKb) * 1024;
|
|
8329
8393
|
const skipped = [];
|
|
8330
8394
|
const candidates = [];
|
|
@@ -8344,7 +8408,7 @@ async function scanLegacyDocuments(options) {
|
|
|
8344
8408
|
skipped.push({ repoPath, reason: "excluded" });
|
|
8345
8409
|
continue;
|
|
8346
8410
|
}
|
|
8347
|
-
const fullPath =
|
|
8411
|
+
const fullPath = path14.join(rootDir, ...repoPath.split("/"));
|
|
8348
8412
|
const fileStat = await stat5(fullPath).catch(() => void 0);
|
|
8349
8413
|
if (!fileStat?.isFile()) {
|
|
8350
8414
|
skipped.push({ repoPath, reason: "unreadable" });
|
|
@@ -8354,7 +8418,7 @@ async function scanLegacyDocuments(options) {
|
|
|
8354
8418
|
skipped.push({ repoPath, reason: "tooLarge" });
|
|
8355
8419
|
continue;
|
|
8356
8420
|
}
|
|
8357
|
-
const content = await
|
|
8421
|
+
const content = await readFile11(fullPath, "utf8").catch(() => void 0);
|
|
8358
8422
|
if (content === void 0) {
|
|
8359
8423
|
skipped.push({ repoPath, reason: "unreadable" });
|
|
8360
8424
|
continue;
|
|
@@ -8446,8 +8510,8 @@ async function listRepositoryPaths(rootDir) {
|
|
|
8446
8510
|
async function walkRepository(rootDir, directory, files) {
|
|
8447
8511
|
const entries = await readdir6(directory, { withFileTypes: true }).catch(() => []);
|
|
8448
8512
|
for (const entry of entries) {
|
|
8449
|
-
const fullPath =
|
|
8450
|
-
const repoPath = normalizeRepoPath4(
|
|
8513
|
+
const fullPath = path14.join(directory, entry.name);
|
|
8514
|
+
const repoPath = normalizeRepoPath4(path14.relative(rootDir, fullPath));
|
|
8451
8515
|
if (entry.isDirectory()) {
|
|
8452
8516
|
if (!excludedDirectoryNames.has(entry.name)) {
|
|
8453
8517
|
await walkRepository(rootDir, fullPath, files);
|
|
@@ -8515,9 +8579,9 @@ function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
|
|
|
8515
8579
|
usedPaths.add(basePath);
|
|
8516
8580
|
return basePath;
|
|
8517
8581
|
}
|
|
8518
|
-
const extension =
|
|
8519
|
-
const directory =
|
|
8520
|
-
const basename =
|
|
8582
|
+
const extension = path14.posix.extname(basePath) || ".md";
|
|
8583
|
+
const directory = path14.posix.dirname(basePath);
|
|
8584
|
+
const basename = path14.posix.basename(basePath, extension);
|
|
8521
8585
|
const uniquePath = `${directory}/${basename}-${hashText(sourcePath, 8)}${extension}`;
|
|
8522
8586
|
usedPaths.add(uniquePath);
|
|
8523
8587
|
return uniquePath;
|
|
@@ -8590,7 +8654,7 @@ function inferTitle2(content, repoPath) {
|
|
|
8590
8654
|
if (heading) return heading;
|
|
8591
8655
|
const htmlHeading = body.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
|
|
8592
8656
|
if (htmlHeading) return htmlHeading;
|
|
8593
|
-
const basename =
|
|
8657
|
+
const basename = path14.posix.basename(repoPath, path14.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
|
|
8594
8658
|
return titleCase(basename || "Imported Document");
|
|
8595
8659
|
}
|
|
8596
8660
|
function stripFrontmatter(content) {
|
|
@@ -8624,7 +8688,7 @@ async function runGit2(args) {
|
|
|
8624
8688
|
|
|
8625
8689
|
// src/runner-actions.ts
|
|
8626
8690
|
import { spawn as spawn4 } from "node:child_process";
|
|
8627
|
-
import
|
|
8691
|
+
import path15 from "node:path";
|
|
8628
8692
|
function buildBackgroundRunnerArgs(options) {
|
|
8629
8693
|
const args = [
|
|
8630
8694
|
"run",
|
|
@@ -8634,7 +8698,7 @@ function buildBackgroundRunnerArgs(options) {
|
|
|
8634
8698
|
"--runner-id",
|
|
8635
8699
|
options.runnerId,
|
|
8636
8700
|
"--root",
|
|
8637
|
-
|
|
8701
|
+
path15.resolve(options.root),
|
|
8638
8702
|
"--session",
|
|
8639
8703
|
options.session,
|
|
8640
8704
|
"--interval-seconds",
|
|
@@ -8752,8 +8816,8 @@ function truncateProcessOutput(value) {
|
|
|
8752
8816
|
|
|
8753
8817
|
// src/git-worktree.ts
|
|
8754
8818
|
import { execFile as execFile5 } from "node:child_process";
|
|
8755
|
-
import { copyFile, lstat, mkdir as
|
|
8756
|
-
import
|
|
8819
|
+
import { copyFile, lstat, mkdir as mkdir12, readdir as readdir7, stat as stat6 } from "node:fs/promises";
|
|
8820
|
+
import path16 from "node:path";
|
|
8757
8821
|
import { promisify as promisify5 } from "node:util";
|
|
8758
8822
|
var execFileAsync5 = promisify5(execFile5);
|
|
8759
8823
|
var exactLocalEnvironmentFiles = /* @__PURE__ */ new Set([".env", ".env.local", ".env.development", ".env.development.local", ".env.test", ".env.test.local", ".env.production", ".env.production.local"]);
|
|
@@ -8785,7 +8849,7 @@ async function prepareGitWorktreeIsolation(rootDir, workItem) {
|
|
|
8785
8849
|
const preparedLocalEnvironmentFileCount2 = await prepareLocalWorktreeEnvironment(repoRoot, worktreePath);
|
|
8786
8850
|
return { ...identity, baseRevision, worktreePath, ...preparedLocalEnvironmentFileCount2 ? { preparedLocalEnvironmentFileCount: preparedLocalEnvironmentFileCount2 } : {} };
|
|
8787
8851
|
}
|
|
8788
|
-
await
|
|
8852
|
+
await mkdir12(path16.dirname(worktreePath), { recursive: true });
|
|
8789
8853
|
const branchExists = await gitCommandSucceeds(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${identity.branch}`]);
|
|
8790
8854
|
const worktreeArgs = branchExists ? ["worktree", "add", worktreePath, identity.branch] : ["worktree", "add", "-b", identity.branch, worktreePath, baseRevision];
|
|
8791
8855
|
await gitOutput(repoRoot, worktreeArgs).catch((error) => {
|
|
@@ -8803,9 +8867,9 @@ async function resolveExistingGitWorktreeIsolation(rootDir, workItem) {
|
|
|
8803
8867
|
return { ...identity, baseRevision, worktreePath };
|
|
8804
8868
|
}
|
|
8805
8869
|
function localWorktreePath(repoRoot, worktreeKey) {
|
|
8806
|
-
const repoName =
|
|
8870
|
+
const repoName = path16.basename(repoRoot);
|
|
8807
8871
|
const worktreeSlug = worktreeKey.split("/").filter(Boolean).pop() ?? "work";
|
|
8808
|
-
return
|
|
8872
|
+
return path16.join(path16.dirname(repoRoot), `${repoName}.worktrees`, worktreeSlug);
|
|
8809
8873
|
}
|
|
8810
8874
|
async function assertExistingWorktree(worktreePath, branch) {
|
|
8811
8875
|
await gitOutput(worktreePath, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -8831,8 +8895,8 @@ async function prepareLocalWorktreeEnvironment(repoRoot, worktreePath) {
|
|
|
8831
8895
|
const candidates = await localEnvironmentFileCandidates(repoRoot);
|
|
8832
8896
|
let preparedCount = 0;
|
|
8833
8897
|
for (const candidate of candidates) {
|
|
8834
|
-
const sourcePath =
|
|
8835
|
-
const targetPath =
|
|
8898
|
+
const sourcePath = path16.join(repoRoot, candidate);
|
|
8899
|
+
const targetPath = path16.join(worktreePath, candidate);
|
|
8836
8900
|
if (await pathExists(targetPath)) {
|
|
8837
8901
|
continue;
|
|
8838
8902
|
}
|
|
@@ -8863,7 +8927,7 @@ async function localEnvironmentFileCandidates(repoRoot) {
|
|
|
8863
8927
|
if (!isRootFileName(name)) {
|
|
8864
8928
|
continue;
|
|
8865
8929
|
}
|
|
8866
|
-
const source = await lstat(
|
|
8930
|
+
const source = await lstat(path16.join(repoRoot, name)).catch(() => void 0);
|
|
8867
8931
|
if (source?.isFile()) {
|
|
8868
8932
|
candidates.push(name);
|
|
8869
8933
|
}
|
|
@@ -8874,7 +8938,7 @@ function isAllowedLocalEnvironmentFile(name) {
|
|
|
8874
8938
|
return exactLocalEnvironmentFiles.has(name) || localEnvironmentFilePattern.test(name);
|
|
8875
8939
|
}
|
|
8876
8940
|
function isRootFileName(name) {
|
|
8877
|
-
return name ===
|
|
8941
|
+
return name === path16.basename(name) && !name.includes("/") && !name.includes("\\");
|
|
8878
8942
|
}
|
|
8879
8943
|
async function gitOutput(cwd, args) {
|
|
8880
8944
|
const { stdout } = await execFileAsync5("git", args, { cwd, maxBuffer: 1024 * 1024 });
|
|
@@ -8907,7 +8971,7 @@ function safeFileError(error) {
|
|
|
8907
8971
|
|
|
8908
8972
|
// src/implementation-handoff.ts
|
|
8909
8973
|
import { execFile as execFile6 } from "node:child_process";
|
|
8910
|
-
import
|
|
8974
|
+
import path17 from "node:path";
|
|
8911
8975
|
import { promisify as promisify6 } from "node:util";
|
|
8912
8976
|
var execFileAsync6 = promisify6(execFile6);
|
|
8913
8977
|
async function completeImplementationHandoff(input) {
|
|
@@ -9175,7 +9239,7 @@ async function cleanupWorktree(run, input) {
|
|
|
9175
9239
|
return { status: "failed", message: "Cleanup skipped because the worktree is not clean after PR handoff." };
|
|
9176
9240
|
}
|
|
9177
9241
|
try {
|
|
9178
|
-
await gitOutput2(run, input.primaryRepoRoot ||
|
|
9242
|
+
await gitOutput2(run, input.primaryRepoRoot || path17.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
|
|
9179
9243
|
return { status: "completed" };
|
|
9180
9244
|
} catch (error) {
|
|
9181
9245
|
return { status: "failed", message: `Cleanup failed: ${safeErrorMessage(error)}` };
|
|
@@ -9896,7 +9960,7 @@ program.command("import").description("Pair an existing checkout and import lega
|
|
|
9896
9960
|
});
|
|
9897
9961
|
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) => {
|
|
9898
9962
|
const pairingRoot = await resolvePairingRoot(options.root, { explicitRoot: command.getOptionValueSource("root") === "cli" });
|
|
9899
|
-
let repositoryLinkId = options.repositoryLink ?? `repo_${
|
|
9963
|
+
let repositoryLinkId = options.repositoryLink ?? `repo_${randomUUID3()}`;
|
|
9900
9964
|
let credential = options.token;
|
|
9901
9965
|
if (options.pairingCode) {
|
|
9902
9966
|
const pairing = await new ApiClient({
|
|
@@ -10082,7 +10146,7 @@ work.command("prompt").description("Print or write an approved work prompt witho
|
|
|
10082
10146
|
}
|
|
10083
10147
|
const prompt = await createRunnerWorkPrompt(context.client, context.metadata.amistioProjectId, workItem);
|
|
10084
10148
|
if (options.out) {
|
|
10085
|
-
await
|
|
10149
|
+
await writeFile12(options.out, prompt, "utf8");
|
|
10086
10150
|
console.log(`Wrote work prompt to ${options.out}.`);
|
|
10087
10151
|
} else {
|
|
10088
10152
|
console.log(prompt);
|
|
@@ -10096,6 +10160,34 @@ program.command("tools").description("List local AI coding tools that the Amisti
|
|
|
10096
10160
|
}
|
|
10097
10161
|
console.log("custom - pass --tool-command to use any other local runner command.");
|
|
10098
10162
|
});
|
|
10163
|
+
var harness = program.command("harness").description("Inspect the built-in Amistio harness, direct providers, and bounded tool adapters");
|
|
10164
|
+
harness.command("status").description("Show the built-in harness boundary and execution policy summary").action(() => {
|
|
10165
|
+
console.log(`Harness: ${builtinAmistioHarnessAdapter.displayName} (${builtinAmistioHarnessAdapter.id})`);
|
|
10166
|
+
console.log("Default: built-in Amistio harness");
|
|
10167
|
+
console.log("Runner-owned boundary: pairing, work claiming, leases, worktree isolation, redaction, finalization, and handoff.");
|
|
10168
|
+
console.log(`Read-only work policy: ${runnerSupportedWorkKinds.filter((workKind) => harnessMutationPolicyForWorkKind(workKind) === "readOnly").join(", ")}`);
|
|
10169
|
+
console.log(`Mutating work policy: ${runnerSupportedWorkKinds.filter((workKind) => harnessMutationPolicyForWorkKind(workKind) === "mutating").join(", ")}`);
|
|
10170
|
+
console.log("Run execution: amistio run --watch");
|
|
10171
|
+
console.log("Local AI clients: amistio tools");
|
|
10172
|
+
console.log("Host execution helper: amistio host-helper status");
|
|
10173
|
+
});
|
|
10174
|
+
harness.command("providers").description("Check safe local readiness for direct and agent-client provider routes").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
|
|
10175
|
+
for (const target of harnessProviderCheckTargets()) {
|
|
10176
|
+
const result = await checkProviderAuthLink({ request: target.request, root: options.root });
|
|
10177
|
+
console.log(`${target.label}: ${result.providerAuthStatus.status}`);
|
|
10178
|
+
for (const line of formatProviderAuthStatusLines(result.providerAuthStatus)) {
|
|
10179
|
+
console.log(` ${line}`);
|
|
10180
|
+
}
|
|
10181
|
+
}
|
|
10182
|
+
});
|
|
10183
|
+
harness.command("tools").description("List bounded Amistio-owned tool adapters available to the harness").action(() => {
|
|
10184
|
+
console.log("Bounded Amistio harness tools. For optional local AI clients, run `amistio tools`.");
|
|
10185
|
+
for (const adapter of boundedToolAdapterCatalog) {
|
|
10186
|
+
const mutation = adapter.mutating ? "mutating" : "read-only";
|
|
10187
|
+
const implemented = adapter.implemented ? "yes" : "no";
|
|
10188
|
+
console.log(`${implemented} ${adapter.kind} ${adapter.id} - ${adapter.displayName}; ${mutation}; ${adapter.concurrency}; resources: ${adapter.serializedResourceKinds.join(", ")}`);
|
|
10189
|
+
}
|
|
10190
|
+
});
|
|
10099
10191
|
var hostHelper = program.command("host-helper").description("Inspect the optional Amistio host helper used for stronger local execution primitives");
|
|
10100
10192
|
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
10193
|
const config = options.path?.trim() ? { command: options.path.trim() } : nativeHostHelperConfigFromEnvironment();
|
|
@@ -10164,7 +10256,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
|
|
|
10164
10256
|
...options.toolCommand ? { toolCommand: options.toolCommand } : {},
|
|
10165
10257
|
...localModelConfig,
|
|
10166
10258
|
streamOutput: options.stream,
|
|
10167
|
-
...sessionPolicy === "none" ? {} : { session: { toolSessionId: `local_orchestration_${
|
|
10259
|
+
...sessionPolicy === "none" ? {} : { session: { toolSessionId: `local_orchestration_${randomUUID3()}`, policy: sessionPolicy, decision: localSessionDecision(sessionPolicy) } }
|
|
10168
10260
|
});
|
|
10169
10261
|
if (!options.stream && result.stdout.trim()) {
|
|
10170
10262
|
console.log(result.stdout.trim());
|
|
@@ -10187,12 +10279,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
10187
10279
|
process.exitCode = 1;
|
|
10188
10280
|
return;
|
|
10189
10281
|
}
|
|
10190
|
-
const runnerId = options.runnerId ??
|
|
10191
|
-
accountId: context.metadata.amistioAccountId,
|
|
10192
|
-
projectId: context.metadata.amistioProjectId,
|
|
10193
|
-
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
10194
|
-
machineId: runnerMachineId()
|
|
10195
|
-
});
|
|
10282
|
+
const runnerId = options.runnerId ?? await resolveLocalRunnerId(runnerIdentityScope(context.metadata));
|
|
10196
10283
|
const resolvedOptions = { ...options, runnerId };
|
|
10197
10284
|
if (resolvedOptions.maxConcurrentWork > MAX_CONCURRENT_RUNNER_WORK) {
|
|
10198
10285
|
console.log(`--max-concurrent-work is capped at ${MAX_CONCURRENT_RUNNER_WORK}.`);
|
|
@@ -10210,7 +10297,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
10210
10297
|
projectId: context.metadata.amistioProjectId,
|
|
10211
10298
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
10212
10299
|
runnerId,
|
|
10213
|
-
rootDir:
|
|
10300
|
+
rootDir: path18.resolve(options.root),
|
|
10214
10301
|
apiUrl: options.apiUrl,
|
|
10215
10302
|
args: buildBackgroundRunnerArgs(resolvedOptions)
|
|
10216
10303
|
});
|
|
@@ -10281,6 +10368,38 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
10281
10368
|
}
|
|
10282
10369
|
});
|
|
10283
10370
|
var runner = program.command("runner").description("Manage local Amistio runner processes");
|
|
10371
|
+
runner.command("repair").description("Repair local runner identity state for this paired repository").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--runner-id <runnerId>", "Fresh runner ID to store instead of generating one").option("--clear-credential", "Delete the local runner credential so this checkout must be paired again").option("--dry-run", "Print repair actions without changing local config").action(async (options) => {
|
|
10372
|
+
const metadata = await readProjectLink(options.root);
|
|
10373
|
+
if (!metadata) {
|
|
10374
|
+
console.log("Repository is not paired. Run `amistio pair` first.");
|
|
10375
|
+
return;
|
|
10376
|
+
}
|
|
10377
|
+
const scope = runnerIdentityScope(metadata);
|
|
10378
|
+
const identityKey = runnerIdentityKey(scope);
|
|
10379
|
+
const identityStore = new LocalRunnerIdentityStore();
|
|
10380
|
+
const credentialStore = new LocalCredentialStore();
|
|
10381
|
+
const stableRunner = stableRunnerId(scope);
|
|
10382
|
+
const currentRunnerId = await identityStore.get(identityKey) ?? stableRunner;
|
|
10383
|
+
const nextRunnerId = options.runnerId?.trim() || createFreshRunnerId();
|
|
10384
|
+
if (options.dryRun) {
|
|
10385
|
+
console.log(`Would rotate local runner ID from ${currentRunnerId} to ${nextRunnerId}.`);
|
|
10386
|
+
if (options.clearCredential) {
|
|
10387
|
+
console.log("Would delete the local runner credential for this paired checkout.");
|
|
10388
|
+
}
|
|
10389
|
+
return;
|
|
10390
|
+
}
|
|
10391
|
+
await identityStore.set(identityKey, nextRunnerId);
|
|
10392
|
+
if (options.clearCredential) {
|
|
10393
|
+
await credentialStore.delete(credentialKey(metadata.amistioAccountId, metadata.amistioProjectId, metadata.repositoryLinkId));
|
|
10394
|
+
}
|
|
10395
|
+
console.log(`Local runner ID repaired: ${currentRunnerId} -> ${nextRunnerId}.`);
|
|
10396
|
+
if (options.clearCredential) {
|
|
10397
|
+
console.log("Deleted the local runner credential. Create a fresh pairing code in the Runner panel, then run the copied pair/import command.");
|
|
10398
|
+
} else {
|
|
10399
|
+
console.log(`Next: amistio run${formatApiUrlFlag(options.apiUrl)} --watch`);
|
|
10400
|
+
console.log("If a startup service uses the old runner ID, reinstall it from the Runner panel command.");
|
|
10401
|
+
}
|
|
10402
|
+
});
|
|
10284
10403
|
runner.command("status").description("Show background runner status for the paired repository").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--runner-id <runnerId>", "Limit status to one runner ID").action(async (options) => {
|
|
10285
10404
|
const context = await loadPairedApiContext(options.root, options.apiUrl);
|
|
10286
10405
|
if (!context) {
|
|
@@ -10387,12 +10506,7 @@ runnerService.command("install").description("Install a user-level startup servi
|
|
|
10387
10506
|
process.exitCode = 1;
|
|
10388
10507
|
return;
|
|
10389
10508
|
}
|
|
10390
|
-
const runnerId = options.runnerId ??
|
|
10391
|
-
accountId: context.metadata.amistioAccountId,
|
|
10392
|
-
projectId: context.metadata.amistioProjectId,
|
|
10393
|
-
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
10394
|
-
machineId: runnerMachineId()
|
|
10395
|
-
});
|
|
10509
|
+
const runnerId = options.runnerId ?? await resolveLocalRunnerId(runnerIdentityScope(context.metadata));
|
|
10396
10510
|
if (options.maxConcurrentWork > MAX_CONCURRENT_RUNNER_WORK) {
|
|
10397
10511
|
console.log(`--max-concurrent-work is capped at ${MAX_CONCURRENT_RUNNER_WORK}.`);
|
|
10398
10512
|
process.exitCode = 1;
|
|
@@ -10404,7 +10518,7 @@ runnerService.command("install").description("Install a user-level startup servi
|
|
|
10404
10518
|
projectId: context.metadata.amistioProjectId,
|
|
10405
10519
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
10406
10520
|
runnerId,
|
|
10407
|
-
rootDir:
|
|
10521
|
+
rootDir: path18.resolve(options.root),
|
|
10408
10522
|
apiUrl: options.apiUrl,
|
|
10409
10523
|
args,
|
|
10410
10524
|
platform
|
|
@@ -10430,12 +10544,7 @@ runnerService.command("status").description("Show the startup service status for
|
|
|
10430
10544
|
console.log("Repository is not paired. Run `amistio pair` first.");
|
|
10431
10545
|
return;
|
|
10432
10546
|
}
|
|
10433
|
-
const runnerId = options.runnerId ??
|
|
10434
|
-
accountId: context.metadata.amistioAccountId,
|
|
10435
|
-
projectId: context.metadata.amistioProjectId,
|
|
10436
|
-
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
10437
|
-
machineId: runnerMachineId()
|
|
10438
|
-
});
|
|
10547
|
+
const runnerId = options.runnerId ?? await resolveLocalRunnerId(runnerIdentityScope(context.metadata));
|
|
10439
10548
|
const metadata = await readRunnerServiceMetadata({ accountId: context.metadata.amistioAccountId, projectId: context.metadata.amistioProjectId, repositoryLinkId: context.metadata.repositoryLinkId, runnerId });
|
|
10440
10549
|
if (!metadata) {
|
|
10441
10550
|
console.log("No startup service metadata found for this paired repository runner.");
|
|
@@ -10454,12 +10563,7 @@ runnerService.command("remove").description("Remove the startup service for this
|
|
|
10454
10563
|
console.log("Repository is not paired. Run `amistio pair` first.");
|
|
10455
10564
|
return;
|
|
10456
10565
|
}
|
|
10457
|
-
const runnerId = options.runnerId ??
|
|
10458
|
-
accountId: context.metadata.amistioAccountId,
|
|
10459
|
-
projectId: context.metadata.amistioProjectId,
|
|
10460
|
-
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
10461
|
-
machineId: runnerMachineId()
|
|
10462
|
-
});
|
|
10566
|
+
const runnerId = options.runnerId ?? await resolveLocalRunnerId(runnerIdentityScope(context.metadata));
|
|
10463
10567
|
const removed = await removeRunnerService({ accountId: context.metadata.amistioAccountId, projectId: context.metadata.amistioProjectId, repositoryLinkId: context.metadata.repositoryLinkId, runnerId });
|
|
10464
10568
|
console.log(removed ? `Removed startup service ${removed.serviceName}.` : "No startup service metadata found for this paired repository runner.");
|
|
10465
10569
|
});
|
|
@@ -11088,7 +11192,7 @@ async function runNextWorkItem({
|
|
|
11088
11192
|
projectId,
|
|
11089
11193
|
result.workItem.workItemId,
|
|
11090
11194
|
finalStatus,
|
|
11091
|
-
`run_${result.workItem.workItemId}_${
|
|
11195
|
+
`run_${result.workItem.workItemId}_${randomUUID3()}`,
|
|
11092
11196
|
runnerId,
|
|
11093
11197
|
{
|
|
11094
11198
|
tool: preview.toolName,
|
|
@@ -11182,7 +11286,7 @@ async function prepareWorktreeForClaimedItem({ apiClient, heartbeatConcurrency,
|
|
|
11182
11286
|
const telemetry = workItemIsolationTelemetry(workItem, { ...identity, baseRevision: workItem.baseRevision ?? "unknown", worktreePath: "" });
|
|
11183
11287
|
const finalAttempt = workItem.attempt >= maxPreflightAttempts;
|
|
11184
11288
|
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}`;
|
|
11185
|
-
const statusResult = await apiClient.updateWorkStatus(projectId, workItem.workItemId, finalAttempt ? "failed" : "approved", `worktree_${finalAttempt ? "failed" : "retry"}_${workItem.workItemId}_${workItem.attempt}_${
|
|
11289
|
+
const statusResult = await apiClient.updateWorkStatus(projectId, workItem.workItemId, finalAttempt ? "failed" : "approved", `worktree_${finalAttempt ? "failed" : "retry"}_${workItem.workItemId}_${workItem.attempt}_${randomUUID3()}`, runnerId, {
|
|
11186
11290
|
...telemetry,
|
|
11187
11291
|
message: statusMessage,
|
|
11188
11292
|
...finalAttempt ? { blockerReason: message } : { releaseClaim: true },
|
|
@@ -11251,7 +11355,7 @@ async function recordFinalizationFailure({ apiClient, durationMs, error, isolati
|
|
|
11251
11355
|
const settlements = await Promise.allSettled([
|
|
11252
11356
|
apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)),
|
|
11253
11357
|
markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage6(error)),
|
|
11254
|
-
apiClient.updateWorkStatus(projectId, workItem.workItemId, "failed", `finalize_failed_${workItem.workItemId}_${workItem.attempt}_${
|
|
11358
|
+
apiClient.updateWorkStatus(projectId, workItem.workItemId, "failed", `finalize_failed_${workItem.workItemId}_${workItem.attempt}_${randomUUID3()}`, runnerId, {
|
|
11255
11359
|
...isolationTelemetry,
|
|
11256
11360
|
tool: toolName,
|
|
11257
11361
|
durationMs,
|
|
@@ -11402,7 +11506,7 @@ async function updateRunnerCommandStatus(apiClient, context, command, status, me
|
|
|
11402
11506
|
runnerId: context.runnerId,
|
|
11403
11507
|
repositoryLinkId: context.repositoryLinkId,
|
|
11404
11508
|
status,
|
|
11405
|
-
idempotencyKey: `runner_command_${command.commandId}_${status}_${
|
|
11509
|
+
idempotencyKey: `runner_command_${command.commandId}_${status}_${randomUUID3()}`,
|
|
11406
11510
|
message,
|
|
11407
11511
|
...error ? { error } : {},
|
|
11408
11512
|
...providerAuthStatus ? { providerAuthStatus } : {}
|
|
@@ -11491,7 +11595,7 @@ async function findRecoveryWorkItem(apiClient, projectId, workItemId) {
|
|
|
11491
11595
|
return workItems.find((item) => item.workItemId === workItemId);
|
|
11492
11596
|
}
|
|
11493
11597
|
async function submitRecoveredHandoff(apiClient, context, workItem, handoff, action) {
|
|
11494
|
-
await apiClient.updateWorkStatus(context.projectId, workItem.workItemId, recoveredHandoffWorkStatus(handoff), `handoff_recovery_${workItem.workItemId}_${action}_${
|
|
11598
|
+
await apiClient.updateWorkStatus(context.projectId, workItem.workItemId, recoveredHandoffWorkStatus(handoff), `handoff_recovery_${workItem.workItemId}_${action}_${randomUUID3()}`, context.runnerId, {
|
|
11495
11599
|
implementationHandoff: handoff,
|
|
11496
11600
|
...handoff.message ? { message: handoff.message } : {},
|
|
11497
11601
|
...workItem.controllingAdrId ? { controllingAdrId: workItem.controllingAdrId } : {},
|
|
@@ -11775,7 +11879,7 @@ ${toolResult.stderr}`);
|
|
|
11775
11879
|
const resultMutation = {
|
|
11776
11880
|
status: "completed",
|
|
11777
11881
|
runnerId,
|
|
11778
|
-
idempotencyKey: `generation_${workItem.workItemId}_${workItem.attempt}_${
|
|
11882
|
+
idempotencyKey: `generation_${workItem.workItemId}_${workItem.attempt}_${randomUUID3()}`,
|
|
11779
11883
|
artifacts,
|
|
11780
11884
|
tool: toolName,
|
|
11781
11885
|
durationMs,
|
|
@@ -11850,7 +11954,7 @@ ${toolResult.stderr}`);
|
|
|
11850
11954
|
const failedResult2 = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
|
|
11851
11955
|
status: "failed",
|
|
11852
11956
|
runnerId,
|
|
11853
|
-
idempotencyKey: `generation_${workItem.workItemId}_${
|
|
11957
|
+
idempotencyKey: `generation_${workItem.workItemId}_${randomUUID3()}`,
|
|
11854
11958
|
tool: toolName,
|
|
11855
11959
|
durationMs,
|
|
11856
11960
|
...failedSessionTelemetry,
|
|
@@ -11900,7 +12004,7 @@ ${toolResult.stderr}`);
|
|
|
11900
12004
|
const resultMutation = {
|
|
11901
12005
|
status: "completed",
|
|
11902
12006
|
runnerId,
|
|
11903
|
-
idempotencyKey: `assistant_${workItem.workItemId}_${
|
|
12007
|
+
idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID3()}`,
|
|
11904
12008
|
answer: answerResult.answer,
|
|
11905
12009
|
sourceBoundary: answerResult.sourceBoundary,
|
|
11906
12010
|
citations: answerResult.citations,
|
|
@@ -11936,7 +12040,7 @@ ${toolResult.stderr}`);
|
|
|
11936
12040
|
const failedMutation = {
|
|
11937
12041
|
status: "failed",
|
|
11938
12042
|
runnerId,
|
|
11939
|
-
idempotencyKey: `assistant_${workItem.workItemId}_${
|
|
12043
|
+
idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID3()}`,
|
|
11940
12044
|
tool: toolName,
|
|
11941
12045
|
durationMs,
|
|
11942
12046
|
...sessionTelemetry,
|
|
@@ -11999,7 +12103,7 @@ ${toolResult.stderr}`);
|
|
|
11999
12103
|
const resultMutation = {
|
|
12000
12104
|
status: "completed",
|
|
12001
12105
|
runnerId,
|
|
12002
|
-
idempotencyKey: `impact_${workItem.workItemId}_${
|
|
12106
|
+
idempotencyKey: `impact_${workItem.workItemId}_${randomUUID3()}`,
|
|
12003
12107
|
report: {
|
|
12004
12108
|
...report,
|
|
12005
12109
|
analyzedRepoRevision: report.analyzedRepoRevision ?? metadata?.lastSyncedRevision
|
|
@@ -12036,7 +12140,7 @@ ${toolResult.stderr}`);
|
|
|
12036
12140
|
const failedMutation = {
|
|
12037
12141
|
status: "failed",
|
|
12038
12142
|
runnerId,
|
|
12039
|
-
idempotencyKey: `impact_${workItem.workItemId}_${
|
|
12143
|
+
idempotencyKey: `impact_${workItem.workItemId}_${randomUUID3()}`,
|
|
12040
12144
|
tool: toolName,
|
|
12041
12145
|
durationMs,
|
|
12042
12146
|
...sessionTelemetry,
|
|
@@ -12097,7 +12201,7 @@ ${toolResult.stderr}`);
|
|
|
12097
12201
|
const resultMutation = {
|
|
12098
12202
|
status: "completed",
|
|
12099
12203
|
runnerId,
|
|
12100
|
-
idempotencyKey: `issue_${workItem.workItemId}_${
|
|
12204
|
+
idempotencyKey: `issue_${workItem.workItemId}_${randomUUID3()}`,
|
|
12101
12205
|
diagnosis,
|
|
12102
12206
|
tool: toolName,
|
|
12103
12207
|
durationMs,
|
|
@@ -12131,7 +12235,7 @@ ${toolResult.stderr}`);
|
|
|
12131
12235
|
const failedMutation = {
|
|
12132
12236
|
status: "failed",
|
|
12133
12237
|
runnerId,
|
|
12134
|
-
idempotencyKey: `issue_${workItem.workItemId}_${
|
|
12238
|
+
idempotencyKey: `issue_${workItem.workItemId}_${randomUUID3()}`,
|
|
12135
12239
|
tool: toolName,
|
|
12136
12240
|
durationMs,
|
|
12137
12241
|
...sessionTelemetry,
|
|
@@ -12192,7 +12296,7 @@ ${toolResult.stderr}`);
|
|
|
12192
12296
|
const resultMutation = {
|
|
12193
12297
|
status: "completed",
|
|
12194
12298
|
runnerId,
|
|
12195
|
-
idempotencyKey: `security_${workItem.workItemId}_${
|
|
12299
|
+
idempotencyKey: `security_${workItem.workItemId}_${randomUUID3()}`,
|
|
12196
12300
|
result: scanResult,
|
|
12197
12301
|
tool: toolName,
|
|
12198
12302
|
durationMs,
|
|
@@ -12226,7 +12330,7 @@ ${toolResult.stderr}`);
|
|
|
12226
12330
|
const failedMutation = {
|
|
12227
12331
|
status: "failed",
|
|
12228
12332
|
runnerId,
|
|
12229
|
-
idempotencyKey: `security_${workItem.workItemId}_${
|
|
12333
|
+
idempotencyKey: `security_${workItem.workItemId}_${randomUUID3()}`,
|
|
12230
12334
|
tool: toolName,
|
|
12231
12335
|
durationMs,
|
|
12232
12336
|
...sessionTelemetry,
|
|
@@ -12287,7 +12391,7 @@ ${toolResult.stderr}`);
|
|
|
12287
12391
|
const resultMutation = {
|
|
12288
12392
|
status: "completed",
|
|
12289
12393
|
runnerId,
|
|
12290
|
-
idempotencyKey: `app_evaluation_${workItem.workItemId}_${
|
|
12394
|
+
idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID3()}`,
|
|
12291
12395
|
result: scanResult,
|
|
12292
12396
|
tool: toolName,
|
|
12293
12397
|
durationMs,
|
|
@@ -12321,7 +12425,7 @@ ${toolResult.stderr}`);
|
|
|
12321
12425
|
const failedMutation = {
|
|
12322
12426
|
status: "failed",
|
|
12323
12427
|
runnerId,
|
|
12324
|
-
idempotencyKey: `app_evaluation_${workItem.workItemId}_${
|
|
12428
|
+
idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID3()}`,
|
|
12325
12429
|
tool: toolName,
|
|
12326
12430
|
durationMs,
|
|
12327
12431
|
...sessionTelemetry,
|
|
@@ -12382,7 +12486,7 @@ ${toolResult.stderr}`);
|
|
|
12382
12486
|
const resultMutation = {
|
|
12383
12487
|
status: "completed",
|
|
12384
12488
|
runnerId,
|
|
12385
|
-
idempotencyKey: `brain_consolidation_${workItem.workItemId}_${
|
|
12489
|
+
idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID3()}`,
|
|
12386
12490
|
result: scanResult,
|
|
12387
12491
|
tool: toolName,
|
|
12388
12492
|
durationMs,
|
|
@@ -12416,7 +12520,7 @@ ${toolResult.stderr}`);
|
|
|
12416
12520
|
const failedMutation = {
|
|
12417
12521
|
status: "failed",
|
|
12418
12522
|
runnerId,
|
|
12419
|
-
idempotencyKey: `brain_consolidation_${workItem.workItemId}_${
|
|
12523
|
+
idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID3()}`,
|
|
12420
12524
|
tool: toolName,
|
|
12421
12525
|
durationMs,
|
|
12422
12526
|
...sessionTelemetry,
|
|
@@ -12478,7 +12582,7 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
|
|
|
12478
12582
|
const resultMutation = {
|
|
12479
12583
|
status: "completed",
|
|
12480
12584
|
runnerId,
|
|
12481
|
-
idempotencyKey: `project_context_${workItem.workItemId}_${
|
|
12585
|
+
idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID3()}`,
|
|
12482
12586
|
result: refreshResult,
|
|
12483
12587
|
tool: toolName,
|
|
12484
12588
|
durationMs,
|
|
@@ -12524,7 +12628,7 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
|
|
|
12524
12628
|
const failedMutation = {
|
|
12525
12629
|
status: "failed",
|
|
12526
12630
|
runnerId,
|
|
12527
|
-
idempotencyKey: `project_context_${workItem.workItemId}_${
|
|
12631
|
+
idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID3()}`,
|
|
12528
12632
|
tool: toolName,
|
|
12529
12633
|
durationMs,
|
|
12530
12634
|
...sessionTelemetry,
|
|
@@ -12585,7 +12689,7 @@ ${toolResult.stderr}`);
|
|
|
12585
12689
|
const resultMutation = {
|
|
12586
12690
|
status: "completed",
|
|
12587
12691
|
runnerId,
|
|
12588
|
-
idempotencyKey: `implementation_verification_${workItem.workItemId}_${
|
|
12692
|
+
idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID3()}`,
|
|
12589
12693
|
result: verificationResult,
|
|
12590
12694
|
tool: toolName,
|
|
12591
12695
|
durationMs,
|
|
@@ -12619,7 +12723,7 @@ ${toolResult.stderr}`);
|
|
|
12619
12723
|
const failedMutation = {
|
|
12620
12724
|
status: "failed",
|
|
12621
12725
|
runnerId,
|
|
12622
|
-
idempotencyKey: `implementation_verification_${workItem.workItemId}_${
|
|
12726
|
+
idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID3()}`,
|
|
12623
12727
|
tool: toolName,
|
|
12624
12728
|
durationMs,
|
|
12625
12729
|
...sessionTelemetry,
|
|
@@ -12680,7 +12784,7 @@ ${toolResult.stderr}`);
|
|
|
12680
12784
|
const resultMutation = {
|
|
12681
12785
|
status: "completed",
|
|
12682
12786
|
runnerId,
|
|
12683
|
-
idempotencyKey: `test_quality_${workItem.workItemId}_${
|
|
12787
|
+
idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID3()}`,
|
|
12684
12788
|
result: scanResult,
|
|
12685
12789
|
tool: toolName,
|
|
12686
12790
|
durationMs,
|
|
@@ -12714,7 +12818,7 @@ ${toolResult.stderr}`);
|
|
|
12714
12818
|
const failedMutation = {
|
|
12715
12819
|
status: "failed",
|
|
12716
12820
|
runnerId,
|
|
12717
|
-
idempotencyKey: `test_quality_${workItem.workItemId}_${
|
|
12821
|
+
idempotencyKey: `test_quality_${workItem.workItemId}_${randomUUID3()}`,
|
|
12718
12822
|
tool: toolName,
|
|
12719
12823
|
durationMs,
|
|
12720
12824
|
...sessionTelemetry,
|
|
@@ -12775,7 +12879,7 @@ ${toolResult.stderr}`);
|
|
|
12775
12879
|
const resultMutation = {
|
|
12776
12880
|
status: "completed",
|
|
12777
12881
|
runnerId,
|
|
12778
|
-
idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${
|
|
12882
|
+
idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID3()}`,
|
|
12779
12883
|
result: gateResult,
|
|
12780
12884
|
tool: toolName,
|
|
12781
12885
|
durationMs,
|
|
@@ -12809,7 +12913,7 @@ ${toolResult.stderr}`);
|
|
|
12809
12913
|
const failedMutation = {
|
|
12810
12914
|
status: "failed",
|
|
12811
12915
|
runnerId,
|
|
12812
|
-
idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${
|
|
12916
|
+
idempotencyKey: `implementation_test_gate_${workItem.workItemId}_${randomUUID3()}`,
|
|
12813
12917
|
tool: toolName,
|
|
12814
12918
|
durationMs,
|
|
12815
12919
|
...sessionTelemetry,
|
|
@@ -12954,6 +13058,14 @@ async function loadPairedApiContext(root, apiUrl) {
|
|
|
12954
13058
|
})
|
|
12955
13059
|
};
|
|
12956
13060
|
}
|
|
13061
|
+
function runnerIdentityScope(metadata) {
|
|
13062
|
+
return {
|
|
13063
|
+
accountId: metadata.amistioAccountId,
|
|
13064
|
+
projectId: metadata.amistioProjectId,
|
|
13065
|
+
repositoryLinkId: metadata.repositoryLinkId,
|
|
13066
|
+
machineId: runnerMachineId()
|
|
13067
|
+
};
|
|
13068
|
+
}
|
|
12957
13069
|
async function loadProjectNextAction(apiClient, projectId, repositoryLinkId, root) {
|
|
12958
13070
|
const [{ workItems }, { documents }, { runners }] = await Promise.all([
|
|
12959
13071
|
apiClient.listWorkItems(projectId),
|
|
@@ -13038,7 +13150,7 @@ async function prepareToolSession({
|
|
|
13038
13150
|
});
|
|
13039
13151
|
return { ...selection, toolSession: toolSession2 };
|
|
13040
13152
|
}
|
|
13041
|
-
const toolSessionId = `tool_session_${
|
|
13153
|
+
const toolSessionId = `tool_session_${randomUUID3()}`;
|
|
13042
13154
|
const { toolSession } = await apiClient.createToolSession(projectId, {
|
|
13043
13155
|
toolSessionId,
|
|
13044
13156
|
repositoryLinkId,
|
|
@@ -13145,6 +13257,23 @@ function truncateLogExcerpt(value) {
|
|
|
13145
13257
|
function formatHostHelperCapability(label, support) {
|
|
13146
13258
|
return `${label}: ${support.supported ? "supported" : "unsupported"}${support.reason ? ` (${support.reason})` : ""}`;
|
|
13147
13259
|
}
|
|
13260
|
+
function harnessProviderCheckTargets() {
|
|
13261
|
+
return [
|
|
13262
|
+
{ label: "GitHub Models Direct", request: { providerId: "github-models", providerClientId: "github-models-api", routeType: "directProvider" } },
|
|
13263
|
+
{ label: "GitHub Copilot SDK", request: { providerId: "github-copilot", providerClientId: "github-copilot-sdk", routeType: "agentClient" } }
|
|
13264
|
+
];
|
|
13265
|
+
}
|
|
13266
|
+
function formatProviderAuthStatusLines(status) {
|
|
13267
|
+
const lines = [
|
|
13268
|
+
`Target: ${status.providerId}:${status.providerClientId}:${status.routeType}`,
|
|
13269
|
+
`Checked: ${status.checkedAt}`
|
|
13270
|
+
];
|
|
13271
|
+
if (status.authMethodLabel) lines.push(`Auth: ${status.authMethodLabel}`);
|
|
13272
|
+
if (status.modelCount !== void 0) lines.push(`Models: ${status.modelCount}`);
|
|
13273
|
+
if (status.message) lines.push(`Message: ${status.message}`);
|
|
13274
|
+
if (status.errorCode) lines.push(`Code: ${status.errorCode}`);
|
|
13275
|
+
return lines;
|
|
13276
|
+
}
|
|
13148
13277
|
function collectRepeatedOption(value, previous) {
|
|
13149
13278
|
return [...previous, value];
|
|
13150
13279
|
}
|
|
@@ -13171,7 +13300,7 @@ function parseReasoningEffort(value) {
|
|
|
13171
13300
|
throw new Error(`Expected reasoning effort auto, low, medium, high, or xhigh; received ${value}.`);
|
|
13172
13301
|
}
|
|
13173
13302
|
function inferRepoName(root) {
|
|
13174
|
-
return
|
|
13303
|
+
return path18.basename(path18.resolve(root)) || "repository";
|
|
13175
13304
|
}
|
|
13176
13305
|
function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
|
|
13177
13306
|
return createHash9("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
|
|
@@ -13448,7 +13577,7 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode(), concurr
|
|
|
13448
13577
|
return {
|
|
13449
13578
|
version: CLI_VERSION,
|
|
13450
13579
|
mode,
|
|
13451
|
-
hostname:
|
|
13580
|
+
hostname: os9.hostname(),
|
|
13452
13581
|
...runnerIsolationCapabilityMetadata(),
|
|
13453
13582
|
maxConcurrentWork: concurrencyMetadata.maxConcurrentWork,
|
|
13454
13583
|
activeClaimLaneIds: concurrencyMetadata.activeClaimLaneIds,
|
|
@@ -13473,7 +13602,7 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode(), concurr
|
|
|
13473
13602
|
};
|
|
13474
13603
|
}
|
|
13475
13604
|
function runnerMachineId() {
|
|
13476
|
-
return createHash9("sha256").update(`${
|
|
13605
|
+
return createHash9("sha256").update(`${os9.hostname()}:${os9.platform()}:${os9.arch()}`).digest("hex").slice(0, 20);
|
|
13477
13606
|
}
|
|
13478
13607
|
async function delay(milliseconds) {
|
|
13479
13608
|
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|