@ansible-flow/node 0.0.0-stage → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -2
- package/dist/ai/agents.d.ts +34 -0
- package/dist/ai/agents.js +16 -0
- package/dist/ai/agents.js.map +7 -0
- package/dist/ai/hostConfig.d.ts +44 -0
- package/dist/ai/hostConfig.js +120 -0
- package/dist/ai/hostConfig.js.map +7 -0
- package/dist/ai/hosts.d.ts +28 -0
- package/dist/ai/hosts.js +9 -0
- package/dist/ai/hosts.js.map +7 -0
- package/dist/ai/writers.d.ts +124 -0
- package/dist/ai/writers.js +75 -0
- package/dist/ai/writers.js.map +7 -0
- package/dist/autoLayout.d.ts +38 -0
- package/dist/autoLayout.js +145 -0
- package/dist/autoLayout.js.map +7 -0
- package/dist/awx/awxWorkflow.d.ts +47 -0
- package/dist/awx/awxWorkflow.js +17 -0
- package/dist/awx/awxWorkflow.js.map +7 -0
- package/dist/awx/client.d.ts +77 -0
- package/dist/awx/client.js +22 -0
- package/dist/awx/client.js.map +7 -0
- package/dist/catalog/bindingCheck.d.ts +4 -0
- package/dist/catalog/bindingCheck.js +69 -0
- package/dist/catalog/bindingCheck.js.map +7 -0
- package/dist/catalog/catalogIndex.d.ts +52 -0
- package/dist/catalog/catalogIndex.js +13 -0
- package/dist/catalog/catalogIndex.js.map +7 -0
- package/dist/catalog/catalogWorker.d.ts +17 -0
- package/dist/catalog/catalogWorker.js +38 -0
- package/dist/catalog/catalogWorker.js.map +7 -0
- package/dist/catalog/dumpFiles.d.ts +3 -0
- package/dist/catalog/dumpFiles.js +19 -0
- package/dist/catalog/dumpFiles.js.map +7 -0
- package/dist/chunks/chunk-2MZQHIXP.js +614 -0
- package/dist/chunks/chunk-2MZQHIXP.js.map +7 -0
- package/dist/chunks/chunk-4FHPTGI4.js +52 -0
- package/dist/chunks/chunk-4FHPTGI4.js.map +7 -0
- package/dist/chunks/chunk-A2NMQGQE.js +189 -0
- package/dist/chunks/chunk-A2NMQGQE.js.map +7 -0
- package/dist/chunks/chunk-EPJKJBNW.js +181 -0
- package/dist/chunks/chunk-EPJKJBNW.js.map +7 -0
- package/dist/chunks/chunk-FG53MLUX.js +136 -0
- package/dist/chunks/chunk-FG53MLUX.js.map +7 -0
- package/dist/chunks/chunk-GAL5LW6Y.js +269 -0
- package/dist/chunks/chunk-GAL5LW6Y.js.map +7 -0
- package/dist/chunks/chunk-HPVT6I36.js +371 -0
- package/dist/chunks/chunk-HPVT6I36.js.map +7 -0
- package/dist/chunks/chunk-I6FIOP7G.js +48 -0
- package/dist/chunks/chunk-I6FIOP7G.js.map +7 -0
- package/dist/chunks/chunk-IIFP6UQR.js +44 -0
- package/dist/chunks/chunk-IIFP6UQR.js.map +7 -0
- package/dist/chunks/chunk-JDPN3LTT.js +18 -0
- package/dist/chunks/chunk-JDPN3LTT.js.map +7 -0
- package/dist/chunks/chunk-LGUG2Q7Z.js +191 -0
- package/dist/chunks/chunk-LGUG2Q7Z.js.map +7 -0
- package/dist/chunks/chunk-NTZRNEZM.js +25 -0
- package/dist/chunks/chunk-NTZRNEZM.js.map +7 -0
- package/dist/chunks/chunk-RETJPSKL.js +123 -0
- package/dist/chunks/chunk-RETJPSKL.js.map +7 -0
- package/dist/chunks/chunk-SSZNBJZQ.js +52 -0
- package/dist/chunks/chunk-SSZNBJZQ.js.map +7 -0
- package/dist/chunks/chunk-SUV3VHKR.js +52 -0
- package/dist/chunks/chunk-SUV3VHKR.js.map +7 -0
- package/dist/chunks/chunk-THNSGAKN.js +133 -0
- package/dist/chunks/chunk-THNSGAKN.js.map +7 -0
- package/dist/chunks/chunk-UNCR5JOZ.js +85 -0
- package/dist/chunks/chunk-UNCR5JOZ.js.map +7 -0
- package/dist/env/probeClient.d.ts +77 -0
- package/dist/env/probeClient.js +244 -0
- package/dist/env/probeClient.js.map +7 -0
- package/dist/env/userSettings.d.ts +33 -0
- package/dist/env/userSettings.js +101 -0
- package/dist/env/userSettings.js.map +7 -0
- package/dist/run/audit.d.ts +2 -0
- package/dist/run/audit.js +10 -0
- package/dist/run/audit.js.map +7 -0
- package/dist/run/exec.d.ts +14 -0
- package/dist/run/exec.js +7 -0
- package/dist/run/exec.js.map +7 -0
- package/dist/run/facts.d.ts +39 -0
- package/dist/run/facts.js +96 -0
- package/dist/run/facts.js.map +7 -0
- package/dist/run/ids.d.ts +2 -0
- package/dist/run/ids.js +8 -0
- package/dist/run/ids.js.map +7 -0
- package/dist/run/pastRuns.d.ts +9 -0
- package/dist/run/pastRuns.js +13 -0
- package/dist/run/pastRuns.js.map +7 -0
- package/dist/run/runFlow.d.ts +78 -0
- package/dist/run/runFlow.js +20 -0
- package/dist/run/runFlow.js.map +7 -0
- package/dist/run/segments.d.ts +43 -0
- package/dist/run/segments.js +157 -0
- package/dist/run/segments.js.map +7 -0
- package/dist/run/stop.d.ts +3 -0
- package/dist/run/stop.js +7 -0
- package/dist/run/stop.js.map +7 -0
- package/dist/run/storage.d.ts +16 -0
- package/dist/run/storage.js +15 -0
- package/dist/run/storage.js.map +7 -0
- package/dist/run/tail.d.ts +5 -0
- package/dist/run/tail.js +7 -0
- package/dist/run/tail.js.map +7 -0
- package/dist/runner/client.d.ts +149 -0
- package/dist/runner/client.js +168 -0
- package/dist/runner/client.js.map +7 -0
- package/dist/runner/devBundle.d.ts +14 -0
- package/dist/runner/devBundle.js +78 -0
- package/dist/runner/devBundle.js.map +7 -0
- package/dist/scenario/cli.d.ts +39 -0
- package/dist/scenario/cli.js +125 -0
- package/dist/scenario/cli.js.map +7 -0
- package/dist/scenario/runner.d.ts +50 -0
- package/dist/scenario/runner.js +185 -0
- package/dist/scenario/runner.js.map +7 -0
- package/dist/sharedRefs.d.ts +22 -0
- package/dist/sharedRefs.js +114 -0
- package/dist/sharedRefs.js.map +7 -0
- package/dist/ssh/exec.d.ts +39 -0
- package/dist/ssh/exec.js +13 -0
- package/dist/ssh/exec.js.map +7 -0
- package/dist/ssh/remote.d.ts +40 -0
- package/dist/ssh/remote.js +25 -0
- package/dist/ssh/remote.js.map +7 -0
- package/dist/ssh/sshRun.d.ts +1 -0
- package/dist/ssh/sshRun.js +24 -0
- package/dist/ssh/sshRun.js.map +7 -0
- package/dist/ssh/sync.d.ts +22 -0
- package/dist/ssh/sync.js +12 -0
- package/dist/ssh/sync.js.map +7 -0
- package/dist/timeouts.d.ts +38 -0
- package/dist/timeouts.js +7 -0
- package/dist/timeouts.js.map +7 -0
- package/package.json +35 -4
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pipe,
|
|
3
|
+
sshWhy,
|
|
4
|
+
tarExe
|
|
5
|
+
} from "./chunk-FG53MLUX.js";
|
|
6
|
+
|
|
7
|
+
// src/node/ssh/sync.ts
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import { mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { SYNC_LISTS, syncCommand } from "@ansible-flow/core/run/ssh";
|
|
13
|
+
var sent = /* @__PURE__ */ new Map();
|
|
14
|
+
function forgetSynced() {
|
|
15
|
+
sent.clear();
|
|
16
|
+
}
|
|
17
|
+
function git(root, args) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
execFile(
|
|
20
|
+
"git",
|
|
21
|
+
["-C", root, ...args],
|
|
22
|
+
{ maxBuffer: 64 * 1024 * 1024, windowsHide: true },
|
|
23
|
+
(error, stdout) => resolve(error ? void 0 : stdout)
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function walk(root, rel = "") {
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const e of readdirSync(join(root, rel), { withFileTypes: true })) {
|
|
30
|
+
const path = rel ? `${rel}/${e.name}` : e.name;
|
|
31
|
+
if (e.isDirectory()) {
|
|
32
|
+
if (e.name !== ".git" && e.name !== "node_modules") out.push(...walk(root, path));
|
|
33
|
+
} else if (e.isFile()) out.push(path);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
async function listFiles(root, include = []) {
|
|
38
|
+
const listed = await git(root, ["ls-files", "-co", "--exclude-standard", "-z"]);
|
|
39
|
+
if (listed === void 0 || listed === "") return { files: walk(root), exec: /* @__PURE__ */ new Set() };
|
|
40
|
+
const also = include.flatMap((dir) => {
|
|
41
|
+
try {
|
|
42
|
+
return walk(root, dir.replace(/\/+$/, ""));
|
|
43
|
+
} catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const files = [.../* @__PURE__ */ new Set([...listed.split("\0").filter(Boolean), ...also])];
|
|
48
|
+
const exec = /* @__PURE__ */ new Set();
|
|
49
|
+
const staged = await git(root, ["ls-files", "-s", "-z"]) ?? "";
|
|
50
|
+
for (const line of staged.split("\0")) {
|
|
51
|
+
const tab = line.indexOf(" ");
|
|
52
|
+
if (tab > 0 && line.startsWith("100755")) exec.add(line.slice(tab + 1));
|
|
53
|
+
}
|
|
54
|
+
return { files, exec };
|
|
55
|
+
}
|
|
56
|
+
function manifest(root, files, exec) {
|
|
57
|
+
const out = /* @__PURE__ */ new Map();
|
|
58
|
+
for (const f of files) {
|
|
59
|
+
try {
|
|
60
|
+
const st = statSync(join(root, f));
|
|
61
|
+
if (!st.isFile()) continue;
|
|
62
|
+
const mode = process.platform === "win32" ? exec.has(f) : (st.mode & 64) !== 0;
|
|
63
|
+
out.set(f, { stamp: `${st.size}:${Math.floor(st.mtimeMs)}`, exec: mode });
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
var queue = /* @__PURE__ */ new Map();
|
|
70
|
+
function syncFolder(target, root, into, include = []) {
|
|
71
|
+
const key = `${target.host}|${target.dir}|${into}`;
|
|
72
|
+
const turn = (queue.get(key) ?? Promise.resolve()).then(
|
|
73
|
+
() => syncNow(target, root, into, include),
|
|
74
|
+
() => syncNow(target, root, into, include)
|
|
75
|
+
);
|
|
76
|
+
queue.set(
|
|
77
|
+
key,
|
|
78
|
+
turn.catch(() => void 0)
|
|
79
|
+
);
|
|
80
|
+
return turn;
|
|
81
|
+
}
|
|
82
|
+
async function syncNow(target, root, into, include = []) {
|
|
83
|
+
const key = `${target.host}|${target.dir}|${into}|${root}`;
|
|
84
|
+
const before = sent.get(key);
|
|
85
|
+
const { files, exec } = await listFiles(root, include);
|
|
86
|
+
const now = manifest(root, files, exec);
|
|
87
|
+
const changed = [...now.keys()].filter((f) => {
|
|
88
|
+
const was = before?.get(f);
|
|
89
|
+
return !was || was.stamp !== now.get(f)?.stamp || was.exec !== now.get(f)?.exec;
|
|
90
|
+
});
|
|
91
|
+
const removed = before ? [...before.keys()].filter((f) => !now.has(f)) : [];
|
|
92
|
+
if (before && changed.length === 0 && removed.length === 0)
|
|
93
|
+
return { ok: true, sent: 0, removed: 0 };
|
|
94
|
+
const lists = mkdtempSync(join(tmpdir(), "afd-sync-"));
|
|
95
|
+
try {
|
|
96
|
+
const nul = (items) => items.length ? `${items.join("\0")}\0` : "";
|
|
97
|
+
writeFileSync(join(lists, SYNC_LISTS.remove), nul(removed));
|
|
98
|
+
writeFileSync(join(lists, SYNC_LISTS.sent), nul(changed));
|
|
99
|
+
writeFileSync(join(lists, SYNC_LISTS.exec), nul(changed.filter((f) => now.get(f)?.exec)));
|
|
100
|
+
const plain = changed.filter((f) => !/[\r\n]/.test(f));
|
|
101
|
+
writeFileSync(join(lists, "files"), plain.join("\n") + (plain.length ? "\n" : ""));
|
|
102
|
+
const tars = [
|
|
103
|
+
{ command: tarExe(), args: ["-cf", "-", ...Object.values(SYNC_LISTS)], cwd: lists },
|
|
104
|
+
...plain.length ? [{ command: tarExe(), args: ["-cf", "-", "-T", join(lists, "files")], cwd: root }] : []
|
|
105
|
+
];
|
|
106
|
+
const there = syncCommand(target, {
|
|
107
|
+
into,
|
|
108
|
+
clean: !before,
|
|
109
|
+
fixModes: process.platform === "win32"
|
|
110
|
+
});
|
|
111
|
+
const r = await pipe(tars, there, { timeoutMs: 6e5 });
|
|
112
|
+
if (r.from !== 0 || r.to !== 0) {
|
|
113
|
+
sent.delete(key);
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
sent: 0,
|
|
117
|
+
removed: 0,
|
|
118
|
+
error: `Could not copy the files to ${target.host}: ${sshWhy(r.stderr)}`
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
sent.set(key, now);
|
|
122
|
+
return { ok: true, sent: plain.length, removed: removed.length };
|
|
123
|
+
} finally {
|
|
124
|
+
rmSync(lists, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
forgetSynced,
|
|
130
|
+
listFiles,
|
|
131
|
+
syncFolder
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=chunk-THNSGAKN.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/node/ssh/sync.ts"],
|
|
4
|
+
"sourcesContent": ["// AFD-247: copies a folder of this machine to the SSH host, and keeps the copy current. The files\n// are the ones Git would see (tracked, and untracked ones no .gitignore names), as they are on\n// disk now: uncommitted work runs. They travel as a tar archive through the connection.\n//\n// After the first copy only what changed is sent: each file's size and time are remembered, and\n// files that are gone here are deleted there. The first copy of a session empties the folder on\n// the host, since nothing is known of what an earlier session, or another person on a shared\n// account, left in it.\nimport { execFile } from 'node:child_process';\nimport { mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { type SshTarget, SYNC_LISTS, syncCommand } from '../../core/run/ssh';\nimport { pipe, sshWhy, tarExe } from './exec';\n\ninterface Entry {\n /** Size and modification time: a file with the same two is taken as unchanged. */\n stamp: string;\n exec: boolean;\n}\n\n/** What was last sent, per host, folder there and folder here. */\nconst sent = new Map<string, Map<string, Entry>>();\n\n/** Forgets what was sent: the next sync copies everything again (tests, a changed host). */\nexport function forgetSynced(): void {\n sent.clear();\n}\n\nfunction git(root: string, args: string[]): Promise<string | undefined> {\n return new Promise((resolve) => {\n execFile(\n 'git',\n ['-C', root, ...args],\n { maxBuffer: 64 * 1024 * 1024, windowsHide: true },\n (error, stdout) => resolve(error ? undefined : stdout),\n );\n });\n}\n\n/** Every file under a folder, for one that is not a Git repository; `.git` is never copied. */\nfunction walk(root: string, rel = ''): string[] {\n const out: string[] = [];\n for (const e of readdirSync(join(root, rel), { withFileTypes: true })) {\n const path = rel ? `${rel}/${e.name}` : e.name;\n if (e.isDirectory()) {\n if (e.name !== '.git' && e.name !== 'node_modules') out.push(...walk(root, path));\n } else if (e.isFile()) out.push(path);\n }\n return out;\n}\n\n/** The files to copy, relative with forward slashes, and which of them Git marks executable. */\nexport async function listFiles(\n root: string,\n include: readonly string[] = [],\n): Promise<{ files: string[]; exec: Set<string> }> {\n const listed = await git(root, ['ls-files', '-co', '--exclude-standard', '-z']);\n // Not a repository, or a folder a repository above it ignores whole: every file under it.\n if (listed === undefined || listed === '') return { files: walk(root), exec: new Set() };\n // `include`: folders copied whatever .gitignore says (code generated for one run).\n const also = include.flatMap((dir) => {\n try {\n return walk(root, dir.replace(/\\/+$/, ''));\n } catch {\n return [];\n }\n });\n const files = [...new Set([...listed.split('\\0').filter(Boolean), ...also])];\n const exec = new Set<string>();\n // `100755 <hash> 0\\t<path>`: the mode Git keeps, which Windows has no other way to know.\n const staged = (await git(root, ['ls-files', '-s', '-z'])) ?? '';\n for (const line of staged.split('\\0')) {\n const tab = line.indexOf('\\t');\n if (tab > 0 && line.startsWith('100755')) exec.add(line.slice(tab + 1));\n }\n return { files, exec };\n}\n\nfunction manifest(root: string, files: string[], exec: Set<string>): Map<string, Entry> {\n const out = new Map<string, Entry>();\n for (const f of files) {\n try {\n const st = statSync(join(root, f));\n if (!st.isFile()) continue;\n const mode = process.platform === 'win32' ? exec.has(f) : (st.mode & 0o100) !== 0;\n out.set(f, { stamp: `${st.size}:${Math.floor(st.mtimeMs)}`, exec: mode });\n } catch {\n // Listed by Git, gone from disk (deleted, not committed yet): not copied.\n }\n }\n return out;\n}\n\nexport interface SyncResult {\n ok: boolean;\n /** Files sent and files deleted on the host. */\n sent: number;\n removed: number;\n error?: string;\n}\n\n/** One sync at a time per folder on the host: a second one waits, then finds nothing to send. */\nconst queue = new Map<string, Promise<unknown>>();\n\n/**\n * Makes `<target.dir>/<into>` on the host hold the files of `root`. Nothing is run when nothing\n * changed since the last sync of this session.\n */\nexport function syncFolder(\n target: SshTarget,\n root: string,\n into: string,\n /** Folders of `root` copied whatever .gitignore says. */\n include: readonly string[] = [],\n): Promise<SyncResult> {\n const key = `${target.host}|${target.dir}|${into}`;\n const turn = (queue.get(key) ?? Promise.resolve()).then(\n () => syncNow(target, root, into, include),\n () => syncNow(target, root, into, include),\n );\n queue.set(\n key,\n turn.catch(() => undefined),\n );\n return turn;\n}\n\nasync function syncNow(\n target: SshTarget,\n root: string,\n into: string,\n /** Folders of `root` copied whatever .gitignore says. */\n include: readonly string[] = [],\n): Promise<SyncResult> {\n const key = `${target.host}|${target.dir}|${into}|${root}`;\n const before = sent.get(key);\n const { files, exec } = await listFiles(root, include);\n const now = manifest(root, files, exec);\n const changed = [...now.keys()].filter((f) => {\n const was = before?.get(f);\n return !was || was.stamp !== now.get(f)?.stamp || was.exec !== now.get(f)?.exec;\n });\n const removed = before ? [...before.keys()].filter((f) => !now.has(f)) : [];\n if (before && changed.length === 0 && removed.length === 0)\n return { ok: true, sent: 0, removed: 0 };\n\n const lists = mkdtempSync(join(tmpdir(), 'afd-sync-'));\n try {\n const nul = (items: string[]) => (items.length ? `${items.join('\\0')}\\0` : '');\n writeFileSync(join(lists, SYNC_LISTS.remove), nul(removed));\n writeFileSync(join(lists, SYNC_LISTS.sent), nul(changed));\n writeFileSync(join(lists, SYNC_LISTS.exec), nul(changed.filter((f) => now.get(f)?.exec)));\n // The names to archive, one a line: file names with a newline in them are not copied.\n const plain = changed.filter((f) => !/[\\r\\n]/.test(f));\n writeFileSync(join(lists, 'files'), plain.join('\\n') + (plain.length ? '\\n' : ''));\n // Two archives, one after the other on the same connection: the lists, then the files. One\n // tar cannot take both folders with a list of names (bsdtar and GNU tar differ there).\n const tars = [\n { command: tarExe(), args: ['-cf', '-', ...Object.values(SYNC_LISTS)], cwd: lists },\n ...(plain.length\n ? [{ command: tarExe(), args: ['-cf', '-', '-T', join(lists, 'files')], cwd: root }]\n : []),\n ];\n const there = syncCommand(target, {\n into,\n clean: !before,\n fixModes: process.platform === 'win32',\n });\n const r = await pipe(tars, there, { timeoutMs: 600_000 });\n if (r.from !== 0 || r.to !== 0) {\n sent.delete(key);\n return {\n ok: false,\n sent: 0,\n removed: 0,\n error: `Could not copy the files to ${target.host}: ${sshWhy(r.stderr)}`,\n };\n }\n sent.set(key, now);\n return { ok: true, sent: plain.length, removed: removed.length };\n } finally {\n rmSync(lists, { recursive: true, force: true });\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;AAQA,SAAS,gBAAgB;AACzB,SAAS,aAAa,aAAa,QAAQ,UAAU,qBAAqB;AAC1E,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAyB,YAAY,mBAAmB;AAUxD,IAAM,OAAO,oBAAI,IAAgC;AAG1C,SAAS,eAAqB;AACnC,OAAK,MAAM;AACb;AAEA,SAAS,IAAI,MAAc,MAA6C;AACtE,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B;AAAA,MACE;AAAA,MACA,CAAC,MAAM,MAAM,GAAG,IAAI;AAAA,MACpB,EAAE,WAAW,KAAK,OAAO,MAAM,aAAa,KAAK;AAAA,MACjD,CAAC,OAAO,WAAW,QAAQ,QAAQ,SAAY,MAAM;AAAA,IACvD;AAAA,EACF,CAAC;AACH;AAGA,SAAS,KAAK,MAAc,MAAM,IAAc;AAC9C,QAAM,MAAgB,CAAC;AACvB,aAAW,KAAK,YAAY,KAAK,MAAM,GAAG,GAAG,EAAE,eAAe,KAAK,CAAC,GAAG;AACrE,UAAM,OAAO,MAAM,GAAG,GAAG,IAAI,EAAE,IAAI,KAAK,EAAE;AAC1C,QAAI,EAAE,YAAY,GAAG;AACnB,UAAI,EAAE,SAAS,UAAU,EAAE,SAAS,eAAgB,KAAI,KAAK,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,IAClF,WAAW,EAAE,OAAO,EAAG,KAAI,KAAK,IAAI;AAAA,EACtC;AACA,SAAO;AACT;AAGA,eAAsB,UACpB,MACA,UAA6B,CAAC,GACmB;AACjD,QAAM,SAAS,MAAM,IAAI,MAAM,CAAC,YAAY,OAAO,sBAAsB,IAAI,CAAC;AAE9E,MAAI,WAAW,UAAa,WAAW,GAAI,QAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,oBAAI,IAAI,EAAE;AAEvF,QAAM,OAAO,QAAQ,QAAQ,CAAC,QAAQ;AACpC,QAAI;AACF,aAAO,KAAK,MAAM,IAAI,QAAQ,QAAQ,EAAE,CAAC;AAAA,IAC3C,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF,CAAC;AACD,QAAM,QAAQ,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,OAAO,MAAM,IAAI,EAAE,OAAO,OAAO,GAAG,GAAG,IAAI,CAAC,CAAC;AAC3E,QAAM,OAAO,oBAAI,IAAY;AAE7B,QAAM,SAAU,MAAM,IAAI,MAAM,CAAC,YAAY,MAAM,IAAI,CAAC,KAAM;AAC9D,aAAW,QAAQ,OAAO,MAAM,IAAI,GAAG;AACrC,UAAM,MAAM,KAAK,QAAQ,GAAI;AAC7B,QAAI,MAAM,KAAK,KAAK,WAAW,QAAQ,EAAG,MAAK,IAAI,KAAK,MAAM,MAAM,CAAC,CAAC;AAAA,EACxE;AACA,SAAO,EAAE,OAAO,KAAK;AACvB;AAEA,SAAS,SAAS,MAAc,OAAiB,MAAuC;AACtF,QAAM,MAAM,oBAAI,IAAmB;AACnC,aAAW,KAAK,OAAO;AACrB,QAAI;AACF,YAAM,KAAK,SAAS,KAAK,MAAM,CAAC,CAAC;AACjC,UAAI,CAAC,GAAG,OAAO,EAAG;AAClB,YAAM,OAAO,QAAQ,aAAa,UAAU,KAAK,IAAI,CAAC,KAAK,GAAG,OAAO,QAAW;AAChF,UAAI,IAAI,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,IAAI,KAAK,MAAM,GAAG,OAAO,CAAC,IAAI,MAAM,KAAK,CAAC;AAAA,IAC1E,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAWA,IAAM,QAAQ,oBAAI,IAA8B;AAMzC,SAAS,WACd,QACA,MACA,MAEA,UAA6B,CAAC,GACT;AACrB,QAAM,MAAM,GAAG,OAAO,IAAI,IAAI,OAAO,GAAG,IAAI,IAAI;AAChD,QAAM,QAAQ,MAAM,IAAI,GAAG,KAAK,QAAQ,QAAQ,GAAG;AAAA,IACjD,MAAM,QAAQ,QAAQ,MAAM,MAAM,OAAO;AAAA,IACzC,MAAM,QAAQ,QAAQ,MAAM,MAAM,OAAO;AAAA,EAC3C;AACA,QAAM;AAAA,IACJ;AAAA,IACA,KAAK,MAAM,MAAM,MAAS;AAAA,EAC5B;AACA,SAAO;AACT;AAEA,eAAe,QACb,QACA,MACA,MAEA,UAA6B,CAAC,GACT;AACrB,QAAM,MAAM,GAAG,OAAO,IAAI,IAAI,OAAO,GAAG,IAAI,IAAI,IAAI,IAAI;AACxD,QAAM,SAAS,KAAK,IAAI,GAAG;AAC3B,QAAM,EAAE,OAAO,KAAK,IAAI,MAAM,UAAU,MAAM,OAAO;AACrD,QAAM,MAAM,SAAS,MAAM,OAAO,IAAI;AACtC,QAAM,UAAU,CAAC,GAAG,IAAI,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM;AAC5C,UAAM,MAAM,QAAQ,IAAI,CAAC;AACzB,WAAO,CAAC,OAAO,IAAI,UAAU,IAAI,IAAI,CAAC,GAAG,SAAS,IAAI,SAAS,IAAI,IAAI,CAAC,GAAG;AAAA,EAC7E,CAAC;AACD,QAAM,UAAU,SAAS,CAAC,GAAG,OAAO,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC;AAC1E,MAAI,UAAU,QAAQ,WAAW,KAAK,QAAQ,WAAW;AACvD,WAAO,EAAE,IAAI,MAAM,MAAM,GAAG,SAAS,EAAE;AAEzC,QAAM,QAAQ,YAAY,KAAK,OAAO,GAAG,WAAW,CAAC;AACrD,MAAI;AACF,UAAM,MAAM,CAAC,UAAqB,MAAM,SAAS,GAAG,MAAM,KAAK,IAAI,CAAC,OAAO;AAC3E,kBAAc,KAAK,OAAO,WAAW,MAAM,GAAG,IAAI,OAAO,CAAC;AAC1D,kBAAc,KAAK,OAAO,WAAW,IAAI,GAAG,IAAI,OAAO,CAAC;AACxD,kBAAc,KAAK,OAAO,WAAW,IAAI,GAAG,IAAI,QAAQ,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAExF,UAAM,QAAQ,QAAQ,OAAO,CAAC,MAAM,CAAC,SAAS,KAAK,CAAC,CAAC;AACrD,kBAAc,KAAK,OAAO,OAAO,GAAG,MAAM,KAAK,IAAI,KAAK,MAAM,SAAS,OAAO,GAAG;AAGjF,UAAM,OAAO;AAAA,MACX,EAAE,SAAS,OAAO,GAAG,MAAM,CAAC,OAAO,KAAK,GAAG,OAAO,OAAO,UAAU,CAAC,GAAG,KAAK,MAAM;AAAA,MAClF,GAAI,MAAM,SACN,CAAC,EAAE,SAAS,OAAO,GAAG,MAAM,CAAC,OAAO,KAAK,MAAM,KAAK,OAAO,OAAO,CAAC,GAAG,KAAK,KAAK,CAAC,IACjF,CAAC;AAAA,IACP;AACA,UAAM,QAAQ,YAAY,QAAQ;AAAA,MAChC;AAAA,MACA,OAAO,CAAC;AAAA,MACR,UAAU,QAAQ,aAAa;AAAA,IACjC,CAAC;AACD,UAAM,IAAI,MAAM,KAAK,MAAM,OAAO,EAAE,WAAW,IAAQ,CAAC;AACxD,QAAI,EAAE,SAAS,KAAK,EAAE,OAAO,GAAG;AAC9B,WAAK,OAAO,GAAG;AACf,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,OAAO,+BAA+B,OAAO,IAAI,KAAK,OAAO,EAAE,MAAM,CAAC;AAAA,MACxE;AAAA,IACF;AACA,SAAK,IAAI,KAAK,GAAG;AACjB,WAAO,EAAE,IAAI,MAAM,MAAM,MAAM,QAAQ,SAAS,QAAQ,OAAO;AAAA,EACjE,UAAE;AACA,WAAO,OAAO,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAChD;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// src/node/ai/hosts.ts
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
var h = (env, ...parts) => join(env.home, ...parts);
|
|
4
|
+
var HOSTS = {
|
|
5
|
+
vscode: {
|
|
6
|
+
id: "vscode",
|
|
7
|
+
label: "VS Code",
|
|
8
|
+
agent: "Copilot",
|
|
9
|
+
mcp: () => [],
|
|
10
|
+
skills: () => [],
|
|
11
|
+
note: "Copilot gets the tools (lm.registerTool) and the skill (chatSkills) from the extension itself."
|
|
12
|
+
},
|
|
13
|
+
cursor: {
|
|
14
|
+
id: "cursor",
|
|
15
|
+
label: "Cursor",
|
|
16
|
+
agent: "Cursor Agent",
|
|
17
|
+
mcp: (env) => [h(env, ".cursor", "mcp.json")],
|
|
18
|
+
skills: (env) => [
|
|
19
|
+
h(env, ".cursor", "skills"),
|
|
20
|
+
h(env, ".agents", "skills"),
|
|
21
|
+
h(env, ".claude", "skills")
|
|
22
|
+
],
|
|
23
|
+
note: "Reads ~/.cursor/mcp.json (sends no roots: the server starts in the home folder) and all three skill folders. vscode.cursor.mcp.registerServer works: shown as extension-<name>, 11 tools, node or the editor runtime. No plugins.registerPath in 3.29. Cursor 3 may open its agent window (Glass), where extensions may not run."
|
|
24
|
+
},
|
|
25
|
+
antigravity: {
|
|
26
|
+
id: "antigravity",
|
|
27
|
+
label: "Antigravity IDE",
|
|
28
|
+
agent: "Antigravity agent",
|
|
29
|
+
mcp: (env) => [h(env, ".gemini", "config", "mcp_config.json")],
|
|
30
|
+
// Its global customization root is ~/.gemini/config (skills/, plugins/); ~/.gemini/skills is
|
|
31
|
+
// Gemini CLI's and not read by Antigravity IDE (AFD-172 probe).
|
|
32
|
+
skills: (env) => [h(env, ".gemini", "config", "skills")],
|
|
33
|
+
plugins: (env) => h(env, ".gemini", "config", "plugins"),
|
|
34
|
+
note: "Reads ~/.gemini/config/mcp_config.json (no roots; cwd is the workspace) and ~/.gemini/config/skills; refreshMcpServers() (vscode.antigravityExtensibility) reloads servers live. Bundles in ~/.gemini/config/plugins load their skills but not their MCP servers; writeMcpConfig folders are not loaded."
|
|
35
|
+
},
|
|
36
|
+
windsurf: {
|
|
37
|
+
id: "windsurf",
|
|
38
|
+
label: "Devin Desktop (Windsurf)",
|
|
39
|
+
agent: "Devin",
|
|
40
|
+
// Devin's own code: %USERPROFILE%\AppData\Roaming\devin on Windows, else $XDG_CONFIG_HOME/devin
|
|
41
|
+
// or ~/.config/devin. It still reads Windsurf's ~/.codeium/windsurf too (both probed, AFD-172).
|
|
42
|
+
mcp: (env) => [
|
|
43
|
+
env.platform === "win32" ? h(env, "AppData", "Roaming", "devin", "mcp_config.json") : join(env.xdgConfig || h(env, ".config"), "devin", "mcp_config.json"),
|
|
44
|
+
h(env, ".codeium", "windsurf", "mcp_config.json")
|
|
45
|
+
],
|
|
46
|
+
// ~/.devin/skills is not read (AFD-172 probe).
|
|
47
|
+
skills: (env) => [h(env, ".codeium", "windsurf", "skills"), h(env, ".agents", "skills")],
|
|
48
|
+
note: "No extension API. Its local agent (the cloud/local switch on local) reads both MCP files (no roots; cwd is the workspace; asks before tool calls) and also runs servers from Claude Code's and Cursor's configs (config importers in devin.exe). Skills from ~/.codeium/windsurf/skills and ~/.agents/skills."
|
|
49
|
+
},
|
|
50
|
+
kiro: {
|
|
51
|
+
id: "kiro",
|
|
52
|
+
label: "Kiro",
|
|
53
|
+
agent: "Kiro agent",
|
|
54
|
+
mcp: (env) => [h(env, ".kiro", "settings", "mcp.json")],
|
|
55
|
+
skills: (env) => [h(env, ".kiro", "skills")],
|
|
56
|
+
plugins: (env) => h(env, ".kiro", "powers", "installed"),
|
|
57
|
+
note: "No extension API. Reads ~/.kiro/settings/mcp.json (no roots; cwd is the workspace; asks before each tool call) and ~/.kiro/skills. A bundle in ~/.kiro/powers/installed is not loaded."
|
|
58
|
+
},
|
|
59
|
+
other: {
|
|
60
|
+
id: "other",
|
|
61
|
+
label: "an editor built on VS Code",
|
|
62
|
+
agent: "its agent",
|
|
63
|
+
mcp: () => [],
|
|
64
|
+
skills: () => [],
|
|
65
|
+
note: "Unknown editor: Set Up AI Assistants and Other agent\u2026 cover it."
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function detectHost(app) {
|
|
69
|
+
const scheme = app.uriScheme.toLowerCase();
|
|
70
|
+
const name = app.appName.toLowerCase();
|
|
71
|
+
const is = (...words) => words.some((w) => scheme.startsWith(w) || name.includes(w));
|
|
72
|
+
if (is("cursor")) return "cursor";
|
|
73
|
+
if (is("antigravity")) return "antigravity";
|
|
74
|
+
if (is("devin", "windsurf")) return "windsurf";
|
|
75
|
+
if (is("kiro")) return "kiro";
|
|
76
|
+
if (scheme.startsWith("vscode") || name.includes("visual studio code") || name.includes("vscodium") || name.includes("code - oss"))
|
|
77
|
+
return "vscode";
|
|
78
|
+
return "other";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
HOSTS,
|
|
83
|
+
detectHost
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=chunk-UNCR5JOZ.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/node/ai/hosts.ts"],
|
|
4
|
+
"sourcesContent": ["// The editor the extension runs in (AFD-172, epic AFD-170): VS Code, or a fork whose built-in\n// agent reads its own MCP and skill configuration. One table of where each agent looks, so the\n// diagnostics and the integration (AFD-173/174) share the paths. Checked against\n// each editor's own code or bundled docs on 2026-11-08 (Cursor 3.29 on VS Code 1.128, Antigravity\n// IDE 1.107, Devin Desktop 3.12 on 1.141, Kiro 1.4 on 1.131). No VS Code import.\nimport { join } from 'node:path';\n\nexport type HostId = 'vscode' | 'cursor' | 'antigravity' | 'windsurf' | 'kiro' | 'other';\n\nexport interface HostEnv {\n home: string;\n platform: NodeJS.Platform;\n /** XDG_CONFIG_HOME, which Devin honours outside Windows. */\n xdgConfig?: string;\n}\n\nexport interface HostInfo {\n id: HostId;\n /** The editor, as people call it. */\n label: string;\n /** Its built-in agent. */\n agent: string;\n /** User-level MCP files the agent reads; the first is where an entry goes. */\n mcp(env: HostEnv): string[];\n /** User-level skill folders the agent reads (each holds one folder per skill). */\n skills(env: HostEnv): string[];\n /** Where plugin bundles go (plugin.json, MCP file, skills/), if the agent loads them. */\n plugins?(env: HostEnv): string;\n /** What is known and what is still to be probed. */\n note: string;\n}\n\nconst h = (env: HostEnv, ...parts: string[]) => join(env.home, ...parts);\n\nexport const HOSTS: Record<HostId, HostInfo> = {\n vscode: {\n id: 'vscode',\n label: 'VS Code',\n agent: 'Copilot',\n mcp: () => [],\n skills: () => [],\n note: 'Copilot gets the tools (lm.registerTool) and the skill (chatSkills) from the extension itself.',\n },\n cursor: {\n id: 'cursor',\n label: 'Cursor',\n agent: 'Cursor Agent',\n mcp: (env) => [h(env, '.cursor', 'mcp.json')],\n skills: (env) => [\n h(env, '.cursor', 'skills'),\n h(env, '.agents', 'skills'),\n h(env, '.claude', 'skills'),\n ],\n note: 'Reads ~/.cursor/mcp.json (sends no roots: the server starts in the home folder) and all three skill folders. vscode.cursor.mcp.registerServer works: shown as extension-<name>, 11 tools, node or the editor runtime. No plugins.registerPath in 3.29. Cursor 3 may open its agent window (Glass), where extensions may not run.',\n },\n antigravity: {\n id: 'antigravity',\n label: 'Antigravity IDE',\n agent: 'Antigravity agent',\n mcp: (env) => [h(env, '.gemini', 'config', 'mcp_config.json')],\n // Its global customization root is ~/.gemini/config (skills/, plugins/); ~/.gemini/skills is\n // Gemini CLI's and not read by Antigravity IDE (AFD-172 probe).\n skills: (env) => [h(env, '.gemini', 'config', 'skills')],\n plugins: (env) => h(env, '.gemini', 'config', 'plugins'),\n note: 'Reads ~/.gemini/config/mcp_config.json (no roots; cwd is the workspace) and ~/.gemini/config/skills; refreshMcpServers() (vscode.antigravityExtensibility) reloads servers live. Bundles in ~/.gemini/config/plugins load their skills but not their MCP servers; writeMcpConfig folders are not loaded.',\n },\n windsurf: {\n id: 'windsurf',\n label: 'Devin Desktop (Windsurf)',\n agent: 'Devin',\n // Devin's own code: %USERPROFILE%\\AppData\\Roaming\\devin on Windows, else $XDG_CONFIG_HOME/devin\n // or ~/.config/devin. It still reads Windsurf's ~/.codeium/windsurf too (both probed, AFD-172).\n mcp: (env) => [\n env.platform === 'win32'\n ? h(env, 'AppData', 'Roaming', 'devin', 'mcp_config.json')\n : join(env.xdgConfig || h(env, '.config'), 'devin', 'mcp_config.json'),\n h(env, '.codeium', 'windsurf', 'mcp_config.json'),\n ],\n // ~/.devin/skills is not read (AFD-172 probe).\n skills: (env) => [h(env, '.codeium', 'windsurf', 'skills'), h(env, '.agents', 'skills')],\n note: \"No extension API. Its local agent (the cloud/local switch on local) reads both MCP files (no roots; cwd is the workspace; asks before tool calls) and also runs servers from Claude Code's and Cursor's configs (config importers in devin.exe). Skills from ~/.codeium/windsurf/skills and ~/.agents/skills.\",\n },\n kiro: {\n id: 'kiro',\n label: 'Kiro',\n agent: 'Kiro agent',\n mcp: (env) => [h(env, '.kiro', 'settings', 'mcp.json')],\n skills: (env) => [h(env, '.kiro', 'skills')],\n plugins: (env) => h(env, '.kiro', 'powers', 'installed'),\n note: 'No extension API. Reads ~/.kiro/settings/mcp.json (no roots; cwd is the workspace; asks before each tool call) and ~/.kiro/skills. A bundle in ~/.kiro/powers/installed is not loaded.',\n },\n other: {\n id: 'other',\n label: 'an editor built on VS Code',\n agent: 'its agent',\n mcp: () => [],\n skills: () => [],\n note: 'Unknown editor: Set Up AI Assistants and Other agent\u2026 cover it.',\n },\n};\n\n/** Which editor this is, from vscode.env.appName and uriScheme (the scheme is the surer of the two). */\nexport function detectHost(app: { appName: string; uriScheme: string }): HostId {\n const scheme = app.uriScheme.toLowerCase();\n const name = app.appName.toLowerCase();\n const is = (...words: string[]) => words.some((w) => scheme.startsWith(w) || name.includes(w));\n if (is('cursor')) return 'cursor';\n if (is('antigravity')) return 'antigravity';\n if (is('devin', 'windsurf')) return 'windsurf';\n if (is('kiro')) return 'kiro';\n if (\n scheme.startsWith('vscode') ||\n name.includes('visual studio code') ||\n name.includes('vscodium') ||\n name.includes('code - oss')\n )\n return 'vscode';\n return 'other';\n}\n"],
|
|
5
|
+
"mappings": ";AAKA,SAAS,YAAY;AA2BrB,IAAM,IAAI,CAAC,QAAiB,UAAoB,KAAK,IAAI,MAAM,GAAG,KAAK;AAEhE,IAAM,QAAkC;AAAA,EAC7C,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA,IACP,KAAK,MAAM,CAAC;AAAA,IACZ,QAAQ,MAAM,CAAC;AAAA,IACf,MAAM;AAAA,EACR;AAAA,EACA,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA,IACP,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,WAAW,UAAU,CAAC;AAAA,IAC5C,QAAQ,CAAC,QAAQ;AAAA,MACf,EAAE,KAAK,WAAW,QAAQ;AAAA,MAC1B,EAAE,KAAK,WAAW,QAAQ;AAAA,MAC1B,EAAE,KAAK,WAAW,QAAQ;AAAA,IAC5B;AAAA,IACA,MAAM;AAAA,EACR;AAAA,EACA,aAAa;AAAA,IACX,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA,IACP,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,WAAW,UAAU,iBAAiB,CAAC;AAAA;AAAA;AAAA,IAG7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,WAAW,UAAU,QAAQ,CAAC;AAAA,IACvD,SAAS,CAAC,QAAQ,EAAE,KAAK,WAAW,UAAU,SAAS;AAAA,IACvD,MAAM;AAAA,EACR;AAAA,EACA,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA;AAAA;AAAA,IAGP,KAAK,CAAC,QAAQ;AAAA,MACZ,IAAI,aAAa,UACb,EAAE,KAAK,WAAW,WAAW,SAAS,iBAAiB,IACvD,KAAK,IAAI,aAAa,EAAE,KAAK,SAAS,GAAG,SAAS,iBAAiB;AAAA,MACvE,EAAE,KAAK,YAAY,YAAY,iBAAiB;AAAA,IAClD;AAAA;AAAA,IAEA,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,YAAY,YAAY,QAAQ,GAAG,EAAE,KAAK,WAAW,QAAQ,CAAC;AAAA,IACvF,MAAM;AAAA,EACR;AAAA,EACA,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA,IACP,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,SAAS,YAAY,UAAU,CAAC;AAAA,IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,SAAS,QAAQ,CAAC;AAAA,IAC3C,SAAS,CAAC,QAAQ,EAAE,KAAK,SAAS,UAAU,WAAW;AAAA,IACvD,MAAM;AAAA,EACR;AAAA,EACA,OAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA,IACP,KAAK,MAAM,CAAC;AAAA,IACZ,QAAQ,MAAM,CAAC;AAAA,IACf,MAAM;AAAA,EACR;AACF;AAGO,SAAS,WAAW,KAAqD;AAC9E,QAAM,SAAS,IAAI,UAAU,YAAY;AACzC,QAAM,OAAO,IAAI,QAAQ,YAAY;AACrC,QAAM,KAAK,IAAI,UAAoB,MAAM,KAAK,CAAC,MAAM,OAAO,WAAW,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;AAC7F,MAAI,GAAG,QAAQ,EAAG,QAAO;AACzB,MAAI,GAAG,aAAa,EAAG,QAAO;AAC9B,MAAI,GAAG,SAAS,UAAU,EAAG,QAAO;AACpC,MAAI,GAAG,MAAM,EAAG,QAAO;AACvB,MACE,OAAO,WAAW,QAAQ,KAC1B,KAAK,SAAS,oBAAoB,KAClC,KAAK,SAAS,UAAU,KACxB,KAAK,SAAS,YAAY;AAE1B,WAAO;AACT,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ProcessSpec } from '@ansible-flow/core/run/command';
|
|
2
|
+
export interface ProbeReady {
|
|
3
|
+
kind: 'ready';
|
|
4
|
+
probe: string;
|
|
5
|
+
ansible_core: string;
|
|
6
|
+
python: string;
|
|
7
|
+
root: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class ProbeError extends Error {
|
|
10
|
+
readonly type: string;
|
|
11
|
+
constructor(message: string, type: string);
|
|
12
|
+
}
|
|
13
|
+
/** A call cancelled by its caller (AFD-39). */
|
|
14
|
+
export declare const CANCELLED_CALL = "Cancelled";
|
|
15
|
+
/** The oldest ansible-core the probe and the generated code support (ADR-0005). */
|
|
16
|
+
export declare const MIN_CORE: readonly [2, 19];
|
|
17
|
+
/** Whether an ansible-core version ("2.19.4", "2.20.0rc1") is at least MIN_CORE. */
|
|
18
|
+
export declare function coreSupported(version: string): boolean;
|
|
19
|
+
export declare class ProbeClient {
|
|
20
|
+
private readonly spec;
|
|
21
|
+
/** Repository root on this machine; probe paths under `inside` map back to it. */
|
|
22
|
+
private readonly root;
|
|
23
|
+
/**
|
|
24
|
+
* The root as the probe sees it (`/work` in a container, `/mnt/c/…` in WSL); none locally.
|
|
25
|
+
* `ready` (AFD-247): the probe says it when it starts (an SSH host knows its own home folder).
|
|
26
|
+
*/
|
|
27
|
+
private inside;
|
|
28
|
+
/** Where the probe's stderr and its lifecycle go (the Ansible Flow log). */
|
|
29
|
+
private readonly log;
|
|
30
|
+
/**
|
|
31
|
+
* AFD-247: done before the probe starts and before each call; a rejection fails that call. An
|
|
32
|
+
* SSH host has a copy of the repository, which this keeps current.
|
|
33
|
+
*/
|
|
34
|
+
private readonly before?;
|
|
35
|
+
private proc?;
|
|
36
|
+
private ready?;
|
|
37
|
+
private next;
|
|
38
|
+
private readonly queue;
|
|
39
|
+
private inflight?;
|
|
40
|
+
private readonly stderr;
|
|
41
|
+
constructor(spec: ProcessSpec,
|
|
42
|
+
/** Repository root on this machine; probe paths under `inside` map back to it. */
|
|
43
|
+
root: string,
|
|
44
|
+
/**
|
|
45
|
+
* The root as the probe sees it (`/work` in a container, `/mnt/c/…` in WSL); none locally.
|
|
46
|
+
* `ready` (AFD-247): the probe says it when it starts (an SSH host knows its own home folder).
|
|
47
|
+
*/
|
|
48
|
+
inside: string | undefined,
|
|
49
|
+
/** Where the probe's stderr and its lifecycle go (the Ansible Flow log). */
|
|
50
|
+
log?: (line: string) => void,
|
|
51
|
+
/**
|
|
52
|
+
* AFD-247: done before the probe starts and before each call; a rejection fails that call. An
|
|
53
|
+
* SSH host has a copy of the repository, which this keeps current.
|
|
54
|
+
*/
|
|
55
|
+
before?: (() => Promise<void>) | undefined);
|
|
56
|
+
/** Last stderr lines (Ansible warnings, tracebacks) for error reports. */
|
|
57
|
+
stderrTail(lines?: number): string;
|
|
58
|
+
start(): Promise<ProbeReady>;
|
|
59
|
+
private spawnProbe;
|
|
60
|
+
/**
|
|
61
|
+
* Queues a call. `signal` cancels it (AFD-39): a queued call leaves the queue; a running one
|
|
62
|
+
* fails and the probe restarts for the calls behind it, as after a timeout.
|
|
63
|
+
*/
|
|
64
|
+
call<T>(method: string, params?: Record<string, unknown>, timeoutMs?: number, signal?: AbortSignal): Promise<T>;
|
|
65
|
+
private cancel;
|
|
66
|
+
/** Sends the next queued call when none is running; its timeout starts now. */
|
|
67
|
+
private pump;
|
|
68
|
+
/** A call that took too long: it fails, and the probe restarts for the calls behind it. */
|
|
69
|
+
private timedOut;
|
|
70
|
+
/** A probe path (in the container or the WSL distribution) as a path on this machine. */
|
|
71
|
+
toHost(path: string): string;
|
|
72
|
+
/** Stops the probe and fails every call, running or queued. */
|
|
73
|
+
dispose(): void;
|
|
74
|
+
private keep;
|
|
75
|
+
/** Ends this process (the running call fails); queued calls stay for the next one. */
|
|
76
|
+
private stopProcess;
|
|
77
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stopStarted
|
|
3
|
+
} from "../chunks/chunk-JDPN3LTT.js";
|
|
4
|
+
import {
|
|
5
|
+
TIMEOUTS
|
|
6
|
+
} from "../chunks/chunk-IIFP6UQR.js";
|
|
7
|
+
|
|
8
|
+
// src/node/env/probeClient.ts
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import * as readline from "node:readline";
|
|
11
|
+
var ProbeError = class extends Error {
|
|
12
|
+
constructor(message, type) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.type = type;
|
|
15
|
+
}
|
|
16
|
+
type;
|
|
17
|
+
};
|
|
18
|
+
var STDERR_LINES = 200;
|
|
19
|
+
var CANCELLED_CALL = "Cancelled";
|
|
20
|
+
var MIN_CORE = [2, 19];
|
|
21
|
+
function coreSupported(version) {
|
|
22
|
+
const [major = 0, minor = 0] = version.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
23
|
+
return major > MIN_CORE[0] || major === MIN_CORE[0] && minor >= MIN_CORE[1];
|
|
24
|
+
}
|
|
25
|
+
var ProbeClient = class {
|
|
26
|
+
constructor(spec, root, inside, log = () => {
|
|
27
|
+
}, before) {
|
|
28
|
+
this.spec = spec;
|
|
29
|
+
this.root = root;
|
|
30
|
+
this.inside = inside;
|
|
31
|
+
this.log = log;
|
|
32
|
+
this.before = before;
|
|
33
|
+
}
|
|
34
|
+
spec;
|
|
35
|
+
root;
|
|
36
|
+
inside;
|
|
37
|
+
log;
|
|
38
|
+
before;
|
|
39
|
+
proc;
|
|
40
|
+
ready;
|
|
41
|
+
next = 1;
|
|
42
|
+
queue = [];
|
|
43
|
+
inflight;
|
|
44
|
+
stderr = [];
|
|
45
|
+
/** Last stderr lines (Ansible warnings, tracebacks) for error reports. */
|
|
46
|
+
stderrTail(lines = 20) {
|
|
47
|
+
return this.stderr.slice(-lines).join("\n");
|
|
48
|
+
}
|
|
49
|
+
start() {
|
|
50
|
+
if (this.ready) return this.ready;
|
|
51
|
+
const ready = (this.before ? this.before() : Promise.resolve()).then(() => this.spawnProbe());
|
|
52
|
+
this.ready = ready;
|
|
53
|
+
ready.catch(() => {
|
|
54
|
+
if (this.ready === ready && !this.proc) this.ready = void 0;
|
|
55
|
+
});
|
|
56
|
+
return ready;
|
|
57
|
+
}
|
|
58
|
+
spawnProbe() {
|
|
59
|
+
const proc = spawn(this.spec.command, this.spec.args, {
|
|
60
|
+
cwd: this.spec.cwd,
|
|
61
|
+
env: { ...process.env, ...this.spec.env },
|
|
62
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
63
|
+
windowsHide: true
|
|
64
|
+
});
|
|
65
|
+
this.proc = proc;
|
|
66
|
+
this.log(`probe: starting ${this.spec.command} for ${this.root}`);
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
let first = true;
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
reject(new Error(`The Ansible probe did not start in time
|
|
71
|
+
${this.stderrTail()}`));
|
|
72
|
+
this.stopProcess(proc, new Error("The Ansible probe did not start in time"));
|
|
73
|
+
}, TIMEOUTS.probeReady);
|
|
74
|
+
readline.createInterface({ input: proc.stdout }).on("line", (line) => {
|
|
75
|
+
let msg;
|
|
76
|
+
try {
|
|
77
|
+
msg = JSON.parse(line);
|
|
78
|
+
} catch {
|
|
79
|
+
this.keep(line);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (first) {
|
|
83
|
+
first = false;
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
if (msg.kind !== "ready") {
|
|
86
|
+
reject(new Error(String(msg.message ?? "The Ansible probe failed to start")));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const ready = msg;
|
|
90
|
+
if (!coreSupported(ready.ansible_core)) {
|
|
91
|
+
const why = `This environment has ansible-core ${ready.ansible_core}; Ansible Flow needs ${MIN_CORE.join(".")} or later. Choose an execution environment or Python with a newer ansible-core (afd.environment.*).`;
|
|
92
|
+
this.log(`probe: ${why}`);
|
|
93
|
+
reject(new Error(why));
|
|
94
|
+
this.stopProcess(proc, new Error(why));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.log(
|
|
98
|
+
`probe: ready (probe ${ready.probe}, ansible-core ${ready.ansible_core}, Python ${ready.python})`
|
|
99
|
+
);
|
|
100
|
+
if (this.inside === "ready") this.inside = ready.root.replace(/\/+$/, "");
|
|
101
|
+
resolve(ready);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const p = this.inflight;
|
|
105
|
+
if (!p || Number(msg.id) !== p.id) return;
|
|
106
|
+
clearTimeout(p.timer);
|
|
107
|
+
this.inflight = void 0;
|
|
108
|
+
const error = msg.error;
|
|
109
|
+
if (error)
|
|
110
|
+
p.reject(new ProbeError(error.message ?? "probe error", error.type ?? "Error"));
|
|
111
|
+
else p.resolve(msg.result);
|
|
112
|
+
this.pump();
|
|
113
|
+
});
|
|
114
|
+
readline.createInterface({ input: proc.stderr }).on("line", (line) => this.keep(line));
|
|
115
|
+
const end = (why) => {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
const err = new Error(`${why}
|
|
118
|
+
${this.stderrTail()}`);
|
|
119
|
+
reject(err);
|
|
120
|
+
this.log(`probe: ${why}`);
|
|
121
|
+
this.stopProcess(proc, err);
|
|
122
|
+
this.pump();
|
|
123
|
+
};
|
|
124
|
+
proc.on("error", (e) => end(`Could not start ${this.spec.command}: ${e.message}`));
|
|
125
|
+
proc.on("exit", (code) => end(`The Ansible probe exited (code ${code})`));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Queues a call. `signal` cancels it (AFD-39): a queued call leaves the queue; a running one
|
|
130
|
+
* fails and the probe restarts for the calls behind it, as after a timeout.
|
|
131
|
+
*/
|
|
132
|
+
call(method, params = {}, timeoutMs = TIMEOUTS.probeCall, signal) {
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
if (signal?.aborted) return reject(new Error(CANCELLED_CALL));
|
|
135
|
+
const pending = {
|
|
136
|
+
id: this.next++,
|
|
137
|
+
method,
|
|
138
|
+
params,
|
|
139
|
+
timeoutMs,
|
|
140
|
+
resolve,
|
|
141
|
+
reject
|
|
142
|
+
};
|
|
143
|
+
signal?.addEventListener("abort", () => this.cancel(pending), { once: true });
|
|
144
|
+
this.queue.push(pending);
|
|
145
|
+
this.pump();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
cancel(p) {
|
|
149
|
+
const queued = this.queue.indexOf(p);
|
|
150
|
+
if (queued >= 0) {
|
|
151
|
+
this.queue.splice(queued, 1);
|
|
152
|
+
p.reject(new Error(CANCELLED_CALL));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (this.inflight !== p) return;
|
|
156
|
+
this.inflight = void 0;
|
|
157
|
+
clearTimeout(p.timer);
|
|
158
|
+
this.log(`probe: "${p.method}" was cancelled; restarting it for the next call`);
|
|
159
|
+
p.reject(new Error(CANCELLED_CALL));
|
|
160
|
+
if (this.proc) this.stopProcess(this.proc, new Error(CANCELLED_CALL));
|
|
161
|
+
this.pump();
|
|
162
|
+
}
|
|
163
|
+
/** Sends the next queued call when none is running; its timeout starts now. */
|
|
164
|
+
pump() {
|
|
165
|
+
if (this.inflight) return;
|
|
166
|
+
const p = this.queue.shift();
|
|
167
|
+
if (!p) return;
|
|
168
|
+
this.inflight = p;
|
|
169
|
+
this.start().then(() => this.before?.()).then(
|
|
170
|
+
() => {
|
|
171
|
+
if (this.inflight !== p) return;
|
|
172
|
+
p.timer = setTimeout(() => this.timedOut(p), p.timeoutMs);
|
|
173
|
+
this.proc?.stdin?.write(
|
|
174
|
+
`${JSON.stringify({ id: p.id, method: p.method, params: p.params })}
|
|
175
|
+
`
|
|
176
|
+
);
|
|
177
|
+
},
|
|
178
|
+
(err) => {
|
|
179
|
+
if (this.inflight !== p) return;
|
|
180
|
+
this.inflight = void 0;
|
|
181
|
+
p.reject(err);
|
|
182
|
+
this.pump();
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
/** A call that took too long: it fails, and the probe restarts for the calls behind it. */
|
|
187
|
+
timedOut(p) {
|
|
188
|
+
if (this.inflight !== p) return;
|
|
189
|
+
this.inflight = void 0;
|
|
190
|
+
const why = `The Ansible probe did not answer "${p.method}" within ${p.timeoutMs / 1e3} s`;
|
|
191
|
+
this.log(`probe: ${why}; restarting it for the next call`);
|
|
192
|
+
p.reject(new Error(why));
|
|
193
|
+
if (this.proc) this.stopProcess(this.proc, new Error(why));
|
|
194
|
+
this.pump();
|
|
195
|
+
}
|
|
196
|
+
/** A probe path (in the container or the WSL distribution) as a path on this machine. */
|
|
197
|
+
toHost(path) {
|
|
198
|
+
if (!this.inside || !path.startsWith(`${this.inside}/`)) return path;
|
|
199
|
+
const rel = path.slice(this.inside.length + 1);
|
|
200
|
+
const sep = this.root.includes("\\") ? "\\" : "/";
|
|
201
|
+
return `${this.root.replace(/[\\/]+$/, "")}${sep}${rel.split("/").join(sep)}`;
|
|
202
|
+
}
|
|
203
|
+
/** Stops the probe and fails every call, running or queued. */
|
|
204
|
+
dispose() {
|
|
205
|
+
const stopped = new Error("The Ansible probe was stopped");
|
|
206
|
+
for (const p of this.queue.splice(0)) p.reject(stopped);
|
|
207
|
+
if (this.proc) this.stopProcess(this.proc, stopped);
|
|
208
|
+
else if (this.inflight) {
|
|
209
|
+
this.inflight.reject(stopped);
|
|
210
|
+
this.inflight = void 0;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
keep(line) {
|
|
214
|
+
this.stderr.push(line);
|
|
215
|
+
if (this.stderr.length > STDERR_LINES) this.stderr.splice(0, this.stderr.length - STDERR_LINES);
|
|
216
|
+
this.log(`probe stderr: ${line}`);
|
|
217
|
+
}
|
|
218
|
+
/** Ends this process (the running call fails); queued calls stay for the next one. */
|
|
219
|
+
stopProcess(proc, err) {
|
|
220
|
+
if (this.proc !== proc) return;
|
|
221
|
+
proc.stdin?.end();
|
|
222
|
+
setTimeout(() => {
|
|
223
|
+
if (proc.exitCode !== null) return;
|
|
224
|
+
proc.kill();
|
|
225
|
+
void stopStarted(this.spec);
|
|
226
|
+
}, 3e3).unref();
|
|
227
|
+
const p = this.inflight;
|
|
228
|
+
if (p) {
|
|
229
|
+
clearTimeout(p.timer);
|
|
230
|
+
this.inflight = void 0;
|
|
231
|
+
p.reject(err);
|
|
232
|
+
}
|
|
233
|
+
this.proc = void 0;
|
|
234
|
+
this.ready = void 0;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
export {
|
|
238
|
+
CANCELLED_CALL,
|
|
239
|
+
MIN_CORE,
|
|
240
|
+
ProbeClient,
|
|
241
|
+
ProbeError,
|
|
242
|
+
coreSupported
|
|
243
|
+
};
|
|
244
|
+
//# sourceMappingURL=probeClient.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/node/env/probeClient.ts"],
|
|
4
|
+
"sourcesContent": ["// JSON-lines session with the probe (ADR-0005): spawn, ready line, request ids, timeouts, restart on\n// the next call after a crash, a stderr ring buffer for error reports, and path mapping back to the\n// repository when the probe runs in a container.\n//\n// The probe answers one request at a time, so calls queue here and go out one by one (AFD-32): a\n// call's timeout counts from when it is sent, never while it waits behind another one, and a call\n// that times out restarts the probe for the calls behind it instead of failing them too.\nimport { type ChildProcess, spawn } from 'node:child_process';\nimport * as readline from 'node:readline';\nimport type { ProcessSpec } from '../../core/run/command';\nimport { stopStarted } from '../run/stop';\nimport { TIMEOUTS } from '../timeouts';\n\nexport interface ProbeReady {\n kind: 'ready';\n probe: string;\n ansible_core: string;\n python: string;\n root: string;\n}\n\nexport class ProbeError extends Error {\n constructor(\n message: string,\n readonly type: string,\n ) {\n super(message);\n }\n}\n\nconst STDERR_LINES = 200;\n/** A call cancelled by its caller (AFD-39). */\nexport const CANCELLED_CALL = 'Cancelled';\n\n/** The oldest ansible-core the probe and the generated code support (ADR-0005). */\nexport const MIN_CORE = [2, 19] as const;\n\n/** Whether an ansible-core version (\"2.19.4\", \"2.20.0rc1\") is at least MIN_CORE. */\nexport function coreSupported(version: string): boolean {\n const [major = 0, minor = 0] = version.split('.').map((p) => Number.parseInt(p, 10) || 0);\n return major > MIN_CORE[0] || (major === MIN_CORE[0] && minor >= MIN_CORE[1]);\n}\n\ninterface Pending {\n id: number;\n method: string;\n params: Record<string, unknown>;\n timeoutMs: number;\n resolve: (value: unknown) => void;\n reject: (reason: Error) => void;\n timer?: NodeJS.Timeout;\n}\n\nexport class ProbeClient {\n private proc?: ChildProcess;\n private ready?: Promise<ProbeReady>;\n private next = 1;\n private readonly queue: Pending[] = [];\n private inflight?: Pending;\n private readonly stderr: string[] = [];\n\n constructor(\n private readonly spec: ProcessSpec,\n /** Repository root on this machine; probe paths under `inside` map back to it. */\n private readonly root: string,\n /**\n * The root as the probe sees it (`/work` in a container, `/mnt/c/\u2026` in WSL); none locally.\n * `ready` (AFD-247): the probe says it when it starts (an SSH host knows its own home folder).\n */\n private inside: string | undefined,\n /** Where the probe's stderr and its lifecycle go (the Ansible Flow log). */\n private readonly log: (line: string) => void = () => {},\n /**\n * AFD-247: done before the probe starts and before each call; a rejection fails that call. An\n * SSH host has a copy of the repository, which this keeps current.\n */\n private readonly before?: () => Promise<void>,\n ) {}\n\n /** Last stderr lines (Ansible warnings, tracebacks) for error reports. */\n stderrTail(lines = 20): string {\n return this.stderr.slice(-lines).join('\\n');\n }\n\n start(): Promise<ProbeReady> {\n if (this.ready) return this.ready;\n const ready = (this.before ? this.before() : Promise.resolve()).then(() => this.spawnProbe());\n this.ready = ready;\n // The step before failed (no connection): the next call tries again.\n ready.catch(() => {\n if (this.ready === ready && !this.proc) this.ready = undefined;\n });\n return ready;\n }\n\n private spawnProbe(): Promise<ProbeReady> {\n const proc = spawn(this.spec.command, this.spec.args, {\n cwd: this.spec.cwd,\n env: { ...process.env, ...this.spec.env },\n stdio: ['pipe', 'pipe', 'pipe'],\n windowsHide: true,\n });\n this.proc = proc;\n this.log(`probe: starting ${this.spec.command} for ${this.root}`);\n return new Promise<ProbeReady>((resolve, reject) => {\n let first = true;\n const timer = setTimeout(() => {\n reject(new Error(`The Ansible probe did not start in time\\n${this.stderrTail()}`));\n this.stopProcess(proc, new Error('The Ansible probe did not start in time'));\n }, TIMEOUTS.probeReady);\n readline\n .createInterface({ input: proc.stdout as NodeJS.ReadableStream })\n .on('line', (line) => {\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(line) as Record<string, unknown>;\n } catch {\n this.keep(line);\n return;\n }\n if (first) {\n first = false;\n clearTimeout(timer);\n if (msg.kind !== 'ready') {\n reject(new Error(String(msg.message ?? 'The Ansible probe failed to start')));\n return;\n }\n const ready = msg as unknown as ProbeReady;\n if (!coreSupported(ready.ansible_core)) {\n const why = `This environment has ansible-core ${ready.ansible_core}; Ansible Flow needs ${MIN_CORE.join('.')} or later. Choose an execution environment or Python with a newer ansible-core (afd.environment.*).`;\n this.log(`probe: ${why}`);\n reject(new Error(why));\n this.stopProcess(proc, new Error(why));\n return;\n }\n this.log(\n `probe: ready (probe ${ready.probe}, ansible-core ${ready.ansible_core}, Python ${ready.python})`,\n );\n if (this.inside === 'ready') this.inside = ready.root.replace(/\\/+$/, '');\n resolve(ready);\n return;\n }\n const p = this.inflight;\n if (!p || Number(msg.id) !== p.id) return;\n clearTimeout(p.timer);\n this.inflight = undefined;\n const error = msg.error as { message?: string; type?: string } | undefined;\n if (error)\n p.reject(new ProbeError(error.message ?? 'probe error', error.type ?? 'Error'));\n else p.resolve(msg.result);\n this.pump();\n });\n readline\n .createInterface({ input: proc.stderr as NodeJS.ReadableStream })\n .on('line', (line) => this.keep(line));\n const end = (why: string) => {\n clearTimeout(timer);\n const err = new Error(`${why}\\n${this.stderrTail()}`);\n reject(err);\n this.log(`probe: ${why}`);\n this.stopProcess(proc, err);\n // The calls behind the one that was running get a fresh probe.\n this.pump();\n };\n proc.on('error', (e) => end(`Could not start ${this.spec.command}: ${e.message}`));\n proc.on('exit', (code) => end(`The Ansible probe exited (code ${code})`));\n });\n }\n\n /**\n * Queues a call. `signal` cancels it (AFD-39): a queued call leaves the queue; a running one\n * fails and the probe restarts for the calls behind it, as after a timeout.\n */\n call<T>(\n method: string,\n params: Record<string, unknown> = {},\n timeoutMs: number = TIMEOUTS.probeCall,\n signal?: AbortSignal,\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n if (signal?.aborted) return reject(new Error(CANCELLED_CALL));\n const pending: Pending = {\n id: this.next++,\n method,\n params,\n timeoutMs,\n resolve: resolve as (v: unknown) => void,\n reject,\n };\n signal?.addEventListener('abort', () => this.cancel(pending), { once: true });\n this.queue.push(pending);\n this.pump();\n });\n }\n\n private cancel(p: Pending): void {\n const queued = this.queue.indexOf(p);\n if (queued >= 0) {\n this.queue.splice(queued, 1);\n p.reject(new Error(CANCELLED_CALL));\n return;\n }\n if (this.inflight !== p) return;\n this.inflight = undefined;\n clearTimeout(p.timer);\n this.log(`probe: \"${p.method}\" was cancelled; restarting it for the next call`);\n p.reject(new Error(CANCELLED_CALL));\n if (this.proc) this.stopProcess(this.proc, new Error(CANCELLED_CALL));\n this.pump();\n }\n\n /** Sends the next queued call when none is running; its timeout starts now. */\n private pump(): void {\n if (this.inflight) return;\n const p = this.queue.shift();\n if (!p) return;\n this.inflight = p;\n this.start()\n .then(() => this.before?.())\n .then(\n () => {\n if (this.inflight !== p) return;\n p.timer = setTimeout(() => this.timedOut(p), p.timeoutMs);\n this.proc?.stdin?.write(\n `${JSON.stringify({ id: p.id, method: p.method, params: p.params })}\\n`,\n );\n },\n (err: Error) => {\n if (this.inflight !== p) return;\n this.inflight = undefined;\n p.reject(err);\n this.pump();\n },\n );\n }\n\n /** A call that took too long: it fails, and the probe restarts for the calls behind it. */\n private timedOut(p: Pending): void {\n if (this.inflight !== p) return;\n this.inflight = undefined;\n const why = `The Ansible probe did not answer \"${p.method}\" within ${p.timeoutMs / 1000} s`;\n this.log(`probe: ${why}; restarting it for the next call`);\n p.reject(new Error(why));\n if (this.proc) this.stopProcess(this.proc, new Error(why));\n this.pump();\n }\n\n /** A probe path (in the container or the WSL distribution) as a path on this machine. */\n toHost(path: string): string {\n if (!this.inside || !path.startsWith(`${this.inside}/`)) return path;\n const rel = path.slice(this.inside.length + 1);\n const sep = this.root.includes('\\\\') ? '\\\\' : '/';\n return `${this.root.replace(/[\\\\/]+$/, '')}${sep}${rel.split('/').join(sep)}`;\n }\n\n /** Stops the probe and fails every call, running or queued. */\n dispose(): void {\n const stopped = new Error('The Ansible probe was stopped');\n for (const p of this.queue.splice(0)) p.reject(stopped);\n if (this.proc) this.stopProcess(this.proc, stopped);\n else if (this.inflight) {\n this.inflight.reject(stopped);\n this.inflight = undefined;\n }\n }\n\n private keep(line: string): void {\n this.stderr.push(line);\n if (this.stderr.length > STDERR_LINES) this.stderr.splice(0, this.stderr.length - STDERR_LINES);\n this.log(`probe stderr: ${line}`);\n }\n\n /** Ends this process (the running call fails); queued calls stay for the next one. */\n private stopProcess(proc: ChildProcess, err: Error): void {\n if (this.proc !== proc) return;\n // Closing stdin ends the serve loop, so the container exits and removes itself.\n proc.stdin?.end();\n setTimeout(() => {\n if (proc.exitCode !== null) return;\n proc.kill();\n // AFD-131: a call that hangs keeps its container running after the client is gone.\n void stopStarted(this.spec);\n }, 3000).unref();\n const p = this.inflight;\n if (p) {\n clearTimeout(p.timer);\n this.inflight = undefined;\n p.reject(err);\n }\n this.proc = undefined;\n this.ready = undefined;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|