@ai-development-environment/control-agent 0.0.96 → 0.0.97

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.
@@ -8576,6 +8576,215 @@ function parseCliHealthJobPayload(value) {
8576
8576
  return { checks };
8577
8577
  }
8578
8578
 
8579
+ // ../agent-contract/src/tailscale.ts
8580
+ var TAILSCALE_SERVE_INSPECT_JOB_KIND = "tailscale.serve.inspect.v1";
8581
+ var TAILSCALE_SERVE_UPSERT_JOB_KIND = "tailscale.serve.upsert.v1";
8582
+ var TAILSCALE_SERVE_REMOVE_JOB_KIND = "tailscale.serve.remove.v1";
8583
+ var TAILSCALE_SERVE_JOB_KINDS = [
8584
+ TAILSCALE_SERVE_INSPECT_JOB_KIND,
8585
+ TAILSCALE_SERVE_UPSERT_JOB_KIND,
8586
+ TAILSCALE_SERVE_REMOVE_JOB_KIND
8587
+ ];
8588
+ var TAILSCALE_SERVE_PROTOCOLS = [
8589
+ "HTTP",
8590
+ "HTTPS",
8591
+ "TCP",
8592
+ "TLS_TERMINATED_TCP"
8593
+ ];
8594
+ var TAILSCALE_DESTINATION_PROTOCOLS = [
8595
+ "HTTP",
8596
+ "HTTPS",
8597
+ "HTTPS_INSECURE",
8598
+ "TCP"
8599
+ ];
8600
+ var TAILSCALE_PROXY_PROTOCOLS = ["NONE", "V1", "V2"];
8601
+ function objectValue13(value, name) {
8602
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
8603
+ throw new Error(`${name} must be an object`);
8604
+ }
8605
+ return value;
8606
+ }
8607
+ function exactKeys3(value, keys, name) {
8608
+ const unexpected = Object.keys(value).find((key) => !keys.includes(key));
8609
+ if (unexpected) throw new Error(`Unexpected ${name} field: ${unexpected}`);
8610
+ }
8611
+ function requiredString(value, name) {
8612
+ if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
8613
+ throw new Error(`${name} must be a non-empty string`);
8614
+ }
8615
+ return value.trim();
8616
+ }
8617
+ function integerPort(value, name) {
8618
+ if (!Number.isInteger(value) || value < 1 || value > 65535) {
8619
+ throw new Error(`${name} must be an integer from 1 through 65535`);
8620
+ }
8621
+ return value;
8622
+ }
8623
+ function enumValue4(value, values, name) {
8624
+ if (typeof value !== "string" || !values.includes(value)) {
8625
+ throw new Error(`${name} is invalid`);
8626
+ }
8627
+ return value;
8628
+ }
8629
+ function normalizeTailscaleMountPath(value) {
8630
+ if (typeof value !== "string" || value.includes("\0") || value.includes("?")) {
8631
+ throw new Error("Tailscale mount path is invalid");
8632
+ }
8633
+ const path = value.trim() || "/";
8634
+ const prefixed = path.startsWith("/") ? path : `/${path}`;
8635
+ return prefixed === "/" ? prefixed : prefixed.replace(/\/+$/, "");
8636
+ }
8637
+ function normalizeDestinationPath(value) {
8638
+ if (typeof value !== "string" || value.includes("\0")) {
8639
+ throw new Error("Tailscale destination path is invalid");
8640
+ }
8641
+ const path = value.trim();
8642
+ if (!path) return "";
8643
+ return path.startsWith("/") ? path : `/${path}`;
8644
+ }
8645
+ function appCapabilities(value) {
8646
+ if (!Array.isArray(value) || value.length > 100) {
8647
+ throw new Error("Tailscale app capabilities must be an array");
8648
+ }
8649
+ const pattern = /^(?:[\p{L}\p{N}-]+\.)+[\p{L}\p{N}-]+\/[\p{L}\p{N}-]+(?:\/[\p{L}\p{N}-]+)*$/u;
8650
+ return [
8651
+ ...new Set(
8652
+ value.map((item, index) => {
8653
+ const capability = requiredString(
8654
+ item,
8655
+ `Tailscale app capabilities[${index}]`
8656
+ );
8657
+ if (!pattern.test(capability)) {
8658
+ throw new Error(
8659
+ `Tailscale app capabilities[${index}] must use domain/name format`
8660
+ );
8661
+ }
8662
+ return capability;
8663
+ })
8664
+ )
8665
+ ].sort();
8666
+ }
8667
+ function parseTailscaleServeRoute(value) {
8668
+ const route = objectValue13(value, "Tailscale Serve route");
8669
+ exactKeys3(
8670
+ route,
8671
+ [
8672
+ "protocol",
8673
+ "listenPort",
8674
+ "mountPath",
8675
+ "destination",
8676
+ "funnel",
8677
+ "appCapabilities",
8678
+ "proxyProtocol"
8679
+ ],
8680
+ "Tailscale Serve route"
8681
+ );
8682
+ const protocol = enumValue4(
8683
+ route.protocol,
8684
+ TAILSCALE_SERVE_PROTOCOLS,
8685
+ "Tailscale Serve protocol"
8686
+ );
8687
+ const listenPort = integerPort(route.listenPort, "Tailscale listen port");
8688
+ const destination = objectValue13(
8689
+ route.destination,
8690
+ "Tailscale Serve destination"
8691
+ );
8692
+ exactKeys3(
8693
+ destination,
8694
+ ["protocol", "port", "path"],
8695
+ "Tailscale Serve destination"
8696
+ );
8697
+ const destinationProtocol = enumValue4(
8698
+ destination.protocol,
8699
+ TAILSCALE_DESTINATION_PROTOCOLS,
8700
+ "Tailscale destination protocol"
8701
+ );
8702
+ const funnel = route.funnel;
8703
+ if (typeof funnel !== "boolean")
8704
+ throw new Error("Tailscale Funnel must be a boolean");
8705
+ const proxyProtocol = enumValue4(
8706
+ route.proxyProtocol,
8707
+ TAILSCALE_PROXY_PROTOCOLS,
8708
+ "Tailscale PROXY protocol"
8709
+ );
8710
+ const capabilities = appCapabilities(route.appCapabilities);
8711
+ const isWeb = protocol === "HTTP" || protocol === "HTTPS";
8712
+ if (isWeb && destinationProtocol === "TCP") {
8713
+ throw new Error("Web listeners require an HTTP(S) destination");
8714
+ }
8715
+ if (!isWeb && destinationProtocol !== "TCP") {
8716
+ throw new Error("TCP listeners require a TCP destination");
8717
+ }
8718
+ if (!isWeb && normalizeTailscaleMountPath(route.mountPath) !== "/") {
8719
+ throw new Error("TCP listeners do not support mount paths");
8720
+ }
8721
+ if (funnel && (protocol === "HTTP" || ![443, 8443, 1e4].includes(listenPort))) {
8722
+ throw new Error(
8723
+ "Funnel requires HTTPS or TLS TCP on port 443, 8443, or 10000"
8724
+ );
8725
+ }
8726
+ if (capabilities.length && (!isWeb || funnel)) {
8727
+ throw new Error(
8728
+ "App capabilities are supported only by private HTTP(S) listeners"
8729
+ );
8730
+ }
8731
+ if (proxyProtocol !== "NONE" && protocol !== "TCP") {
8732
+ throw new Error("PROXY protocol is supported only by TCP listeners");
8733
+ }
8734
+ return {
8735
+ protocol,
8736
+ listenPort,
8737
+ mountPath: isWeb ? normalizeTailscaleMountPath(route.mountPath) : "/",
8738
+ destination: {
8739
+ protocol: destinationProtocol,
8740
+ port: integerPort(destination.port, "Tailscale destination port"),
8741
+ path: isWeb ? normalizeDestinationPath(destination.path) : ""
8742
+ },
8743
+ funnel,
8744
+ appCapabilities: capabilities,
8745
+ proxyProtocol
8746
+ };
8747
+ }
8748
+ function tailscaleServeInspectPayload(value) {
8749
+ const payload = objectValue13(value, "Tailscale Serve inspect payload");
8750
+ exactKeys3(payload, ["operationId"], "Tailscale Serve inspect payload");
8751
+ return { operationId: requiredString(payload.operationId, "operationId") };
8752
+ }
8753
+ function mutationPayload(value, allowPrevious) {
8754
+ const payload = objectValue13(value, "Tailscale Serve mutation payload");
8755
+ exactKeys3(
8756
+ payload,
8757
+ [
8758
+ "operationId",
8759
+ "templateId",
8760
+ "revision",
8761
+ "route",
8762
+ ...allowPrevious ? ["previousRoute"] : []
8763
+ ],
8764
+ "Tailscale Serve mutation payload"
8765
+ );
8766
+ if (!Number.isInteger(payload.revision) || payload.revision < 1) {
8767
+ throw new Error("Tailscale template revision must be a positive integer");
8768
+ }
8769
+ return {
8770
+ operationId: requiredString(payload.operationId, "operationId"),
8771
+ templateId: requiredString(payload.templateId, "templateId"),
8772
+ revision: payload.revision,
8773
+ route: parseTailscaleServeRoute(payload.route)
8774
+ };
8775
+ }
8776
+ function tailscaleServeUpsertPayload(value) {
8777
+ const base = mutationPayload(value, true);
8778
+ const payload = value;
8779
+ return {
8780
+ ...base,
8781
+ previousRoute: payload.previousRoute === null ? null : parseTailscaleServeRoute(payload.previousRoute)
8782
+ };
8783
+ }
8784
+ function tailscaleServeRemovePayload(value) {
8785
+ return mutationPayload(value, false);
8786
+ }
8787
+
8579
8788
  // src/inventory.ts
8580
8789
  var AGENT_VERSION = process.env.CONTROL_AGENT_VERSION ?? "0.1.0";
8581
8790
  var PORTABLE_AGENT_CAPABILITIES = [
@@ -8599,9 +8808,11 @@ var MACOS_AGENT_CAPABILITIES = [
8599
8808
  ...IOS_BUILD_JOB_KINDS,
8600
8809
  ...SIGNING_ASSET_JOB_KINDS
8601
8810
  ];
8811
+ var UNIX_AGENT_CAPABILITIES = [...TAILSCALE_SERVE_JOB_KINDS];
8602
8812
  function capabilitiesForPlatform(targetPlatform) {
8603
8813
  return [
8604
8814
  ...PORTABLE_AGENT_CAPABILITIES,
8815
+ ...targetPlatform === "darwin" || targetPlatform === "linux" ? UNIX_AGENT_CAPABILITIES : [],
8605
8816
  ...targetPlatform === "darwin" ? MACOS_AGENT_CAPABILITIES : []
8606
8817
  ];
8607
8818
  }
@@ -10897,8 +11108,8 @@ async function writeJsonReport(input, kind, timeoutMs, signal) {
10897
11108
  ),
10898
11109
  `Could not generate ${filename}`
10899
11110
  );
10900
- const serialized = await readFile2(temporary, "utf8");
10901
- const parsed = JSON.parse(serialized);
11111
+ const serialized2 = await readFile2(temporary, "utf8");
11112
+ const parsed = JSON.parse(serialized2);
10902
11113
  const normalized = kind === "TEST_RESULTS" ? normalizeTestResults(parsed) : normalizeCoverage(parsed);
10903
11114
  await rename2(temporary, destination);
10904
11115
  return {
@@ -18385,6 +18596,280 @@ var autoSyncWorktree = async (payload, timeoutMs, signal) => {
18385
18596
  };
18386
18597
  };
18387
18598
 
18599
+ // src/handlers/tailscale.ts
18600
+ import { accessSync as accessSync2, constants as constants2 } from "node:fs";
18601
+ var MAX_OUTPUT_BYTES2 = 2 * 1024 * 1024;
18602
+ var mutationQueues = /* @__PURE__ */ new Map();
18603
+ function objectValue14(value) {
18604
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
18605
+ }
18606
+ function resolveTailscaleExecutable(platform2 = process.platform, resolve12 = findExecutable, isExecutable2 = (path) => {
18607
+ accessSync2(path, constants2.X_OK);
18608
+ return true;
18609
+ }) {
18610
+ const resolved = resolve12("tailscale", {
18611
+ overrideVariable: "CONTROL_AGENT_TAILSCALE_EXECUTABLE"
18612
+ });
18613
+ if (resolved) return resolved;
18614
+ if (platform2 === "darwin") {
18615
+ for (const path of [
18616
+ "/Applications/Tailscale.app/Contents/MacOS/Tailscale",
18617
+ "/Applications/Tailscale.app/Contents/MacOS/tailscale"
18618
+ ]) {
18619
+ try {
18620
+ if (isExecutable2(path)) return path;
18621
+ } catch {
18622
+ }
18623
+ }
18624
+ }
18625
+ throw new Error(
18626
+ "Tailscale CLI was not found. Install Tailscale or set CONTROL_AGENT_TAILSCALE_EXECUTABLE."
18627
+ );
18628
+ }
18629
+ function parseTailscaleJson(value, label) {
18630
+ try {
18631
+ const parsed = JSON.parse(value);
18632
+ const object = objectValue14(parsed);
18633
+ if (!object) throw new Error("expected an object");
18634
+ return object;
18635
+ } catch (error) {
18636
+ throw new Error(
18637
+ `${label} returned malformed JSON: ${error instanceof Error ? error.message : String(error)}`
18638
+ );
18639
+ }
18640
+ }
18641
+ function tailscaleCommandFailure(command3, result) {
18642
+ const reason = result.timedOut ? "timed out" : result.cancelled ? "was cancelled" : result.stderr.trim() || `exited with status ${result.exitCode}`;
18643
+ return new Error(`${command3} failed: ${reason}`);
18644
+ }
18645
+ async function captureTailscale(executable, args, timeoutMs, signal) {
18646
+ const result = await captureCommand({
18647
+ command: executable,
18648
+ args,
18649
+ timeoutMs,
18650
+ signal,
18651
+ maxOutputBytes: MAX_OUTPUT_BYTES2
18652
+ });
18653
+ if (result.outputTruncated) {
18654
+ throw new Error(`tailscale ${args.join(" ")} exceeded the output limit`);
18655
+ }
18656
+ if (result.exitCode !== 0) {
18657
+ throw tailscaleCommandFailure(`tailscale ${args.join(" ")}`, result);
18658
+ }
18659
+ return result;
18660
+ }
18661
+ function backendIdentity(status2) {
18662
+ const self2 = objectValue14(status2.Self);
18663
+ const rawAddresses = Array.isArray(self2?.TailscaleIPs) ? self2.TailscaleIPs : status2.TailscaleIPs;
18664
+ const addresses = Array.isArray(rawAddresses) ? rawAddresses.filter((value) => typeof value === "string") : [];
18665
+ const dnsName = typeof self2?.DNSName === "string" ? self2.DNSName : null;
18666
+ return {
18667
+ dnsHostname: dnsName?.replace(/\.$/, "") ?? null,
18668
+ ipv4: addresses.filter((value) => value.includes(".")),
18669
+ ipv6: addresses.filter((value) => value.includes(":")),
18670
+ backendState: typeof status2.BackendState === "string" ? status2.BackendState : "Unknown"
18671
+ };
18672
+ }
18673
+ function destinationFromProxy(proxy) {
18674
+ const parsed = new URL(proxy);
18675
+ if (parsed.hostname !== "127.0.0.1") {
18676
+ throw new Error("Only 127.0.0.1 Tailscale Serve targets are managed");
18677
+ }
18678
+ const rawProtocol = parsed.protocol.replace(/:$/, "");
18679
+ const protocol = rawProtocol === "https+insecure" ? "HTTPS_INSECURE" : rawProtocol === "https" ? "HTTPS" : rawProtocol === "tcp" ? "TCP" : "HTTP";
18680
+ return {
18681
+ protocol,
18682
+ port: Number(parsed.port),
18683
+ path: protocol === "TCP" ? "" : parsed.pathname === "/" ? "" : parsed.pathname
18684
+ };
18685
+ }
18686
+ function portFromListener(value) {
18687
+ const match = value.match(/:(\d+)$/) ?? value.match(/^(\d+)$/);
18688
+ return match ? Number(match[1]) : null;
18689
+ }
18690
+ function normalizeTailscaleServeSnapshot(status2, serve, inspectedAt = (/* @__PURE__ */ new Date()).toISOString()) {
18691
+ const routes = [];
18692
+ const tcp = objectValue14(serve.TCP) ?? {};
18693
+ const web = objectValue14(serve.Web) ?? {};
18694
+ const funnel = objectValue14(serve.AllowFunnel) ?? {};
18695
+ for (const [listener, rawWeb] of Object.entries(web)) {
18696
+ const listenPort = portFromListener(listener);
18697
+ const webConfig = objectValue14(rawWeb);
18698
+ const handlers2 = objectValue14(webConfig?.Handlers);
18699
+ if (!listenPort || !handlers2) continue;
18700
+ const tcpConfig = objectValue14(tcp[String(listenPort)]);
18701
+ const protocol = tcpConfig?.HTTPS === true ? "HTTPS" : "HTTP";
18702
+ const isFunnel = Object.entries(funnel).some(
18703
+ ([key, enabled]) => enabled === true && portFromListener(key) === listenPort
18704
+ );
18705
+ for (const [mountPath, rawHandler] of Object.entries(handlers2)) {
18706
+ const handler = objectValue14(rawHandler);
18707
+ if (!handler || typeof handler.Proxy !== "string") continue;
18708
+ const capabilities = Array.isArray(handler.AcceptAppCaps) ? handler.AcceptAppCaps.filter(
18709
+ (value) => typeof value === "string"
18710
+ ) : [];
18711
+ try {
18712
+ routes.push(
18713
+ parseTailscaleServeRoute({
18714
+ protocol,
18715
+ listenPort,
18716
+ mountPath,
18717
+ destination: destinationFromProxy(handler.Proxy),
18718
+ funnel: isFunnel,
18719
+ appCapabilities: capabilities,
18720
+ proxyProtocol: "NONE"
18721
+ })
18722
+ );
18723
+ } catch {
18724
+ }
18725
+ }
18726
+ }
18727
+ for (const [listener, rawTcp] of Object.entries(tcp)) {
18728
+ const listenPort = portFromListener(listener);
18729
+ const config = objectValue14(rawTcp);
18730
+ if (!listenPort || !config || typeof config.TCPForward !== "string")
18731
+ continue;
18732
+ const destination = config.TCPForward.startsWith("tcp://") ? config.TCPForward : `tcp://${config.TCPForward}`;
18733
+ const proxy = config.ProxyProtocol;
18734
+ try {
18735
+ routes.push(
18736
+ parseTailscaleServeRoute({
18737
+ protocol: config.TerminateTLS ? "TLS_TERMINATED_TCP" : "TCP",
18738
+ listenPort,
18739
+ mountPath: "/",
18740
+ destination: destinationFromProxy(destination),
18741
+ funnel: Object.entries(funnel).some(
18742
+ ([key, enabled]) => enabled === true && portFromListener(key) === listenPort
18743
+ ),
18744
+ appCapabilities: [],
18745
+ proxyProtocol: proxy === 1 || proxy === "1" || proxy === "V1" ? "V1" : proxy === 2 || proxy === "2" || proxy === "V2" ? "V2" : "NONE"
18746
+ })
18747
+ );
18748
+ } catch {
18749
+ }
18750
+ }
18751
+ return {
18752
+ identity: backendIdentity(status2),
18753
+ routes: routes.sort(
18754
+ (first, second) => `${first.listenPort}:${first.mountPath}`.localeCompare(
18755
+ `${second.listenPort}:${second.mountPath}`
18756
+ )
18757
+ ),
18758
+ inspectedAt
18759
+ };
18760
+ }
18761
+ async function inspect(executable, timeoutMs, signal) {
18762
+ const status2 = await captureTailscale(
18763
+ executable,
18764
+ ["status", "--json"],
18765
+ timeoutMs,
18766
+ signal
18767
+ );
18768
+ const serve = await captureTailscale(
18769
+ executable,
18770
+ ["serve", "status", "--json"],
18771
+ timeoutMs,
18772
+ signal
18773
+ );
18774
+ return normalizeTailscaleServeSnapshot(
18775
+ parseTailscaleJson(status2.stdout, "tailscale status --json"),
18776
+ parseTailscaleJson(serve.stdout, "tailscale serve status --json")
18777
+ );
18778
+ }
18779
+ function listenerArguments(route) {
18780
+ const flag = route.protocol === "TLS_TERMINATED_TCP" ? "tls-terminated-tcp" : route.protocol.toLowerCase();
18781
+ return [
18782
+ `--${flag}=${route.listenPort}`,
18783
+ ...route.protocol === "HTTP" || route.protocol === "HTTPS" ? [`--set-path=${route.mountPath}`] : []
18784
+ ];
18785
+ }
18786
+ function routeTarget(route) {
18787
+ const scheme = route.destination.protocol === "HTTPS_INSECURE" ? "https+insecure" : route.destination.protocol.toLowerCase();
18788
+ return `${scheme}://127.0.0.1:${route.destination.port}${route.destination.path}`;
18789
+ }
18790
+ function tailscaleUpsertArguments(route) {
18791
+ const normalized = parseTailscaleServeRoute(route);
18792
+ return [
18793
+ normalized.funnel ? "funnel" : "serve",
18794
+ "--bg",
18795
+ "--yes",
18796
+ ...listenerArguments(normalized),
18797
+ ...normalized.appCapabilities.length ? [`--accept-app-caps=${normalized.appCapabilities.join(",")}`] : [],
18798
+ ...normalized.proxyProtocol === "NONE" ? [] : [`--proxy-protocol=${normalized.proxyProtocol === "V1" ? "1" : "2"}`],
18799
+ routeTarget(normalized)
18800
+ ];
18801
+ }
18802
+ function tailscaleRemoveArguments(route) {
18803
+ const normalized = parseTailscaleServeRoute(route);
18804
+ return [
18805
+ normalized.funnel ? "funnel" : "serve",
18806
+ ...listenerArguments(normalized),
18807
+ "off"
18808
+ ];
18809
+ }
18810
+ function tailscaleUpsertCommandSequence(route, previousRoute) {
18811
+ return [
18812
+ ...previousRoute && JSON.stringify(previousRoute) !== JSON.stringify(route) ? [tailscaleRemoveArguments(previousRoute)] : [],
18813
+ tailscaleUpsertArguments(route)
18814
+ ];
18815
+ }
18816
+ async function serialized(agentId, work) {
18817
+ const previous = mutationQueues.get(agentId) ?? Promise.resolve();
18818
+ let release2;
18819
+ const current = new Promise((resolve12) => {
18820
+ release2 = resolve12;
18821
+ });
18822
+ const tail = previous.then(() => current);
18823
+ mutationQueues.set(agentId, tail);
18824
+ await previous;
18825
+ try {
18826
+ return await work();
18827
+ } finally {
18828
+ release2();
18829
+ if (mutationQueues.get(agentId) === tail) mutationQueues.delete(agentId);
18830
+ }
18831
+ }
18832
+ function success2(snapshot) {
18833
+ return {
18834
+ exitCode: 0,
18835
+ signal: null,
18836
+ timedOut: false,
18837
+ cancelled: false,
18838
+ snapshot
18839
+ };
18840
+ }
18841
+ var inspectTailscaleServe = async (rawPayload, timeoutMs, signal) => {
18842
+ tailscaleServeInspectPayload(rawPayload);
18843
+ const executable = resolveTailscaleExecutable();
18844
+ return success2(await inspect(executable, timeoutMs, signal));
18845
+ };
18846
+ var upsertTailscaleServe = async (rawPayload, timeoutMs, signal, _onLog, context) => {
18847
+ const payload = tailscaleServeUpsertPayload(rawPayload);
18848
+ const executable = resolveTailscaleExecutable();
18849
+ return serialized(context?.agentId ?? "default", async () => {
18850
+ for (const args of tailscaleUpsertCommandSequence(
18851
+ payload.route,
18852
+ payload.previousRoute
18853
+ )) {
18854
+ await captureTailscale(executable, args, timeoutMs, signal);
18855
+ }
18856
+ return success2(await inspect(executable, timeoutMs, signal));
18857
+ });
18858
+ };
18859
+ var removeTailscaleServe = async (rawPayload, timeoutMs, signal, _onLog, context) => {
18860
+ const payload = tailscaleServeRemovePayload(rawPayload);
18861
+ const executable = resolveTailscaleExecutable();
18862
+ return serialized(context?.agentId ?? "default", async () => {
18863
+ await captureTailscale(
18864
+ executable,
18865
+ tailscaleRemoveArguments(payload.route),
18866
+ timeoutMs,
18867
+ signal
18868
+ );
18869
+ return success2(await inspect(executable, timeoutMs, signal));
18870
+ });
18871
+ };
18872
+
18388
18873
  // src/handlers/index.ts
18389
18874
  var handlers = {
18390
18875
  [CLI_HEALTH_JOB_KIND]: runCliHealth,
@@ -18437,7 +18922,10 @@ var handlers = {
18437
18922
  [SIGNING_PROFILE_INSTALL_JOB_KIND]: installSigningProfile,
18438
18923
  [SIGNING_PROFILE_DELETE_JOB_KIND]: deleteSigningProfile,
18439
18924
  [SIGNING_IDENTITY_IMPORT_JOB_KIND]: importSigningIdentity,
18440
- [SIGNING_IDENTITY_DELETE_JOB_KIND]: deleteSigningIdentity
18925
+ [SIGNING_IDENTITY_DELETE_JOB_KIND]: deleteSigningIdentity,
18926
+ [TAILSCALE_SERVE_INSPECT_JOB_KIND]: inspectTailscaleServe,
18927
+ [TAILSCALE_SERVE_UPSERT_JOB_KIND]: upsertTailscaleServe,
18928
+ [TAILSCALE_SERVE_REMOVE_JOB_KIND]: removeTailscaleServe
18441
18929
  };
18442
18930
 
18443
18931
  // src/repository-coordinator.ts
@@ -52441,8 +52929,8 @@ var ClaudeAdapter = class {
52441
52929
  dir: worktree.folder,
52442
52930
  limit: 500
52443
52931
  });
52444
- const serialized = JSON.stringify(history);
52445
- if (serialized.includes('"permissionMode":"plan"')) kind = "PLAN";
52932
+ const serialized2 = JSON.stringify(history);
52933
+ if (serialized2.includes('"permissionMode":"plan"')) kind = "PLAN";
52446
52934
  finalOutput = firstString([...history].reverse());
52447
52935
  this.hydrationCache.set(session.sessionId, {
52448
52936
  lastModified: lastModifiedKey,
@@ -53008,8 +53496,8 @@ var CodexAdapter = class {
53008
53496
  })
53009
53497
  );
53010
53498
  const hydrated = asRecord(detail.thread);
53011
- const serialized = JSON.stringify(hydrated.turns ?? []);
53012
- if (serialized.includes('"type":"plan"')) kind = "PLAN";
53499
+ const serialized2 = JSON.stringify(hydrated.turns ?? []);
53500
+ if (serialized2.includes('"type":"plan"')) kind = "PLAN";
53013
53501
  finalOutput = firstString(
53014
53502
  Array.isArray(hydrated.turns) ? [...hydrated.turns].reverse() : []
53015
53503
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-development-environment/control-agent",
3
- "version": "0.0.96",
3
+ "version": "0.0.97",
4
4
  "description": "Control agent for the AI Development Environment",
5
5
  "license": "MIT",
6
6
  "repository": {