@abelxiaoxing/cadence 2.0.15 → 2.0.17

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.
@@ -6,7 +6,7 @@ import { spawn } from "node:child_process";
6
6
  import { mkdirSync as mkdirSync6 } from "node:fs";
7
7
  import { request } from "node:http";
8
8
  import { homedir as homedir6 } from "node:os";
9
- import { dirname as dirname13, join as join15, resolve as resolve7 } from "node:path";
9
+ import { dirname as dirname13, join as join16, resolve as resolve7 } from "node:path";
10
10
  import { fileURLToPath as fileURLToPath2 } from "node:url";
11
11
 
12
12
  // src/install-config.mjs
@@ -3233,6 +3233,7 @@ function buildUnit(u) {
3233
3233
  "KillMode=mixed",
3234
3234
  "TimeoutStopSec=90",
3235
3235
  "Restart=on-failure",
3236
+ "RestartPreventExitStatus=78",
3236
3237
  "RestartSec=10",
3237
3238
  "",
3238
3239
  "[Install]",
@@ -3426,7 +3427,7 @@ function autostartStatus(s) {
3426
3427
 
3427
3428
  // src/service/coordination.ts
3428
3429
  import { randomBytes as randomBytes2, randomUUID as randomUUID3 } from "node:crypto";
3429
- import { opendirSync, realpathSync as realpathSync4 } from "node:fs";
3430
+ import { opendirSync } from "node:fs";
3430
3431
  import { hostname as hostname2 } from "node:os";
3431
3432
  import { dirname as dirname5, isAbsolute as isAbsolute4, join as join5, relative as relative3, sep as sep4 } from "node:path";
3432
3433
  var registrationKeys = [
@@ -3562,19 +3563,25 @@ function recoveryDigest(registry, selected) {
3562
3563
  }
3563
3564
 
3564
3565
  class ServiceCoordination {
3565
- store;
3566
+ project;
3566
3567
  files;
3567
- directory;
3568
3568
  registryDirectory;
3569
3569
  stateHome;
3570
3570
  constructor(workspace, stateHome) {
3571
- this.store = new TaskStore(resolveWorkspace(workspace).root, stateHome);
3572
- const namespace = dirname5(this.store.privateRoot);
3571
+ this.project = workspace === null ? undefined : new TaskStore(resolveWorkspace(workspace).root, stateHome);
3572
+ this.stateHome = this.project ? dirname5(dirname5(this.project.privateRoot)) : canonicalAmbientRoot(stateHome ?? defaultStateHome());
3573
+ const namespace = join5(this.stateHome, "cadence-assistant-v1");
3573
3574
  this.files = new PrivateFiles(namespace);
3574
- this.stateHome = dirname5(namespace);
3575
- this.directory = join5(this.store.privateRoot, "service-v1");
3576
3575
  this.registryDirectory = join5(namespace, "service-registry-v1");
3577
3576
  }
3577
+ get store() {
3578
+ if (!this.project)
3579
+ throw new ContractError("forbidden");
3580
+ return this.project;
3581
+ }
3582
+ get directory() {
3583
+ return join5(this.store.privateRoot, "service-v1");
3584
+ }
3578
3585
  path(name) {
3579
3586
  return join5(this.directory, name);
3580
3587
  }
@@ -3595,7 +3602,7 @@ class ServiceCoordination {
3595
3602
  const o = closed(json(snapshot, REGISTRY_BYTES), ["schema", "entries"]);
3596
3603
  const entries = list(o.entries, REGISTRY_ENTRIES, decodeReservation);
3597
3604
  for (const [i, row] of entries.entries()) {
3598
- if (row.stateHome !== this.stateHome || realpathSync4(row.root) !== row.root)
3605
+ if (row.stateHome !== this.stateHome || canonicalAmbientRoot(row.root) !== row.root)
3599
3606
  refused();
3600
3607
  if (entries.slice(0, i).some((other) => rootsOverlap(row.root, other.root) || row.endpoint === other.endpoint))
3601
3608
  refused();
@@ -3787,6 +3794,7 @@ class ServiceCoordination {
3787
3794
  }
3788
3795
  }
3789
3796
  acquire(input, resolvedEndpoint = input.endpoint, initializeNew, reselectExisting) {
3797
+ this.store;
3790
3798
  const config = decodeOwnerConfig(input);
3791
3799
  const bound = endpoint(resolvedEndpoint);
3792
3800
  if (!endpointMatches(config.endpoint, bound))
@@ -4207,7 +4215,7 @@ class OwnerLease {
4207
4215
 
4208
4216
  // src/service/gateway-cli.ts
4209
4217
  import { homedir as homedir5 } from "node:os";
4210
- import { dirname as dirname8, join as join9, resolve as resolve6 } from "node:path";
4218
+ import { dirname as dirname10, join as join12, resolve as resolve6 } from "node:path";
4211
4219
  import { fileURLToPath } from "node:url";
4212
4220
 
4213
4221
  // src/service/config.ts
@@ -4261,6 +4269,7 @@ function localOrigin(policy, actualPort) {
4261
4269
  // src/service/gateway-store.ts
4262
4270
  import { randomBytes as randomBytes4, randomUUID as randomUUID5 } from "node:crypto";
4263
4271
  import { opendirSync as opendirSync2 } from "node:fs";
4272
+ import { hostname as hostname3 } from "node:os";
4264
4273
  import { join as join7, resolve as resolve4 } from "node:path";
4265
4274
 
4266
4275
  // src/service/auth.ts
@@ -4635,6 +4644,8 @@ function decodeGatewayProject(value) {
4635
4644
  return project;
4636
4645
  }
4637
4646
  function registration(value) {
4647
+ const hasHost = !!value && typeof value === "object" && Object.hasOwn(value, "host");
4648
+ const hasRecovery = !!value && typeof value === "object" && Object.hasOwn(value, "recovery");
4638
4649
  const o = closed(value, [
4639
4650
  "schema",
4640
4651
  "epoch",
@@ -4643,8 +4654,22 @@ function registration(value) {
4643
4654
  "capability",
4644
4655
  "selection",
4645
4656
  "pid",
4646
- "phase"
4657
+ "phase",
4658
+ ...hasHost ? ["host"] : [],
4659
+ ...hasRecovery ? ["recovery"] : []
4647
4660
  ]);
4661
+ let recovery;
4662
+ if (hasRecovery) {
4663
+ const r = closed(o.recovery, ["receipt", "sha256", "stamp"]);
4664
+ const receipt = text(r.receipt, 128, true);
4665
+ if (!/^recovery-v1\/[a-f0-9]{64}\/(?:0|[1-9][0-9]{0,2})\.json$/.test(receipt))
4666
+ throw new ContractError("corrupt-selected");
4667
+ recovery = {
4668
+ receipt,
4669
+ sha256: decodeHash(r.sha256),
4670
+ stamp: text(r.stamp, 512, true)
4671
+ };
4672
+ }
4648
4673
  const endpoint2 = text(o.endpoint, 128, true), capability = text(o.capability, 43, true);
4649
4674
  if (!/^http:\/\/(?:127\.0\.0\.1|\[::1\]):[1-9][0-9]{0,4}$/.test(endpoint2) || !/^[A-Za-z0-9_-]{43}$/.test(capability))
4650
4675
  throw new ContractError("corrupt-selected");
@@ -4655,7 +4680,9 @@ function registration(value) {
4655
4680
  endpoint: endpoint2,
4656
4681
  capability,
4657
4682
  selection: decodeHash(o.selection),
4658
- pid: integer(o.pid, 1),
4683
+ pid: integer(o.pid, 1, 2147483647),
4684
+ ...hasHost ? { host: text(o.host, 255, true) } : {},
4685
+ ...recovery ? { recovery } : {},
4659
4686
  phase: oneOf(o.phase, ["alive", "stopping", "stopped", "quarantined"])
4660
4687
  };
4661
4688
  }
@@ -4715,6 +4742,8 @@ class GatewayStore {
4715
4742
  const previous = this.read();
4716
4743
  if (previous && previous.phase !== "stopped")
4717
4744
  throw new ContractError("quarantine");
4745
+ if (previous?.recovery)
4746
+ this.associatedProjects();
4718
4747
  this.lock = this.files.create(this.path("gateway.lock"), encoded({ epoch: randomUUID5() }));
4719
4748
  const source = { serverConfig: this.serverConfig, token };
4720
4749
  if (!previous)
@@ -4734,6 +4763,8 @@ class GatewayStore {
4734
4763
  capability: randomBytes4(32).toString("base64url"),
4735
4764
  selection: hash(inputBytes),
4736
4765
  pid: process.pid,
4766
+ host: hostname3(),
4767
+ ...previous?.recovery ? { recovery: previous.recovery } : {},
4737
4768
  phase: "alive"
4738
4769
  };
4739
4770
  this.selected = this.selected ? this.files.replace(this.path("gateway.json"), this.selected, encoded(this.value)) : this.files.create(this.path("gateway.json"), encoded(this.value));
@@ -4777,6 +4808,21 @@ class GatewayStore {
4777
4808
  }
4778
4809
  return out;
4779
4810
  }
4811
+ associatedProjects() {
4812
+ const projects = this.projects(), recovery = this.read()?.recovery;
4813
+ if (!recovery)
4814
+ return projects;
4815
+ const receipt = this.files.read(this.path(recovery.receipt), 65536);
4816
+ if (!receipt || receipt.identity !== recovery.sha256 || receipt.stamp !== recovery.stamp)
4817
+ throw new ContractError("corrupt-selected");
4818
+ const value = json(receipt, 65536);
4819
+ literal(value.schema, "cadence-workbench-recovery/1");
4820
+ const retired = list(value.retiredProjects, 128, (entry) => {
4821
+ const row = closed(entry, ["id", "stamp"]);
4822
+ return { id: decodeHash(row.id), stamp: text(row.stamp, 512, true) };
4823
+ });
4824
+ return projects.filter((project) => !retired.some((row) => row.id === project.id && row.stamp === this.files.read(this.path(`projects/${project.id}.json`))?.stamp));
4825
+ }
4780
4826
  saveProject(project) {
4781
4827
  this.assert();
4782
4828
  const p = decodeGatewayProject(project), path2 = this.path(`projects/${p.id}.json`), before = this.files.read(path2);
@@ -4792,827 +4838,285 @@ class GatewayStore {
4792
4838
  }
4793
4839
  }
4794
4840
 
4795
- // src/service/runtime.ts
4796
- import { homedir as homedir4 } from "node:os";
4797
- import { dirname as dirname7, isAbsolute as isAbsolute6, join as join8, resolve as resolve5 } from "node:path";
4798
- var STARTUP_INPUT_BYTES = 16 * 1024;
4799
- function absolute(value) {
4800
- const result = text(value, 4096, true);
4801
- if (!isAbsolute6(result))
4802
- throw new ContractError("invalid");
4803
- return result;
4804
- }
4805
- function decodeHostConfig(value) {
4806
- const o = closed(value, [
4807
- "schema",
4808
- "http",
4809
- "cwd",
4810
- "agentDir",
4811
- "tokenSource",
4812
- "tokenFile"
4813
- ]);
4814
- const tokenSource = oneOf(o.tokenSource, [
4815
- "generated",
4816
- "environment",
4817
- "file",
4818
- "config"
4819
- ]);
4820
- const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
4821
- if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
4822
- throw new ContractError("invalid");
4841
+ // src/service/recovery-cli.ts
4842
+ import { isatty } from "node:tty";
4843
+
4844
+ // src/history/recovery.ts
4845
+ import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
4846
+ import { lstatSync as lstatSync7, opendirSync as opendirSync4 } from "node:fs";
4847
+ import { join as join9, relative as relative4 } from "node:path";
4848
+
4849
+ // src/service/recovery.ts
4850
+ async function inspectRecovery(coordination, history) {
4851
+ const selected = coordination.recoverySnapshot();
4852
+ const facts = history ? await history.inspect(selected.registration.epoch) : null;
4853
+ if (facts)
4854
+ decodeHash(facts.digest);
4855
+ if (coordination.recoverySnapshot().digest !== selected.digest)
4856
+ throw new ContractError("conflict");
4823
4857
  return {
4824
- schema: literal(o.schema, "cadence-service-host/1"),
4825
- http: decodeHttpPolicy(o.http),
4826
- cwd: absolute(o.cwd),
4827
- agentDir: absolute(o.agentDir),
4828
- tokenSource,
4829
- tokenFile
4858
+ epoch: selected.registration.epoch,
4859
+ coordinationDigest: selected.digest,
4860
+ history: facts,
4861
+ owner: recoveryOwnerStatus(selected.registration.pid, selected.registration.host),
4862
+ resolution: "local-interactive-only"
4830
4863
  };
4831
4864
  }
4832
- function readHostConfig(c) {
4833
- const owner = c.configuration();
4834
- if (!owner)
4835
- return;
4836
- const snapshot = c.files.read(c.path("host-config.json"));
4837
- if (!snapshot)
4838
- throw new ContractError("corrupt-selected");
4839
- const config = decodeHostConfig(json(snapshot));
4840
- if (!config.http.port || owner.endpoint !== localOrigin(config.http, config.http.port) || owner.policyIdentity !== hash(encoded(config)))
4865
+ async function resolveRecovery(coordination, confirmation, history) {
4866
+ if (typeof confirmation.assertLocalAuthority !== "function" || confirmation.acknowledgeEscapedProcesses !== true || confirmation.processes !== "externally-resolved" || confirmation.credentials !== "independently-resolved" || confirmation.storage !== "independently-resolved")
4867
+ throw new ContractError("forbidden");
4868
+ const expected = structuredClone(confirmation.expected);
4869
+ const authority = () => confirmation.assertLocalAuthority(structuredClone(expected));
4870
+ authority();
4871
+ const release = coordination.prepareRecoveryRelease(expected.coordinationDigest, authority);
4872
+ const current = await inspectRecovery(coordination, history);
4873
+ if (JSON.stringify(current) !== JSON.stringify(expected))
4841
4874
  throw new ContractError("conflict");
4842
- return config;
4843
- }
4844
- function hostEnvironment(ambient) {
4845
- let route;
4846
- const facts = networkSnapshot(ambient);
4847
- try {
4848
- if (facts.NODE_OPTIONS.present)
4849
- throw new Error;
4850
- route = sdkNetworkEnvironment(facts, networkSnapshot(undefined));
4851
- } catch {
4852
- throw new ContractError("unsupported-context");
4853
- }
4854
- const result = Object.create(null);
4855
- for (const key of Object.keys(ambient)) {
4856
- const d = Object.getOwnPropertyDescriptor(ambient, key);
4857
- if (!d || !("value" in d) || typeof d.value !== "string")
4858
- throw new ContractError("invalid");
4859
- if (/^(?:CADENCE_|PI_|ABEL_|TRELLIS_|GIT_)/i.test(key) || networkKeys.some((name) => name === key))
4860
- continue;
4861
- result[key] = d.value;
4862
- }
4863
- if (route.redactionLiterals.length) {
4864
- result.HTTP_PROXY = route.env.HTTP_PROXY;
4865
- result.HTTPS_PROXY = route.env.HTTPS_PROXY;
4866
- result.NODE_USE_ENV_PROXY = "1";
4867
- }
4868
- result.PI_OFFLINE = "1";
4869
- result.NO_COLOR = "1";
4870
- if (!sameNetwork(facts, networkSnapshot(ambient)))
4871
- throw new ContractError("stale");
4872
- return result;
4873
- }
4874
- function selectStart(options, ambient) {
4875
- const workspace = resolveWorkspace(options.workspace), c = new ServiceCoordination(workspace.root, options.stateHome);
4876
- const old = readHostConfig(c);
4877
- const env = Object.getOwnPropertyDescriptor(ambient, "CADENCE_WEB_TOKEN");
4878
- if (env && (!("value" in env) || typeof env.value !== "string"))
4879
- throw new ContractError("invalid");
4880
- let fromFile;
4881
- if (options.config) {
4882
- const path2 = resolve5(options.config), f = new PrivateFiles(dirname7(path2));
4883
- const bytes = f.read(path2);
4884
- if (!bytes)
4885
- throw new ContractError("corrupt-selected");
4886
- fromFile = decodeHostConfig(json(bytes));
4887
- }
4888
- const base = old ?? fromFile ?? decodeHostConfig({
4889
- schema: "cadence-service-host/1",
4890
- cwd: workspace.cwd,
4891
- http: {
4892
- schema: "cadence-service-http/1",
4893
- host: options.host ?? "127.0.0.1",
4894
- port: options.port ?? 17423,
4895
- proxy: null
4896
- },
4897
- agentDir: resolve5(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join8(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
4898
- tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
4899
- tokenFile: options.tokenFile ? resolve5(options.tokenFile) : null
4875
+ if (current.owner !== "absent" || !current.history)
4876
+ throw new ContractError("quarantine");
4877
+ const fact = await history.append({
4878
+ ...confirmation,
4879
+ expected
4900
4880
  });
4901
- const server = options.serverConfig !== undefined ? resolve5(options.serverConfig) : old?.tokenSource === "config" ? old.tokenFile : null;
4902
- let selected = base;
4903
- if (server) {
4904
- if (options.tokenFile !== undefined)
4905
- throw new ContractError("conflict");
4906
- const { port } = readServerConfig(server).config;
4907
- selected = decodeHostConfig({
4908
- ...base,
4909
- http: { ...base.http, port },
4910
- tokenSource: "config",
4911
- tokenFile: server
4912
- });
4913
- }
4914
- if (fromFile && encoded(fromFile) !== encoded(selected) || options.port !== undefined && options.port !== selected.http.port || options.host !== undefined && options.host !== selected.http.host || options.agentDir !== undefined && resolve5(options.agentDir) !== selected.agentDir || options.tokenFile !== undefined && resolve5(options.tokenFile) !== selected.tokenFile || env && selected.tokenSource !== "environment" || selected.tokenSource === "environment" && !env || resolveWorkspace(selected.cwd).root !== workspace.root)
4881
+ authority();
4882
+ if (fact.epoch !== expected.epoch || fact.coordinationDigest !== expected.coordinationDigest || fact.historyDigest !== expected.history?.digest)
4915
4883
  throw new ContractError("conflict");
4884
+ release(() => history.assertCommitted(fact));
4916
4885
  return {
4917
- workspace: workspace.root,
4918
- stateHome: c.stateHome,
4919
- config: selected,
4920
- environmentToken: env?.value ?? null
4886
+ status: "resolved",
4887
+ epoch: fact.epoch,
4888
+ recoveryId: fact.recoveryId,
4889
+ committedSeq: fact.committedSeq,
4890
+ notice: "operator-acknowledgment-not-certification"
4921
4891
  };
4922
4892
  }
4923
- function decodeHostInput(value) {
4924
- const o = closed(value, [
4925
- "workspace",
4926
- "stateHome",
4927
- "config",
4928
- "environmentToken"
4929
- ]);
4930
- const config = decodeHostConfig(o.config);
4931
- const environmentToken = o.environmentToken === null ? null : text(o.environmentToken, 1024, true);
4932
- if (config.tokenSource === "environment" !== (environmentToken !== null))
4933
- throw new ContractError("invalid");
4934
- return {
4935
- workspace: absolute(o.workspace),
4936
- stateHome: absolute(o.stateHome),
4937
- config,
4938
- environmentToken
4893
+
4894
+ // src/history/files.ts
4895
+ import { createHash as createHash5, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "node:crypto";
4896
+ import {
4897
+ closeSync as closeSync5,
4898
+ constants as constants5,
4899
+ fstatSync as fstatSync4,
4900
+ fsyncSync as fsyncSync3,
4901
+ lstatSync as lstatSync6,
4902
+ opendirSync as opendirSync3,
4903
+ openSync as openSync5,
4904
+ statfsSync
4905
+ } from "node:fs";
4906
+ import { open } from "node:fs/promises";
4907
+ import { dirname as dirname8, join as join8 } from "node:path";
4908
+
4909
+ // src/history/admission.ts
4910
+ function decodeRunAdmission(value) {
4911
+ const o = closed(value, ["schema", "runId", "identity", "graph"]);
4912
+ const result = {
4913
+ schema: literal(o.schema, "cadence-run-admission/2"),
4914
+ runId: decodeId(o.runId),
4915
+ identity: decodeRunIdentity(o.identity),
4916
+ graph: nullable(o.graph, decodeGraph)
4939
4917
  };
4940
- }
4941
- function portValue(value) {
4942
- if (!/^(0|[1-9][0-9]{0,4})$/.test(value))
4918
+ const { identity, graph } = result;
4919
+ if (identity.kind === "start" !== (graph !== null))
4943
4920
  throw new ContractError("invalid");
4944
- return integer(Number(value), 0, 65535);
4945
- }
4946
- var STARTUP_STAGES = [
4947
- "package",
4948
- "pi-agent-directory",
4949
- "pi-auth",
4950
- "pi-models",
4951
- "pi-runtime",
4952
- "service"
4953
- ];
4954
- function decodeStartupReport(value) {
4955
- const o = closed(value, ["status", "error", "stage"]);
4956
- return {
4957
- status: oneOf(o.status, ["refused", "quarantined"]),
4958
- error: oneOf(o.error, ERROR_CODES),
4959
- stage: oneOf(o.stage, STARTUP_STAGES)
4960
- };
4921
+ if (graph && (graph.taskId !== identity.context.task?.id || graph.taskKind !== identity.context.task.kind || graph.taskRevision > identity.context.task.revision || graph.revision !== identity.graph?.revision || hash(canonicalJson(graph)) !== identity.graph.sha256 || canonicalJson(graph.assignments.map((a) => a.id)) !== canonicalJson(identity.assignments.map((a) => a.assignmentId)) || graph.assignments.some((a) => canonicalJson(a.model) !== canonicalJson(identity.context.model))))
4922
+ throw new ContractError("conflict");
4923
+ return result;
4961
4924
  }
4962
4925
 
4963
- // src/service/gateway-cli.ts
4964
- async function gatewayMain(argv, hostUrl) {
4965
- const operation = argv.shift();
4966
- if (![
4967
- "start",
4968
- "status",
4969
- "diagnose",
4970
- "stop",
4971
- "token-link",
4972
- "token-rotate"
4973
- ].includes(operation ?? ""))
4974
- throw new ContractError("invalid");
4975
- let config = serverConfigPath(homedir5()), stateHome = defaultStateHome(), agentDir, policyFile;
4976
- let foreground = false, reveal = false;
4977
- const seen = new Set;
4978
- while (argv.length) {
4979
- const key = argv.shift();
4980
- if (seen.has(key))
4981
- throw new ContractError("invalid");
4982
- seen.add(key);
4983
- if (key === "--foreground" && operation === "start")
4984
- foreground = true;
4985
- else if (key === "--reveal" && operation === "token-link")
4986
- reveal = true;
4987
- else if (argv[0] && ["--server-config", "--state-home", "--agent-dir", "--config"].includes(key)) {
4988
- const value = resolve6(argv.shift());
4989
- if (key === "--server-config")
4990
- config = value;
4991
- if (key === "--state-home")
4992
- stateHome = value;
4993
- if (key === "--agent-dir")
4994
- agentDir = value;
4995
- if (key === "--config")
4996
- policyFile = value;
4997
- } else
4998
- throw new ContractError("invalid");
4999
- }
5000
- const store = new GatewayStore(config, stateHome);
5001
- let result;
5002
- if (operation === "start") {
5003
- const network = networkSnapshot(process.env), env = hostEnvironment(process.env);
5004
- if (!seen.has("--server-config"))
5005
- ensureServerConfig(homedir5());
5006
- const server = readServerConfig(config).config;
5007
- const selectedFile = store.files.read(store.path("input.json"));
5008
- const selected = selectedFile ? decodeGatewayInput(json(selectedFile)) : undefined;
5009
- let policy = selected?.policy ?? decodeHttpPolicy({
5010
- schema: "cadence-service-http/1",
5011
- host: "127.0.0.1",
5012
- port: server.port,
5013
- proxy: null
5014
- });
5015
- if (policyFile) {
5016
- const file = new PrivateFiles(dirname8(policyFile)).read(policyFile);
5017
- if (!file)
5018
- throw new ContractError("invalid");
5019
- policy = decodeHttpPolicy(json(file));
5020
- }
5021
- const input = decodeGatewayInput({
5022
- kind: "workbench",
5023
- serverConfig: config,
5024
- stateHome: store.stateHome,
5025
- agentDir: agentDir ?? selected?.agentDir ?? resolve6(process.env.PI_CODING_AGENT_DIR ?? join9(homedir5(), ".pi", "agent")),
5026
- policy: { ...policy, port: server.port }
5027
- });
5028
- const status = await serviceStatus(store);
5029
- if (status && typeof status === "object" && "status" in status && status.status === "alive") {
5030
- if (!selected || encoded(selected) !== encoded(input))
5031
- throw new ContractError("conflict");
5032
- result = status;
5033
- } else
5034
- result = await launchHost(input, store, foreground, hostUrl, dirname8(dirname8(fileURLToPath(hostUrl))), env, network);
5035
- if (!foreground && result && typeof result === "object" && "status" in result && result.status === "alive") {
5036
- const reply = closed(await nativeCall(store, "token-link", true), [
5037
- "link"
5038
- ]);
5039
- process.stderr.write(`Cadence workbench: ${text(reply.link, 4096, true)}
5040
- No project or default workspace was created.
5041
- `);
5042
- }
5043
- } else if (operation === "stop")
5044
- result = await stopService(store);
5045
- else if (operation === "status" || operation === "diagnose")
5046
- result = await serviceStatus(store, operation === "diagnose");
5047
- else {
5048
- if (operation === "token-link" && !reveal)
5049
- throw new ContractError("forbidden");
5050
- result = await nativeCall(store, operation, reveal);
5051
- }
5052
- if (result !== null)
5053
- process.stdout.write(`${JSON.stringify(result)}
5054
- `);
5055
- if (result && typeof result === "object" && "status" in result && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)))
5056
- process.exitCode = 1;
5057
- }
5058
-
5059
- // src/service/launchd.ts
5060
- import { rmSync as rmSync2 } from "node:fs";
5061
- import { dirname as dirname9, join as join10 } from "node:path";
5062
- var LABEL = "com.abelxiaoxing.cadence";
5063
- var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
5064
- <!-- Generated by @abelxiaoxing/cadence`;
5065
- function plistPath(s) {
5066
- return join10(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
4926
+ // src/history/read-io.ts
4927
+ import {
4928
+ closeSync as closeSync4,
4929
+ constants as constants4,
4930
+ fstatSync as fstatSync3,
4931
+ lstatSync as lstatSync5,
4932
+ openSync as openSync4,
4933
+ readSync as readSync3
4934
+ } from "node:fs";
4935
+ import { dirname as dirname7 } from "node:path";
4936
+ function bad() {
4937
+ throw new ContractError("corrupt-selected");
5067
4938
  }
5068
- var xml = (value) => checked(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
5069
- function buildPlist(u) {
5070
- const string = (value) => ` <string>${xml(value)}</string>`;
5071
- return [
5072
- '<?xml version="1.0" encoding="UTF-8"?>',
5073
- "<!-- Generated by @abelxiaoxing/cadence; `cadence server install` and `restart` rewrite this file. -->",
5074
- '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
5075
- '<plist version="1.0">',
5076
- "<dict>",
5077
- "\t<key>Label</key>",
5078
- ` <string>${LABEL}</string>`,
5079
- "\t<key>ProgramArguments</key>",
5080
- "\t<array>",
5081
- ...[
5082
- u.node,
5083
- u.cli,
5084
- ...u.workspace === null ? ["workbench", "start", "--foreground"] : ["start", "--foreground", "--workspace", u.workspace],
5085
- "--server-config",
5086
- u.serverConfig
5087
- ].map(string),
5088
- "\t</array>",
5089
- ...u.workspace === null ? ["<!-- Cadence workbench gateway; no project workspace -->"] : [],
5090
- "\t<key>WorkingDirectory</key>",
5091
- ` <string>${xml(u.workspace ?? dirname9(dirname9(u.cli)))}</string>`,
5092
- "\t<key>EnvironmentVariables</key>",
5093
- "\t<dict>",
5094
- ...u.environment.flatMap(([k, v]) => [
5095
- ` <key>${xml(k)}</key>`,
5096
- string(v)
5097
- ]),
5098
- "\t</dict>",
5099
- "\t<key>RunAtLoad</key>",
5100
- "\t<true/>",
5101
- "\t<key>ProcessType</key>",
5102
- "\t<string>Background</string>",
5103
- "\t<key>StandardOutPath</key>",
5104
- ` <string>${xml(u.log)}</string>`,
5105
- "\t<key>StandardErrorPath</key>",
5106
- ` <string>${xml(u.log)}</string>`,
5107
- "</dict>",
5108
- "</plist>",
5109
- ""
5110
- ].join(`
5111
- `);
4939
+ var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
4940
+ function selected(files, path2, read, walks) {
4941
+ if (!files.files.directory(dirname7(path2), false, walks))
4942
+ bad();
4943
+ const before = lstatSync5(path2);
4944
+ if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
4945
+ bad();
4946
+ const fd = openSync4(path2, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
4947
+ try {
4948
+ if (stamp2(fstatSync3(fd)) !== stamp2(before))
4949
+ bad();
4950
+ const result = read(fd, before.size, stamp2(before));
4951
+ if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync5(path2)) !== stamp2(before))
4952
+ bad();
4953
+ return result;
4954
+ } finally {
4955
+ closeSync4(fd);
4956
+ }
5112
4957
  }
5113
- function plistWorkspace(text2) {
5114
- if (text2?.includes("<!-- Cadence workbench gateway; no project workspace -->"))
5115
- return null;
5116
- const m = text2?.match(/^\t<key>WorkingDirectory<\/key>\n\t<string>([^<]*)<\/string>$/m);
5117
- return m?.[1].replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
4958
+ function assertIncarnation(files, path2, expected) {
4959
+ selected(files, path2, (_fd, _size, current) => {
4960
+ if (current !== expected)
4961
+ bad();
4962
+ });
5118
4963
  }
5119
- var domain = (s) => `gui/${s.uid}`;
5120
- var target = (s) => `${domain(s)}/${LABEL}`;
5121
- var loaded = (s) => s.launchctl(["print", target(s)]).status === 0;
5122
- function disabled(s) {
5123
- return new RegExp(`"${LABEL.replaceAll(".", "\\.")}" => (true|disabled)`).test(s.launchctl(["print-disabled", domain(s)]).stdout);
4964
+ function statSize(files, path2, maximum) {
4965
+ return selected(files, path2, (_fd, size) => {
4966
+ if (size > maximum)
4967
+ bad();
4968
+ return size;
4969
+ });
5124
4970
  }
5125
- async function configureLaunchAgent(s, mode, requested) {
5126
- if (s.uid === undefined)
5127
- return { status: "unavailable" };
5128
- const path2 = plistPath(s);
5129
- const previous = readGenerated(path2, MARKER2);
5130
- if (previous === null)
5131
- return { status: "foreign", unit: path2 };
5132
- if (mode === "restart" && previous === undefined)
5133
- return { status: "absent" };
5134
- const sdk = sdkPackages(s.root);
5135
- const kept = plistWorkspace(previous);
5136
- const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
5137
- if (workspace !== null && !sdk)
5138
- return { status: "missing-pi-sdk" };
5139
- const refused2 = await settle(s, kept);
5140
- if (refused2)
5141
- return { ...refused2, unit: path2 };
5142
- if (loaded(s))
5143
- s.launchctl(["bootout", target(s)]);
5144
- write(path2, buildPlist({
5145
- node: s.node,
5146
- cli: join10(s.root, "src", "service-cli.mjs"),
5147
- workspace,
5148
- serverConfig: serverConfigPath(s.home),
5149
- environment: serviceEnvironment(s),
5150
- log: join10(s.home, "Library", "Logs", "cadence.log")
5151
- }));
5152
- const result = {
5153
- unit: path2,
5154
- workspace,
5155
- ...redirectedFrom ? { redirectedFrom } : {}
4971
+
4972
+ // src/history/types.ts
4973
+ import { createHash as createHash4 } from "node:crypto";
4974
+ var RECORD_BYTES = LIMITS.controlBytes + 64 * 1024;
4975
+ var LEGACY_FORMAT = "cadence-history-store/2";
4976
+ var FORMAT = "cadence-history-store/3";
4977
+ function decodeFormat(value) {
4978
+ const o = closed(value, ["schema", "storeId", "workspaceId", "rootIdentity"]);
4979
+ return {
4980
+ schema: oneOf(o.schema, [FORMAT, LEGACY_FORMAT]),
4981
+ storeId: decodeId(o.storeId),
4982
+ workspaceId: decodeId(o.workspaceId),
4983
+ rootIdentity: decodeHash(o.rootIdentity)
5156
4984
  };
5157
- if (mode === "install")
5158
- s.launchctl(["enable", target(s)]);
5159
- else if (disabled(s))
5160
- return { status: "disabled", ...result };
5161
- if (workspace !== null && !regularFile(join10(agentDirectory(s), "auth.json")))
5162
- return { status: "waiting-for-pi-login", ...result };
5163
- const started = s.launchctl(["bootstrap", domain(s), path2]).status === 0;
5164
- return { status: started ? "started" : "failed", ...result };
5165
- }
5166
- async function stopLaunchAgent(s) {
5167
- const path2 = plistPath(s);
5168
- const previous = readGenerated(path2, MARKER2);
5169
- if (previous === null)
5170
- return { status: "foreign", unit: path2 };
5171
- if (previous === undefined)
5172
- return { status: "absent" };
5173
- const workspace = plistWorkspace(previous);
5174
- const refused2 = await settle(s, workspace);
5175
- return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
5176
- }
5177
- async function removeLaunchAgent(s) {
5178
- const path2 = plistPath(s);
5179
- const previous = readGenerated(path2, MARKER2);
5180
- if (previous === null)
5181
- return { status: "foreign", unit: path2 };
5182
- if (previous === undefined)
5183
- return { status: "absent" };
5184
- const refused2 = await settle(s, plistWorkspace(previous));
5185
- if (refused2)
5186
- return { ...refused2, unit: path2 };
5187
- if (s.uid !== undefined && loaded(s))
5188
- s.launchctl(["bootout", target(s)]);
5189
- rmSync2(path2);
5190
- return { status: "removed", unit: path2 };
5191
4985
  }
5192
- function launchAgentStatus(s) {
5193
- const path2 = plistPath(s);
5194
- const previous = readGenerated(path2, MARKER2);
5195
- if (previous === null)
5196
- return { status: "foreign", unit: path2 };
5197
- if (previous === undefined)
5198
- return { status: "absent" };
5199
- const printed = s.uid === undefined ? undefined : s.launchctl(["print", target(s)]);
4986
+ function decodeArtifact(value) {
4987
+ const o = closed(value, ["runId", "artifactId", "part", "bytes", "sha256"]);
5200
4988
  return {
5201
- status: printed?.status !== 0 ? "unloaded" : /^\s*state = running$/m.test(printed.stdout) ? "active" : "inactive",
5202
- enabled: s.uid !== undefined && disabled(s) ? "disabled" : "enabled",
5203
- unit: path2,
5204
- workspace: plistWorkspace(previous)
4989
+ runId: decodeId(o.runId),
4990
+ artifactId: decodeId(o.artifactId),
4991
+ part: integer(o.part),
4992
+ bytes: integer(o.bytes, 1, LIMITS.artifactPartBytes),
4993
+ sha256: decodeHash(o.sha256)
5205
4994
  };
5206
4995
  }
5207
-
5208
- // src/service/login-run.ts
5209
- import { rmSync as rmSync3 } from "node:fs";
5210
- import { dirname as dirname10, isAbsolute as isAbsolute7, join as join11 } from "node:path";
5211
- var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
5212
- var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
5213
- var RUN_VALUE = "Cadence";
5214
- var MARKER3 = "' Generated by @abelxiaoxing/cadence";
5215
- function launcherPath(s) {
5216
- const local = s.env.LOCALAPPDATA;
5217
- return join11(local && isAbsolute7(local) ? local : join11(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
5218
- }
5219
- var literal2 = (value) => `"${checked(value).replaceAll('"', '""')}"`;
5220
- function argument2(value) {
5221
- if (checked(value).includes('"'))
5222
- throw new ContractError("invalid");
5223
- return `"${value}"`;
5224
- }
5225
- function buildLauncher(u) {
5226
- const command = `${argument2(u.node)} ${argument2(u.cli)} ${u.workspace === null ? "workbench start" : `start --workspace ${argument2(u.workspace)}`} --server-config ${argument2(u.serverConfig)}`;
5227
- return [
5228
- `${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
5229
- `' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
5230
- 'Set shell = CreateObject("WScript.Shell")',
5231
- ...u.environment.length ? ['Set env = shell.Environment("Process")'] : [],
5232
- ...u.environment.map(([k, v]) => `env(${literal2(k)}) = ${literal2(v)}`),
5233
- ...u.workspace === null ? ["' Cadence workbench gateway; no project workspace"] : [],
5234
- `shell.CurrentDirectory = ${literal2(u.workspace ?? dirname10(dirname10(u.cli)))}`,
5235
- `shell.Run ${literal2(command)}, 0, False`,
5236
- ""
5237
- ].join(`\r
5238
- `);
5239
- }
5240
- var utf16 = (text2) => Buffer.concat([Buffer.from([255, 254]), Buffer.from(text2, "utf16le")]);
5241
- function launcherWorkspace(text2) {
5242
- if (text2?.includes("' Cadence workbench gateway; no project workspace"))
5243
- return null;
5244
- return text2?.match(/^shell\.CurrentDirectory = "((?:[^"]|"")*)"\r?$/m)?.[1].replaceAll('""', '"');
5245
- }
5246
- var runEntry = (path2, s) => `"${wscript(s.env)}" "${path2}"`;
5247
- var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
5248
- function approvedOff(s) {
5249
- const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
5250
- const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
5251
- return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
5252
- }
5253
- async function configureLoginRun(s, mode, requested) {
5254
- const path2 = launcherPath(s);
5255
- const previous = readGenerated(path2, MARKER3);
5256
- if (previous === null)
5257
- return { status: "foreign", unit: path2 };
5258
- if (previous === undefined && present(s, RUN_KEY))
5259
- return { status: "foreign", unit: `${RUN_KEY}\\${RUN_VALUE}` };
5260
- if (mode === "restart" && previous === undefined)
5261
- return { status: "absent" };
5262
- const sdk = sdkPackages(s.root);
5263
- const kept = launcherWorkspace(previous);
5264
- const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
5265
- if (workspace !== null && !sdk)
5266
- return { status: "missing-pi-sdk" };
5267
- const refused2 = await settle(s, kept);
5268
- if (refused2)
5269
- return { ...refused2, unit: path2 };
5270
- const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
5271
- write(path2, utf16(buildLauncher({
5272
- node: s.node,
5273
- cli: join11(s.root, "src", "service-cli.mjs"),
5274
- workspace,
5275
- serverConfig: serverConfigPath(s.home),
5276
- environment
5277
- })));
5278
- const result = {
5279
- unit: path2,
5280
- workspace,
5281
- ...redirectedFrom ? { redirectedFrom } : {}
4996
+ function decodeRetainedDetail(value) {
4997
+ const o = closed(value, [
4998
+ "detailId",
4999
+ "origin",
5000
+ "owner",
5001
+ "descriptor",
5002
+ "content",
5003
+ "coverage"
5004
+ ]);
5005
+ const origin = closed(o.origin, ["kind", "id"]);
5006
+ const r = {
5007
+ detailId: decodeId(o.detailId),
5008
+ origin: {
5009
+ kind: oneOf(origin.kind, ["worker", "owner"]),
5010
+ id: decodeId(origin.id)
5011
+ },
5012
+ owner: decodeRecordOwner(o.owner),
5013
+ descriptor: nullable(o.descriptor, decodeDetailDescriptor),
5014
+ content: decodeContentRef(o.content),
5015
+ coverage: decodeCoverage(o.coverage)
5282
5016
  };
5283
- if (mode === "install") {
5284
- const entry = ["/v", RUN_VALUE, "/t", "REG_SZ", "/d", runEntry(path2, s)];
5285
- if (s.reg(["add", RUN_KEY, ...entry, "/f"]).status !== 0)
5286
- return { status: "failed", ...result };
5287
- s.reg(["delete", APPROVED_KEY, "/v", RUN_VALUE, "/f"]);
5017
+ if (r.origin.kind === "worker" !== (r.descriptor !== null) || r.descriptor && r.content.field !== r.descriptor.field)
5018
+ invalid();
5019
+ return r;
5020
+ }
5021
+ function decodeSavedEvent(value, owner) {
5022
+ const kind = closedTag(value);
5023
+ if (kind === "capture") {
5024
+ const o2 = closed(value, ["kind", "id", "value"]), v = decodeRecordingEvent(o2.value);
5025
+ if (owner || v.event.kind === "stored-chunk" || v.event.kind === "stored-end")
5026
+ invalid();
5027
+ return { kind, id: decodeId(o2.id), value: v };
5288
5028
  }
5289
- if (workspace !== null && !regularFile(join11(agentDirectory(s), "auth.json")))
5290
- return { status: "waiting-for-pi-login", ...result };
5291
- return { status: s.launch(path2) ? "started" : "failed", ...result };
5029
+ if (kind === "owner") {
5030
+ const o2 = closed(value, ["kind", "id", "value"]);
5031
+ if (!owner)
5032
+ invalid();
5033
+ const v = decodeOwnerRecordingInput({
5034
+ link: owner,
5035
+ observedMs: 0,
5036
+ events: [o2.value]
5037
+ }).events[0];
5038
+ if (v.kind === "content-chunk" || v.kind === "content-end")
5039
+ invalid();
5040
+ return { kind, id: decodeId(o2.id), value: v };
5041
+ }
5042
+ if (kind === "chunk") {
5043
+ const o2 = closed(value, [
5044
+ "kind",
5045
+ "detailId",
5046
+ "source",
5047
+ "artifact",
5048
+ "offset",
5049
+ "start",
5050
+ "length"
5051
+ ]);
5052
+ const source2 = nullable(o2.source, decodeSourceRange);
5053
+ if (source2 === null !== (owner !== null))
5054
+ invalid();
5055
+ const artifact = decodeArtifact(o2.artifact), offset = integer(o2.offset, 0, LIMITS.detailBytes), start = integer(o2.start, 0, LIMITS.artifactPartBytes), length = integer(o2.length, 1, LIMITS.chunkBytes);
5056
+ if (start + length > artifact.bytes || offset + length > LIMITS.detailBytes)
5057
+ invalid();
5058
+ return {
5059
+ kind,
5060
+ detailId: decodeId(o2.detailId),
5061
+ source: source2,
5062
+ artifact,
5063
+ offset,
5064
+ start,
5065
+ length
5066
+ };
5067
+ }
5068
+ const o = closed(value, ["kind", "detail", "source"]);
5069
+ literal(kind, "end");
5070
+ const detail = decodeRetainedDetail(o.detail), source = nullable(o.source, decodeSourceRange);
5071
+ if (detail.origin.kind === "owner" !== (owner !== null) || source === null !== (owner !== null))
5072
+ invalid();
5073
+ return { kind: "end", detail, source };
5292
5074
  }
5293
- async function stopLoginRun(s) {
5294
- const path2 = launcherPath(s);
5295
- const previous = readGenerated(path2, MARKER3);
5296
- if (previous === null)
5297
- return { status: "foreign", unit: path2 };
5298
- if (previous === undefined)
5299
- return { status: "absent" };
5300
- const workspace = launcherWorkspace(previous);
5301
- const refused2 = await settle(s, workspace);
5302
- return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
5075
+ function closedTag(value) {
5076
+ if (!value || typeof value !== "object" || !("kind" in value) || typeof value.kind !== "string")
5077
+ invalid();
5078
+ return value.kind;
5303
5079
  }
5304
- async function removeLoginRun(s) {
5305
- const path2 = launcherPath(s);
5306
- const previous = readGenerated(path2, MARKER3);
5307
- if (previous === null)
5308
- return { status: "foreign", unit: path2 };
5309
- if (previous === undefined)
5310
- return { status: "absent" };
5311
- const refused2 = await settle(s, launcherWorkspace(previous));
5312
- if (refused2)
5313
- return { ...refused2, unit: path2 };
5314
- for (const key of [RUN_KEY, APPROVED_KEY])
5315
- if (present(s, key))
5316
- s.reg(["delete", key, "/v", RUN_VALUE, "/f"]);
5317
- rmSync3(path2);
5318
- return { status: "removed", unit: path2 };
5080
+ function decodeAdmissionIntent(value) {
5081
+ const o = closed(value, [
5082
+ "commandId",
5083
+ "operationId",
5084
+ "runId",
5085
+ "identity",
5086
+ "createdTaskId"
5087
+ ]);
5088
+ return {
5089
+ commandId: decodeId(o.commandId),
5090
+ operationId: decodeId(o.operationId),
5091
+ runId: nullable(o.runId, decodeId),
5092
+ identity: decodeCommandIdentity(o.identity),
5093
+ createdTaskId: nullable(o.createdTaskId, decodeId)
5094
+ };
5319
5095
  }
5320
- async function loginRunStatus(s) {
5321
- const path2 = launcherPath(s);
5322
- const previous = readGenerated(path2, MARKER3);
5323
- if (previous === null)
5324
- return { status: "foreign", unit: path2 };
5325
- if (previous === undefined)
5326
- return { status: "absent" };
5327
- const workspace = launcherWorkspace(previous);
5328
- const owner = workspace !== undefined ? await s.ownerStatus(workspace, serverConfigPath(s.home)) : "unavailable";
5096
+ function decodeAuthIntent(value) {
5097
+ const o = closed(value, [
5098
+ "operationId",
5099
+ "epoch",
5100
+ "runId",
5101
+ "childRunId",
5102
+ "sourceSelectionId",
5103
+ "validityMs"
5104
+ ]);
5329
5105
  return {
5330
- status: owner === "alive" ? "active" : "inactive",
5331
- enabled: present(s, RUN_KEY) && !approvedOff(s) ? "enabled" : "disabled",
5332
- unit: path2,
5333
- workspace
5106
+ operationId: decodeId(o.operationId),
5107
+ epoch: decodeId(o.epoch),
5108
+ runId: decodeId(o.runId),
5109
+ childRunId: decodeId(o.childRunId),
5110
+ sourceSelectionId: decodeId(o.sourceSelectionId),
5111
+ validityMs: integer(o.validityMs, 1)
5334
5112
  };
5335
5113
  }
5336
-
5337
- // src/service/recovery-cli.ts
5338
- import { isatty } from "node:tty";
5339
-
5340
- // src/history/recovery.ts
5341
- import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
5342
- import { lstatSync as lstatSync7, opendirSync as opendirSync4 } from "node:fs";
5343
- import { join as join13, relative as relative4 } from "node:path";
5344
-
5345
- // src/service/recovery.ts
5346
- async function inspectRecovery(coordination, history) {
5347
- const selected = coordination.recoverySnapshot();
5348
- const facts = history ? await history.inspect(selected.registration.epoch) : null;
5349
- if (facts)
5350
- decodeHash(facts.digest);
5351
- if (coordination.recoverySnapshot().digest !== selected.digest)
5352
- throw new ContractError("conflict");
5114
+ function decodeAuthSettlement(value) {
5115
+ const o = closed(value, ["operationId", "outcome", "admissionClosed"]);
5353
5116
  return {
5354
- epoch: selected.registration.epoch,
5355
- coordinationDigest: selected.digest,
5356
- history: facts,
5357
- owner: recoveryOwnerStatus(selected.registration.pid, selected.registration.host),
5358
- resolution: "local-interactive-only"
5359
- };
5360
- }
5361
- async function resolveRecovery(coordination, confirmation, history) {
5362
- if (typeof confirmation.assertLocalAuthority !== "function" || confirmation.acknowledgeEscapedProcesses !== true || confirmation.processes !== "externally-resolved" || confirmation.credentials !== "independently-resolved" || confirmation.storage !== "independently-resolved")
5363
- throw new ContractError("forbidden");
5364
- const expected = structuredClone(confirmation.expected);
5365
- const authority = () => confirmation.assertLocalAuthority(structuredClone(expected));
5366
- authority();
5367
- const release = coordination.prepareRecoveryRelease(expected.coordinationDigest, authority);
5368
- const current = await inspectRecovery(coordination, history);
5369
- if (JSON.stringify(current) !== JSON.stringify(expected))
5370
- throw new ContractError("conflict");
5371
- if (current.owner !== "absent" || !current.history)
5372
- throw new ContractError("quarantine");
5373
- const fact = await history.append({
5374
- ...confirmation,
5375
- expected
5376
- });
5377
- authority();
5378
- if (fact.epoch !== expected.epoch || fact.coordinationDigest !== expected.coordinationDigest || fact.historyDigest !== expected.history?.digest)
5379
- throw new ContractError("conflict");
5380
- release(() => history.assertCommitted(fact));
5381
- return {
5382
- status: "resolved",
5383
- epoch: fact.epoch,
5384
- recoveryId: fact.recoveryId,
5385
- committedSeq: fact.committedSeq,
5386
- notice: "operator-acknowledgment-not-certification"
5387
- };
5388
- }
5389
-
5390
- // src/history/files.ts
5391
- import { createHash as createHash5, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "node:crypto";
5392
- import {
5393
- closeSync as closeSync5,
5394
- constants as constants5,
5395
- fstatSync as fstatSync4,
5396
- fsyncSync as fsyncSync3,
5397
- lstatSync as lstatSync6,
5398
- opendirSync as opendirSync3,
5399
- openSync as openSync5,
5400
- statfsSync
5401
- } from "node:fs";
5402
- import { open } from "node:fs/promises";
5403
- import { dirname as dirname12, join as join12 } from "node:path";
5404
-
5405
- // src/history/admission.ts
5406
- function decodeRunAdmission(value) {
5407
- const o = closed(value, ["schema", "runId", "identity", "graph"]);
5408
- const result = {
5409
- schema: literal(o.schema, "cadence-run-admission/2"),
5410
- runId: decodeId(o.runId),
5411
- identity: decodeRunIdentity(o.identity),
5412
- graph: nullable(o.graph, decodeGraph)
5413
- };
5414
- const { identity, graph } = result;
5415
- if (identity.kind === "start" !== (graph !== null))
5416
- throw new ContractError("invalid");
5417
- if (graph && (graph.taskId !== identity.context.task?.id || graph.taskKind !== identity.context.task.kind || graph.taskRevision > identity.context.task.revision || graph.revision !== identity.graph?.revision || hash(canonicalJson(graph)) !== identity.graph.sha256 || canonicalJson(graph.assignments.map((a) => a.id)) !== canonicalJson(identity.assignments.map((a) => a.assignmentId)) || graph.assignments.some((a) => canonicalJson(a.model) !== canonicalJson(identity.context.model))))
5418
- throw new ContractError("conflict");
5419
- return result;
5420
- }
5421
-
5422
- // src/history/read-io.ts
5423
- import {
5424
- closeSync as closeSync4,
5425
- constants as constants4,
5426
- fstatSync as fstatSync3,
5427
- lstatSync as lstatSync5,
5428
- openSync as openSync4,
5429
- readSync as readSync3
5430
- } from "node:fs";
5431
- import { dirname as dirname11 } from "node:path";
5432
- function bad() {
5433
- throw new ContractError("corrupt-selected");
5434
- }
5435
- var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
5436
- function selected(files, path2, read, walks) {
5437
- if (!files.files.directory(dirname11(path2), false, walks))
5438
- bad();
5439
- const before = lstatSync5(path2);
5440
- if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
5441
- bad();
5442
- const fd = openSync4(path2, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
5443
- try {
5444
- if (stamp2(fstatSync3(fd)) !== stamp2(before))
5445
- bad();
5446
- const result = read(fd, before.size, stamp2(before));
5447
- if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync5(path2)) !== stamp2(before))
5448
- bad();
5449
- return result;
5450
- } finally {
5451
- closeSync4(fd);
5452
- }
5453
- }
5454
- function assertIncarnation(files, path2, expected) {
5455
- selected(files, path2, (_fd, _size, current) => {
5456
- if (current !== expected)
5457
- bad();
5458
- });
5459
- }
5460
- function statSize(files, path2, maximum) {
5461
- return selected(files, path2, (_fd, size) => {
5462
- if (size > maximum)
5463
- bad();
5464
- return size;
5465
- });
5466
- }
5467
-
5468
- // src/history/types.ts
5469
- import { createHash as createHash4 } from "node:crypto";
5470
- var RECORD_BYTES = LIMITS.controlBytes + 64 * 1024;
5471
- var LEGACY_FORMAT = "cadence-history-store/2";
5472
- var FORMAT = "cadence-history-store/3";
5473
- function decodeFormat(value) {
5474
- const o = closed(value, ["schema", "storeId", "workspaceId", "rootIdentity"]);
5475
- return {
5476
- schema: oneOf(o.schema, [FORMAT, LEGACY_FORMAT]),
5477
- storeId: decodeId(o.storeId),
5478
- workspaceId: decodeId(o.workspaceId),
5479
- rootIdentity: decodeHash(o.rootIdentity)
5480
- };
5481
- }
5482
- function decodeArtifact(value) {
5483
- const o = closed(value, ["runId", "artifactId", "part", "bytes", "sha256"]);
5484
- return {
5485
- runId: decodeId(o.runId),
5486
- artifactId: decodeId(o.artifactId),
5487
- part: integer(o.part),
5488
- bytes: integer(o.bytes, 1, LIMITS.artifactPartBytes),
5489
- sha256: decodeHash(o.sha256)
5490
- };
5491
- }
5492
- function decodeRetainedDetail(value) {
5493
- const o = closed(value, [
5494
- "detailId",
5495
- "origin",
5496
- "owner",
5497
- "descriptor",
5498
- "content",
5499
- "coverage"
5500
- ]);
5501
- const origin = closed(o.origin, ["kind", "id"]);
5502
- const r = {
5503
- detailId: decodeId(o.detailId),
5504
- origin: {
5505
- kind: oneOf(origin.kind, ["worker", "owner"]),
5506
- id: decodeId(origin.id)
5507
- },
5508
- owner: decodeRecordOwner(o.owner),
5509
- descriptor: nullable(o.descriptor, decodeDetailDescriptor),
5510
- content: decodeContentRef(o.content),
5511
- coverage: decodeCoverage(o.coverage)
5512
- };
5513
- if (r.origin.kind === "worker" !== (r.descriptor !== null) || r.descriptor && r.content.field !== r.descriptor.field)
5514
- invalid();
5515
- return r;
5516
- }
5517
- function decodeSavedEvent(value, owner) {
5518
- const kind = closedTag(value);
5519
- if (kind === "capture") {
5520
- const o2 = closed(value, ["kind", "id", "value"]), v = decodeRecordingEvent(o2.value);
5521
- if (owner || v.event.kind === "stored-chunk" || v.event.kind === "stored-end")
5522
- invalid();
5523
- return { kind, id: decodeId(o2.id), value: v };
5524
- }
5525
- if (kind === "owner") {
5526
- const o2 = closed(value, ["kind", "id", "value"]);
5527
- if (!owner)
5528
- invalid();
5529
- const v = decodeOwnerRecordingInput({
5530
- link: owner,
5531
- observedMs: 0,
5532
- events: [o2.value]
5533
- }).events[0];
5534
- if (v.kind === "content-chunk" || v.kind === "content-end")
5535
- invalid();
5536
- return { kind, id: decodeId(o2.id), value: v };
5537
- }
5538
- if (kind === "chunk") {
5539
- const o2 = closed(value, [
5540
- "kind",
5541
- "detailId",
5542
- "source",
5543
- "artifact",
5544
- "offset",
5545
- "start",
5546
- "length"
5547
- ]);
5548
- const source2 = nullable(o2.source, decodeSourceRange);
5549
- if (source2 === null !== (owner !== null))
5550
- invalid();
5551
- const artifact = decodeArtifact(o2.artifact), offset = integer(o2.offset, 0, LIMITS.detailBytes), start = integer(o2.start, 0, LIMITS.artifactPartBytes), length = integer(o2.length, 1, LIMITS.chunkBytes);
5552
- if (start + length > artifact.bytes || offset + length > LIMITS.detailBytes)
5553
- invalid();
5554
- return {
5555
- kind,
5556
- detailId: decodeId(o2.detailId),
5557
- source: source2,
5558
- artifact,
5559
- offset,
5560
- start,
5561
- length
5562
- };
5563
- }
5564
- const o = closed(value, ["kind", "detail", "source"]);
5565
- literal(kind, "end");
5566
- const detail = decodeRetainedDetail(o.detail), source = nullable(o.source, decodeSourceRange);
5567
- if (detail.origin.kind === "owner" !== (owner !== null) || source === null !== (owner !== null))
5568
- invalid();
5569
- return { kind: "end", detail, source };
5570
- }
5571
- function closedTag(value) {
5572
- if (!value || typeof value !== "object" || !("kind" in value) || typeof value.kind !== "string")
5573
- invalid();
5574
- return value.kind;
5575
- }
5576
- function decodeAdmissionIntent(value) {
5577
- const o = closed(value, [
5578
- "commandId",
5579
- "operationId",
5580
- "runId",
5581
- "identity",
5582
- "createdTaskId"
5583
- ]);
5584
- return {
5585
- commandId: decodeId(o.commandId),
5586
- operationId: decodeId(o.operationId),
5587
- runId: nullable(o.runId, decodeId),
5588
- identity: decodeCommandIdentity(o.identity),
5589
- createdTaskId: nullable(o.createdTaskId, decodeId)
5590
- };
5591
- }
5592
- function decodeAuthIntent(value) {
5593
- const o = closed(value, [
5594
- "operationId",
5595
- "epoch",
5596
- "runId",
5597
- "childRunId",
5598
- "sourceSelectionId",
5599
- "validityMs"
5600
- ]);
5601
- return {
5602
- operationId: decodeId(o.operationId),
5603
- epoch: decodeId(o.epoch),
5604
- runId: decodeId(o.runId),
5605
- childRunId: decodeId(o.childRunId),
5606
- sourceSelectionId: decodeId(o.sourceSelectionId),
5607
- validityMs: integer(o.validityMs, 1)
5608
- };
5609
- }
5610
- function decodeAuthSettlement(value) {
5611
- const o = closed(value, ["operationId", "outcome", "admissionClosed"]);
5612
- return {
5613
- operationId: decodeId(o.operationId),
5614
- outcome: oneOf(o.outcome, ["resolved", "refused", "failed"]),
5615
- admissionClosed: bool(o.admissionClosed)
5117
+ operationId: decodeId(o.operationId),
5118
+ outcome: oneOf(o.outcome, ["resolved", "refused", "failed"]),
5119
+ admissionClosed: bool(o.admissionClosed)
5616
5120
  };
5617
5121
  }
5618
5122
  function decodeRecoveryBridge(v) {
@@ -5931,14 +5435,14 @@ class HistoryFiles {
5931
5435
  this.coordination = coordination;
5932
5436
  this.format = format;
5933
5437
  this.formatSnapshot = formatSnapshot;
5934
- this.directory = join12(coordination.directory, "history-v2");
5438
+ this.directory = join8(coordination.directory, "history-v2");
5935
5439
  this.files = coordination.files;
5936
5440
  }
5937
5441
  static open(coordination) {
5938
- const dir = join12(coordination.directory, "history-v2");
5442
+ const dir = join8(coordination.directory, "history-v2");
5939
5443
  if (!coordination.files.directory(dir))
5940
5444
  return;
5941
- const file = coordination.files.read(join12(dir, "format.json"));
5445
+ const file = coordination.files.read(join8(dir, "format.json"));
5942
5446
  if (!file)
5943
5447
  corrupt();
5944
5448
  const format = decodeFormat(json(file));
@@ -5954,7 +5458,7 @@ class HistoryFiles {
5954
5458
  existing.assertWritable();
5955
5459
  return existing;
5956
5460
  }
5957
- const dir = join12(c.directory, "history-v2");
5461
+ const dir = join8(c.directory, "history-v2");
5958
5462
  const format = {
5959
5463
  schema: FORMAT,
5960
5464
  storeId: randomUUID6(),
@@ -5964,19 +5468,19 @@ class HistoryFiles {
5964
5468
  c.files.directory(dir, true);
5965
5469
  for (const name of ["commands", "ledger", "runs", "indexes"]) {
5966
5470
  lease.assert();
5967
- c.files.directory(join12(dir, name), true);
5471
+ c.files.directory(join8(dir, name), true);
5968
5472
  }
5969
5473
  lease.assert();
5970
- const keyPath = join12(dir, "read-proof-key.json");
5474
+ const keyPath = join8(dir, "read-proof-key.json");
5971
5475
  c.files.create(keyPath, encoded({
5972
5476
  schema: "cadence-history-read-key/1",
5973
5477
  storeId: format.storeId,
5974
5478
  key: randomBytes5(32).toString("base64url")
5975
5479
  }));
5976
5480
  lease.assert();
5977
- const snapshot = c.files.create(join12(dir, "format.json"), encoded(format));
5481
+ const snapshot = c.files.create(join8(dir, "format.json"), encoded(format));
5978
5482
  const result = new HistoryFiles(c, format, snapshot);
5979
- result.syncParents(join12(dir, "format.json"));
5483
+ result.syncParents(join8(dir, "format.json"));
5980
5484
  result.assertWritable();
5981
5485
  return result;
5982
5486
  }
@@ -6011,7 +5515,7 @@ class HistoryFiles {
6011
5515
  throw new ContractError("quarantine");
6012
5516
  }
6013
5517
  assertFormat() {
6014
- this.files.assert(join12(this.directory, "format.json"), this.formatSnapshot);
5518
+ this.files.assert(join8(this.directory, "format.json"), this.formatSnapshot);
6015
5519
  }
6016
5520
  readProofMaterial(budget) {
6017
5521
  if (this.format.schema !== FORMAT)
@@ -6020,7 +5524,7 @@ class HistoryFiles {
6020
5524
  this.assertReadFormat(budget);
6021
5525
  else
6022
5526
  this.assertFormat();
6023
- const selected2 = this.readBudgeted(join12(this.directory, "read-proof-key.json"), 512, budget);
5527
+ const selected2 = this.readBudgeted(join8(this.directory, "read-proof-key.json"), 512, budget);
6024
5528
  if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
6025
5529
  corrupt();
6026
5530
  try {
@@ -6048,17 +5552,17 @@ class HistoryFiles {
6048
5552
  return;
6049
5553
  }
6050
5554
  this.assertFormat();
6051
- assertIncarnation(this, join12(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
5555
+ assertIncarnation(this, join8(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
6052
5556
  }
6053
5557
  segmentPath(segment) {
6054
- return join12(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
5558
+ return join8(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
6055
5559
  }
6056
5560
  artifactPath(ref) {
6057
- return join12(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
5561
+ return join8(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
6058
5562
  }
6059
5563
  admissionPath(runId) {
6060
5564
  decodeId(runId);
6061
- return join12(this.directory, "runs", runId, "admission.json");
5565
+ return join8(this.directory, "runs", runId, "admission.json");
6062
5566
  }
6063
5567
  writeAdmission(lease, input) {
6064
5568
  lease.assert();
@@ -6084,7 +5588,7 @@ class HistoryFiles {
6084
5588
  readCommand(commandId, budget) {
6085
5589
  decodeId(commandId);
6086
5590
  this.assertReadFormat(budget);
6087
- return this.readBudgeted(join12(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
5591
+ return this.readBudgeted(join8(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
6088
5592
  }
6089
5593
  readAdmission(runId, identity, budget) {
6090
5594
  return this.readAdmissionIdentity(runId, hash(canonicalJson(identity)), budget);
@@ -6105,7 +5609,7 @@ class HistoryFiles {
6105
5609
  syncParents(file) {
6106
5610
  if (process.platform === "win32")
6107
5611
  return;
6108
- for (let path2 = dirname12(file);; path2 = dirname12(path2)) {
5612
+ for (let path2 = dirname8(file);; path2 = dirname8(path2)) {
6109
5613
  if (!this.files.directory(path2))
6110
5614
  corrupt();
6111
5615
  const fd = openSync5(path2, constants5.O_RDONLY | constants5.O_DIRECTORY);
@@ -6116,13 +5620,13 @@ class HistoryFiles {
6116
5620
  }
6117
5621
  if (path2 === this.coordination.directory)
6118
5622
  break;
6119
- if (path2 === dirname12(path2))
5623
+ if (path2 === dirname8(path2))
6120
5624
  corrupt();
6121
5625
  }
6122
5626
  }
6123
5627
  assertReadFormat(budget) {
6124
5628
  budget.reserve(this.formatSnapshot.bytes.length);
6125
- this.files.assert(join12(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
5629
+ this.files.assert(join8(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
6126
5630
  }
6127
5631
  assertPublished(segment, end, seq) {
6128
5632
  const fence = publicationFences.get(this.directory);
@@ -6160,7 +5664,7 @@ class HistoryFiles {
6160
5664
  const dir = opendirSync3(path2);
6161
5665
  try {
6162
5666
  for (let e = dir.readSync();e; e = dir.readSync()) {
6163
- const p = join12(path2, e.name), st = lstatSync6(p);
5667
+ const p = join8(path2, e.name), st = lstatSync6(p);
6164
5668
  if (st.isDirectory())
6165
5669
  bytes += walk(p, depth + 1);
6166
5670
  else {
@@ -6195,7 +5699,7 @@ class HistoryFiles {
6195
5699
  state.bytes = 0;
6196
5700
  state.identity = null;
6197
5701
  let count = 0;
6198
- const ledger = join12(this.directory, "ledger");
5702
+ const ledger = join8(this.directory, "ledger");
6199
5703
  if (!this.files.directory(ledger))
6200
5704
  corrupt();
6201
5705
  const dir = opendirSync3(ledger);
@@ -6512,7 +6016,7 @@ class HistoryFiles {
6512
6016
  checkpoint(lease, state) {
6513
6017
  lease.assert();
6514
6018
  this.assertWritable();
6515
- const path2 = join12(this.directory, "checkpoint.json"), old = this.files.read(path2);
6019
+ const path2 = join8(this.directory, "checkpoint.json"), old = this.files.read(path2);
6516
6020
  const body = encoded({
6517
6021
  schema: "cadence-history-checkpoint/2",
6518
6022
  storeId: this.format.storeId,
@@ -6939,7 +6443,7 @@ function digestHistory(files, exclude) {
6939
6443
  dir.closeSync();
6940
6444
  }
6941
6445
  for (const name of names2.sort()) {
6942
- const selected2 = join13(path2, name);
6446
+ const selected2 = join9(path2, name);
6943
6447
  if (selected2 === exclude)
6944
6448
  continue;
6945
6449
  const rel = relative4(files.directory, selected2);
@@ -7127,19 +6631,260 @@ class HistoryRecovery {
7127
6631
  }
7128
6632
  }
7129
6633
 
7130
- // src/service/recovery-cli.ts
7131
- var warning = "PID absence is not proof that descendants stopped or credential writes settled. Inspect/stop old processes externally, independently inspect the selected Pi credential store and SDK locks, and inspect retained history/storage. Cadence does not kill, repair, reset, migrate or certify those resources. Unknown historical outcomes remain unknown. A new Start is required.";
7132
- function native() {
7133
- if (!isatty(0) || !isatty(1) || !process.stdin.isTTY || !process.stdout.isTTY)
7134
- throw new ContractError("forbidden");
6634
+ // src/service/gateway-recovery.ts
6635
+ import { opendirSync as opendirSync5 } from "node:fs";
6636
+ import { hostname as hostname4 } from "node:os";
6637
+ import { join as join10 } from "node:path";
6638
+ function required(resources, name) {
6639
+ const file = resources.get(name);
6640
+ if (!file)
6641
+ throw new ContractError("corrupt-selected");
6642
+ return file;
7135
6643
  }
7136
- async function confirm(expected) {
6644
+ function names2(store, path2, maximum) {
6645
+ if (!store.files.directory(path2))
6646
+ return [];
6647
+ const stream = opendirSync5(path2), out = [];
6648
+ try {
6649
+ for (let row = stream.readSync();row; row = stream.readSync()) {
6650
+ if (out.length >= maximum || !row.isFile() && !row.isDirectory())
6651
+ throw new ContractError("corrupt-selected");
6652
+ out.push(row.name);
6653
+ }
6654
+ } finally {
6655
+ stream.closeSync();
6656
+ }
6657
+ return out.sort();
6658
+ }
6659
+ function capture(store) {
6660
+ const out = new Map;
6661
+ const keep = (name) => {
6662
+ const file = store.files.read(store.path(name));
6663
+ if (!file)
6664
+ throw new ContractError("corrupt-selected");
6665
+ out.set(name, file);
6666
+ };
6667
+ for (const name of names2(store, store.directory, 9)) {
6668
+ if ([
6669
+ "gateway.json",
6670
+ "gateway.lock",
6671
+ "input.json",
6672
+ "access-config.json",
6673
+ "access.json"
6674
+ ].includes(name))
6675
+ keep(name);
6676
+ else if (name === "projects" || name === "commands") {
6677
+ for (const child of names2(store, store.path(name), name === "projects" ? 128 : 2048)) {
6678
+ if (name === "projects" ? !/^[a-f0-9]{64}\.json$/.test(child) : !/^[a-f0-9-]{36}(?:\.result)?\.json$/.test(child))
6679
+ throw new ContractError("corrupt-selected");
6680
+ keep(`${name}/${child}`);
6681
+ }
6682
+ } else if (name !== "recovery-v1" || !store.files.directory(store.path(name)))
6683
+ throw new ContractError("corrupt-selected");
6684
+ }
6685
+ for (const name of [
6686
+ "gateway.json",
6687
+ "input.json",
6688
+ "access-config.json",
6689
+ "access.json"
6690
+ ])
6691
+ if (!out.has(name))
6692
+ throw new ContractError("corrupt-selected");
6693
+ for (const [name, file] of out) {
6694
+ if (!name.startsWith("commands/"))
6695
+ continue;
6696
+ if (name.endsWith(".result.json")) {
6697
+ if (!out.has(name.replace(".result.json", ".json")))
6698
+ throw new ContractError("corrupt-selected");
6699
+ decodeProjectEntry(json(file));
6700
+ } else {
6701
+ decodeId(name.slice("commands/".length, -5));
6702
+ const intent = closed(json(file), ["path", "epoch"]);
6703
+ text(intent.path, 4096, true);
6704
+ decodeId(intent.epoch);
6705
+ }
6706
+ }
6707
+ return out;
6708
+ }
6709
+ function signature(resources) {
6710
+ return hash(encoded([...resources].sort(([a], [b]) => a.localeCompare(b, "en")).map(([name, file]) => [
6711
+ name,
6712
+ file.stamp,
6713
+ ["gateway.json", "access.json", "access-config.json"].includes(name) ? null : file.identity
6714
+ ])));
6715
+ }
6716
+ function state(store) {
6717
+ const registration2 = store.read();
6718
+ if (!registration2)
6719
+ throw new ContractError("unavailable");
6720
+ const resources = capture(store), lock = resources.get("gateway.lock");
6721
+ if (!lock)
6722
+ throw new ContractError("unavailable");
6723
+ decodeId(closed(json(lock), ["epoch"]).epoch);
6724
+ const input = decodeGatewayInput(json(required(resources, "input.json")));
6725
+ if (input.serverConfig !== store.serverConfig || input.stateHome !== store.stateHome)
6726
+ throw new ContractError("conflict");
6727
+ const projects = store.projects();
6728
+ store.associatedProjects();
6729
+ const incarnation = hash(encoded([lock.identity, lock.stamp]));
6730
+ if (registration2.phase === "stopped" && !registration2.recovery?.receipt.startsWith(`recovery-v1/${incarnation}/`))
6731
+ throw new ContractError("corrupt-selected");
6732
+ const archive = store.path(`recovery-v1/${incarnation}`);
6733
+ const attempts = [];
6734
+ let previousResolver = null;
6735
+ const entries = names2(store, archive, 128);
6736
+ for (let n = 0;n < entries.length; n++) {
6737
+ if (!entries.includes(`${n}.json`))
6738
+ throw new ContractError("corrupt-selected");
6739
+ const file = store.files.read(join10(archive, `${n}.json`), 65536);
6740
+ if (!file)
6741
+ throw new ContractError("corrupt-selected");
6742
+ const row = closed(json(file, 65536), [
6743
+ "schema",
6744
+ "lock",
6745
+ "epoch",
6746
+ "digest",
6747
+ "pid",
6748
+ "host",
6749
+ "legacyHost",
6750
+ "retiredProjects"
6751
+ ]);
6752
+ literal(row.schema, "cadence-workbench-recovery/1");
6753
+ literal(row.lock, lock.bytes.toString("utf8"));
6754
+ literal(row.epoch, registration2.epoch);
6755
+ decodeHash(row.digest);
6756
+ if (!Array.isArray(row.retiredProjects) || row.retiredProjects.length > 128)
6757
+ throw new ContractError("corrupt-selected");
6758
+ for (const entry of row.retiredProjects) {
6759
+ const project = closed(entry, ["id", "stamp"]);
6760
+ decodeHash(project.id);
6761
+ text(project.stamp, 512, true);
6762
+ }
6763
+ if (typeof row.legacyHost !== "boolean")
6764
+ throw new ContractError("corrupt-selected");
6765
+ previousResolver = recoveryOwnerStatus(integer(row.pid, 1, 2147483647), text(row.host, 255, true));
6766
+ attempts.push(file);
6767
+ }
6768
+ if (encoded(store.read()) !== encoded(registration2))
6769
+ throw new ContractError("conflict");
6770
+ for (const [name, file] of resources)
6771
+ store.files.assert(store.path(name), file);
6772
+ const localOwner = recoveryOwnerStatus(registration2.pid, registration2.host ?? hostname4());
6773
+ const inspection = {
6774
+ scope: "workbench",
6775
+ epoch: registration2.epoch,
6776
+ coordinationDigest: hash(encoded([
6777
+ signature(resources),
6778
+ attempts.map((a) => [a.identity, a.stamp])
6779
+ ])),
6780
+ owner: registration2.host ? localOwner : localOwner === "live" ? "live" : "ambiguous",
6781
+ process: { pid: registration2.pid, host: registration2.host ?? null },
6782
+ legacyHost: !registration2.host,
6783
+ previousResolver,
6784
+ projects: projects.length,
6785
+ openings: [...resources.keys()].filter((name) => name.startsWith("commands/") && !name.endsWith(".result.json")).length,
6786
+ resolution: registration2.host ? "local-interactive-only" : "local-host-confirmation-required"
6787
+ };
6788
+ return {
6789
+ registration: registration2,
6790
+ resources,
6791
+ lock,
6792
+ archive,
6793
+ incarnation,
6794
+ attempts,
6795
+ localOwner,
6796
+ inspection
6797
+ };
6798
+ }
6799
+ function inspectGatewayRecovery(store) {
6800
+ const before = state(store), after = state(store);
6801
+ if (encoded(before.inspection) !== encoded(after.inspection))
6802
+ throw new ContractError("conflict");
6803
+ return before.inspection;
6804
+ }
6805
+ function resolveGatewayRecovery(store, expected, assertAuthority, legacyHostConfirmed = false) {
6806
+ assertAuthority();
6807
+ const selected2 = state(store);
6808
+ if (encoded(selected2.inspection) !== encoded(expected))
6809
+ throw new ContractError("conflict");
6810
+ const absent2 = () => {
6811
+ assertAuthority();
6812
+ if (expected.legacyHost && !legacyHostConfirmed || recoveryOwnerStatus(selected2.registration.pid, selected2.registration.host ?? hostname4()) !== "absent")
6813
+ throw new ContractError("quarantine");
6814
+ };
6815
+ absent2();
6816
+ if (expected.previousResolver !== null && expected.previousResolver !== "absent")
6817
+ throw new ContractError("quarantine");
6818
+ if (selected2.attempts.length >= 128)
6819
+ throw new ContractError("capacity");
6820
+ const receiptPath = join10(selected2.archive, `${selected2.attempts.length}.json`);
6821
+ const receipt = store.files.create(receiptPath, encoded({
6822
+ schema: "cadence-workbench-recovery/1",
6823
+ lock: selected2.lock.bytes.toString("utf8"),
6824
+ epoch: expected.epoch,
6825
+ digest: expected.coordinationDigest,
6826
+ pid: process.pid,
6827
+ host: hostname4(),
6828
+ legacyHost: expected.legacyHost,
6829
+ retiredProjects: [...selected2.resources].filter(([name]) => name.startsWith("projects/")).map(([name, file]) => ({
6830
+ id: name.slice("projects/".length, -5),
6831
+ stamp: file.stamp
6832
+ }))
6833
+ }), 65536);
6834
+ const assert = () => {
6835
+ absent2();
6836
+ store.associatedProjects();
6837
+ const current = capture(store);
6838
+ if (signature(current) !== signature(selected2.resources))
6839
+ throw new ContractError("conflict");
6840
+ for (const [name, file] of selected2.resources)
6841
+ store.files.assert(store.path(name), file);
6842
+ if (names2(store, selected2.archive, 128).length !== selected2.attempts.length + 1)
6843
+ throw new ContractError("conflict");
6844
+ for (const [n, file] of selected2.attempts.entries())
6845
+ store.files.assert(join10(selected2.archive, `${n}.json`), file, 65536);
6846
+ store.files.assert(receiptPath, receipt, 65536);
6847
+ };
6848
+ assert();
6849
+ const registration2 = store.files.replace(store.path("gateway.json"), required(selected2.resources, "gateway.json"), encoded({
6850
+ ...selected2.registration,
6851
+ phase: "stopped",
6852
+ recovery: {
6853
+ receipt: `recovery-v1/${selected2.incarnation}/${selected2.attempts.length}.json`,
6854
+ sha256: receipt.identity,
6855
+ stamp: receipt.stamp
6856
+ }
6857
+ }));
6858
+ selected2.resources.set("gateway.json", registration2);
6859
+ assert();
6860
+ store.files.remove(store.path("gateway.lock"), selected2.lock);
6861
+ return {
6862
+ status: "resolved",
6863
+ scope: "workbench",
6864
+ epoch: expected.epoch,
6865
+ notice: "operator-acknowledgment-only-project-fences-preserved"
6866
+ };
6867
+ }
6868
+
6869
+ // src/service/recovery-cli.ts
6870
+ var warning = "PID absence is not proof that descendants stopped or credential writes settled. Inspect/stop old processes externally, independently inspect the selected Pi credential store and SDK locks, and inspect retained history/storage. Cadence does not kill, repair, reset, migrate or certify those resources. Unknown historical outcomes remain unknown. A new Start is required.";
6871
+ function native() {
6872
+ if (!isatty(0) || !isatty(1) || !process.stdin.isTTY || !process.stdout.isTTY)
6873
+ throw new ContractError("forbidden");
6874
+ }
6875
+ async function confirm(expected) {
7137
6876
  native();
7138
6877
  if (process.stdin.readableLength)
7139
6878
  throw new ContractError("forbidden");
7140
6879
  const identity = JSON.stringify(expected);
7141
6880
  const deadline = performance.now() + LIMITS.grantMs;
7142
6881
  const prompts = [
6882
+ ...expected.legacyHost ? [
6883
+ [
6884
+ "Legacy registration has no host identity. Independently verify this state originated on THIS host and is not shared/copied from another host. Type local-host",
6885
+ "local-host"
6886
+ ]
6887
+ ] : [],
7143
6888
  [
7144
6889
  "Confirm old processes stopped externally; acknowledge escaped-descendant uncertainty. Type processes",
7145
6890
  "processes"
@@ -7153,11 +6898,11 @@ async function confirm(expected) {
7153
6898
  "storage"
7154
6899
  ],
7155
6900
  [
7156
- `${expected.scope === "global-registry" ? "Release ONLY the global registry lock; all project fences remain." : "Release ONLY the displayed unchanged service-owned fences."} Type resolve ${expected.epoch}`,
6901
+ `${expected.scope === "global-registry" ? "Release ONLY the global registry lock; all project fences remain." : expected.scope === "workbench" ? "Release ONLY the workbench fence and retire its old project associations; all project fences remain." : "Release ONLY the displayed unchanged service-owned fences."} Type resolve ${expected.epoch}`,
7157
6902
  `resolve ${expected.epoch}`
7158
6903
  ]
7159
6904
  ];
7160
- await new Promise((resolve7, reject) => {
6905
+ await new Promise((resolve5, reject) => {
7161
6906
  let index = 0, line = "", done = false;
7162
6907
  const raw = process.stdin.isRaw;
7163
6908
  const finish = (ok) => {
@@ -7173,7 +6918,7 @@ async function confirm(expected) {
7173
6918
  process.stdout.write(`\x1B[?2004l
7174
6919
  `);
7175
6920
  if (ok)
7176
- resolve7();
6921
+ resolve5();
7177
6922
  else
7178
6923
  reject(new ContractError("forbidden"));
7179
6924
  };
@@ -7213,80 +6958,678 @@ async function confirm(expected) {
7213
6958
  throw new ContractError("stale");
7214
6959
  };
7215
6960
  }
7216
- async function localRecovery(c, operation) {
7217
- if (operation === "resolve")
7218
- native();
7219
- const registryLock = c.registryLockDiagnostic();
7220
- if (registryLock) {
7221
- let expected2;
7222
- try {
7223
- expected2 = c.inspectRegistryRecovery();
7224
- } catch {
7225
- const blocked = {
7226
- scope: "global-registry",
7227
- ...registryLock,
7228
- coordination: "unavailable",
7229
- resolution: "blocked-preserve-state",
7230
- notice: "Registry state is incomplete, changed or unsafe. Preserve it; no lock or project resource was released."
7231
- };
7232
- if (operation === "inspect")
7233
- return blocked;
7234
- process.stdout.write(`${JSON.stringify(blocked)}
7235
- `);
7236
- throw new ContractError("quarantine");
7237
- }
7238
- const view2 = {
7239
- ...expected2,
7240
- process: {
7241
- ...expected2.process,
7242
- host: diagnosticText(expected2.process.host, 255)
7243
- },
7244
- notice: `Global registry lock blocks every project in this state home. Recovery preserves its original bytes and all project reservations, owner/checkout locks and history. Inspect project fences separately afterward. ${warning}`
7245
- };
7246
- if (operation === "inspect")
7247
- return view2;
7248
- process.stdout.write(`${JSON.stringify(view2)}
6961
+ async function localRegistryRecovery(c, operation) {
6962
+ if (operation === "resolve")
6963
+ native();
6964
+ const registryLock = c.registryLockDiagnostic();
6965
+ if (registryLock) {
6966
+ let expected;
6967
+ try {
6968
+ expected = c.inspectRegistryRecovery();
6969
+ } catch {
6970
+ const blocked = {
6971
+ scope: "global-registry",
6972
+ ...registryLock,
6973
+ coordination: "unavailable",
6974
+ resolution: "blocked-preserve-state",
6975
+ notice: "Registry state is incomplete, changed or unsafe. Preserve it; no lock or project resource was released."
6976
+ };
6977
+ if (operation === "inspect")
6978
+ return blocked;
6979
+ process.stdout.write(`${JSON.stringify(blocked)}
6980
+ `);
6981
+ throw new ContractError("quarantine");
6982
+ }
6983
+ const view = {
6984
+ ...expected,
6985
+ process: {
6986
+ ...expected.process,
6987
+ host: diagnosticText(expected.process.host, 255)
6988
+ },
6989
+ notice: `Global registry lock blocks every project in this state home. Recovery preserves its original bytes and all project reservations, owner/checkout locks and history. Inspect project fences separately afterward. ${warning}`
6990
+ };
6991
+ if (operation === "inspect")
6992
+ return view;
6993
+ process.stdout.write(`${JSON.stringify(view)}
6994
+ `);
6995
+ if (expected.owner !== "absent" || expected.previousResolver !== null && expected.previousResolver !== "absent")
6996
+ throw new ContractError("quarantine");
6997
+ const assertFresh = await confirm(expected);
6998
+ return c.resolveRegistryRecovery(expected, assertFresh);
6999
+ }
7000
+ return;
7001
+ }
7002
+ async function localRecovery(c, operation) {
7003
+ const registry = await localRegistryRecovery(c, operation);
7004
+ if (registry !== undefined)
7005
+ return registry;
7006
+ const history = new HistoryRecovery(c);
7007
+ const expected = await inspectRecovery(c, history);
7008
+ const registration2 = c.recoverySnapshot().registration;
7009
+ const view = {
7010
+ ...expected,
7011
+ process: {
7012
+ pid: registration2.pid,
7013
+ host: diagnosticText(registration2.host, 255)
7014
+ },
7015
+ notice: warning
7016
+ };
7017
+ if (operation === "inspect")
7018
+ return view;
7019
+ process.stdout.write(`${JSON.stringify(view)}
7020
+ `);
7021
+ if (expected.owner !== "absent")
7022
+ throw new ContractError("quarantine");
7023
+ const assertFresh = await confirm(expected);
7024
+ return resolveRecovery(c, {
7025
+ expected,
7026
+ acknowledgeEscapedProcesses: true,
7027
+ processes: "externally-resolved",
7028
+ credentials: "independently-resolved",
7029
+ storage: "independently-resolved",
7030
+ assertLocalAuthority: (choice) => {
7031
+ assertFresh();
7032
+ if (JSON.stringify(choice) !== JSON.stringify(expected))
7033
+ throw new ContractError("stale");
7034
+ }
7035
+ }, history);
7036
+ }
7037
+ async function localGatewayRecovery(store, operation) {
7038
+ const registry = await localRegistryRecovery(new ServiceCoordination(null, store.stateHome), operation);
7039
+ if (registry !== undefined)
7040
+ return registry;
7041
+ let expected;
7042
+ try {
7043
+ const observed = store.inspect();
7044
+ if (observed.status === "absent" || observed.status === "stopped") {
7045
+ if (operation === "resolve")
7046
+ throw new ContractError("unavailable");
7047
+ return { ...observed, scope: "workbench" };
7048
+ }
7049
+ expected = inspectGatewayRecovery(store);
7050
+ } catch (error) {
7051
+ if (operation === "resolve")
7052
+ throw error;
7053
+ return {
7054
+ status: "unavailable",
7055
+ scope: "workbench",
7056
+ resolution: "blocked-preserve-state",
7057
+ error: error instanceof ContractError ? error.code : "unavailable",
7058
+ notice: "Workbench state is incomplete, changed or unsafe. Preserve it; no resource was released."
7059
+ };
7060
+ }
7061
+ const view = {
7062
+ ...expected,
7063
+ process: {
7064
+ ...expected.process,
7065
+ host: expected.process.host === null ? null : diagnosticText(expected.process.host, 255)
7066
+ },
7067
+ notice: `Recovery releases ONLY the workbench fence and retires its exact old project associations. Project references, reservations, owner/checkout locks, opening receipts and history remain unchanged. Explicit Open must associate a project again; recover affected project owners separately. ${warning}`
7068
+ };
7069
+ if (operation === "inspect")
7070
+ return view;
7071
+ process.stdout.write(`${JSON.stringify(view)}
7072
+ `);
7073
+ if (!expected.legacyHost && expected.owner !== "absent" || expected.owner === "live" || expected.previousResolver !== null && expected.previousResolver !== "absent")
7074
+ throw new ContractError("quarantine");
7075
+ const authority = await confirm(expected);
7076
+ return resolveGatewayRecovery(store, expected, authority, expected.legacyHost);
7077
+ }
7078
+
7079
+ // src/service/runtime.ts
7080
+ import { homedir as homedir4 } from "node:os";
7081
+ import { dirname as dirname9, isAbsolute as isAbsolute6, join as join11, resolve as resolve5 } from "node:path";
7082
+ var STARTUP_INPUT_BYTES = 16 * 1024;
7083
+ function absolute(value) {
7084
+ const result = text(value, 4096, true);
7085
+ if (!isAbsolute6(result))
7086
+ throw new ContractError("invalid");
7087
+ return result;
7088
+ }
7089
+ function decodeHostConfig(value) {
7090
+ const o = closed(value, [
7091
+ "schema",
7092
+ "http",
7093
+ "cwd",
7094
+ "agentDir",
7095
+ "tokenSource",
7096
+ "tokenFile"
7097
+ ]);
7098
+ const tokenSource = oneOf(o.tokenSource, [
7099
+ "generated",
7100
+ "environment",
7101
+ "file",
7102
+ "config"
7103
+ ]);
7104
+ const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
7105
+ if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
7106
+ throw new ContractError("invalid");
7107
+ return {
7108
+ schema: literal(o.schema, "cadence-service-host/1"),
7109
+ http: decodeHttpPolicy(o.http),
7110
+ cwd: absolute(o.cwd),
7111
+ agentDir: absolute(o.agentDir),
7112
+ tokenSource,
7113
+ tokenFile
7114
+ };
7115
+ }
7116
+ function readHostConfig(c) {
7117
+ const owner = c.configuration();
7118
+ if (!owner)
7119
+ return;
7120
+ const snapshot = c.files.read(c.path("host-config.json"));
7121
+ if (!snapshot)
7122
+ throw new ContractError("corrupt-selected");
7123
+ const config = decodeHostConfig(json(snapshot));
7124
+ if (!config.http.port || owner.endpoint !== localOrigin(config.http, config.http.port) || owner.policyIdentity !== hash(encoded(config)))
7125
+ throw new ContractError("conflict");
7126
+ return config;
7127
+ }
7128
+ function hostEnvironment(ambient) {
7129
+ let route;
7130
+ const facts = networkSnapshot(ambient);
7131
+ try {
7132
+ if (facts.NODE_OPTIONS.present)
7133
+ throw new Error;
7134
+ route = sdkNetworkEnvironment(facts, networkSnapshot(undefined));
7135
+ } catch {
7136
+ throw new ContractError("unsupported-context");
7137
+ }
7138
+ const result = Object.create(null);
7139
+ for (const key of Object.keys(ambient)) {
7140
+ const d = Object.getOwnPropertyDescriptor(ambient, key);
7141
+ if (!d || !("value" in d) || typeof d.value !== "string")
7142
+ throw new ContractError("invalid");
7143
+ if (/^(?:CADENCE_|PI_|ABEL_|TRELLIS_|GIT_)/i.test(key) || networkKeys.some((name) => name === key))
7144
+ continue;
7145
+ result[key] = d.value;
7146
+ }
7147
+ if (route.redactionLiterals.length) {
7148
+ result.HTTP_PROXY = route.env.HTTP_PROXY;
7149
+ result.HTTPS_PROXY = route.env.HTTPS_PROXY;
7150
+ result.NODE_USE_ENV_PROXY = "1";
7151
+ }
7152
+ result.PI_OFFLINE = "1";
7153
+ result.NO_COLOR = "1";
7154
+ if (!sameNetwork(facts, networkSnapshot(ambient)))
7155
+ throw new ContractError("stale");
7156
+ return result;
7157
+ }
7158
+ function selectStart(options, ambient) {
7159
+ const workspace = resolveWorkspace(options.workspace), c = new ServiceCoordination(workspace.root, options.stateHome);
7160
+ const old = readHostConfig(c);
7161
+ const env = Object.getOwnPropertyDescriptor(ambient, "CADENCE_WEB_TOKEN");
7162
+ if (env && (!("value" in env) || typeof env.value !== "string"))
7163
+ throw new ContractError("invalid");
7164
+ let fromFile;
7165
+ if (options.config) {
7166
+ const path2 = resolve5(options.config), f = new PrivateFiles(dirname9(path2));
7167
+ const bytes = f.read(path2);
7168
+ if (!bytes)
7169
+ throw new ContractError("corrupt-selected");
7170
+ fromFile = decodeHostConfig(json(bytes));
7171
+ }
7172
+ const base = old ?? fromFile ?? decodeHostConfig({
7173
+ schema: "cadence-service-host/1",
7174
+ cwd: workspace.cwd,
7175
+ http: {
7176
+ schema: "cadence-service-http/1",
7177
+ host: options.host ?? "127.0.0.1",
7178
+ port: options.port ?? 17423,
7179
+ proxy: null
7180
+ },
7181
+ agentDir: resolve5(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join11(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
7182
+ tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
7183
+ tokenFile: options.tokenFile ? resolve5(options.tokenFile) : null
7184
+ });
7185
+ const server = options.serverConfig !== undefined ? resolve5(options.serverConfig) : old?.tokenSource === "config" ? old.tokenFile : null;
7186
+ let selected2 = base;
7187
+ if (server) {
7188
+ if (options.tokenFile !== undefined)
7189
+ throw new ContractError("conflict");
7190
+ const { port } = readServerConfig(server).config;
7191
+ selected2 = decodeHostConfig({
7192
+ ...base,
7193
+ http: { ...base.http, port },
7194
+ tokenSource: "config",
7195
+ tokenFile: server
7196
+ });
7197
+ }
7198
+ if (fromFile && encoded(fromFile) !== encoded(selected2) || options.port !== undefined && options.port !== selected2.http.port || options.host !== undefined && options.host !== selected2.http.host || options.agentDir !== undefined && resolve5(options.agentDir) !== selected2.agentDir || options.tokenFile !== undefined && resolve5(options.tokenFile) !== selected2.tokenFile || env && selected2.tokenSource !== "environment" || selected2.tokenSource === "environment" && !env || resolveWorkspace(selected2.cwd).root !== workspace.root)
7199
+ throw new ContractError("conflict");
7200
+ return {
7201
+ workspace: workspace.root,
7202
+ stateHome: c.stateHome,
7203
+ config: selected2,
7204
+ environmentToken: env?.value ?? null
7205
+ };
7206
+ }
7207
+ function decodeHostInput(value) {
7208
+ const o = closed(value, [
7209
+ "workspace",
7210
+ "stateHome",
7211
+ "config",
7212
+ "environmentToken"
7213
+ ]);
7214
+ const config = decodeHostConfig(o.config);
7215
+ const environmentToken = o.environmentToken === null ? null : text(o.environmentToken, 1024, true);
7216
+ if (config.tokenSource === "environment" !== (environmentToken !== null))
7217
+ throw new ContractError("invalid");
7218
+ return {
7219
+ workspace: absolute(o.workspace),
7220
+ stateHome: absolute(o.stateHome),
7221
+ config,
7222
+ environmentToken
7223
+ };
7224
+ }
7225
+ function portValue(value) {
7226
+ if (!/^(0|[1-9][0-9]{0,4})$/.test(value))
7227
+ throw new ContractError("invalid");
7228
+ return integer(Number(value), 0, 65535);
7229
+ }
7230
+ var STARTUP_STAGES = [
7231
+ "package",
7232
+ "pi-agent-directory",
7233
+ "pi-auth",
7234
+ "pi-models",
7235
+ "pi-runtime",
7236
+ "service"
7237
+ ];
7238
+ function decodeStartupReport(value) {
7239
+ const o = closed(value, ["status", "error", "stage"]);
7240
+ return {
7241
+ status: oneOf(o.status, ["refused", "quarantined"]),
7242
+ error: oneOf(o.error, ERROR_CODES),
7243
+ stage: oneOf(o.stage, STARTUP_STAGES)
7244
+ };
7245
+ }
7246
+
7247
+ // src/service/gateway-cli.ts
7248
+ async function gatewayMain(argv, hostUrl) {
7249
+ const operation = argv.shift();
7250
+ if (![
7251
+ "start",
7252
+ "status",
7253
+ "diagnose",
7254
+ "stop",
7255
+ "token-link",
7256
+ "token-rotate",
7257
+ "recover"
7258
+ ].includes(operation ?? ""))
7259
+ throw new ContractError("invalid");
7260
+ const recovery = operation === "recover" ? argv.shift() : undefined;
7261
+ if (operation === "recover" && recovery !== "inspect" && recovery !== "resolve")
7262
+ throw new ContractError("invalid");
7263
+ let config = serverConfigPath(homedir5()), stateHome = defaultStateHome(), agentDir, policyFile;
7264
+ let foreground = false, reveal = false;
7265
+ const seen = new Set;
7266
+ while (argv.length) {
7267
+ const key = argv.shift();
7268
+ if (seen.has(key))
7269
+ throw new ContractError("invalid");
7270
+ seen.add(key);
7271
+ if (key === "--foreground" && operation === "start")
7272
+ foreground = true;
7273
+ else if (key === "--reveal" && operation === "token-link")
7274
+ reveal = true;
7275
+ else if (argv[0] && (operation === "recover" ? ["--server-config", "--state-home"] : ["--server-config", "--state-home", "--agent-dir", "--config"]).includes(key)) {
7276
+ const value = resolve6(argv.shift());
7277
+ if (key === "--server-config")
7278
+ config = value;
7279
+ if (key === "--state-home")
7280
+ stateHome = value;
7281
+ if (key === "--agent-dir")
7282
+ agentDir = value;
7283
+ if (key === "--config")
7284
+ policyFile = value;
7285
+ } else
7286
+ throw new ContractError("invalid");
7287
+ }
7288
+ const store = new GatewayStore(config, stateHome);
7289
+ let result;
7290
+ if (operation === "start") {
7291
+ const network = networkSnapshot(process.env), env = hostEnvironment(process.env);
7292
+ if (!seen.has("--server-config"))
7293
+ ensureServerConfig(homedir5());
7294
+ const server = readServerConfig(config).config;
7295
+ const selectedFile = store.files.read(store.path("input.json"));
7296
+ const selected2 = selectedFile ? decodeGatewayInput(json(selectedFile)) : undefined;
7297
+ let policy = selected2?.policy ?? decodeHttpPolicy({
7298
+ schema: "cadence-service-http/1",
7299
+ host: "127.0.0.1",
7300
+ port: server.port,
7301
+ proxy: null
7302
+ });
7303
+ if (policyFile) {
7304
+ const file = new PrivateFiles(dirname10(policyFile)).read(policyFile);
7305
+ if (!file)
7306
+ throw new ContractError("invalid");
7307
+ policy = decodeHttpPolicy(json(file));
7308
+ }
7309
+ const input = decodeGatewayInput({
7310
+ kind: "workbench",
7311
+ serverConfig: config,
7312
+ stateHome: store.stateHome,
7313
+ agentDir: agentDir ?? selected2?.agentDir ?? resolve6(process.env.PI_CODING_AGENT_DIR ?? join12(homedir5(), ".pi", "agent")),
7314
+ policy: { ...policy, port: server.port }
7315
+ });
7316
+ const status = await serviceStatus(store);
7317
+ if (status && typeof status === "object" && "status" in status && status.status === "alive") {
7318
+ if (!selected2 || encoded(selected2) !== encoded(input))
7319
+ throw new ContractError("conflict");
7320
+ result = status;
7321
+ } else
7322
+ result = await launchHost(input, store, foreground, hostUrl, dirname10(dirname10(fileURLToPath(hostUrl))), env, network);
7323
+ if (!foreground && result && typeof result === "object" && "status" in result && result.status === "alive") {
7324
+ const reply = closed(await nativeCall(store, "token-link", true), [
7325
+ "link"
7326
+ ]);
7327
+ process.stderr.write(`Cadence workbench: ${text(reply.link, 4096, true)}
7328
+ No project or default workspace was created.
7329
+ `);
7330
+ }
7331
+ } else if (operation === "recover" && (recovery === "inspect" || recovery === "resolve"))
7332
+ result = await localGatewayRecovery(store, recovery);
7333
+ else if (operation === "stop")
7334
+ result = await stopService(store);
7335
+ else if (operation === "status" || operation === "diagnose")
7336
+ result = await serviceStatus(store, operation === "diagnose");
7337
+ else {
7338
+ if (operation === "token-link" && !reveal)
7339
+ throw new ContractError("forbidden");
7340
+ result = await nativeCall(store, operation, reveal);
7341
+ }
7342
+ if (result !== null)
7343
+ process.stdout.write(`${JSON.stringify(result)}
7344
+ `);
7345
+ if (result && typeof result === "object" && "status" in result && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)))
7346
+ process.exitCode = managementExitCode(result, foreground);
7347
+ if (result && typeof result === "object" && "status" in result && ["unavailable", "quarantined", "refused", "unconfirmed"].includes(String(result.status)))
7348
+ process.stderr.write(`Preserve state; inspect locally: cadence workbench recover inspect --server-config ${JSON.stringify(config)} --state-home ${JSON.stringify(store.stateHome)}. Eligible recovery requires recover resolve in a local terminal; it never starts work.
7349
+ `);
7350
+ }
7351
+
7352
+ // src/service/launchd.ts
7353
+ import { rmSync as rmSync2 } from "node:fs";
7354
+ import { dirname as dirname11, join as join13 } from "node:path";
7355
+ var LABEL = "com.abelxiaoxing.cadence";
7356
+ var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
7357
+ <!-- Generated by @abelxiaoxing/cadence`;
7358
+ function plistPath(s) {
7359
+ return join13(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
7360
+ }
7361
+ var xml = (value) => checked(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
7362
+ function buildPlist(u) {
7363
+ const string = (value) => ` <string>${xml(value)}</string>`;
7364
+ return [
7365
+ '<?xml version="1.0" encoding="UTF-8"?>',
7366
+ "<!-- Generated by @abelxiaoxing/cadence; `cadence server install` and `restart` rewrite this file. -->",
7367
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
7368
+ '<plist version="1.0">',
7369
+ "<dict>",
7370
+ "\t<key>Label</key>",
7371
+ ` <string>${LABEL}</string>`,
7372
+ "\t<key>ProgramArguments</key>",
7373
+ "\t<array>",
7374
+ ...[
7375
+ u.node,
7376
+ u.cli,
7377
+ ...u.workspace === null ? ["workbench", "start", "--foreground"] : ["start", "--foreground", "--workspace", u.workspace],
7378
+ "--server-config",
7379
+ u.serverConfig
7380
+ ].map(string),
7381
+ "\t</array>",
7382
+ ...u.workspace === null ? ["<!-- Cadence workbench gateway; no project workspace -->"] : [],
7383
+ "\t<key>WorkingDirectory</key>",
7384
+ ` <string>${xml(u.workspace ?? dirname11(dirname11(u.cli)))}</string>`,
7385
+ "\t<key>EnvironmentVariables</key>",
7386
+ "\t<dict>",
7387
+ ...u.environment.flatMap(([k, v]) => [
7388
+ ` <key>${xml(k)}</key>`,
7389
+ string(v)
7390
+ ]),
7391
+ "\t</dict>",
7392
+ "\t<key>RunAtLoad</key>",
7393
+ "\t<true/>",
7394
+ "\t<key>ProcessType</key>",
7395
+ "\t<string>Background</string>",
7396
+ "\t<key>StandardOutPath</key>",
7397
+ ` <string>${xml(u.log)}</string>`,
7398
+ "\t<key>StandardErrorPath</key>",
7399
+ ` <string>${xml(u.log)}</string>`,
7400
+ "</dict>",
7401
+ "</plist>",
7402
+ ""
7403
+ ].join(`
7404
+ `);
7405
+ }
7406
+ function plistWorkspace(text2) {
7407
+ if (text2?.includes("<!-- Cadence workbench gateway; no project workspace -->"))
7408
+ return null;
7409
+ const m = text2?.match(/^\t<key>WorkingDirectory<\/key>\n\t<string>([^<]*)<\/string>$/m);
7410
+ return m?.[1].replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
7411
+ }
7412
+ var domain = (s) => `gui/${s.uid}`;
7413
+ var target = (s) => `${domain(s)}/${LABEL}`;
7414
+ var loaded = (s) => s.launchctl(["print", target(s)]).status === 0;
7415
+ function disabled(s) {
7416
+ return new RegExp(`"${LABEL.replaceAll(".", "\\.")}" => (true|disabled)`).test(s.launchctl(["print-disabled", domain(s)]).stdout);
7417
+ }
7418
+ async function configureLaunchAgent(s, mode, requested) {
7419
+ if (s.uid === undefined)
7420
+ return { status: "unavailable" };
7421
+ const path2 = plistPath(s);
7422
+ const previous = readGenerated(path2, MARKER2);
7423
+ if (previous === null)
7424
+ return { status: "foreign", unit: path2 };
7425
+ if (mode === "restart" && previous === undefined)
7426
+ return { status: "absent" };
7427
+ const sdk = sdkPackages(s.root);
7428
+ const kept = plistWorkspace(previous);
7429
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
7430
+ if (workspace !== null && !sdk)
7431
+ return { status: "missing-pi-sdk" };
7432
+ const refused2 = await settle(s, kept);
7433
+ if (refused2)
7434
+ return { ...refused2, unit: path2 };
7435
+ if (loaded(s))
7436
+ s.launchctl(["bootout", target(s)]);
7437
+ write(path2, buildPlist({
7438
+ node: s.node,
7439
+ cli: join13(s.root, "src", "service-cli.mjs"),
7440
+ workspace,
7441
+ serverConfig: serverConfigPath(s.home),
7442
+ environment: serviceEnvironment(s),
7443
+ log: join13(s.home, "Library", "Logs", "cadence.log")
7444
+ }));
7445
+ const result = {
7446
+ unit: path2,
7447
+ workspace,
7448
+ ...redirectedFrom ? { redirectedFrom } : {}
7449
+ };
7450
+ if (mode === "install")
7451
+ s.launchctl(["enable", target(s)]);
7452
+ else if (disabled(s))
7453
+ return { status: "disabled", ...result };
7454
+ if (workspace !== null && !regularFile(join13(agentDirectory(s), "auth.json")))
7455
+ return { status: "waiting-for-pi-login", ...result };
7456
+ const started = s.launchctl(["bootstrap", domain(s), path2]).status === 0;
7457
+ return { status: started ? "started" : "failed", ...result };
7458
+ }
7459
+ async function stopLaunchAgent(s) {
7460
+ const path2 = plistPath(s);
7461
+ const previous = readGenerated(path2, MARKER2);
7462
+ if (previous === null)
7463
+ return { status: "foreign", unit: path2 };
7464
+ if (previous === undefined)
7465
+ return { status: "absent" };
7466
+ const workspace = plistWorkspace(previous);
7467
+ const refused2 = await settle(s, workspace);
7468
+ return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
7469
+ }
7470
+ async function removeLaunchAgent(s) {
7471
+ const path2 = plistPath(s);
7472
+ const previous = readGenerated(path2, MARKER2);
7473
+ if (previous === null)
7474
+ return { status: "foreign", unit: path2 };
7475
+ if (previous === undefined)
7476
+ return { status: "absent" };
7477
+ const refused2 = await settle(s, plistWorkspace(previous));
7478
+ if (refused2)
7479
+ return { ...refused2, unit: path2 };
7480
+ if (s.uid !== undefined && loaded(s))
7481
+ s.launchctl(["bootout", target(s)]);
7482
+ rmSync2(path2);
7483
+ return { status: "removed", unit: path2 };
7484
+ }
7485
+ function launchAgentStatus(s) {
7486
+ const path2 = plistPath(s);
7487
+ const previous = readGenerated(path2, MARKER2);
7488
+ if (previous === null)
7489
+ return { status: "foreign", unit: path2 };
7490
+ if (previous === undefined)
7491
+ return { status: "absent" };
7492
+ const printed = s.uid === undefined ? undefined : s.launchctl(["print", target(s)]);
7493
+ return {
7494
+ status: printed?.status !== 0 ? "unloaded" : /^\s*state = running$/m.test(printed.stdout) ? "active" : "inactive",
7495
+ enabled: s.uid !== undefined && disabled(s) ? "disabled" : "enabled",
7496
+ unit: path2,
7497
+ workspace: plistWorkspace(previous)
7498
+ };
7499
+ }
7500
+
7501
+ // src/service/login-run.ts
7502
+ import { rmSync as rmSync3 } from "node:fs";
7503
+ import { dirname as dirname12, isAbsolute as isAbsolute7, join as join14 } from "node:path";
7504
+ var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
7505
+ var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
7506
+ var RUN_VALUE = "Cadence";
7507
+ var MARKER3 = "' Generated by @abelxiaoxing/cadence";
7508
+ function launcherPath(s) {
7509
+ const local = s.env.LOCALAPPDATA;
7510
+ return join14(local && isAbsolute7(local) ? local : join14(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
7511
+ }
7512
+ var literal2 = (value) => `"${checked(value).replaceAll('"', '""')}"`;
7513
+ function argument2(value) {
7514
+ if (checked(value).includes('"'))
7515
+ throw new ContractError("invalid");
7516
+ return `"${value}"`;
7517
+ }
7518
+ function buildLauncher(u) {
7519
+ const command = `${argument2(u.node)} ${argument2(u.cli)} ${u.workspace === null ? "workbench start" : `start --workspace ${argument2(u.workspace)}`} --server-config ${argument2(u.serverConfig)}`;
7520
+ return [
7521
+ `${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
7522
+ `' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
7523
+ 'Set shell = CreateObject("WScript.Shell")',
7524
+ ...u.environment.length ? ['Set env = shell.Environment("Process")'] : [],
7525
+ ...u.environment.map(([k, v]) => `env(${literal2(k)}) = ${literal2(v)}`),
7526
+ ...u.workspace === null ? ["' Cadence workbench gateway; no project workspace"] : [],
7527
+ `shell.CurrentDirectory = ${literal2(u.workspace ?? dirname12(dirname12(u.cli)))}`,
7528
+ `shell.Run ${literal2(command)}, 0, False`,
7529
+ ""
7530
+ ].join(`\r
7249
7531
  `);
7250
- if (expected2.owner !== "absent" || expected2.previousResolver !== null && expected2.previousResolver !== "absent")
7251
- throw new ContractError("quarantine");
7252
- const assertFresh2 = await confirm(expected2);
7253
- return c.resolveRegistryRecovery(expected2, assertFresh2);
7532
+ }
7533
+ var utf16 = (text2) => Buffer.concat([Buffer.from([255, 254]), Buffer.from(text2, "utf16le")]);
7534
+ function launcherWorkspace(text2) {
7535
+ if (text2?.includes("' Cadence workbench gateway; no project workspace"))
7536
+ return null;
7537
+ return text2?.match(/^shell\.CurrentDirectory = "((?:[^"]|"")*)"\r?$/m)?.[1].replaceAll('""', '"');
7538
+ }
7539
+ var runEntry = (path2, s) => `"${wscript(s.env)}" "${path2}"`;
7540
+ var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
7541
+ function approvedOff(s) {
7542
+ const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
7543
+ const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
7544
+ return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
7545
+ }
7546
+ async function configureLoginRun(s, mode, requested) {
7547
+ const path2 = launcherPath(s);
7548
+ const previous = readGenerated(path2, MARKER3);
7549
+ if (previous === null)
7550
+ return { status: "foreign", unit: path2 };
7551
+ if (previous === undefined && present(s, RUN_KEY))
7552
+ return { status: "foreign", unit: `${RUN_KEY}\\${RUN_VALUE}` };
7553
+ if (mode === "restart" && previous === undefined)
7554
+ return { status: "absent" };
7555
+ const sdk = sdkPackages(s.root);
7556
+ const kept = launcherWorkspace(previous);
7557
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
7558
+ if (workspace !== null && !sdk)
7559
+ return { status: "missing-pi-sdk" };
7560
+ const refused2 = await settle(s, kept);
7561
+ if (refused2)
7562
+ return { ...refused2, unit: path2 };
7563
+ const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
7564
+ write(path2, utf16(buildLauncher({
7565
+ node: s.node,
7566
+ cli: join14(s.root, "src", "service-cli.mjs"),
7567
+ workspace,
7568
+ serverConfig: serverConfigPath(s.home),
7569
+ environment
7570
+ })));
7571
+ const result = {
7572
+ unit: path2,
7573
+ workspace,
7574
+ ...redirectedFrom ? { redirectedFrom } : {}
7575
+ };
7576
+ if (mode === "install") {
7577
+ const entry = ["/v", RUN_VALUE, "/t", "REG_SZ", "/d", runEntry(path2, s)];
7578
+ if (s.reg(["add", RUN_KEY, ...entry, "/f"]).status !== 0)
7579
+ return { status: "failed", ...result };
7580
+ s.reg(["delete", APPROVED_KEY, "/v", RUN_VALUE, "/f"]);
7254
7581
  }
7255
- const history = new HistoryRecovery(c);
7256
- const expected = await inspectRecovery(c, history);
7257
- const registration2 = c.recoverySnapshot().registration;
7258
- const view = {
7259
- ...expected,
7260
- process: {
7261
- pid: registration2.pid,
7262
- host: diagnosticText(registration2.host, 255)
7263
- },
7264
- notice: warning
7582
+ if (workspace !== null && !regularFile(join14(agentDirectory(s), "auth.json")))
7583
+ return { status: "waiting-for-pi-login", ...result };
7584
+ return { status: s.launch(path2) ? "started" : "failed", ...result };
7585
+ }
7586
+ async function stopLoginRun(s) {
7587
+ const path2 = launcherPath(s);
7588
+ const previous = readGenerated(path2, MARKER3);
7589
+ if (previous === null)
7590
+ return { status: "foreign", unit: path2 };
7591
+ if (previous === undefined)
7592
+ return { status: "absent" };
7593
+ const workspace = launcherWorkspace(previous);
7594
+ const refused2 = await settle(s, workspace);
7595
+ return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
7596
+ }
7597
+ async function removeLoginRun(s) {
7598
+ const path2 = launcherPath(s);
7599
+ const previous = readGenerated(path2, MARKER3);
7600
+ if (previous === null)
7601
+ return { status: "foreign", unit: path2 };
7602
+ if (previous === undefined)
7603
+ return { status: "absent" };
7604
+ const refused2 = await settle(s, launcherWorkspace(previous));
7605
+ if (refused2)
7606
+ return { ...refused2, unit: path2 };
7607
+ for (const key of [RUN_KEY, APPROVED_KEY])
7608
+ if (present(s, key))
7609
+ s.reg(["delete", key, "/v", RUN_VALUE, "/f"]);
7610
+ rmSync3(path2);
7611
+ return { status: "removed", unit: path2 };
7612
+ }
7613
+ async function loginRunStatus(s) {
7614
+ const path2 = launcherPath(s);
7615
+ const previous = readGenerated(path2, MARKER3);
7616
+ if (previous === null)
7617
+ return { status: "foreign", unit: path2 };
7618
+ if (previous === undefined)
7619
+ return { status: "absent" };
7620
+ const workspace = launcherWorkspace(previous);
7621
+ const owner = workspace !== undefined ? await s.ownerStatus(workspace, serverConfigPath(s.home)) : "unavailable";
7622
+ return {
7623
+ status: owner === "alive" ? "active" : "inactive",
7624
+ enabled: present(s, RUN_KEY) && !approvedOff(s) ? "enabled" : "disabled",
7625
+ unit: path2,
7626
+ workspace
7265
7627
  };
7266
- if (operation === "inspect")
7267
- return view;
7268
- process.stdout.write(`${JSON.stringify(view)}
7269
- `);
7270
- if (expected.owner !== "absent")
7271
- throw new ContractError("quarantine");
7272
- const assertFresh = await confirm(expected);
7273
- return resolveRecovery(c, {
7274
- expected,
7275
- acknowledgeEscapedProcesses: true,
7276
- processes: "externally-resolved",
7277
- credentials: "independently-resolved",
7278
- storage: "independently-resolved",
7279
- assertLocalAuthority: (choice) => {
7280
- assertFresh();
7281
- if (JSON.stringify(choice) !== JSON.stringify(expected))
7282
- throw new ContractError("stale");
7283
- }
7284
- }, history);
7285
7628
  }
7286
7629
 
7287
7630
  // src/service/system-service.ts
7288
- import { existsSync as existsSync2, realpathSync as realpathSync5, rmSync as rmSync4 } from "node:fs";
7289
- import { isAbsolute as isAbsolute8, join as join14 } from "node:path";
7631
+ import { existsSync as existsSync2, realpathSync as realpathSync4, rmSync as rmSync4 } from "node:fs";
7632
+ import { isAbsolute as isAbsolute8, join as join15 } from "node:path";
7290
7633
  var MARKER4 = "# Generated by @abelxiaoxing/cadence";
7291
7634
  function boundary(s) {
7292
7635
  if (s.platform !== "linux" || s.scope !== "system")
@@ -7328,8 +7671,8 @@ function sameStateEnvironment(s, previous) {
7328
7671
  return false;
7329
7672
  values.set(key, value.slice(at + 1).replaceAll("%%", "%"));
7330
7673
  }
7331
- const state = s.env.XDG_STATE_HOME;
7332
- return values.get("HOME") === s.home && values.get("XDG_STATE_HOME") === (state && isAbsolute8(state) ? state : undefined);
7674
+ const state2 = s.env.XDG_STATE_HOME;
7675
+ return values.get("HOME") === s.home && values.get("XDG_STATE_HOME") === (state2 && isAbsolute8(state2) ? state2 : undefined);
7333
7676
  } catch {
7334
7677
  return false;
7335
7678
  }
@@ -7367,12 +7710,12 @@ async function configureSystemService(s, mode, requested) {
7367
7710
  const selected2 = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : unitWorkspace(previous), false);
7368
7711
  if (selected2.workspace !== null && !sdk)
7369
7712
  return { status: "missing-pi-sdk", scope: "system" };
7370
- const selectedReal = selected2.workspace && existsSync2(selected2.workspace) ? realpathSync5(selected2.workspace) : undefined;
7713
+ const selectedReal = selected2.workspace && existsSync2(selected2.workspace) ? realpathSync4(selected2.workspace) : undefined;
7371
7714
  const agentDir = agentDirectory(s);
7372
7715
  const content = buildUnit({
7373
7716
  system: true,
7374
7717
  node: s.node,
7375
- cli: join14(s.root, "src", "service-cli.mjs"),
7718
+ cli: join15(s.root, "src", "service-cli.mjs"),
7376
7719
  workspace: selected2.workspace,
7377
7720
  serverConfig: serviceConfigPath(s),
7378
7721
  agentDir,
@@ -7404,7 +7747,7 @@ async function configureSystemService(s, mode, requested) {
7404
7747
  return { status: "foreign", ...result };
7405
7748
  if (selectedReal && selected2.workspace) {
7406
7749
  try {
7407
- if (realpathSync5(selected2.workspace) !== selectedReal)
7750
+ if (realpathSync4(selected2.workspace) !== selectedReal)
7408
7751
  return { status: "workspace-changed", ...result };
7409
7752
  } catch {
7410
7753
  return { status: "workspace-changed", ...result };
@@ -7421,7 +7764,7 @@ async function configureSystemService(s, mode, requested) {
7421
7764
  return { status: "failed", ...result, ...selected2 };
7422
7765
  if (mode === "install" && s.systemctl(["enable", s.unit]).status !== 0)
7423
7766
  return { status: "failed", ...result, ...selected2 };
7424
- if (selected2.workspace !== null && !regularFile(join14(agentDir, "auth.json")))
7767
+ if (selected2.workspace !== null && !regularFile(join15(agentDir, "auth.json")))
7425
7768
  return { status: "waiting-for-pi-login", ...result, ...selected2 };
7426
7769
  s.systemctl(["reset-failed", s.unit]);
7427
7770
  return {
@@ -7583,7 +7926,12 @@ async function stopService(c) {
7583
7926
  }
7584
7927
  const deadline = performance.now() + LIMITS.stopWaitMs;
7585
7928
  while (performance.now() < deadline) {
7586
- const observed2 = c.inspect();
7929
+ let observed2;
7930
+ try {
7931
+ observed2 = c.inspect();
7932
+ } catch {
7933
+ observed2 = { status: "unavailable" };
7934
+ }
7587
7935
  if ("epoch" in observed2 && observed2.epoch === epoch && observed2.status === "stopped")
7588
7936
  return { status: "stopped", epoch };
7589
7937
  if (observed2.status === "quarantined")
@@ -7597,6 +7945,25 @@ async function stopService(c) {
7597
7945
  function failedStatus(result) {
7598
7946
  return !!(result && typeof result === "object" && ("status" in result) && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)));
7599
7947
  }
7948
+ function managementExitCode(result, foreground = false) {
7949
+ if (foreground) {
7950
+ try {
7951
+ decodeStartupReport(result);
7952
+ return 78;
7953
+ } catch {}
7954
+ }
7955
+ return 1;
7956
+ }
7957
+ function observedServerResult(result, service) {
7958
+ return {
7959
+ ...result,
7960
+ service,
7961
+ ...result.status === "started" && service !== "alive" ? {
7962
+ managerStatus: result.status,
7963
+ status: service === "quarantined" ? "quarantined" : "unconfirmed"
7964
+ } : {}
7965
+ };
7966
+ }
7600
7967
  function serverConfigHint(error) {
7601
7968
  return `Cadence changed nothing: ${error.path} ${error.reason}.
7602
7969
  Fix it and run the command again; the file holds the WebUI port and token as ${PORT_KEY}=<1-65535> and ${TOKEN_KEY}=<at least 32 characters>, one line each.
@@ -7626,10 +7993,10 @@ function startupGuidance(result, input) {
7626
7993
  return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
7627
7994
  `;
7628
7995
  case "pi-auth":
7629
- return `${join15(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
7996
+ return `${join16(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
7630
7997
  `;
7631
7998
  case "pi-models":
7632
- return `${join15(agent, "models.json")} must be your own single-link regular file using only fields Cadence's pinned Pi SDK supports; a field only newer Pi understands is refused.
7999
+ return `${join16(agent, "models.json")} must be your own single-link regular file using only fields Cadence's pinned Pi SDK supports; a field only newer Pi understands is refused.
7633
8000
  `;
7634
8001
  case "pi-runtime":
7635
8002
  return `Loading Pi's SDK model runtime failed after the owner registered (optional SDK packages missing, or sources the SDK refuses); it stays quarantined.
@@ -7759,6 +8126,8 @@ async function launchHost(input, c, foreground, hostUrl, cwd, env, network) {
7759
8126
  process.stdout.write(`${JSON.stringify(result)}
7760
8127
  `);
7761
8128
  await closure;
8129
+ if (failedStatus(result))
8130
+ process.exitCode = managementExitCode(result, true);
7762
8131
  return null;
7763
8132
  }
7764
8133
  return result;
@@ -7845,7 +8214,7 @@ async function serverAction(s, action, requested, linger) {
7845
8214
  }
7846
8215
  }
7847
8216
  function serverGuidance(s, action, result, link) {
7848
- const cli = piUserInstall(s) ? `node ${JSON.stringify(join15(s.root, "src", "service-cli.mjs"))}` : "cadence";
8217
+ const cli = piUserInstall(s) ? `node ${JSON.stringify(join16(s.root, "src", "service-cli.mjs"))}` : "cadence";
7849
8218
  const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
7850
8219
  ` : "";
7851
8220
  return note + serverStatusGuidance(s, action, result, cli, link);
@@ -7877,6 +8246,11 @@ function managerDetail(s) {
7877
8246
  }
7878
8247
  function serverStatusGuidance(s, action, result, cli, link) {
7879
8248
  const workspace = JSON.stringify(result.workspace ?? "");
8249
+ if (result.workspace === null && ["quarantined", "unconfirmed", "unavailable"].includes(result.status))
8250
+ return `Cadence workbench is not ready (${result.status}); no successful startup or Stop is inferred.
8251
+ Inspect locally: ${cli} workbench recover inspect --server-config ${JSON.stringify(serviceConfigPath(s))}
8252
+ Use the definition's same account and state home; eligible recovery requires recover resolve in a local terminal. Preserve locks and history.
8253
+ `;
7880
8254
  if (result.workspace === null && result.status === "started")
7881
8255
  return `Cadence unified workbench: ${link ?? result.endpoint ?? result.service ?? "unconfirmed"}
7882
8256
  No default workspace was created. Projects are opened explicitly.
@@ -7907,7 +8281,7 @@ ${manage}
7907
8281
  ${linger}`;
7908
8282
  }
7909
8283
  case "waiting-for-pi-login":
7910
- return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join15(agentDirectory(s), "auth.json")}.
8284
+ return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join16(agentDirectory(s), "auth.json")}.
7911
8285
  Log in to Pi, then: ${cli} server restart${flags}
7912
8286
  ${config}
7913
8287
  ${lingerGuidance(result.linger)}`;
@@ -7953,7 +8327,10 @@ ${lingerGuidance(result.linger)}`;
7953
8327
  `;
7954
8328
  case "unconfirmed":
7955
8329
  case "quarantined":
7956
- return `Cadence changed nothing: its owner's orderly Stop was not confirmed (${result.status}). Inspect: ${cli} diagnose --workspace ${workspace}
8330
+ return `Cadence owner reports ${result.status}; successful startup or orderly Stop is not confirmed.
8331
+ Inspect: ${cli} diagnose --workspace ${workspace}
8332
+ Local recovery: ${cli} recover inspect --workspace ${workspace}
8333
+ Preserve uncertain state; recovery requires separate fresh terminal confirmation.
7957
8334
  `;
7958
8335
  case "failed":
7959
8336
  return `Could not register or start Cadence's service; ${managerDetail(s)}.
@@ -8057,9 +8434,26 @@ async function serverMain(argv, hostUrl) {
8057
8434
  }
8058
8435
  let link;
8059
8436
  if (result.status === "started" && result.workspace !== undefined) {
8060
- Object.assign(result, await observeStarted(result.workspace, serviceConfigPath(s)));
8437
+ const workspace = result.workspace;
8438
+ const observation = await observeStarted(workspace, serviceConfigPath(s));
8439
+ result = {
8440
+ ...observedServerResult(result, observation.service),
8441
+ ...observation
8442
+ };
8061
8443
  if (result.service === "alive")
8062
- link = await privateLink(result.workspace, undefined, serviceConfigPath(s));
8444
+ link = await privateLink(workspace, undefined, serviceConfigPath(s));
8445
+ }
8446
+ if (action === "status" && result.workspace !== undefined && ["active", "activating", "failed"].includes(result.status)) {
8447
+ const target2 = result.workspace === null ? new GatewayStore(serviceConfigPath(s)) : new ServiceCoordination(result.workspace);
8448
+ const observed2 = await serviceStatus(target2);
8449
+ if (observed2 && typeof observed2 === "object" && "status" in observed2) {
8450
+ const service = String(observed2.status);
8451
+ result = {
8452
+ ...result,
8453
+ service,
8454
+ ...service === "quarantined" ? { managerStatus: result.status, status: service } : {}
8455
+ };
8456
+ }
8063
8457
  }
8064
8458
  process.stderr.write(serverGuidance(s, action, result, link));
8065
8459
  process.stdout.write(`${JSON.stringify(result)}
@@ -8132,6 +8526,8 @@ async function projectMain(argv, hostUrl) {
8132
8526
  process.exitCode = 1;
8133
8527
  }
8134
8528
  async function managementMain(argv, hostUrl) {
8529
+ const foregroundStart = argv.includes("--foreground") && (argv[0] === "start" || argv[0] === "workbench" && argv[1] === "start");
8530
+ const recoveryCommand = argv[0] === "recover" || argv[0] === "workbench" && argv[1] === "recover";
8135
8531
  try {
8136
8532
  if (!process.versions.node || process.versions.bun)
8137
8533
  throw new ContractError("unsupported-context");
@@ -8146,15 +8542,16 @@ async function managementMain(argv, hostUrl) {
8146
8542
  "diagnose",
8147
8543
  "stop",
8148
8544
  "token-link",
8149
- "token-rotate"
8545
+ "token-rotate",
8546
+ "recover"
8150
8547
  ].includes(command ?? "") && !argv.includes("--workspace")) {
8151
8548
  await gatewayMain([command, ...argv], hostUrl);
8152
8549
  return;
8153
8550
  }
8154
8551
  if (command === "--help" && !argv.length) {
8155
8552
  process.stdout.write(`cadence start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
8156
- Without --workspace these commands address the unified workbench (except project recovery); no default project directory is created.
8157
- cadence workbench start|status|diagnose|stop|token-link --reveal|token-rotate [--server-config PATH] [--state-home PATH]
8553
+ Without --workspace these commands (including recover) address the unified workbench, never cwd; no default project directory is created. Project recovery requires explicit --workspace PATH. Recovery checks the account-wide registry lock first.
8554
+ cadence workbench start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--server-config PATH] [--state-home PATH]
8158
8555
  cadence server install [--workspace PATH] [--no-linger]|status|restart|stop|uninstall
8159
8556
  Linux server options: --system (root, no linger) or --user; --name NAME selects an independent service; install also accepts --port PORT (required for a new named service). Root defaults to --system. No automatic sudo or profile migration.
8160
8557
  cadence project open [PATH] [--create] [--agent-dir PATH] [--state-home PATH]
@@ -8258,11 +8655,18 @@ Recovery resolve requires this local native terminal, exact unchanged dead-owner
8258
8655
  process.stdout.write(`${JSON.stringify(result)}
8259
8656
  `);
8260
8657
  if (failedStatus(result))
8261
- process.exitCode = 1;
8262
- } catch {
8263
- process.stderr.write(`{"error":"service-refused"}
8658
+ process.exitCode = managementExitCode(result, foreground);
8659
+ } catch (error) {
8660
+ process.stderr.write(`${JSON.stringify({
8661
+ error: "service-refused",
8662
+ ...recoveryCommand ? {
8663
+ code: error instanceof ContractError ? error.code : "unavailable",
8664
+ stage: "recovery",
8665
+ notice: "Use workbench recover for account/workbench state or explicit --workspace for an existing project; preserve state on refusal."
8666
+ } : {}
8667
+ })}
8264
8668
  `);
8265
- process.exitCode = 1;
8669
+ process.exitCode = foregroundStart && (error instanceof ContractError || error instanceof ServerConfigRefusal) ? 78 : 1;
8266
8670
  }
8267
8671
  }
8268
8672