@steerable/agent-shell 0.6.15

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 (204) hide show
  1. package/LICENSE +91 -0
  2. package/contracts/tool-contract.json +326 -0
  3. package/dist/attachments.d.ts +41 -0
  4. package/dist/attachments.js +147 -0
  5. package/dist/brand.d.ts +24 -0
  6. package/dist/brand.js +92 -0
  7. package/dist/host/http-routes.d.ts +21 -0
  8. package/dist/host/http-routes.js +55 -0
  9. package/dist/host/ipc.d.ts +11 -0
  10. package/dist/host/ipc.js +20 -0
  11. package/dist/host/pack-assembly.d.ts +86 -0
  12. package/dist/host/pack-assembly.js +32 -0
  13. package/dist/host/runtime.d.ts +69 -0
  14. package/dist/host/runtime.js +207 -0
  15. package/dist/host/visible-terminal-exec.d.ts +21 -0
  16. package/dist/host/visible-terminal-exec.js +151 -0
  17. package/dist/hosted-web-search.d.ts +13 -0
  18. package/dist/hosted-web-search.js +82 -0
  19. package/dist/image-attachment.d.ts +39 -0
  20. package/dist/image-attachment.js +133 -0
  21. package/dist/insights/flush.d.ts +10 -0
  22. package/dist/insights/flush.js +139 -0
  23. package/dist/insights/record.d.ts +17 -0
  24. package/dist/insights/record.js +44 -0
  25. package/dist/json-store.d.ts +9 -0
  26. package/dist/json-store.js +22 -0
  27. package/dist/llm/index.d.ts +23 -0
  28. package/dist/llm/index.js +105 -0
  29. package/dist/llm/ollama.d.ts +23 -0
  30. package/dist/llm/ollama.js +242 -0
  31. package/dist/llm/openai-compat.d.ts +20 -0
  32. package/dist/llm/openai-compat.js +199 -0
  33. package/dist/llm/sidecar-provider.d.ts +37 -0
  34. package/dist/llm/sidecar-provider.js +163 -0
  35. package/dist/llm/tool-choice.d.ts +35 -0
  36. package/dist/llm/tool-choice.js +85 -0
  37. package/dist/llm/types.d.ts +122 -0
  38. package/dist/llm/types.js +1 -0
  39. package/dist/local-backend/agent-capability.d.ts +101 -0
  40. package/dist/local-backend/agent-capability.js +174 -0
  41. package/dist/local-backend/ai-title.d.ts +43 -0
  42. package/dist/local-backend/ai-title.js +173 -0
  43. package/dist/local-backend/auto-continue-helper.d.ts +80 -0
  44. package/dist/local-backend/auto-continue-helper.js +83 -0
  45. package/dist/local-backend/branch-helper.d.ts +24 -0
  46. package/dist/local-backend/branch-helper.js +27 -0
  47. package/dist/local-backend/context-compactor.d.ts +81 -0
  48. package/dist/local-backend/context-compactor.js +213 -0
  49. package/dist/local-backend/coreloop-stream.d.ts +245 -0
  50. package/dist/local-backend/coreloop-stream.js +277 -0
  51. package/dist/local-backend/deferred-detector.d.ts +15 -0
  52. package/dist/local-backend/deferred-detector.js +124 -0
  53. package/dist/local-backend/history-helper.d.ts +30 -0
  54. package/dist/local-backend/history-helper.js +34 -0
  55. package/dist/local-backend/interrupted-helper.d.ts +29 -0
  56. package/dist/local-backend/interrupted-helper.js +25 -0
  57. package/dist/local-backend/live-stream.d.ts +36 -0
  58. package/dist/local-backend/live-stream.js +23 -0
  59. package/dist/local-backend/llm-diagnose.d.ts +37 -0
  60. package/dist/local-backend/llm-diagnose.js +284 -0
  61. package/dist/local-backend/message-triggers.d.ts +27 -0
  62. package/dist/local-backend/message-triggers.js +67 -0
  63. package/dist/local-backend/pack-backend-routes.d.ts +31 -0
  64. package/dist/local-backend/pack-backend-routes.js +62 -0
  65. package/dist/local-backend/pack-turn-hooks.d.ts +37 -0
  66. package/dist/local-backend/pack-turn-hooks.js +67 -0
  67. package/dist/local-backend/prompt-builder.d.ts +101 -0
  68. package/dist/local-backend/prompt-builder.js +246 -0
  69. package/dist/local-backend/regenerate-helper.d.ts +62 -0
  70. package/dist/local-backend/regenerate-helper.js +75 -0
  71. package/dist/local-backend/router.d.ts +175 -0
  72. package/dist/local-backend/router.js +3139 -0
  73. package/dist/local-backend/skill-install.d.ts +19 -0
  74. package/dist/local-backend/skill-install.js +71 -0
  75. package/dist/local-backend/skill-loader.d.ts +92 -0
  76. package/dist/local-backend/skill-loader.js +146 -0
  77. package/dist/local-backend/skills/00-identity/SKILL.md +32 -0
  78. package/dist/local-backend/skills/10-goal/SKILL.md +59 -0
  79. package/dist/local-backend/skills/11-loop/SKILL.md +71 -0
  80. package/dist/local-backend/skills/12-create-skill/SKILL.md +88 -0
  81. package/dist/local-backend/skills/70-plan-mode/SKILL.md +58 -0
  82. package/dist/local-backend/skills/80-tool-usage/SKILL.md +70 -0
  83. package/dist/local-backend/skills/81-anti-deferred/SKILL.md +53 -0
  84. package/dist/local-backend/skills/82-data-grounding/SKILL.md +56 -0
  85. package/dist/local-backend/skills/85-local-exec/SKILL.md +86 -0
  86. package/dist/local-backend/skills/86-proactive-coding/SKILL.md +51 -0
  87. package/dist/local-backend/subagent-profiles.d.ts +30 -0
  88. package/dist/local-backend/subagent-profiles.js +74 -0
  89. package/dist/local-backend/task-process.d.ts +12 -0
  90. package/dist/local-backend/task-process.js +176 -0
  91. package/dist/local-backend/task-service.d.ts +135 -0
  92. package/dist/local-backend/task-service.js +565 -0
  93. package/dist/local-backend/turn-duration.d.ts +2 -0
  94. package/dist/local-backend/turn-duration.js +9 -0
  95. package/dist/local-backend/turn-timeline.d.ts +16 -0
  96. package/dist/local-backend/turn-timeline.js +42 -0
  97. package/dist/local-backend/worktree-service.d.ts +84 -0
  98. package/dist/local-backend/worktree-service.js +243 -0
  99. package/dist/local-edit.d.ts +48 -0
  100. package/dist/local-edit.js +44 -0
  101. package/dist/local-executor.d.ts +255 -0
  102. package/dist/local-executor.js +881 -0
  103. package/dist/local-script-registry.d.ts +28 -0
  104. package/dist/local-script-registry.js +63 -0
  105. package/dist/log.d.ts +13 -0
  106. package/dist/log.js +12 -0
  107. package/dist/main.d.ts +1 -0
  108. package/dist/main.js +855 -0
  109. package/dist/mcp-executor.d.ts +45 -0
  110. package/dist/mcp-executor.js +241 -0
  111. package/dist/mcp-server-registry.d.ts +104 -0
  112. package/dist/mcp-server-registry.js +234 -0
  113. package/dist/preload-default.d.ts +1 -0
  114. package/dist/preload-default.js +9 -0
  115. package/dist/preload.cjs +395 -0
  116. package/dist/preload.d.ts +20 -0
  117. package/dist/preload.js +411 -0
  118. package/dist/product-config.d.ts +43 -0
  119. package/dist/product-config.js +26 -0
  120. package/dist/project-registry.d.ts +55 -0
  121. package/dist/project-registry.js +106 -0
  122. package/dist/project-rules.d.ts +15 -0
  123. package/dist/project-rules.js +102 -0
  124. package/dist/runtime.d.ts +62 -0
  125. package/dist/runtime.js +217 -0
  126. package/dist/scenario/pack.d.ts +8 -0
  127. package/dist/scenario/pack.js +1 -0
  128. package/dist/scenario/registry.d.ts +24 -0
  129. package/dist/scenario/registry.js +31 -0
  130. package/dist/server/http-server.d.ts +39 -0
  131. package/dist/server/http-server.js +361 -0
  132. package/dist/server/index.d.ts +1 -0
  133. package/dist/server/index.js +107 -0
  134. package/dist/server/sse-bus.d.ts +14 -0
  135. package/dist/server/sse-bus.js +31 -0
  136. package/dist/shell-adapt.d.ts +21 -0
  137. package/dist/shell-adapt.js +104 -0
  138. package/dist/sidecar/boot.d.ts +36 -0
  139. package/dist/sidecar/boot.js +343 -0
  140. package/dist/sidecar/egress-hint.d.ts +15 -0
  141. package/dist/sidecar/egress-hint.js +46 -0
  142. package/dist/sidecar/egress-proxy.d.ts +183 -0
  143. package/dist/sidecar/egress-proxy.js +419 -0
  144. package/dist/sidecar/errors.d.ts +22 -0
  145. package/dist/sidecar/errors.js +38 -0
  146. package/dist/sidecar/exec-sandbox.d.ts +48 -0
  147. package/dist/sidecar/exec-sandbox.js +94 -0
  148. package/dist/sidecar/handle.d.ts +32 -0
  149. package/dist/sidecar/handle.js +53 -0
  150. package/dist/sidecar/index.d.ts +14 -0
  151. package/dist/sidecar/index.js +13 -0
  152. package/dist/sidecar/proxy-detect.d.ts +50 -0
  153. package/dist/sidecar/proxy-detect.js +182 -0
  154. package/dist/sidecar/reverse-approval.d.ts +55 -0
  155. package/dist/sidecar/reverse-approval.js +86 -0
  156. package/dist/sidecar/reverse-ask-user.d.ts +34 -0
  157. package/dist/sidecar/reverse-ask-user.js +59 -0
  158. package/dist/sidecar/reverse-spawn.d.ts +19 -0
  159. package/dist/sidecar/reverse-spawn.js +161 -0
  160. package/dist/sidecar/reverse-tools.d.ts +29 -0
  161. package/dist/sidecar/reverse-tools.js +106 -0
  162. package/dist/sidecar/safety-patterns.d.ts +41 -0
  163. package/dist/sidecar/safety-patterns.js +157 -0
  164. package/dist/sidecar/storage-path.d.ts +14 -0
  165. package/dist/sidecar/storage-path.js +35 -0
  166. package/dist/sidecar/supervisor.d.ts +218 -0
  167. package/dist/sidecar/supervisor.js +932 -0
  168. package/dist/sidecar/types.d.ts +601 -0
  169. package/dist/sidecar/types.js +1 -0
  170. package/dist/single-instance.d.ts +11 -0
  171. package/dist/single-instance.js +21 -0
  172. package/dist/storage/empty-chats.d.ts +9 -0
  173. package/dist/storage/empty-chats.js +16 -0
  174. package/dist/storage/index.d.ts +373 -0
  175. package/dist/storage/index.js +1158 -0
  176. package/dist/storage/insights-redact.d.ts +2 -0
  177. package/dist/storage/insights-redact.js +30 -0
  178. package/dist/storage/insights-settings.d.ts +53 -0
  179. package/dist/storage/insights-settings.js +92 -0
  180. package/dist/storage/llm-settings.d.ts +120 -0
  181. package/dist/storage/llm-settings.js +233 -0
  182. package/dist/storage/local-store-singleton.d.ts +28 -0
  183. package/dist/storage/local-store-singleton.js +38 -0
  184. package/dist/storage/message-order.d.ts +25 -0
  185. package/dist/storage/message-order.js +27 -0
  186. package/dist/storage/pack-migrations.d.ts +22 -0
  187. package/dist/storage/pack-migrations.js +24 -0
  188. package/dist/storage/pack-seeds.d.ts +36 -0
  189. package/dist/storage/pack-seeds.js +42 -0
  190. package/dist/storage/telemetry-settings.d.ts +38 -0
  191. package/dist/storage/telemetry-settings.js +59 -0
  192. package/dist/storage/usage-summary.d.ts +55 -0
  193. package/dist/storage/usage-summary.js +38 -0
  194. package/dist/storage/web-search-settings.d.ts +38 -0
  195. package/dist/storage/web-search-settings.js +74 -0
  196. package/dist/storage/write-lease.d.ts +26 -0
  197. package/dist/storage/write-lease.js +74 -0
  198. package/dist/terminal-manager.d.ts +83 -0
  199. package/dist/terminal-manager.js +506 -0
  200. package/dist/tool-router.d.ts +228 -0
  201. package/dist/tool-router.js +930 -0
  202. package/dist/tool-search-rank.d.ts +42 -0
  203. package/dist/tool-search-rank.js +96 -0
  204. package/package.json +67 -0
@@ -0,0 +1,53 @@
1
+ let sidecarSupervisor = null;
2
+ let pendingSupervisor = null;
3
+ export function setSidecarSupervisor(supervisor) {
4
+ sidecarSupervisor = supervisor;
5
+ }
6
+ /**
7
+ * Register the in-flight boot promise so early callers (e.g. the renderer's
8
+ * skills request racing sidecar boot) can await readiness instead of
9
+ * observing a bare `null` and degrading. `main.ts` registers this
10
+ * synchronously inside `maybeStartSidecar()`, before the first await.
11
+ */
12
+ export function setSidecarSupervisorPending(pending) {
13
+ pendingSupervisor = pending;
14
+ }
15
+ /** The supervised sidecar handle, when the sidecar path is active. */
16
+ export function getSidecarSupervisor() {
17
+ return isSidecarEnabled() ? sidecarSupervisor : null;
18
+ }
19
+ export function isSidecarEnabled() {
20
+ return process.env.STEERABLE_USE_SIDECAR !== '0' && sidecarSupervisor != null;
21
+ }
22
+ /**
23
+ * Await the sidecar handle through boot: returns the live handle immediately
24
+ * when already up, otherwise waits on the registered boot promise. Returns
25
+ * null when the sidecar is disabled, no boot is in flight, boot failed, or
26
+ * the wait exceeds `timeoutMs` (bounded so a hung python env degrades the
27
+ * caller to its no-sidecar fallback instead of hanging the request).
28
+ */
29
+ export async function whenSidecarSupervisor(timeoutMs = 20_000) {
30
+ if (process.env.STEERABLE_USE_SIDECAR === '0')
31
+ return null;
32
+ const live = getSidecarSupervisor();
33
+ if (live)
34
+ return live;
35
+ const pending = pendingSupervisor;
36
+ if (!pending)
37
+ return null;
38
+ let timer;
39
+ const timeout = new Promise((resolve) => {
40
+ timer = setTimeout(() => resolve(null), timeoutMs);
41
+ });
42
+ // A rejected boot promise (python env broken etc.) is already logged by the
43
+ // boot path; waiters degrade to their no-sidecar fallback instead of
44
+ // crashing on someone else's boot failure.
45
+ const settled = pending.catch(() => null);
46
+ try {
47
+ return await Promise.race([settled, timeout]);
48
+ }
49
+ finally {
50
+ if (timer)
51
+ clearTimeout(timer);
52
+ }
53
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Public API for the steerable-sidecar bridge.
3
+ *
4
+ * Usage from main.ts:
5
+ *
6
+ * import { SidecarSupervisor } from './sidecar';
7
+ * const sidecar = await SidecarSupervisor.start();
8
+ * await sidecar.ping();
9
+ * const result = await sidecar.invokeTool('list_events', { limit: 10 });
10
+ * await sidecar.shutdown();
11
+ */
12
+ export { SidecarSupervisor } from './supervisor.js';
13
+ export { SidecarBootError, SidecarMethodError, SidecarShutdownError, SidecarSandboxUnavailableError, } from './errors.js';
14
+ export type { SidecarChatStreamHandlers, SidecarChatStreamRequest, SidecarChildEvent, SidecarHealthSnapshot, SidecarMethodOptions, SidecarRawChunk, SidecarSandboxPosture, SidecarSessionForkResult, SidecarStartOptions, SidecarStreamChunk, SidecarStreamDone, SidecarStreamError, SidecarToolResult, } from './types.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Public API for the steerable-sidecar bridge.
3
+ *
4
+ * Usage from main.ts:
5
+ *
6
+ * import { SidecarSupervisor } from './sidecar';
7
+ * const sidecar = await SidecarSupervisor.start();
8
+ * await sidecar.ping();
9
+ * const result = await sidecar.invokeTool('list_events', { limit: 10 });
10
+ * await sidecar.shutdown();
11
+ */
12
+ export { SidecarSupervisor } from './supervisor.js';
13
+ export { SidecarBootError, SidecarMethodError, SidecarShutdownError, SidecarSandboxUnavailableError, } from './errors.js';
@@ -0,0 +1,50 @@
1
+ /**
2
+ * W4-8: ambient proxy detection for the sidecar sandbox egress allow-list.
3
+ *
4
+ * The sidecar's LLM clients (httpx, `trust_env=True`) honor ambient proxy
5
+ * configuration: `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` env vars, and —
6
+ * when no env proxy is set — the macOS System Configuration proxy via
7
+ * `urllib.request.getproxies()`. A configured proxy is therefore an
8
+ * *effective egress point* of the sidecar: if the Seatbelt allow-list names
9
+ * only the provider endpoint, every LLM call is denied at connect time for
10
+ * proxy users (EPERM → httpx "All connection attempts failed").
11
+ *
12
+ * The allow-list deliberately takes the union of env and system proxies
13
+ * rather than replicating `getproxies()` precedence (env shadows system):
14
+ * both endpoints are operator-configured, and the bypass-list semantics
15
+ * (NO_PROXY / ExceptionsList) are not faithfully surfaced by
16
+ * `getproxies()` on macOS, so a proxied daemon and a direct localhost
17
+ * target can both be in play within one boot.
18
+ */
19
+ /**
20
+ * Normalize a proxy env value (`http://host:port`, `host:port`, `socks5://…`)
21
+ * to the `host[:port]` form the sandbox profile expects. Returns null for
22
+ * unparseable values — a malformed proxy var must not fail profile
23
+ * generation (the sidecar would fall back unsandboxed).
24
+ */
25
+ export declare function endpointFromProxyUrl(raw: string): string | null;
26
+ /** Proxy endpoints declared through the process environment. */
27
+ export declare function envProxyEndpoints(env: NodeJS.ProcessEnv): string[];
28
+ /**
29
+ * Parse `scutil --proxy` output into `host:port` endpoints. Only enabled
30
+ * protocols yield an entry; the ExceptionsList is intentionally ignored
31
+ * (see module docstring — bypass state is not reliably knowable here).
32
+ */
33
+ export declare function parseScutilProxyOutput(output: string): string[];
34
+ /**
35
+ * macOS System Configuration proxy endpoints. Empty off-darwin (the
36
+ * Seatbelt sandbox only exists on macOS, so the allow-list only matters
37
+ * there) and on any scutil failure — detection must never break boot.
38
+ */
39
+ export declare function detectSystemProxyEndpoints(platform?: NodeJS.Platform): Promise<string[]>;
40
+ /**
41
+ * Parse `reg query` output for ProxyServer into `host:port` endpoints.
42
+ * The value is either `host:port` (single proxy) or
43
+ * `protocol=host:port;...` (per-protocol). Only enabled proxies yield an
44
+ * entry; the ProxyEnable flag is intentionally ignored (the value is
45
+ * still present when disabled, and the bypass list is not reliably
46
+ * surfaced here).
47
+ */
48
+ export declare function parseWindowsRegProxyOutput(output: string): string[];
49
+ /** All ambient proxy endpoints the sidecar may legitimately egress to. */
50
+ export declare function collectAmbientProxyEndpoints(env?: NodeJS.ProcessEnv): Promise<string[]>;
@@ -0,0 +1,182 @@
1
+ /**
2
+ * W4-8: ambient proxy detection for the sidecar sandbox egress allow-list.
3
+ *
4
+ * The sidecar's LLM clients (httpx, `trust_env=True`) honor ambient proxy
5
+ * configuration: `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` env vars, and —
6
+ * when no env proxy is set — the macOS System Configuration proxy via
7
+ * `urllib.request.getproxies()`. A configured proxy is therefore an
8
+ * *effective egress point* of the sidecar: if the Seatbelt allow-list names
9
+ * only the provider endpoint, every LLM call is denied at connect time for
10
+ * proxy users (EPERM → httpx "All connection attempts failed").
11
+ *
12
+ * The allow-list deliberately takes the union of env and system proxies
13
+ * rather than replicating `getproxies()` precedence (env shadows system):
14
+ * both endpoints are operator-configured, and the bypass-list semantics
15
+ * (NO_PROXY / ExceptionsList) are not faithfully surfaced by
16
+ * `getproxies()` on macOS, so a proxied daemon and a direct localhost
17
+ * target can both be in play within one boot.
18
+ */
19
+ import { execFile } from 'node:child_process';
20
+ import { promisify } from 'node:util';
21
+ const execFileAsync = promisify(execFile);
22
+ const PROXY_ENV_VARS = [
23
+ 'HTTP_PROXY',
24
+ 'HTTPS_PROXY',
25
+ 'ALL_PROXY',
26
+ 'http_proxy',
27
+ 'https_proxy',
28
+ 'all_proxy',
29
+ ];
30
+ /** Default ports per proxy scheme when the URL omits one. */
31
+ const SCHEME_DEFAULT_PORT = {
32
+ http: 80,
33
+ https: 443,
34
+ socks5: 1080,
35
+ socks5h: 1080,
36
+ socks4: 1080,
37
+ };
38
+ /**
39
+ * Normalize a proxy env value (`http://host:port`, `host:port`, `socks5://…`)
40
+ * to the `host[:port]` form the sandbox profile expects. Returns null for
41
+ * unparseable values — a malformed proxy var must not fail profile
42
+ * generation (the sidecar would fall back unsandboxed).
43
+ */
44
+ export function endpointFromProxyUrl(raw) {
45
+ const text = raw.trim();
46
+ if (!text)
47
+ return null;
48
+ try {
49
+ const withScheme = text.includes('://') ? text : `http://${text}`;
50
+ const url = new URL(withScheme);
51
+ const host = url.hostname;
52
+ if (!host)
53
+ return null;
54
+ if (url.port)
55
+ return `${host}:${url.port}`;
56
+ const scheme = url.protocol.replace(':', '');
57
+ const fallback = SCHEME_DEFAULT_PORT[scheme];
58
+ return fallback ? `${host}:${fallback}` : host;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /** Proxy endpoints declared through the process environment. */
65
+ export function envProxyEndpoints(env) {
66
+ const out = new Set();
67
+ for (const name of PROXY_ENV_VARS) {
68
+ const value = env[name];
69
+ if (!value)
70
+ continue;
71
+ const endpoint = endpointFromProxyUrl(value);
72
+ if (endpoint)
73
+ out.add(endpoint);
74
+ }
75
+ return [...out];
76
+ }
77
+ /**
78
+ * Parse `scutil --proxy` output into `host:port` endpoints. Only enabled
79
+ * protocols yield an entry; the ExceptionsList is intentionally ignored
80
+ * (see module docstring — bypass state is not reliably knowable here).
81
+ */
82
+ export function parseScutilProxyOutput(output) {
83
+ const field = (key) => {
84
+ const match = output.match(new RegExp(`^\\s*${key}\\s*:\\s*(\\S+)\\s*$`, 'm'));
85
+ return match?.[1];
86
+ };
87
+ const out = new Set();
88
+ const protocols = [
89
+ { enable: 'HTTPEnable', host: 'HTTPProxy', port: 'HTTPPort', fallback: 80 },
90
+ { enable: 'HTTPSEnable', host: 'HTTPSProxy', port: 'HTTPSPort', fallback: 443 },
91
+ { enable: 'SOCKSEnable', host: 'SOCKSProxy', port: 'SOCKSPort', fallback: 1080 },
92
+ ];
93
+ for (const p of protocols) {
94
+ if (field(p.enable) !== '1')
95
+ continue;
96
+ const host = field(p.host);
97
+ if (!host)
98
+ continue;
99
+ out.add(`${host}:${field(p.port) ?? p.fallback}`);
100
+ }
101
+ return [...out];
102
+ }
103
+ /**
104
+ * macOS System Configuration proxy endpoints. Empty off-darwin (the
105
+ * Seatbelt sandbox only exists on macOS, so the allow-list only matters
106
+ * there) and on any scutil failure — detection must never break boot.
107
+ */
108
+ export async function detectSystemProxyEndpoints(platform = process.platform) {
109
+ if (platform === 'darwin') {
110
+ try {
111
+ const { stdout } = await execFileAsync('/usr/sbin/scutil', ['--proxy'], {
112
+ timeout: 5_000,
113
+ });
114
+ return parseScutilProxyOutput(stdout);
115
+ }
116
+ catch {
117
+ return [];
118
+ }
119
+ }
120
+ if (platform === 'win32') {
121
+ return detectWindowsRegistryProxyEndpoints();
122
+ }
123
+ return [];
124
+ }
125
+ /**
126
+ * Windows Registry proxy endpoints (HKCU\Software\Microsoft\Windows\
127
+ * CurrentVersion\Internet Settings). Empty on any reg.exe failure —
128
+ * detection must never break boot.
129
+ */
130
+ async function detectWindowsRegistryProxyEndpoints() {
131
+ try {
132
+ const { stdout } = await execFileAsync('reg.exe', [
133
+ 'query',
134
+ 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings',
135
+ '/v',
136
+ 'ProxyServer',
137
+ ], { timeout: 5_000 });
138
+ return parseWindowsRegProxyOutput(stdout);
139
+ }
140
+ catch {
141
+ return [];
142
+ }
143
+ }
144
+ /**
145
+ * Parse `reg query` output for ProxyServer into `host:port` endpoints.
146
+ * The value is either `host:port` (single proxy) or
147
+ * `protocol=host:port;...` (per-protocol). Only enabled proxies yield an
148
+ * entry; the ProxyEnable flag is intentionally ignored (the value is
149
+ * still present when disabled, and the bypass list is not reliably
150
+ * surfaced here).
151
+ */
152
+ export function parseWindowsRegProxyOutput(output) {
153
+ const match = output.match(/ProxyServer\s+REG_SZ\s+(.+)$/m);
154
+ if (!match)
155
+ return [];
156
+ const value = match[1].trim();
157
+ if (!value)
158
+ return [];
159
+ const out = new Set();
160
+ // Per-protocol form: "http=host:port;https=host:port;..."
161
+ if (value.includes('=')) {
162
+ for (const part of value.split(';')) {
163
+ const eq = part.indexOf('=');
164
+ if (eq < 0)
165
+ continue;
166
+ const endpoint = part.slice(eq + 1).trim();
167
+ if (endpoint)
168
+ out.add(endpoint);
169
+ }
170
+ }
171
+ else {
172
+ // Single proxy form: "host:port"
173
+ out.add(value);
174
+ }
175
+ return [...out];
176
+ }
177
+ /** All ambient proxy endpoints the sidecar may legitimately egress to. */
178
+ export async function collectAmbientProxyEndpoints(env = process.env) {
179
+ const envEndpoints = envProxyEndpoints(env);
180
+ const systemEndpoints = await detectSystemProxyEndpoints();
181
+ return [...new Set([...envEndpoints, ...systemEndpoints])];
182
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * W4-1 reverse channel: serve the sidecar-hosted CoreLoop's
3
+ * `approval.request` calls by asking the desktop user (Electron renderer
4
+ * modal) and returning the 7-variant decision.
5
+ *
6
+ * Fail-closed by construction: no window, no listener, a renderer error,
7
+ * or an invalid reply all become `deny_once` — never a hang, never an
8
+ * auto-allow. The sidecar additionally bounds the wait (`timeoutMs` on the
9
+ * approval config), so a wedged renderer degrades to `timed_out` there.
10
+ *
11
+ * The renderer answers via the `approval:decide` invoke with the requestId
12
+ * echoed back; decisions are validated against the algebra's kind set
13
+ * before crossing back to the sidecar.
14
+ */
15
+ import type { SidecarReverseHandler } from './types.js';
16
+ /**
17
+ * The approval algebra's decision variants (mirrors
18
+ * `steerable_agent_runtime.approval.APPROVAL_KINDS`). `timed_out` is
19
+ * synthesized by the sidecar on timeout, never by the UI.
20
+ */
21
+ export declare const APPROVAL_DECISION_KINDS: readonly ["allow_once", "allow_for_session", "allow_always", "deny_once", "deny_for_session", "deny_always", "abort"];
22
+ export type ApprovalDecisionKind = (typeof APPROVAL_DECISION_KINDS)[number];
23
+ export interface ApprovalPromptRequest {
24
+ /** Correlation id echoed back by the renderer's `approval:decide`. */
25
+ requestId: string;
26
+ toolName: string;
27
+ arguments: Record<string, unknown>;
28
+ mode: string;
29
+ category: string;
30
+ round: number;
31
+ }
32
+ export interface ApprovalBridgeDeps {
33
+ /**
34
+ * Push the prompt to all renderer windows (main.ts wires
35
+ * `webContents.send('approval:request', …)`). Returning false/undefined
36
+ * means no window was available → fail closed.
37
+ */
38
+ broadcast: (channel: 'approval:request', payload: ApprovalPromptRequest) => void;
39
+ /** True when at least one renderer window can receive the prompt. */
40
+ hasWindow: () => boolean;
41
+ onLog?: (line: string) => void;
42
+ }
43
+ export interface ApprovalBridge {
44
+ /** Reverse-channel handler for the sidecar's `approval.request`. */
45
+ handler: SidecarReverseHandler;
46
+ /**
47
+ * IPC entry point for the renderer's answer (`ipcMain.handle(
48
+ * 'approval:decide')`). Unknown requestIds are dropped (late answer to an
49
+ * already-settled prompt).
50
+ */
51
+ decide: (payload: unknown) => {
52
+ ok: boolean;
53
+ };
54
+ }
55
+ export declare function createApprovalBridge(deps: ApprovalBridgeDeps): ApprovalBridge;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * W4-1 reverse channel: serve the sidecar-hosted CoreLoop's
3
+ * `approval.request` calls by asking the desktop user (Electron renderer
4
+ * modal) and returning the 7-variant decision.
5
+ *
6
+ * Fail-closed by construction: no window, no listener, a renderer error,
7
+ * or an invalid reply all become `deny_once` — never a hang, never an
8
+ * auto-allow. The sidecar additionally bounds the wait (`timeoutMs` on the
9
+ * approval config), so a wedged renderer degrades to `timed_out` there.
10
+ *
11
+ * The renderer answers via the `approval:decide` invoke with the requestId
12
+ * echoed back; decisions are validated against the algebra's kind set
13
+ * before crossing back to the sidecar.
14
+ */
15
+ import { randomUUID } from 'node:crypto';
16
+ /**
17
+ * The approval algebra's decision variants (mirrors
18
+ * `steerable_agent_runtime.approval.APPROVAL_KINDS`). `timed_out` is
19
+ * synthesized by the sidecar on timeout, never by the UI.
20
+ */
21
+ export const APPROVAL_DECISION_KINDS = [
22
+ 'allow_once',
23
+ 'allow_for_session',
24
+ 'allow_always',
25
+ 'deny_once',
26
+ 'deny_for_session',
27
+ 'deny_always',
28
+ 'abort',
29
+ ];
30
+ export function createApprovalBridge(deps) {
31
+ const pending = new Map();
32
+ const deny = (reason) => ({ kind: 'deny_once', reason });
33
+ return {
34
+ handler: async (params) => {
35
+ const p = (params ?? {});
36
+ const toolName = typeof p.toolName === 'string' ? p.toolName : '';
37
+ if (!toolName) {
38
+ return { kind: 'deny_once', reason: 'malformed approval request (no toolName)' };
39
+ }
40
+ if (!deps.hasWindow()) {
41
+ deps.onLog?.(`approval: no renderer window; denying ${toolName} once`);
42
+ return deny('no renderer window available to approve the call');
43
+ }
44
+ const requestId = randomUUID();
45
+ const prompt = {
46
+ requestId,
47
+ toolName,
48
+ arguments: p.arguments && typeof p.arguments === 'object'
49
+ ? p.arguments
50
+ : {},
51
+ mode: typeof p.mode === 'string' ? p.mode : 'other',
52
+ category: typeof p.category === 'string' ? p.category : toolName,
53
+ round: typeof p.round === 'number' ? p.round : 0,
54
+ };
55
+ deps.onLog?.(`approval: prompting for ${toolName} (${prompt.mode}/${prompt.category})`);
56
+ return await new Promise((resolve) => {
57
+ pending.set(requestId, { resolve });
58
+ try {
59
+ deps.broadcast('approval:request', prompt);
60
+ }
61
+ catch (err) {
62
+ pending.delete(requestId);
63
+ resolve(deny(`approval prompt failed: ${err instanceof Error ? err.message : String(err)}`));
64
+ }
65
+ });
66
+ },
67
+ decide: (payload) => {
68
+ const p = (payload ?? {});
69
+ const requestId = typeof p.requestId === 'string' ? p.requestId : '';
70
+ const entry = pending.get(requestId);
71
+ if (!entry)
72
+ return { ok: false };
73
+ pending.delete(requestId);
74
+ const kind = APPROVAL_DECISION_KINDS.find((k) => k === p.kind);
75
+ if (!kind) {
76
+ entry.resolve(deny(`renderer returned an invalid decision kind: ${String(p.kind)}`));
77
+ return { ok: true };
78
+ }
79
+ entry.resolve({
80
+ kind,
81
+ reason: typeof p.reason === 'string' ? p.reason : '',
82
+ });
83
+ return { ok: true };
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * W8 ask_user 反向通道:sidecar 内 CoreLoop 的 `ask_user.request` 反向调用
3
+ * 由桌面用户(renderer 问题卡片)应答,返回答案映射。
4
+ *
5
+ * 与审批桥不同,这里 fail-open:无窗口、renderer 抛错、应答畸形都归一为
6
+ * 空 `answers`——模型收到「用户未作答」继续推进,绝不挂起一轮。问题本身
7
+ * 不设桌面侧超时(用户离开多久都可能回来作答,对齐 Claude Code 的阻塞
8
+ * 语义);sidecar 侧 HostAskUserHandler 自身已对调用异常 fail-open。
9
+ *
10
+ * renderer 经 `ask-user:answer` invoke 回带 requestId 应答。
11
+ */
12
+ import type { SidecarReverseHandler } from './types.js';
13
+ /** 广播到 renderer 的请示载荷(renderer 原样渲染问题卡片)。 */
14
+ export interface AskUserPromptRequest {
15
+ /** 关联 id:renderer 的 `ask-user:answer` 原样回带。 */
16
+ requestId: string;
17
+ intro: string;
18
+ questions: Array<Record<string, unknown>>;
19
+ }
20
+ export interface AskUserBridgeDeps {
21
+ broadcast: (channel: 'ask-user:request', payload: AskUserPromptRequest) => void;
22
+ /** 是否有能应答的窗口/浏览器连接;没有则立即空答。 */
23
+ hasWindow: () => boolean;
24
+ onLog?: (line: string) => void;
25
+ }
26
+ export interface AskUserBridge {
27
+ /** 注册为 sidecar 的 `ask_user.request` 反向方法处理器。 */
28
+ handler: SidecarReverseHandler;
29
+ /** renderer `ask-user:answer` 的入口;返回是否匹配到待应答请求。 */
30
+ answer: (payload: unknown) => {
31
+ ok: boolean;
32
+ };
33
+ }
34
+ export declare function createAskUserBridge(deps: AskUserBridgeDeps): AskUserBridge;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * W8 ask_user 反向通道:sidecar 内 CoreLoop 的 `ask_user.request` 反向调用
3
+ * 由桌面用户(renderer 问题卡片)应答,返回答案映射。
4
+ *
5
+ * 与审批桥不同,这里 fail-open:无窗口、renderer 抛错、应答畸形都归一为
6
+ * 空 `answers`——模型收到「用户未作答」继续推进,绝不挂起一轮。问题本身
7
+ * 不设桌面侧超时(用户离开多久都可能回来作答,对齐 Claude Code 的阻塞
8
+ * 语义);sidecar 侧 HostAskUserHandler 自身已对调用异常 fail-open。
9
+ *
10
+ * renderer 经 `ask-user:answer` invoke 回带 requestId 应答。
11
+ */
12
+ import { randomUUID } from 'node:crypto';
13
+ const EMPTY_REPLY = { answers: {} };
14
+ export function createAskUserBridge(deps) {
15
+ const pending = new Map();
16
+ return {
17
+ handler: (params) => {
18
+ const p = (params ?? {});
19
+ const questions = Array.isArray(p.questions) ? p.questions : [];
20
+ if (questions.length === 0) {
21
+ deps.onLog?.('ask_user: malformed request (no questions); answering empty');
22
+ return Promise.resolve({ ...EMPTY_REPLY });
23
+ }
24
+ if (!deps.hasWindow()) {
25
+ deps.onLog?.('ask_user: no renderer window; answering empty');
26
+ return Promise.resolve({ ...EMPTY_REPLY });
27
+ }
28
+ const requestId = randomUUID();
29
+ const prompt = {
30
+ requestId,
31
+ intro: typeof p.intro === 'string' ? p.intro : '',
32
+ questions: questions,
33
+ };
34
+ return new Promise((resolve) => {
35
+ pending.set(requestId, { resolve });
36
+ try {
37
+ deps.broadcast('ask-user:request', prompt);
38
+ }
39
+ catch (err) {
40
+ pending.delete(requestId);
41
+ deps.onLog?.(`ask_user: broadcast failed: ${String(err)}`);
42
+ resolve({ ...EMPTY_REPLY });
43
+ }
44
+ });
45
+ },
46
+ answer: (payload) => {
47
+ const p = (payload ?? {});
48
+ const entry = typeof p.requestId === 'string' ? pending.get(p.requestId) : undefined;
49
+ if (!entry)
50
+ return { ok: false };
51
+ pending.delete(p.requestId);
52
+ const answers = p.answers && typeof p.answers === 'object' && !Array.isArray(p.answers)
53
+ ? p.answers
54
+ : {};
55
+ entry.resolve({ answers });
56
+ return { ok: true };
57
+ },
58
+ };
59
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * W4.1.1 reverse channel: serve the sidecar's `host.process.spawn` by
3
+ * spawning the command confined on Windows via the win-spawn-helper Rust
4
+ * binary (restricted token + Job Object; contract: steerable-framework
5
+ * docs/spec/safety.md "Host capability surface").
6
+ *
7
+ * Fail closed by construction: on non-Windows platforms (which have local
8
+ * rewriter backends and never legitimately route here) or when the helper
9
+ * binary is absent, the handler throws — the sidecar surfaces a tool error
10
+ * and the command never runs unconfined.
11
+ *
12
+ * Enforcement honesty: the helper confines filesystem writes only. When the
13
+ * sidecar policy also constrains the network (`network: false` or an
14
+ * allow-list), the helper's exit frame lists those fields under
15
+ * `sandbox.notEnforced` and this handler reports `partial`.
16
+ */
17
+ import type { SidecarReverseHandler } from './types.js';
18
+ export declare function resolveWinSpawnHelperPath(): string | null;
19
+ export declare function createProcessSpawnHandler(): SidecarReverseHandler;