@galaxy-stack/ai-coder-core 0.1.0 → 0.3.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/CHANGELOG.d/2026-09-19-advisory-guard-policy.md +25 -0
  2. package/CHANGELOG.d/2026-09-19-research-citation-parsing.md +16 -0
  3. package/CHANGELOG.d/2026-09-19-research-rejection-evidence.md +15 -0
  4. package/CHANGELOG.d/2026-09-19-test-journal-reporter.md +8 -0
  5. package/CHANGELOG.d/2026-09-21-evidence-missing-remediation.md +15 -0
  6. package/CHANGELOG.d/2026-09-21-observation-nudge-context.md +12 -0
  7. package/CHANGELOG.d/2026-09-22-model-retry-budget.md +18 -0
  8. package/CHANGELOG.d/2026-09-22-no-progress-recovery-turn.md +19 -0
  9. package/CHANGELOG.d/2026-09-23-agent-platform.md +5 -0
  10. package/CHANGELOG.d/2026-09-23-conservative-output-reserve.md +16 -0
  11. package/CHANGELOG.d/2026-09-24-approval-workspace-review.md +7 -0
  12. package/CHANGELOG.d/2026-09-24-incremental-ollama.md +5 -0
  13. package/CHANGELOG.d/2026-09-25-tail-anchored-bounding.md +5 -0
  14. package/CHANGELOG.d/2026-09-26-default-model-deepseek.md +8 -0
  15. package/CHANGELOG.d/README.md +16 -0
  16. package/CHANGELOG.md +166 -0
  17. package/README.md +119 -8
  18. package/dist/adapters/node/config/manual-provider-config.d.ts +19 -0
  19. package/dist/adapters/node/config/manual-provider-config.d.ts.map +1 -0
  20. package/dist/adapters/node/config/manual-provider-config.js +91 -0
  21. package/dist/adapters/node/config/manual-provider-config.js.map +1 -0
  22. package/dist/adapters/node/host/command-containment.d.ts +45 -0
  23. package/dist/adapters/node/host/command-containment.d.ts.map +1 -0
  24. package/dist/adapters/node/host/command-containment.js +580 -0
  25. package/dist/adapters/node/host/command-containment.js.map +1 -0
  26. package/dist/adapters/node/host/content-hash.d.ts +2 -0
  27. package/dist/adapters/node/host/content-hash.d.ts.map +1 -0
  28. package/dist/adapters/node/host/content-hash.js +5 -0
  29. package/dist/adapters/node/host/content-hash.js.map +1 -0
  30. package/dist/adapters/node/host/file-run-store.d.ts +29 -0
  31. package/dist/adapters/node/host/file-run-store.d.ts.map +1 -0
  32. package/dist/adapters/node/host/file-run-store.js +94 -0
  33. package/dist/adapters/node/host/file-run-store.js.map +1 -0
  34. package/dist/adapters/node/host/host-environment.d.ts +9 -0
  35. package/dist/adapters/node/host/host-environment.d.ts.map +1 -0
  36. package/dist/adapters/node/host/host-environment.js +29 -0
  37. package/dist/adapters/node/host/host-environment.js.map +1 -0
  38. package/dist/adapters/node/host/node-command-port.d.ts +21 -0
  39. package/dist/adapters/node/host/node-command-port.d.ts.map +1 -0
  40. package/dist/adapters/node/host/node-command-port.js +281 -0
  41. package/dist/adapters/node/host/node-command-port.js.map +1 -0
  42. package/dist/adapters/node/host/node-workspace-evidence-verifier.d.ts +22 -0
  43. package/dist/adapters/node/host/node-workspace-evidence-verifier.d.ts.map +1 -0
  44. package/dist/adapters/node/host/node-workspace-evidence-verifier.js +384 -0
  45. package/dist/adapters/node/host/node-workspace-evidence-verifier.js.map +1 -0
  46. package/dist/adapters/node/host/node-workspace-port.d.ts +87 -0
  47. package/dist/adapters/node/host/node-workspace-port.d.ts.map +1 -0
  48. package/dist/adapters/node/host/node-workspace-port.js +860 -0
  49. package/dist/adapters/node/host/node-workspace-port.js.map +1 -0
  50. package/dist/adapters/node/host/node-workspace-snapshot.d.ts +61 -0
  51. package/dist/adapters/node/host/node-workspace-snapshot.d.ts.map +1 -0
  52. package/dist/adapters/node/host/node-workspace-snapshot.js +282 -0
  53. package/dist/adapters/node/host/node-workspace-snapshot.js.map +1 -0
  54. package/dist/adapters/node/host/path-scope.d.ts +17 -0
  55. package/dist/adapters/node/host/path-scope.d.ts.map +1 -0
  56. package/dist/adapters/node/host/path-scope.js +89 -0
  57. package/dist/adapters/node/host/path-scope.js.map +1 -0
  58. package/dist/adapters/node/host/project-tools.d.ts +42 -0
  59. package/dist/adapters/node/host/project-tools.d.ts.map +1 -0
  60. package/dist/adapters/node/host/project-tools.js +362 -0
  61. package/dist/adapters/node/host/project-tools.js.map +1 -0
  62. package/dist/adapters/node/mcp/mcp-client.d.ts +206 -0
  63. package/dist/adapters/node/mcp/mcp-client.d.ts.map +1 -0
  64. package/dist/adapters/node/mcp/mcp-client.js +128 -0
  65. package/dist/adapters/node/mcp/mcp-client.js.map +1 -0
  66. package/dist/adapters/node/mcp/oauth-provider.d.ts +39 -0
  67. package/dist/adapters/node/mcp/oauth-provider.d.ts.map +1 -0
  68. package/dist/adapters/node/mcp/oauth-provider.js +168 -0
  69. package/dist/adapters/node/mcp/oauth-provider.js.map +1 -0
  70. package/dist/adapters/node/memory/sqlite-memory.d.ts +22 -0
  71. package/dist/adapters/node/memory/sqlite-memory.d.ts.map +1 -0
  72. package/dist/adapters/node/memory/sqlite-memory.js +83 -0
  73. package/dist/adapters/node/memory/sqlite-memory.js.map +1 -0
  74. package/dist/adapters/node/provider/ollama-chat-stream.d.ts +17 -0
  75. package/dist/adapters/node/provider/ollama-chat-stream.d.ts.map +1 -0
  76. package/dist/adapters/node/provider/ollama-chat-stream.js +176 -0
  77. package/dist/adapters/node/provider/ollama-chat-stream.js.map +1 -0
  78. package/dist/adapters/node/provider/ollama-coding-model.d.ts +89 -0
  79. package/dist/adapters/node/provider/ollama-coding-model.d.ts.map +1 -0
  80. package/dist/adapters/node/provider/ollama-coding-model.js +464 -0
  81. package/dist/adapters/node/provider/ollama-coding-model.js.map +1 -0
  82. package/dist/adapters/node/provider/ollama-research-port.d.ts +32 -0
  83. package/dist/adapters/node/provider/ollama-research-port.d.ts.map +1 -0
  84. package/dist/adapters/node/provider/ollama-research-port.js +346 -0
  85. package/dist/adapters/node/provider/ollama-research-port.js.map +1 -0
  86. package/dist/adapters/node/skills/directory-skills.d.ts +12 -0
  87. package/dist/adapters/node/skills/directory-skills.d.ts.map +1 -0
  88. package/dist/adapters/node/skills/directory-skills.js +90 -0
  89. package/dist/adapters/node/skills/directory-skills.js.map +1 -0
  90. package/dist/adapters/node/tools/tool-executor.d.ts +91 -0
  91. package/dist/adapters/node/tools/tool-executor.d.ts.map +1 -0
  92. package/dist/adapters/node/tools/tool-executor.js +948 -0
  93. package/dist/adapters/node/tools/tool-executor.js.map +1 -0
  94. package/dist/adapters/node/tools/workspace-review.d.ts +13 -0
  95. package/dist/adapters/node/tools/workspace-review.d.ts.map +1 -0
  96. package/dist/adapters/node/tools/workspace-review.js +81 -0
  97. package/dist/adapters/node/tools/workspace-review.js.map +1 -0
  98. package/dist/agent/index.d.ts +79 -0
  99. package/dist/agent/index.d.ts.map +1 -0
  100. package/dist/agent/index.js +97 -0
  101. package/dist/agent/index.js.map +1 -0
  102. package/dist/approval/approval-policy.d.ts.map +1 -1
  103. package/dist/approval/approval-policy.js +4 -1
  104. package/dist/approval/approval-policy.js.map +1 -1
  105. package/dist/context/checkpoint.d.ts +5 -0
  106. package/dist/context/checkpoint.d.ts.map +1 -1
  107. package/dist/context/checkpoint.js +36 -4
  108. package/dist/context/checkpoint.js.map +1 -1
  109. package/dist/context/context-manager.d.ts.map +1 -1
  110. package/dist/context/context-manager.js +21 -8
  111. package/dist/context/context-manager.js.map +1 -1
  112. package/dist/context/context-profile.d.ts +1 -1
  113. package/dist/context/context-profile.js +2 -2
  114. package/dist/context/tool-output.d.ts.map +1 -1
  115. package/dist/context/tool-output.js +23 -4
  116. package/dist/context/tool-output.js.map +1 -1
  117. package/dist/prompt/prompt-assembler.d.ts +2 -1
  118. package/dist/prompt/prompt-assembler.d.ts.map +1 -1
  119. package/dist/prompt/prompt-assembler.js +40 -3
  120. package/dist/prompt/prompt-assembler.js.map +1 -1
  121. package/dist/runtime/completion-gate.d.ts +1 -1
  122. package/dist/runtime/completion-gate.d.ts.map +1 -1
  123. package/dist/runtime/completion-gate.js +12 -0
  124. package/dist/runtime/completion-gate.js.map +1 -1
  125. package/dist/runtime/index.d.ts +1 -0
  126. package/dist/runtime/index.d.ts.map +1 -1
  127. package/dist/runtime/index.js +1 -0
  128. package/dist/runtime/index.js.map +1 -1
  129. package/dist/runtime/research-citations.d.ts +15 -0
  130. package/dist/runtime/research-citations.d.ts.map +1 -0
  131. package/dist/runtime/research-citations.js +49 -0
  132. package/dist/runtime/research-citations.js.map +1 -0
  133. package/dist/runtime/run-controller.d.ts +6 -0
  134. package/dist/runtime/run-controller.d.ts.map +1 -1
  135. package/dist/runtime/run-controller.js +276 -65
  136. package/dist/runtime/run-controller.js.map +1 -1
  137. package/dist/runtime/runtime-types.d.ts +30 -0
  138. package/dist/runtime/runtime-types.d.ts.map +1 -1
  139. package/dist/tools/settings-types.js +1 -1
  140. package/dist/tools/settings-types.js.map +1 -1
  141. package/dist/tools/tool-registry.js +1 -1
  142. package/dist/tools/tool-registry.js.map +1 -1
  143. package/docs/AGENT_PLATFORM.md +91 -0
  144. package/docs/ARCHITECTURE.md +13 -2
  145. package/docs/GALAXY_AGENT_PLATFORM_PLAN.md +176 -0
  146. package/docs/GALAXY_AGENT_PLATFORM_TODO.md +181 -0
  147. package/docs/HOST_CONFORMANCE.md +38 -0
  148. package/docs/PROMPT_CONTRACT.md +9 -0
  149. package/package.json +25 -9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manual-provider-config.js","sourceRoot":"","sources":["../../../../src/adapters/node/config/manual-provider-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ export type CommandContainmentMode = "best_effort" | "required";
2
+ export type CommandContainmentBackend = "linux_bubblewrap" | "macos_sandbox_exec" | "none";
3
+ export interface CommandContainmentProbeResult {
4
+ readonly available: boolean;
5
+ readonly backend: CommandContainmentBackend;
6
+ readonly descendantChildSpawned: boolean;
7
+ readonly descendantQuiescent: boolean;
8
+ readonly executable: string | null;
9
+ readonly hardLinkWriteDenied: boolean;
10
+ readonly networkDenied: boolean;
11
+ readonly outsideWriteDenied: boolean;
12
+ readonly privateTempWrite: boolean;
13
+ readonly reason: string | null;
14
+ readonly systemRead: boolean;
15
+ readonly toolchainExecution: boolean;
16
+ readonly verified: boolean;
17
+ readonly workspaceWrite: boolean;
18
+ }
19
+ export interface CommandContainmentStatus {
20
+ readonly active: boolean;
21
+ readonly backend: CommandContainmentBackend;
22
+ readonly executable: string | null;
23
+ readonly filesystemReads: "host_visible" | "system_and_workspace";
24
+ readonly filesystemWrites: "uncontained" | "workspace_and_private_temp";
25
+ readonly mode: CommandContainmentMode;
26
+ readonly network: "denied" | "uncontained";
27
+ readonly reason: string | null;
28
+ readonly state: "active" | "probe_failed" | "unavailable" | "unverified";
29
+ readonly verified: boolean;
30
+ }
31
+ /**
32
+ * Run the host conformance check once per process. A binary merely existing is
33
+ * not enough. Only a backend that passes every probe assertion can activate.
34
+ */
35
+ export declare function probeCommandContainment(): Promise<CommandContainmentProbeResult>;
36
+ export declare function containmentStatus(mode: CommandContainmentMode, probe: CommandContainmentProbeResult): CommandContainmentStatus;
37
+ export declare function buildBubblewrapArguments(input: Readonly<{
38
+ command: string;
39
+ commandArguments: readonly string[];
40
+ cwd: string;
41
+ environment: NodeJS.ProcessEnv;
42
+ workspace: string;
43
+ }>): readonly string[];
44
+ export declare function assertWorkspaceHasNoHardLinks(workspace: string): Promise<void>;
45
+ //# sourceMappingURL=command-containment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-containment.d.ts","sourceRoot":"","sources":["../../../../src/adapters/node/host/command-containment.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG,UAAU,CAAC;AAEhE,MAAM,MAAM,yBAAyB,GACjC,kBAAkB,GAClB,oBAAoB,GACpB,MAAM,CAAC;AAEX,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;IAC5C,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;IACzC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,eAAe,EAAE,cAAc,GAAG,sBAAsB,CAAC;IAClE,QAAQ,CAAC,gBAAgB,EAAE,aAAa,GAAG,4BAA4B,CAAC;IACxE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,aAAa,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,cAAc,GAAG,aAAa,GAAG,YAAY,CAAC;IACzE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAohBD;;;GAGG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,6BAA6B,CAAC,CAGtF;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,sBAAsB,EAC5B,KAAK,EAAE,6BAA6B,GACnC,wBAAwB,CAmC1B;AAMD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,QAAQ,CAAC;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,GAAG,SAAS,MAAM,EAAE,CAuBrB;AAED,wBAAsB,6BAA6B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBpF"}
@@ -0,0 +1,580 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { constants, existsSync } from "node:fs";
4
+ import { access, link, lstat, mkdtemp, readFile, readdir, realpath, rm, writeFile } from "node:fs/promises";
5
+ import { createServer } from "node:net";
6
+ import { tmpdir } from "node:os";
7
+ import { delimiter, join, relative, sep } from "node:path";
8
+ const MACOS_SANDBOX_EXECUTABLE = "/usr/bin/sandbox-exec";
9
+ /*
10
+ * Prototype profile used only by the conformance probe. It is intentionally
11
+ * not selected for real NodeCommandPort runs: the probe currently demonstrates
12
+ * that pathname-based Seatbelt write rules do not protect outside inodes
13
+ * reachable through workspace hard links, and do not supervise descendants
14
+ * that create a new process group.
15
+ *
16
+ * The profile is a write/network experiment, not a confidentiality boundary.
17
+ * Broad reads keep the installed Node/toolchain visible. There are no TTY,
18
+ * pseudo-terminal, or network allowances.
19
+ */
20
+ const MACOS_CONFORMANCE_PROFILE = `(version 1)
21
+ (deny default)
22
+ (allow file-read*)
23
+ (allow process-exec)
24
+ (allow process-fork)
25
+ (allow signal (target self))
26
+ (allow sysctl-read)
27
+ (allow file-write*
28
+ (subpath (param "WORKSPACE"))
29
+ (subpath (param "PRIVATE_TMP"))
30
+ (literal "/dev/stdout")
31
+ (literal "/dev/stderr")
32
+ (literal "/dev/null"))
33
+ (allow mach-lookup
34
+ (global-name "com.apple.system.opendirectoryd.libinfo")
35
+ (global-name "com.apple.system.opendirectoryd.membership")
36
+ (global-name "com.apple.bsd.dirhelper")
37
+ (global-name "com.apple.SecurityServer")
38
+ (global-name "com.apple.trustd")
39
+ (global-name "com.apple.trustd.agent"))`;
40
+ const PROBE_TIMEOUT_MS = 5_000;
41
+ const DESCENDANT_OBSERVATION_MS = 700;
42
+ const MACOS_PROBE_SCRIPT = String.raw `
43
+ const fs = require("node:fs");
44
+ const net = require("node:net");
45
+ const { spawn } = require("node:child_process");
46
+ const result = {
47
+ descendantChildSpawned: false,
48
+ hardLinkWriteDenied: false,
49
+ networkDenied: false,
50
+ outsideWriteDenied: false,
51
+ privateTempWrite: false,
52
+ systemRead: false,
53
+ workspaceWrite: false,
54
+ };
55
+ try {
56
+ fs.readFileSync("/etc/hosts");
57
+ result.systemRead = true;
58
+ fs.writeFileSync(process.env.GALAXY_PROBE_WORKSPACE_FILE, "workspace-ok", "utf8");
59
+ result.workspaceWrite = true;
60
+ fs.writeFileSync(process.env.GALAXY_PROBE_TEMP_FILE, "temp-ok", "utf8");
61
+ result.privateTempWrite = true;
62
+ try {
63
+ fs.writeFileSync(process.env.GALAXY_PROBE_OUTSIDE_FILE, "changed", "utf8");
64
+ } catch (error) {
65
+ result.outsideWriteDenied = Boolean(error);
66
+ }
67
+ try {
68
+ fs.writeFileSync(process.env.GALAXY_PROBE_HARD_LINK_FILE, "changed-through-link", "utf8");
69
+ } catch (error) {
70
+ result.hardLinkWriteDenied = error && (error.code === "EACCES" || error.code === "EPERM");
71
+ }
72
+ const lateScript = 'setTimeout(() => require("node:fs").writeFileSync(process.env.GALAXY_PROBE_LATE_FILE, "late", "utf8"), 250)';
73
+ const child = spawn(process.execPath, ["-e", lateScript], {
74
+ detached: true,
75
+ env: process.env,
76
+ stdio: "ignore",
77
+ });
78
+ result.descendantChildSpawned = child.pid !== undefined;
79
+ child.unref();
80
+ } catch {}
81
+ let finished = false;
82
+ const finish = () => {
83
+ if (finished) return;
84
+ finished = true;
85
+ process.stdout.write(JSON.stringify(result));
86
+ };
87
+ const socket = net.createConnection({
88
+ host: "127.0.0.1",
89
+ port: Number(process.env.GALAXY_PROBE_PORT),
90
+ });
91
+ socket.once("connect", () => {
92
+ socket.destroy();
93
+ finish();
94
+ });
95
+ socket.once("error", (error) => {
96
+ result.networkDenied = Boolean(error);
97
+ finish();
98
+ });
99
+ socket.setTimeout(2_000, () => {
100
+ result.networkDenied = true;
101
+ socket.destroy();
102
+ finish();
103
+ });
104
+ `;
105
+ let cachedProbe;
106
+ function freezeProbe(result) {
107
+ return Object.freeze({ ...result });
108
+ }
109
+ function unavailableProbe(backend, executable, available, reason) {
110
+ return freezeProbe({
111
+ available,
112
+ backend,
113
+ descendantChildSpawned: false,
114
+ descendantQuiescent: false,
115
+ executable,
116
+ hardLinkWriteDenied: false,
117
+ networkDenied: false,
118
+ outsideWriteDenied: false,
119
+ privateTempWrite: false,
120
+ reason,
121
+ systemRead: false,
122
+ toolchainExecution: false,
123
+ verified: false,
124
+ workspaceWrite: false,
125
+ });
126
+ }
127
+ function errorMessage(error) {
128
+ return error instanceof Error ? error.message : String(error);
129
+ }
130
+ function delay(milliseconds) {
131
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
132
+ }
133
+ async function isExecutable(path) {
134
+ try {
135
+ await access(path, constants.X_OK);
136
+ return true;
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
142
+ async function executableOnPath(name) {
143
+ for (const entry of (process.env.PATH ?? "").split(delimiter)) {
144
+ if (entry.length === 0)
145
+ continue;
146
+ const candidate = join(entry, name);
147
+ if (await isExecutable(candidate))
148
+ return candidate;
149
+ }
150
+ return null;
151
+ }
152
+ function killProbeProcessGroup(pid) {
153
+ if (pid === undefined)
154
+ return;
155
+ try {
156
+ process.kill(-pid, "SIGKILL");
157
+ }
158
+ catch {
159
+ try {
160
+ process.kill(pid, "SIGKILL");
161
+ }
162
+ catch {
163
+ // Nothing remains to terminate.
164
+ }
165
+ }
166
+ }
167
+ async function runProbeProcess(executable, args, env) {
168
+ return await new Promise((resolveResult) => {
169
+ const child = spawn(executable, args, {
170
+ detached: true,
171
+ env,
172
+ shell: false,
173
+ stdio: ["ignore", "pipe", "pipe"],
174
+ });
175
+ let didTimeOut = false;
176
+ let forceFinish;
177
+ let settled = false;
178
+ let stderr = "";
179
+ let stdout = "";
180
+ const finish = (exitCode) => {
181
+ if (settled)
182
+ return;
183
+ settled = true;
184
+ clearTimeout(timeout);
185
+ if (forceFinish !== undefined)
186
+ clearTimeout(forceFinish);
187
+ resolveResult(Object.freeze({ exitCode, stderr, stdout, timedOut: didTimeOut }));
188
+ };
189
+ const timeout = setTimeout(() => {
190
+ didTimeOut = true;
191
+ killProbeProcessGroup(child.pid);
192
+ forceFinish = setTimeout(() => finish(null), 1_000);
193
+ forceFinish.unref();
194
+ }, PROBE_TIMEOUT_MS);
195
+ timeout.unref();
196
+ child.stdout?.setEncoding("utf8");
197
+ child.stderr?.setEncoding("utf8");
198
+ child.stdout?.on("data", (chunk) => {
199
+ if (stdout.length < 64_000)
200
+ stdout += chunk;
201
+ });
202
+ child.stderr?.on("data", (chunk) => {
203
+ if (stderr.length < 64_000)
204
+ stderr += chunk;
205
+ });
206
+ child.once("error", (error) => {
207
+ stderr += errorMessage(error);
208
+ finish(null);
209
+ });
210
+ child.once("close", (exitCode) => finish(exitCode));
211
+ });
212
+ }
213
+ async function listenOnLoopback(server) {
214
+ return await new Promise((resolvePort, reject) => {
215
+ const onError = (error) => reject(error);
216
+ server.once("error", onError);
217
+ server.listen(0, "127.0.0.1", () => {
218
+ server.off("error", onError);
219
+ const address = server.address();
220
+ if (address === null || typeof address === "string") {
221
+ reject(new Error("The containment probe did not receive a TCP port."));
222
+ return;
223
+ }
224
+ resolvePort(address.port);
225
+ });
226
+ });
227
+ }
228
+ async function closeServer(server) {
229
+ if (!server.listening)
230
+ return;
231
+ await new Promise((resolveClose) => server.close(() => resolveClose()));
232
+ }
233
+ async function probeMacOsSeatbelt() {
234
+ if (!(await isExecutable(MACOS_SANDBOX_EXECUTABLE))) {
235
+ return unavailableProbe("macos_sandbox_exec", MACOS_SANDBOX_EXECUTABLE, false, `${MACOS_SANDBOX_EXECUTABLE} is not executable.`);
236
+ }
237
+ const temporaryRoots = [];
238
+ const server = createServer((socket) => socket.destroy());
239
+ let networkConnections = 0;
240
+ server.on("connection", () => {
241
+ networkConnections += 1;
242
+ });
243
+ try {
244
+ const makeTemporaryRoot = async (prefix) => {
245
+ const root = await mkdtemp(join(tmpdir(), prefix));
246
+ temporaryRoots.push(root);
247
+ return await realpath(root);
248
+ };
249
+ // Allocate sequentially so a rejection cannot race successful siblings
250
+ // that have not yet registered themselves for the finally cleanup.
251
+ const workspace = await makeTemporaryRoot("galaxy-containment-workspace-");
252
+ const privateTemp = await makeTemporaryRoot("galaxy-containment-temp-");
253
+ const outside = await makeTemporaryRoot("galaxy-containment-outside-");
254
+ const workspaceFile = join(workspace, `workspace-${randomUUID()}`);
255
+ const privateTempFile = join(privateTemp, `temp-${randomUUID()}`);
256
+ const outsideFile = join(outside, `sentinel-${randomUUID()}`);
257
+ const outsideHardLinkTarget = join(outside, `hard-link-target-${randomUUID()}`);
258
+ const workspaceHardLink = join(workspace, `hard-link-${randomUUID()}`);
259
+ const lateFile = join(workspace, `late-${randomUUID()}`);
260
+ const sentinel = `outside-${randomUUID()}`;
261
+ const hardLinkSentinel = `hard-link-outside-${randomUUID()}`;
262
+ await Promise.all([
263
+ writeFile(outsideFile, sentinel, "utf8"),
264
+ writeFile(outsideHardLinkTarget, hardLinkSentinel, "utf8"),
265
+ ]);
266
+ await link(outsideHardLinkTarget, workspaceHardLink);
267
+ const port = await listenOnLoopback(server);
268
+ const environment = {
269
+ CI: "1",
270
+ FORCE_COLOR: "0",
271
+ GALAXY_PROBE_HARD_LINK_FILE: workspaceHardLink,
272
+ GALAXY_PROBE_LATE_FILE: lateFile,
273
+ GALAXY_PROBE_OUTSIDE_FILE: outsideFile,
274
+ GALAXY_PROBE_PORT: String(port),
275
+ GALAXY_PROBE_TEMP_FILE: privateTempFile,
276
+ GALAXY_PROBE_WORKSPACE_FILE: workspaceFile,
277
+ PATH: process.env.PATH,
278
+ TEMP: privateTemp,
279
+ TMP: privateTemp,
280
+ TMPDIR: privateTemp,
281
+ };
282
+ const processResult = await runProbeProcess(MACOS_SANDBOX_EXECUTABLE, [
283
+ "-D",
284
+ `WORKSPACE=${workspace}`,
285
+ "-D",
286
+ `PRIVATE_TMP=${privateTemp}`,
287
+ "-p",
288
+ MACOS_CONFORMANCE_PROFILE,
289
+ process.execPath,
290
+ "-e",
291
+ MACOS_PROBE_SCRIPT,
292
+ ], environment);
293
+ await delay(DESCENDANT_OBSERVATION_MS);
294
+ let payload;
295
+ try {
296
+ payload = JSON.parse(processResult.stdout);
297
+ }
298
+ catch {
299
+ payload = undefined;
300
+ }
301
+ const [workspaceContent, privateTempContent, outsideContent, hardLinkContent, lateContent] = await Promise.all([
302
+ readFile(workspaceFile, "utf8").catch(() => ""),
303
+ readFile(privateTempFile, "utf8").catch(() => ""),
304
+ readFile(outsideFile, "utf8").catch(() => ""),
305
+ readFile(outsideHardLinkTarget, "utf8").catch(() => ""),
306
+ readFile(lateFile, "utf8").catch(() => ""),
307
+ ]);
308
+ const systemRead = payload?.systemRead === true;
309
+ const workspaceWrite = payload?.workspaceWrite === true && workspaceContent === "workspace-ok";
310
+ const privateTempWrite = payload?.privateTempWrite === true && privateTempContent === "temp-ok";
311
+ const outsideWriteDenied = payload?.outsideWriteDenied === true && outsideContent === sentinel;
312
+ const hardLinkWriteDenied = payload?.hardLinkWriteDenied === true && hardLinkContent === hardLinkSentinel;
313
+ const networkDenied = payload?.networkDenied === true && networkConnections === 0;
314
+ const descendantChildSpawned = payload?.descendantChildSpawned === true;
315
+ const descendantQuiescent = descendantChildSpawned && lateContent.length === 0;
316
+ const toolchainExecution = processResult.exitCode === 0 && !processResult.timedOut;
317
+ const assertions = {
318
+ descendantChildSpawned,
319
+ descendantQuiescent,
320
+ hardLinkWriteDenied,
321
+ networkDenied,
322
+ outsideWriteDenied,
323
+ privateTempWrite,
324
+ systemRead,
325
+ toolchainExecution,
326
+ workspaceWrite,
327
+ };
328
+ const failedAssertions = Object.entries(assertions)
329
+ .filter(([, passed]) => !passed)
330
+ .map(([name]) => name);
331
+ const verified = failedAssertions.length === 0;
332
+ const detail = processResult.timedOut
333
+ ? "The Seatbelt conformance probe timed out."
334
+ : processResult.exitCode !== 0
335
+ ? `The Seatbelt conformance probe exited with ${String(processResult.exitCode)}: ${processResult.stderr.trim()}`
336
+ : !verified
337
+ ? `The Seatbelt conformance probe failed: ${failedAssertions.join(", ")}.`
338
+ : null;
339
+ return freezeProbe({
340
+ available: true,
341
+ backend: "macos_sandbox_exec",
342
+ descendantChildSpawned,
343
+ descendantQuiescent,
344
+ executable: MACOS_SANDBOX_EXECUTABLE,
345
+ hardLinkWriteDenied,
346
+ networkDenied,
347
+ outsideWriteDenied,
348
+ privateTempWrite,
349
+ reason: detail,
350
+ systemRead,
351
+ toolchainExecution,
352
+ verified,
353
+ workspaceWrite,
354
+ });
355
+ }
356
+ catch (error) {
357
+ return unavailableProbe("macos_sandbox_exec", MACOS_SANDBOX_EXECUTABLE, true, `The Seatbelt conformance probe failed: ${errorMessage(error)}`);
358
+ }
359
+ finally {
360
+ await closeServer(server).catch(() => undefined);
361
+ await Promise.all(temporaryRoots.map(async (root) => {
362
+ await rm(root, { force: true, recursive: true });
363
+ })).catch(() => undefined);
364
+ }
365
+ }
366
+ async function probeLinuxBubblewrap() {
367
+ const executable = await executableOnPath("bwrap");
368
+ if (executable === null) {
369
+ return unavailableProbe("linux_bubblewrap", null, false, "bubblewrap (bwrap) is not available on PATH.");
370
+ }
371
+ const temporaryRoots = [];
372
+ const server = createServer((socket) => socket.destroy());
373
+ let networkConnections = 0;
374
+ server.on("connection", () => {
375
+ networkConnections += 1;
376
+ });
377
+ try {
378
+ const makeTemporaryRoot = async (prefix) => {
379
+ const root = await mkdtemp(join(tmpdir(), prefix));
380
+ temporaryRoots.push(root);
381
+ return await realpath(root);
382
+ };
383
+ const workspace = await makeTemporaryRoot("galaxy-bwrap-workspace-");
384
+ const outside = await makeTemporaryRoot("galaxy-bwrap-outside-");
385
+ const workspaceFile = join(workspace, `workspace-${randomUUID()}`);
386
+ const outsideFile = join(outside, `sentinel-${randomUUID()}`);
387
+ const lateFile = join(workspace, `late-${randomUUID()}`);
388
+ const hardLinkTarget = join(outside, `hard-link-target-${randomUUID()}`);
389
+ const hardLinkPath = join(workspace, `hard-link-${randomUUID()}`);
390
+ const sentinel = `outside-${randomUUID()}`;
391
+ await Promise.all([
392
+ writeFile(outsideFile, sentinel, "utf8"),
393
+ writeFile(hardLinkTarget, sentinel, "utf8"),
394
+ ]);
395
+ await link(hardLinkTarget, hardLinkPath);
396
+ let hardLinkWriteDenied = false;
397
+ try {
398
+ await assertWorkspaceHasNoHardLinks(workspace);
399
+ }
400
+ catch {
401
+ hardLinkWriteDenied = true;
402
+ }
403
+ await rm(hardLinkPath);
404
+ const port = await listenOnLoopback(server);
405
+ const environment = {
406
+ CI: "1",
407
+ FORCE_COLOR: "0",
408
+ GALAXY_PROBE_HARD_LINK_FILE: "/workspace/nonexistent-hard-link",
409
+ GALAXY_PROBE_LATE_FILE: `/workspace/${lateFile.split(sep).at(-1) ?? "late"}`,
410
+ GALAXY_PROBE_OUTSIDE_FILE: outsideFile,
411
+ GALAXY_PROBE_PORT: String(port),
412
+ GALAXY_PROBE_TEMP_FILE: "/tmp/probe-temp",
413
+ GALAXY_PROBE_WORKSPACE_FILE: `/workspace/${workspaceFile.split(sep).at(-1) ?? "workspace"}`,
414
+ PATH: "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
415
+ TEMP: "/tmp",
416
+ TMP: "/tmp",
417
+ TMPDIR: "/tmp",
418
+ };
419
+ const processResult = await runProbeProcess(executable, buildBubblewrapArguments({
420
+ command: process.execPath,
421
+ commandArguments: Object.freeze(["-e", MACOS_PROBE_SCRIPT]),
422
+ cwd: workspace,
423
+ environment,
424
+ workspace,
425
+ }), environment);
426
+ await delay(DESCENDANT_OBSERVATION_MS);
427
+ let payload;
428
+ try {
429
+ payload = JSON.parse(processResult.stdout);
430
+ }
431
+ catch {
432
+ payload = undefined;
433
+ }
434
+ const [workspaceContent, outsideContent, lateContent] = await Promise.all([
435
+ readFile(workspaceFile, "utf8").catch(() => ""),
436
+ readFile(outsideFile, "utf8").catch(() => ""),
437
+ readFile(lateFile, "utf8").catch(() => ""),
438
+ ]);
439
+ const assertions = {
440
+ descendantChildSpawned: payload?.descendantChildSpawned === true,
441
+ descendantQuiescent: payload?.descendantChildSpawned === true && lateContent.length === 0,
442
+ hardLinkWriteDenied,
443
+ networkDenied: payload?.networkDenied === true && networkConnections === 0,
444
+ outsideWriteDenied: payload?.outsideWriteDenied === true && outsideContent === sentinel,
445
+ privateTempWrite: payload?.privateTempWrite === true,
446
+ systemRead: payload?.systemRead === true,
447
+ toolchainExecution: processResult.exitCode === 0 && !processResult.timedOut,
448
+ workspaceWrite: payload?.workspaceWrite === true && workspaceContent === "workspace-ok",
449
+ };
450
+ const failed = Object.entries(assertions).filter(([, passed]) => !passed).map(([name]) => name);
451
+ return freezeProbe({
452
+ available: true,
453
+ backend: "linux_bubblewrap",
454
+ ...assertions,
455
+ executable,
456
+ reason: failed.length === 0 ? null : `The bubblewrap conformance probe failed: ${failed.join(", ")}.`,
457
+ verified: failed.length === 0,
458
+ });
459
+ }
460
+ catch (error) {
461
+ return unavailableProbe("linux_bubblewrap", executable, true, `The bubblewrap conformance probe failed: ${errorMessage(error)}`);
462
+ }
463
+ finally {
464
+ await closeServer(server).catch(() => undefined);
465
+ await Promise.all(temporaryRoots.map(async (root) => rm(root, { force: true, recursive: true }))).catch(() => undefined);
466
+ }
467
+ }
468
+ async function detectCommandContainment() {
469
+ try {
470
+ return process.platform === "darwin"
471
+ ? await probeMacOsSeatbelt()
472
+ : process.platform === "linux"
473
+ ? await probeLinuxBubblewrap()
474
+ : unavailableProbe("none", null, false, `No command-containment backend is implemented for '${process.platform}'.`);
475
+ }
476
+ catch (error) {
477
+ return unavailableProbe("none", null, false, `Command-containment detection failed closed: ${errorMessage(error)}`);
478
+ }
479
+ }
480
+ /**
481
+ * Run the host conformance check once per process. A binary merely existing is
482
+ * not enough. Only a backend that passes every probe assertion can activate.
483
+ */
484
+ export async function probeCommandContainment() {
485
+ cachedProbe ??= detectCommandContainment();
486
+ return await cachedProbe;
487
+ }
488
+ export function containmentStatus(mode, probe) {
489
+ const assertionsVerified = probe.descendantChildSpawned
490
+ && probe.descendantQuiescent
491
+ && probe.hardLinkWriteDenied
492
+ && probe.networkDenied
493
+ && probe.outsideWriteDenied
494
+ && probe.privateTempWrite
495
+ && probe.systemRead
496
+ && probe.toolchainExecution
497
+ && probe.workspaceWrite;
498
+ const active = probe.backend === "linux_bubblewrap" && probe.verified && assertionsVerified;
499
+ const state = !probe.available
500
+ ? "unavailable"
501
+ : active
502
+ ? "active"
503
+ : probe.verified
504
+ ? "unverified"
505
+ : "probe_failed";
506
+ const reason = active ? null : probe.verified && !assertionsVerified
507
+ ? "The backend claimed verification without passing every containment assertion."
508
+ : probe.verified
509
+ ? "The backend passed its probe but this platform implementation is not activated."
510
+ : probe.reason;
511
+ return Object.freeze({
512
+ active,
513
+ backend: probe.backend,
514
+ executable: probe.executable,
515
+ filesystemReads: active ? "system_and_workspace" : "host_visible",
516
+ filesystemWrites: active ? "workspace_and_private_temp" : "uncontained",
517
+ mode,
518
+ network: active ? "denied" : "uncontained",
519
+ reason,
520
+ state,
521
+ verified: active,
522
+ });
523
+ }
524
+ const BUBBLEWRAP_READ_ONLY_ROOTS = Object.freeze([
525
+ "/bin", "/etc", "/lib", "/lib64", "/nix/store", "/opt", "/sbin", "/usr",
526
+ ]);
527
+ export function buildBubblewrapArguments(input) {
528
+ const relativeCwd = relative(input.workspace, input.cwd);
529
+ if (relativeCwd === ".." || relativeCwd.startsWith(`..${sep}`)) {
530
+ throw new Error("Contained command cwd must remain inside the workspace.");
531
+ }
532
+ const args = [
533
+ "--die-with-parent", "--new-session", "--unshare-all", "--disable-userns", "--cap-drop", "ALL",
534
+ "--clearenv", "--proc", "/proc", "--dev", "/dev", "--tmpfs", "/tmp",
535
+ ];
536
+ for (const root of BUBBLEWRAP_READ_ONLY_ROOTS) {
537
+ try {
538
+ if (existsSync(root))
539
+ args.push("--ro-bind", root, root);
540
+ }
541
+ catch {
542
+ // Missing optional system roots are intentionally invisible.
543
+ }
544
+ }
545
+ args.push("--bind", input.workspace, "/workspace");
546
+ for (const [name, value] of Object.entries(input.environment).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
547
+ if (value !== undefined)
548
+ args.push("--setenv", name, value);
549
+ }
550
+ const sandboxCwd = relativeCwd.length === 0 ? "/workspace" : `/workspace/${relativeCwd.split(sep).join("/")}`;
551
+ args.push("--chdir", sandboxCwd, "--", input.command, ...input.commandArguments);
552
+ return Object.freeze(args);
553
+ }
554
+ export async function assertWorkspaceHasNoHardLinks(workspace) {
555
+ let entries = 0;
556
+ const visit = async (directory) => {
557
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
558
+ entries += 1;
559
+ if (entries > 100_000) {
560
+ throw Object.assign(new Error("Contained command hard-link scan exceeded 100000 workspace entries."), {
561
+ code: "LIMIT_EXCEEDED",
562
+ });
563
+ }
564
+ const path = join(directory, entry.name);
565
+ const info = await lstat(path);
566
+ if (info.isSymbolicLink())
567
+ continue;
568
+ if (info.isDirectory()) {
569
+ await visit(path);
570
+ }
571
+ else if (info.isFile() && info.nlink > 1) {
572
+ throw Object.assign(new Error(`Contained command refused hard-linked workspace file '${relative(workspace, path)}'.`), {
573
+ code: "PERMISSION_DENIED",
574
+ });
575
+ }
576
+ }
577
+ };
578
+ await visit(workspace);
579
+ }
580
+ //# sourceMappingURL=command-containment.js.map