@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.
Files changed (3) hide show
  1. package/README.md +9 -3
  2. package/dist/index.js +312 -183
  3. 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, stop the foreground process and start or pair a new runner from the Runner panel. Forgotten runner IDs are intentionally blocked from heartbeats and work claims, so the CLI exits instead of retrying with the tombstoned identity.
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 randomUUID2 } from "node:crypto";
5
- import { writeFile as writeFile11 } from "node:fs/promises";
6
- import os8 from "node:os";
7
- import path17 from "node:path";
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 path18 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
3149
- return new URL(`${base}${path18}`);
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 createHash4 } from "node:crypto";
5535
- import { mkdir as mkdir8, readFile as readFile7, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
5536
- import os5 from "node:os";
5537
- import path9 from "node:path";
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 ?? os5.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 = path9.join(input.metadataDir ?? defaultRunnerMetadataDir(), `${runnerServiceKey(input)}.service.log`);
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: path9.resolve(input.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 mkdir8(path9.dirname(descriptor.metadata.serviceFilePath), { recursive: true });
5578
- await mkdir8(input.metadataDir ?? defaultRunnerMetadataDir(), { recursive: true });
5579
- await writeFile8(descriptor.metadata.serviceFilePath, descriptor.content, { encoding: "utf8", mode: 384 });
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 readFile7(runnerServiceMetadataPath(input, metadataDir), "utf8"));
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 mkdir8(metadataDir, { recursive: true });
5612
- await writeFile8(runnerServiceMetadataPath(metadata, metadataDir), JSON.stringify(metadata, null, 2), { encoding: "utf8", mode: 384 });
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 path9.join(homeDir, "Library", "LaunchAgents", `${serviceName}.plist`);
5791
+ return path10.join(homeDir, "Library", "LaunchAgents", `${serviceName}.plist`);
5696
5792
  }
5697
- return path9.join(homeDir, ".config", "systemd", "user", `${serviceName}.service`);
5793
+ return path10.join(homeDir, ".config", "systemd", "user", `${serviceName}.service`);
5698
5794
  }
5699
5795
  function runnerServiceMetadataPath(input, metadataDir) {
5700
- return path9.join(metadataDir, `${runnerServiceKey(input)}.service.json`);
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 createHash4("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.runnerId}`).digest("hex");
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 createHash5 } from "node:crypto";
6035
- import { mkdir as mkdir9, readdir as readdir5, readFile as readFile8, stat as stat4, writeFile as writeFile9 } from "node:fs/promises";
6036
- import path10 from "node:path";
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 readFile8(fullPath, "utf8");
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 mkdir9(path10.dirname(fullPath), { recursive: true });
6183
- await writeFile9(fullPath, createSyncedDocumentContent(document), "utf8");
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 readFile8(fullPath, "utf8");
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 = path10.join(rootDir, syncRoot);
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 = path10.join(directory, entry.name);
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 (path10.isAbsolute(repoPath)) {
6452
+ if (path11.isAbsolute(repoPath)) {
6357
6453
  throw new Error(`Refusing to use absolute repo path: ${repoPath}`);
6358
6454
  }
6359
- const normalized = path10.normalize(repoPath);
6360
- if (normalized === ".." || normalized.startsWith(`..${path10.sep}`)) {
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 = path10.resolve(rootDir);
6364
- const fullPath = path10.resolve(root, normalized);
6365
- if (!fullPath.startsWith(`${root}${path10.sep}`)) {
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 = path10.normalize(repoPath);
6372
- return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path10.sep}`)) || normalized === htmlSyncRoot || normalized.startsWith(`${htmlSyncRoot}${path10.sep}`);
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 path10.relative(rootDir, fullPath).split(path10.sep).join("/");
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 || path10.basename(repoPath, path10.extname(repoPath));
6485
+ return htmlHeading || path11.basename(repoPath, path11.extname(repoPath));
6390
6486
  }
6391
6487
  async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingDocuments, options) {
6392
- const root = path10.resolve(rootDir);
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 readFile8(fullPath, "utf8").catch(() => void 0);
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 = path10.join(rootDir, syncRoot);
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 = path10.join(directory, entry.name);
6492
- const repoPath = normalizeRepoPath3(path10.relative(rootDir, fullPath));
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_${createHash5("sha256").update(`${metadata.amistioAccountId}:${metadata.amistioProjectId}:${metadata.repositoryLinkId}:${repoPath}`).digest("hex").slice(0, 24)}`;
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 mkdir10, readFile as readFile9, writeFile as writeFile10 } from "node:fs/promises";
6555
- import os6 from "node:os";
6556
- import path11 from "node:path";
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 mkdir10(path11.dirname(this.filePath), { recursive: true });
6571
- await writeFile10(this.filePath, JSON.stringify(data, null, 2), "utf8");
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 readFile9(this.filePath, "utf8"));
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 path11.join(os6.homedir(), "Library", "Application Support", "Amistio", "tool-sessions.json");
6679
+ return path12.join(os7.homedir(), "Library", "Application Support", "Amistio", "tool-sessions.json");
6584
6680
  }
6585
6681
  if (process.platform === "win32") {
6586
- return path11.join(process.env.APPDATA ?? os6.homedir(), "Amistio", "tool-sessions.json");
6682
+ return path12.join(process.env.APPDATA ?? os7.homedir(), "Amistio", "tool-sessions.json");
6587
6683
  }
6588
- return path11.join(process.env.XDG_STATE_HOME ?? path11.join(os6.homedir(), ".local", "state"), "amistio", "tool-sessions.json");
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 path12 from "node:path";
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("\\\\") || path12.isAbsolute(trimmed) || path12.win32.isAbsolute(trimmed);
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 = path12.win32.isAbsolute(trimmed) && !trimmed.startsWith("/");
8083
- const relativePath = useWindowsPathRules ? path12.win32.relative(path12.win32.resolve(options.repositoryRoot), path12.win32.resolve(trimmed)) : path12.relative(path12.resolve(options.repositoryRoot), path12.resolve(trimmed));
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 os7 from "node:os";
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: () => os7.totalmem(),
8174
- freemem: () => os7.freemem(),
8175
- loadavg: () => os7.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 readFile10, stat as stat5 } from "node:fs/promises";
8289
- import path13 from "node:path";
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 = path13.resolve(rootDir);
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 ?? path13.basename(root)) || "repository";
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 = path13.resolve(options.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 = path13.join(rootDir, ...repoPath.split("/"));
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 readFile10(fullPath, "utf8").catch(() => void 0);
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 = path13.join(directory, entry.name);
8450
- const repoPath = normalizeRepoPath4(path13.relative(rootDir, fullPath));
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 = path13.posix.extname(basePath) || ".md";
8519
- const directory = path13.posix.dirname(basePath);
8520
- const basename = path13.posix.basename(basePath, extension);
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 = path13.posix.basename(repoPath, path13.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
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 path14 from "node:path";
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
- path14.resolve(options.root),
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 mkdir11, readdir as readdir7, stat as stat6 } from "node:fs/promises";
8756
- import path15 from "node:path";
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 mkdir11(path15.dirname(worktreePath), { recursive: true });
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 = path15.basename(repoRoot);
8870
+ const repoName = path16.basename(repoRoot);
8807
8871
  const worktreeSlug = worktreeKey.split("/").filter(Boolean).pop() ?? "work";
8808
- return path15.join(path15.dirname(repoRoot), `${repoName}.worktrees`, worktreeSlug);
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 = path15.join(repoRoot, candidate);
8835
- const targetPath = path15.join(worktreePath, candidate);
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(path15.join(repoRoot, name)).catch(() => void 0);
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 === path15.basename(name) && !name.includes("/") && !name.includes("\\");
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 path16 from "node:path";
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 || path16.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
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_${randomUUID2()}`;
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 writeFile11(options.out, prompt, "utf8");
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_${randomUUID2()}`, policy: sessionPolicy, decision: localSessionDecision(sessionPolicy) } }
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 ?? stableRunnerId({
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: path17.resolve(options.root),
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 ?? stableRunnerId({
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: path17.resolve(options.root),
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 ?? stableRunnerId({
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 ?? stableRunnerId({
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}_${randomUUID2()}`,
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}_${randomUUID2()}`, runnerId, {
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}_${randomUUID2()}`, runnerId, {
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}_${randomUUID2()}`,
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}_${randomUUID2()}`, context.runnerId, {
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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}_${randomUUID2()}`,
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_${randomUUID2()}`;
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 path17.basename(path17.resolve(root)) || "repository";
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: os8.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(`${os8.hostname()}:${os8.platform()}:${os8.arch()}`).digest("hex").slice(0, 20);
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amistio/cli",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",