windows-development-sandbox 1.11.7 → 1.13.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.
Files changed (246) hide show
  1. package/README.md +13 -6
  2. package/config.example.json +15 -4
  3. package/dist/app/features/access/access.def.d.ts +190 -0
  4. package/dist/app/features/access/access.def.js +273 -0
  5. package/dist/app/features/access/access.run.d.ts +79 -0
  6. package/dist/app/features/access/access.run.js +129 -0
  7. package/dist/app/features/access/index.d.ts +2 -0
  8. package/dist/app/features/access/index.js +3 -0
  9. package/dist/app/features/agents/agent-entry.js +5 -0
  10. package/dist/app/features/agents/agents.def.js +1 -0
  11. package/dist/app/features/apps/apps-rows.d.ts +4 -1
  12. package/dist/app/features/apps/apps-rows.js +7 -2
  13. package/dist/app/features/apps/apps.def.d.ts +19 -0
  14. package/dist/app/features/apps/apps.def.js +50 -12
  15. package/dist/app/features/apps/apps.run.d.ts +2 -0
  16. package/dist/app/features/apps/apps.run.js +1 -1
  17. package/dist/app/features/audit/audit.def.d.ts +350 -0
  18. package/dist/app/features/audit/audit.def.js +457 -0
  19. package/dist/app/features/audit/audit.run.d.ts +110 -0
  20. package/dist/app/features/audit/audit.run.js +133 -0
  21. package/dist/app/features/audit/index.d.ts +2 -0
  22. package/dist/app/features/audit/index.js +3 -0
  23. package/dist/app/features/defs.d.ts +349 -4
  24. package/dist/app/features/defs.js +16 -1
  25. package/dist/app/features/files/files.def.js +1 -0
  26. package/dist/app/features/health/health.def.js +1 -0
  27. package/dist/app/features/sandbox/sandbox.def.js +3 -0
  28. package/dist/app/features/sessions/sessions.def.d.ts +36 -0
  29. package/dist/app/features/sessions/sessions.def.js +77 -9
  30. package/dist/app/features/sessions/sessions.run.d.ts +25 -2
  31. package/dist/app/features/sessions/sessions.run.js +71 -3
  32. package/dist/app/features/settings/settings.def.d.ts +8 -0
  33. package/dist/app/features/settings/settings.def.js +17 -0
  34. package/dist/app/features/settings/settings.run.d.ts +3 -0
  35. package/dist/app/features/settings/settings.run.js +16 -2
  36. package/dist/app/features/web/web.def.d.ts +9 -0
  37. package/dist/app/features/web/web.def.js +26 -1
  38. package/dist/app/features/web/web.run.d.ts +3 -0
  39. package/dist/app/features/web/web.run.js +11 -3
  40. package/dist/app/kernel/access.d.ts +33 -0
  41. package/dist/app/kernel/access.js +44 -0
  42. package/dist/app/kernel/define.d.ts +26 -2
  43. package/dist/app/kernel/index.d.ts +1 -0
  44. package/dist/app/kernel/index.js +1 -0
  45. package/dist/app/kernel/rpc.d.ts +5 -2
  46. package/dist/app/registry.d.ts +4 -1
  47. package/dist/app/registry.js +26 -4
  48. package/dist/cli/args.d.ts +0 -3
  49. package/dist/cli/args.js +3 -4
  50. package/dist/cli/commands/index.d.ts +1 -1
  51. package/dist/cli/commands/index.js +1 -1
  52. package/dist/cli/commands/launch/prepare.js +1 -1
  53. package/dist/cli/commands/maintenance/index.d.ts +0 -1
  54. package/dist/cli/commands/maintenance/index.js +0 -1
  55. package/dist/cli/commands/registry.js +3 -2
  56. package/dist/cli/commands/remote.d.ts +2 -0
  57. package/dist/cli/commands/remote.js +49 -0
  58. package/dist/cli/commands/sessions/reopen.js +3 -0
  59. package/dist/cli/commands/web.js +66 -0
  60. package/dist/cli/manifest.d.ts +63 -45
  61. package/dist/cli/manifest.js +33 -18
  62. package/dist/cli/menu/io-menu.d.ts +5 -3
  63. package/dist/cli/menu/io-menu.js +12 -5
  64. package/dist/cli/menu/pages/apps-page.js +2 -15
  65. package/dist/cli/menu/pages/maintenance-page.d.ts +4 -0
  66. package/dist/cli/menu/pages/maintenance-page.js +51 -8
  67. package/dist/cli/menu/remote-menu.d.ts +19 -0
  68. package/dist/cli/menu/remote-menu.js +108 -0
  69. package/dist/cli/menu/views.js +4 -0
  70. package/dist/cli/operations/handler.js +15 -1
  71. package/dist/cli/services/remote-client.d.ts +16 -0
  72. package/dist/cli/services/remote-client.js +141 -0
  73. package/dist/cli/services/sessions-service.d.ts +5 -0
  74. package/dist/cli/services/sessions-service.js +17 -1
  75. package/dist/core/config/defaults.js +12 -2
  76. package/dist/core/local-config/file.js +16 -2
  77. package/dist/core/local-config/overrides.js +7 -0
  78. package/dist/core/run/versions.js +9 -8
  79. package/dist/core/schema/apps.d.ts +9 -0
  80. package/dist/core/schema/apps.js +9 -0
  81. package/dist/core/schema/config.d.ts +23 -3
  82. package/dist/core/schema/config.js +42 -5
  83. package/dist/core/schema/sandbox.d.ts +1 -0
  84. package/dist/core/schema/sandbox.js +11 -3
  85. package/dist/core/schema/web.d.ts +51 -0
  86. package/dist/core/schema/web.js +46 -1
  87. package/dist/core/util/fs.d.ts +5 -0
  88. package/dist/core/util/fs.js +23 -0
  89. package/dist/core/util/index.d.ts +1 -1
  90. package/dist/core/util/index.js +1 -1
  91. package/dist/guest/apps/app-installer.js +50 -4
  92. package/dist/guest/apps/index.d.ts +1 -1
  93. package/dist/guest/apps/index.js +1 -1
  94. package/dist/guest/apps/sources.js +1 -1
  95. package/dist/guest/audit/parse.d.ts +32 -2
  96. package/dist/guest/audit/parse.js +43 -5
  97. package/dist/guest/audit/process-audit.d.ts +3 -1
  98. package/dist/guest/audit/process-audit.js +9 -6
  99. package/dist/guest/boot/boot.js +12 -3
  100. package/dist/guest/boot/guide.d.ts +1 -1
  101. package/dist/guest/boot/guide.js +27 -3
  102. package/dist/guest/daemon/daemon.js +2 -0
  103. package/dist/guest/daemon/idle-shutdown.js +2 -1
  104. package/dist/guest/daemon/sessions.d.ts +23 -2
  105. package/dist/guest/daemon/sessions.js +151 -20
  106. package/dist/guest/entries/daemon.js +2 -1
  107. package/dist/guest/git/decision.js +7 -0
  108. package/dist/guest/sessions/cmdline.d.ts +14 -0
  109. package/dist/guest/sessions/cmdline.js +10 -0
  110. package/dist/guest/setup/addon-mcp.d.ts +2 -0
  111. package/dist/guest/setup/addon-mcp.js +49 -0
  112. package/dist/guest/setup/agent-addons.d.ts +9 -0
  113. package/dist/guest/setup/agent-addons.js +22 -0
  114. package/dist/guest/setup/headroom.d.ts +7 -0
  115. package/dist/guest/setup/headroom.js +60 -0
  116. package/dist/guest/setup/managed-policy.d.ts +2 -1
  117. package/dist/guest/setup/managed-policy.js +15 -6
  118. package/dist/guest/toolchains/plan.js +8 -3
  119. package/dist/guest/toolchains/setup.js +3 -0
  120. package/dist/host/audit/attribute.d.ts +46 -0
  121. package/dist/host/audit/attribute.js +76 -0
  122. package/dist/host/audit/collect.d.ts +29 -3
  123. package/dist/host/audit/collect.js +256 -61
  124. package/dist/host/audit/git-commits.d.ts +29 -0
  125. package/dist/host/audit/git-commits.js +157 -0
  126. package/dist/host/audit/index.d.ts +5 -0
  127. package/dist/host/audit/index.js +6 -0
  128. package/dist/host/audit/log.d.ts +45 -7
  129. package/dist/host/audit/log.js +148 -45
  130. package/dist/host/audit/redact.d.ts +2 -0
  131. package/dist/host/audit/redact.js +67 -0
  132. package/dist/host/channel/channel.d.ts +5 -1
  133. package/dist/host/channel/channel.js +12 -0
  134. package/dist/host/guard/agent-files.js +7 -1
  135. package/dist/host/guard/commands.js +13 -1
  136. package/dist/host/guard/git-config.js +27 -7
  137. package/dist/host/guard/git-dirs.js +78 -3
  138. package/dist/host/guard/mark-of-the-web.js +3 -1
  139. package/dist/host/guard/nested-repos.js +7 -2
  140. package/dist/host/guard/store.js +1 -0
  141. package/dist/host/guard/types.d.ts +2 -0
  142. package/dist/host/launch/mappings.js +11 -1
  143. package/dist/host/launch/project-scan.js +2 -21
  144. package/dist/host/launch/stage.js +4 -0
  145. package/dist/host/life/session-ledger.js +2 -1
  146. package/dist/host/remote/access-store.d.ts +156 -0
  147. package/dist/host/remote/access-store.js +308 -0
  148. package/dist/host/remote/cert.d.ts +20 -0
  149. package/dist/host/remote/cert.js +103 -0
  150. package/dist/host/remote/door-client.d.ts +13 -0
  151. package/dist/host/remote/door-client.js +54 -0
  152. package/dist/host/remote/firewall.d.ts +44 -0
  153. package/dist/host/remote/firewall.js +118 -0
  154. package/dist/host/remote/gate.d.ts +125 -0
  155. package/dist/host/remote/gate.js +391 -0
  156. package/dist/host/remote/index.d.ts +8 -0
  157. package/dist/host/remote/index.js +10 -0
  158. package/dist/host/remote/lan.d.ts +24 -0
  159. package/dist/host/remote/lan.js +88 -0
  160. package/dist/host/remote/tls-store.d.ts +22 -0
  161. package/dist/host/remote/tls-store.js +63 -0
  162. package/dist/host/remote/windows-helper.d.ts +67 -0
  163. package/dist/host/remote/windows-helper.js +129 -0
  164. package/dist/host/web/bridge.d.ts +5 -1
  165. package/dist/host/web/bridge.js +6 -2
  166. package/dist/host/web/browser.js +1 -1
  167. package/dist/host/web/control.d.ts +4 -1
  168. package/dist/host/web/control.js +28 -4
  169. package/dist/host/web/rpc.d.ts +16 -2
  170. package/dist/host/web/rpc.js +37 -4
  171. package/dist/host/web/runner.d.ts +4 -0
  172. package/dist/host/web/runner.js +12 -0
  173. package/dist/host/web/server.d.ts +18 -2
  174. package/dist/host/web/server.js +97 -24
  175. package/dist/host/web/state.d.ts +5 -0
  176. package/dist/host/web/state.js +8 -0
  177. package/dist/runtime/agent.js +184 -18
  178. package/dist/runtime/daemon.js +772 -338
  179. package/dist/runtime/db.js +184 -18
  180. package/dist/runtime/gitguard.js +6 -0
  181. package/dist/runtime/main.js +966 -553
  182. package/dist/runtime/netproxy.js +184 -18
  183. package/dist/runtime/view.js +186 -18
  184. package/dist/selftest/app.test.js +66 -7
  185. package/dist/selftest/audit.test.js +354 -47
  186. package/dist/selftest/channel.test.js +16 -0
  187. package/dist/selftest/cmdline.test.js +16 -0
  188. package/dist/selftest/git-access.test.js +8 -0
  189. package/dist/selftest/hcs.test.js +35 -0
  190. package/dist/selftest/hostile-input.test.js +12 -1
  191. package/dist/selftest/index.js +2 -0
  192. package/dist/selftest/live-daemon.test.js +25 -0
  193. package/dist/selftest/local-config.test.js +30 -0
  194. package/dist/selftest/menu-screens.js +1 -1
  195. package/dist/selftest/menu.test.js +3 -3
  196. package/dist/selftest/remote.test.d.ts +2 -0
  197. package/dist/selftest/remote.test.js +536 -0
  198. package/dist/selftest/repo-guard.test.js +56 -2
  199. package/dist/selftest/resume.test.js +2 -1
  200. package/dist/selftest/sharing.test.js +9 -2
  201. package/dist/selftest/support/engine-golden.js +9 -0
  202. package/dist/selftest/support/fixtures.d.ts +14 -0
  203. package/dist/selftest/support/web-preview.js +157 -0
  204. package/dist/selftest/toolchains.test.js +147 -2
  205. package/dist/selftest/web.test.js +2 -0
  206. package/dist/shared/apps/agent-addons.d.ts +64 -0
  207. package/dist/shared/apps/agent-addons.js +95 -0
  208. package/dist/shared/apps/catalogue.d.ts +33 -2
  209. package/dist/shared/apps/catalogue.js +102 -11
  210. package/dist/shared/apps/index.d.ts +3 -2
  211. package/dist/shared/apps/index.js +3 -2
  212. package/dist/shared/apps/selection.d.ts +2 -0
  213. package/dist/shared/apps/selection.js +7 -0
  214. package/dist/shared/channel/schema.d.ts +23 -0
  215. package/dist/shared/channel/schema.js +8 -0
  216. package/dist/shared/pty/protocol.d.ts +2 -0
  217. package/dist/shared/pty/protocol.js +2 -0
  218. package/dist/sync/watcher.js +9 -1
  219. package/dist/web/index.html +2 -2
  220. package/dist/web/{main-RQFN4WBY.css → main-UAQPI5XD.css} +1 -1
  221. package/dist/web/main-Z2WYAE2Z.js +221 -0
  222. package/native/csharp/HcsHost/AssemblyInfo.cs +5 -3
  223. package/native/csharp/HcsHost/Hcs.cs +9 -0
  224. package/native/csharp/HcsHost/Program.cs +26 -1
  225. package/native/csharp/HcsHost/Share.cs +173 -0
  226. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  227. package/native/csharp/HostTools/Program.cs +17 -1
  228. package/native/csharp/HostTools/WebHelper.cs +499 -0
  229. package/native/scripts/get-network-profile.ps1 +19 -0
  230. package/native/scripts/set-folder-access.ps1 +11 -0
  231. package/native/scripts/set-web-firewall.ps1 +103 -0
  232. package/package.json +1 -1
  233. package/sandbox/addons/ccusage/package-lock.json +111 -0
  234. package/sandbox/addons/ccusage/package.json +7 -0
  235. package/sandbox/addons/context7/package-lock.json +1313 -0
  236. package/sandbox/addons/context7/package.json +7 -0
  237. package/sandbox/addons/headroom/requirements.in +1 -0
  238. package/sandbox/addons/headroom/requirements.txt +2922 -0
  239. package/sandbox/addons/playwright-mcp/package-lock.json +56 -0
  240. package/sandbox/addons/playwright-mcp/package.json +7 -0
  241. package/sandbox/csharp/PtyHost.cs +20 -0
  242. package/schemas/config.schema.json +78 -4
  243. package/schemas/sandbox-config.schema.json +2 -20
  244. package/dist/cli/commands/maintenance/audit.d.ts +0 -3
  245. package/dist/cli/commands/maintenance/audit.js +0 -58
  246. package/dist/web/main-K2JMCAZI.js +0 -219
@@ -10,6 +10,9 @@ export declare const webStatusRun: import("../../kernel/define.js").Implementati
10
10
  running: import("zod").ZodBoolean;
11
11
  port: import("zod").ZodNullable<import("zod").ZodNumber>;
12
12
  address: import("zod").ZodNullable<import("zod").ZodString>;
13
+ lanAddress: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
14
+ lanFingerprint: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
15
+ lanProblem: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
13
16
  }, import("zod/v4/core").$strip>, "web.status">, import("../../../cli/commands/context.js").Context>;
14
17
  export declare const startWebRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
15
18
  port: import("zod").ZodNumber;
@@ -60,7 +60,15 @@ export const webStatusRun = implement(webStatus, async (ctx) => {
60
60
  requireOnPc('web');
61
61
  const { running, state } = await controlOf(ctx).status();
62
62
  const port = running && state ? state.port : null;
63
- return { running, port, address: port ? 'http://127.0.0.1:' + port + '/' : null };
63
+ const lan = running ? (state?.lan ?? null) : null;
64
+ return {
65
+ running,
66
+ port,
67
+ address: port ? 'http://127.0.0.1:' + port + '/' : null,
68
+ lanAddress: lan?.url ?? null,
69
+ lanFingerprint: lan?.fingerprint ?? null,
70
+ lanProblem: lan?.problem ?? null,
71
+ };
64
72
  });
65
73
  export const startWebRun = implement(startWeb, async (ctx, _input, io) => {
66
74
  requireOnPc('web');
@@ -73,8 +81,8 @@ export const openWebRun = implement(openWeb, async (ctx, _input, io) => {
73
81
  const started = await startServer(ctx, io, control);
74
82
  if (started.wasRunning)
75
83
  io.log(started.message);
76
- const { state } = await control.status();
77
- if (!state)
84
+ const { running, state } = await control.status();
85
+ if (!running || !state)
78
86
  return fail('The web UI stopped right away.', 'Run sandbox web start again.');
79
87
  openBrowser(control.url(state));
80
88
  return { message: 'Opened in your browser.' };
@@ -0,0 +1,33 @@
1
+ /** What a definition needs. Default (fails closed): a query needs view, an operation owner. */
2
+ export type Access = 'view' | 'control' | 'owner';
3
+ /** A role the owner can give: view only, or full control (everything but the owner's own things). */
4
+ export type RemoteRole = 'view' | 'control';
5
+ /** Who runs a definition: the owner, or someone the owner let in. */
6
+ export type Caller = {
7
+ readonly kind: 'owner';
8
+ } | {
9
+ readonly kind: 'windows';
10
+ readonly user: string;
11
+ readonly role: RemoteRole;
12
+ } | {
13
+ readonly kind: 'device';
14
+ readonly id: string;
15
+ readonly name: string;
16
+ readonly role: RemoteRole;
17
+ };
18
+ export declare const OWNER: Caller;
19
+ /** A definition's need, as written: fixed, or from its input (settings.set on a web.* key needs owner). */
20
+ export type AccessRule = Access | ((input: unknown) => Access);
21
+ /** The most a caller may do. */
22
+ export declare function callerAccess(caller: Caller): Access;
23
+ /** The caller may run something that needs `need`. */
24
+ export declare function allows(caller: Caller, need: Access): boolean;
25
+ /** What a definition needs for this input. A rule that throws on a strange input needs owner. */
26
+ export declare function accessNeeded(def: {
27
+ readonly kind: 'operation' | 'query';
28
+ readonly access?: AccessRule;
29
+ }, input: unknown): Access;
30
+ /** Who the caller is, in a few words (the audit log, the page's header). */
31
+ export declare function callerLabel(caller: Caller): string;
32
+ /** The refusal text for a caller that may not. */
33
+ export declare function refusalText(caller: Caller, need: Access): string;
@@ -0,0 +1,44 @@
1
+ // Who may run what (docs/design/remote-access.md): every definition needs view, control or owner; the caller of a run
2
+ // has a role. The owner (at the PC, or through the token link) may do everything; another Windows user of this PC or a
3
+ // paired device has the role the owner gave them, never owner. Pure (the web bundle shows what the role allows).
4
+ export const OWNER = { kind: 'owner' };
5
+ const RANK = { view: 0, control: 1, owner: 2 };
6
+ /** The most a caller may do. */
7
+ export function callerAccess(caller) {
8
+ return caller.kind === 'owner' ? 'owner' : caller.role;
9
+ }
10
+ /** The caller may run something that needs `need`. */
11
+ export function allows(caller, need) {
12
+ return RANK[callerAccess(caller)] >= RANK[need];
13
+ }
14
+ /** What a definition needs for this input. A rule that throws on a strange input needs owner. */
15
+ export function accessNeeded(def, input) {
16
+ const rule = def.access;
17
+ if (rule === undefined)
18
+ return def.kind === 'query' ? 'view' : 'owner';
19
+ if (typeof rule === 'string')
20
+ return rule;
21
+ try {
22
+ return rule(input);
23
+ }
24
+ catch {
25
+ return 'owner';
26
+ }
27
+ }
28
+ /** Who the caller is, in a few words (the audit log, the page's header). */
29
+ export function callerLabel(caller) {
30
+ switch (caller.kind) {
31
+ case 'owner':
32
+ return 'you';
33
+ case 'windows':
34
+ return 'Windows user ' + caller.user;
35
+ case 'device':
36
+ return 'device ' + caller.name;
37
+ }
38
+ }
39
+ /** The refusal text for a caller that may not. */
40
+ export function refusalText(caller, need) {
41
+ if (need === 'owner')
42
+ return 'Only the owner of this PC can do that, at the PC.';
43
+ return callerAccess(caller) === 'view' ? 'Your access is view only.' : 'You may not do that.';
44
+ }
@@ -1,6 +1,7 @@
1
1
  import type { z } from 'zod';
2
+ import type { AccessRule } from './access.js';
2
3
  import type { OperationIO } from './io.js';
3
- import type { Page } from './view.js';
4
+ import type { Page, Target } from './view.js';
4
5
  export type SurfaceName = 'cli' | 'menu' | 'web';
5
6
  export declare const SURFACE_NAMES: readonly SurfaceName[];
6
7
  /** Where a definition is offered: everywhere, or everywhere but these surfaces (each with the reason). */
@@ -63,6 +64,11 @@ interface Definition<Id extends string, I extends z.ZodType, O extends z.ZodType
63
64
  readonly output: O;
64
65
  /** Default: every surface. */
65
66
  readonly surfaces?: Surfaces;
67
+ /**
68
+ * What a caller other than the owner needs (docs/design/remote-access.md). Default (fails closed): view for a query,
69
+ * owner for an operation; an operation says 'control' to be open to someone the owner gave control.
70
+ */
71
+ readonly access?: AccessRule;
66
72
  readonly cli?: CliHint;
67
73
  /** The output in one text: what an operation did, or a value a query read (CLI output, menu notice, web note). */
68
74
  text?(output: z.output<O>): string;
@@ -77,6 +83,13 @@ export interface OperationDef<I extends z.ZodType = z.ZodType, O extends z.ZodTy
77
83
  readonly kind: 'operation';
78
84
  /** Shown as dangerous (red); it asks for its own confirmation through io.confirm. */
79
85
  readonly danger?: boolean;
86
+ /** Its result must stay on screen until dismissed (a one-time code to type elsewhere): the web keeps it open. */
87
+ readonly keepResult?: boolean;
88
+ /**
89
+ * Where every surface goes once it succeeded: a new session's terminal is attached on the command line and in the
90
+ * menu, and opened in the web UI.
91
+ */
92
+ opens?(output: z.output<O>): Target | undefined;
80
93
  }
81
94
  export interface QueryDef<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodType, Id extends string = string> extends Definition<Id, I, O> {
82
95
  readonly kind: 'query';
@@ -103,6 +116,15 @@ export interface ViewTabs<P> {
103
116
  readonly label: string;
104
117
  }[];
105
118
  }
119
+ /**
120
+ * A filter of a view: one parameter picked from fixed values (the audit's kind and period). The web shows a small
121
+ * choice per filter above the page (changing one keeps the view's other parameters); the command line has the query's
122
+ * flags for the same thing, and the menu shows the first value. The first value is the default.
123
+ */
124
+ export interface ViewFilter<P> extends ViewTabs<P> {
125
+ /** What the choice is about ("Period"), for its label. */
126
+ readonly label: string;
127
+ }
106
128
  export interface ViewDef<Q extends QueryMap = QueryMap, P = Record<string, never>, Id extends string = string> {
107
129
  readonly kind: 'view';
108
130
  readonly id: Id;
@@ -115,7 +137,9 @@ export interface ViewDef<Q extends QueryMap = QueryMap, P = Record<string, never
115
137
  readonly pickers?: readonly QueryDef[];
116
138
  /** A parameter switched with tabs above the page (Settings: all projects or this project), first choice default. */
117
139
  readonly tabs?: ViewTabs<P>;
118
- /** Parameters taken from the address as they are (the log shown), besides the tabs' one. */
140
+ /** Parameters picked from fixed values above the page (the audit's kind and period), first value default. */
141
+ readonly filters?: readonly ViewFilter<P>[];
142
+ /** Parameters taken from the address as they are (the log shown), besides the tabs' and filters' ones. */
119
143
  readonly params?: readonly (keyof P & string)[];
120
144
  /** The input of each query for these view parameters (default: each query's defaults). */
121
145
  inputs?(params: P): ViewInputs<Q>;
@@ -1,3 +1,4 @@
1
+ export * from './access.js';
1
2
  export * from './define.js';
2
3
  export * from './errors.js';
3
4
  export * from './fields.js';
@@ -1,5 +1,6 @@
1
1
  // The kernel of the shared core: pure (zod and nothing of Node, the terminal or the DOM), so the CLI, the menu, the
2
2
  // web server and the browser all import the same definitions, view model, form fields, questions and RPC messages.
3
+ export * from './access.js';
3
4
  export * from './define.js';
4
5
  export * from './errors.js';
5
6
  export * from './fields.js';
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import type { Access, Caller } from './access.js';
2
3
  import { type OperationIO } from './io.js';
3
4
  /** One call or subscription on the socket (chosen by the browser, unique while it runs). */
4
5
  export declare const RpcIdSchema: z.ZodString;
@@ -168,6 +169,8 @@ export interface RpcHost {
168
169
  kind(id: string): 'operation' | 'query' | undefined;
169
170
  /** How often a query is read again while subscribed (undefined: once). */
170
171
  liveMs(id: string): number | undefined;
171
- /** Runs an allowed definition (input and output checked). */
172
- run(id: string, input: unknown, io: OperationIO): Promise<unknown>;
172
+ /** What a caller other than the owner needs to run it with this input (undefined: not here). */
173
+ need(id: string, input: unknown): Access | undefined;
174
+ /** Runs an allowed definition (input and output checked); refused when `caller` (default: the owner) may not. */
175
+ run(id: string, input: unknown, io: OperationIO, caller?: Caller): Promise<unknown>;
173
176
  }
@@ -6,5 +6,8 @@ export declare function implementationOf(id: string): Implementation<AnyDefiniti
6
6
  export declare function runDefinition(id: string, ctx: AppContext, input: unknown, io: OperationIO): Promise<unknown>;
7
7
  /** Reads a query in-process (the menu): typed output, and no questions. */
8
8
  export declare function readQuery<D extends QueryDef>(def: D, ctx: AppContext, input?: unknown): Promise<OutputOf<D>>;
9
- /** What the web server may run: the registered definitions whose surfaces include the web. */
9
+ /**
10
+ * What the web server may run: the registered definitions whose surfaces include the web, each refused to a caller
11
+ * whose role does not reach what it needs (docs/design/remote-access.md).
12
+ */
10
13
  export declare function createRpcHost(ctx: AppContext): RpcHost;
@@ -1,4 +1,5 @@
1
- import { invoke, NO_QUESTIONS, onSurface, } from './kernel/index.js';
1
+ import { fail } from '../core/util/errors.js';
2
+ import { accessNeeded, allows, invoke, OWNER, refusalText, NO_QUESTIONS, onSurface, } from './kernel/index.js';
2
3
  import { addAgentRun, listAgentsRun, makeDefaultAgentRun, removeAgentRun, startAgentRun, } from './features/agents/index.js';
3
4
  import { defaultAccountRun, listAccountsRun, removeAccountRun, renameAccountRun, saveAccountRun, useAccountRun, } from './features/accounts/index.js';
4
5
  import { listAppsRun, requestAppRun, setCustomAppsRun, toggleAppRun } from './features/apps/index.js';
@@ -9,13 +10,15 @@ import { addProjectRun, allProjectsRun, chooseProjectsRun, excludeProjectRun, in
9
10
  import { checkHealthRun, enableSandboxRun, lastHealthRun, repairHealthRun } from './features/health/index.js';
10
11
  import { hideWindowRun, sandboxStatusRun, showWindowRun, stopSandboxRun } from './features/sandbox/index.js';
11
12
  import { listLogsRun, tailLogRun } from './features/logs/index.js';
12
- import { listSessionsRun, restartSessionsRun } from './features/sessions/index.js';
13
+ import { listAuditRun } from './features/audit/index.js';
14
+ import { closeSessionRun, listSessionsRun, restartSessionsRun, startShellRun } from './features/sessions/index.js';
13
15
  import { exportConfigRun, getSettingRun, importConfigRun, listSettingsRun, resetSettingRun, setSettingRun, } from './features/settings/index.js';
14
16
  import { migrateSettingsRun, runUpdateRun, updatesStatusRun } from './features/updates/index.js';
15
17
  import { checkEngineRun, cleanEngineRun, engineLeftoversRun, engineStatusRun } from './features/engine/index.js';
16
18
  import { openWebRun, startWebRun, stopWebRun, webStatusRun } from './features/web/index.js';
17
19
  import { vpnLoginRun, vpnLogoutRun, vpnStatusRun } from './features/vpn/index.js';
18
20
  import { githubLoginRun, githubLogoutRun, githubStatusRun } from './features/github/index.js';
21
+ import { accessStatusRun, allowDeviceRun, lanOffRun, lanOnRun, revokeAccessRun, revokeAllAccessRun, } from './features/access/index.js';
19
22
  export const IMPLEMENTATIONS = [
20
23
  sandboxStatusRun,
21
24
  showWindowRun,
@@ -23,6 +26,8 @@ export const IMPLEMENTATIONS = [
23
26
  stopSandboxRun,
24
27
  listSessionsRun,
25
28
  restartSessionsRun,
29
+ startShellRun,
30
+ closeSessionRun,
26
31
  startAgentRun,
27
32
  addAgentRun,
28
33
  makeDefaultAgentRun,
@@ -69,6 +74,7 @@ export const IMPLEMENTATIONS = [
69
74
  containedOffRun,
70
75
  listLogsRun,
71
76
  tailLogRun,
77
+ listAuditRun,
72
78
  runUpdateRun,
73
79
  migrateSettingsRun,
74
80
  updatesStatusRun,
@@ -86,6 +92,12 @@ export const IMPLEMENTATIONS = [
86
92
  githubLoginRun,
87
93
  githubLogoutRun,
88
94
  webStatusRun,
95
+ accessStatusRun,
96
+ allowDeviceRun,
97
+ revokeAccessRun,
98
+ revokeAllAccessRun,
99
+ lanOnRun,
100
+ lanOffRun,
89
101
  ];
90
102
  export function implementationOf(id) {
91
103
  return IMPLEMENTATIONS.find((i) => i.def.id === id);
@@ -101,7 +113,10 @@ export function runDefinition(id, ctx, input, io) {
101
113
  export async function readQuery(def, ctx, input = {}) {
102
114
  return (await runDefinition(def.id, ctx, input, NO_QUESTIONS));
103
115
  }
104
- /** What the web server may run: the registered definitions whose surfaces include the web. */
116
+ /**
117
+ * What the web server may run: the registered definitions whose surfaces include the web, each refused to a caller
118
+ * whose role does not reach what it needs (docs/design/remote-access.md).
119
+ */
105
120
  export function createRpcHost(ctx) {
106
121
  const allowed = (id) => {
107
122
  const impl = implementationOf(id);
@@ -113,10 +128,17 @@ export function createRpcHost(ctx) {
113
128
  const def = allowed(id)?.def;
114
129
  return def?.kind === 'query' ? def.live?.everyMs : undefined;
115
130
  },
116
- run: (id, input, io) => {
131
+ need: (id, input) => {
132
+ const impl = allowed(id);
133
+ return impl ? accessNeeded(impl.def, input) : undefined;
134
+ },
135
+ run: async (id, input, io, caller = OWNER) => {
117
136
  const impl = allowed(id);
118
137
  if (!impl)
119
138
  throw new Error('Not allowed on the web: ' + id);
139
+ const need = accessNeeded(impl.def, input);
140
+ if (!allows(caller, need))
141
+ fail(refusalText(caller, need));
120
142
  return invoke(impl, ctx, input, io);
121
143
  },
122
144
  };
@@ -42,9 +42,6 @@ export interface CliOptions {
42
42
  all: boolean;
43
43
  /** launch --session <agent>@<folder> (repeatable): several sessions at once, folders made absolute. */
44
44
  sessions: SessionWant[];
45
- /** audit: only events of this kind, and containing this text. */
46
- kind?: string;
47
- grep?: string;
48
45
  /** migrate --dry-run, and --restore <backup folder>. */
49
46
  dryRun: boolean;
50
47
  /** doctor --fix: repair what it finds without asking. */
package/dist/cli/args.js CHANGED
@@ -217,11 +217,10 @@ export function parseCli(argv, wrapper = false) {
217
217
  keepOld: Boolean(o.keepOld),
218
218
  withPaths: Boolean(o.withPaths),
219
219
  all: Boolean(o.all),
220
- sessions: (o.session ?? []).map((s) => parseSessionWant(s)),
220
+ // launch --session is repeatable (a list); `audit --session <id>` is one id, which its definition reads.
221
+ sessions: (Array.isArray(o.session) ? o.session : []).map((s) => parseSessionWant(s)),
221
222
  timeout: o.timeout ?? 600,
222
- days: o.days ?? (name === 'audit' ? 1 : 7),
223
- kind: o.kind,
224
- grep: o.grep,
223
+ days: o.days ?? 7,
225
224
  dryRun: Boolean(o.dryRun),
226
225
  fix: Boolean(o.fix),
227
226
  auto: o.auto === true,
@@ -6,6 +6,6 @@ export { repairCommand } from './repair/index.js';
6
6
  export { contextOrDefaults } from './context.js';
7
7
  export { attach, db, exec } from './sessions/index.js';
8
8
  export { resume } from './resume.js';
9
- export { audit, claudeHost, config, doctor, hostCommand, logs, migrateBeforeCommand, sync, updatesBeforeCommand, watcher, } from './maintenance/index.js';
9
+ export { claudeHost, config, doctor, hostCommand, logs, migrateBeforeCommand, sync, updatesBeforeCommand, watcher, } from './maintenance/index.js';
10
10
  export { agentCommandsCommand, syncAgentCommands } from './agent-commands.js';
11
11
  export { HANDLERS, type Handler } from './registry.js';
@@ -6,6 +6,6 @@ export { repairCommand } from './repair/index.js';
6
6
  export { contextOrDefaults } from './context.js';
7
7
  export { attach, db, exec } from './sessions/index.js';
8
8
  export { resume } from './resume.js';
9
- export { audit, claudeHost, config, doctor, hostCommand, logs, migrateBeforeCommand, sync, updatesBeforeCommand, watcher, } from './maintenance/index.js';
9
+ export { claudeHost, config, doctor, hostCommand, logs, migrateBeforeCommand, sync, updatesBeforeCommand, watcher, } from './maintenance/index.js';
10
10
  export { agentCommandsCommand, syncAgentCommands } from './agent-commands.js';
11
11
  export { HANDLERS } from './registry.js';
@@ -7,7 +7,7 @@ import { RepoGuardStore, readDataSize, saveMarkedProjects } from '../../../host/
7
7
  import { ensureConpty, ensureTerminal } from '../../../host/tools/index.js';
8
8
  import { planView, viewSkipped, saveActiveViews, WorkspaceView } from '../../../host/workspace-view/index.js';
9
9
  import { saveForwards } from '../../../host/network/index.js';
10
- import { saveAuditSettings } from '../../../host/audit/collect.js';
10
+ import { saveAuditSettings } from '../../../host/audit/index.js';
11
11
  import { humanBytes } from '../../../core/disk.js';
12
12
  import { syncBeforeLaunch, watcherRunning } from '../../../sync/index.js';
13
13
  import { errMsg } from '../../../core/util/index.js';
@@ -4,6 +4,5 @@ export * from './config.js';
4
4
  export * from './doctor.js';
5
5
  export * from './host-claude.js';
6
6
  export * from './watch.js';
7
- export * from './audit.js';
8
7
  export * from './migrate.js';
9
8
  export * from './update.js';
@@ -5,6 +5,5 @@ export * from './config.js';
5
5
  export * from './doctor.js';
6
6
  export * from './host-claude.js';
7
7
  export * from './watch.js';
8
- export * from './audit.js';
9
8
  export * from './migrate.js';
10
9
  export * from './update.js';
@@ -8,13 +8,14 @@ import { containedStatus, showStorage } from '../../app/features/storage/index.j
8
8
  import { listProjects } from '../../app/features/projects/index.js';
9
9
  import { definitionHandler } from '../operations/index.js';
10
10
  import { fail } from '../../core/util/index.js';
11
- import { audit, claudeHost, config, doctor, hostCommand, logs, sync, watcher } from './maintenance/index.js';
11
+ import { claudeHost, config, doctor, hostCommand, logs, sync, watcher } from './maintenance/index.js';
12
12
  import { engine } from './engine.js';
13
13
  import { resume } from './resume.js';
14
14
  import { agentCommandsCommand } from './agent-commands.js';
15
15
  import { vpn } from './vpn.js';
16
16
  import { github } from './github.js';
17
17
  import { web, webServer } from './web.js';
18
+ import { remote } from './remote.js';
18
19
  export const HANDLERS = {
19
20
  launch: (ctx) => launch(ctx),
20
21
  generate: (ctx) => launch(ctx, true),
@@ -29,7 +30,6 @@ export const HANDLERS = {
29
30
  projects: definitionHandler(listProjects),
30
31
  sync: sync,
31
32
  logs: logs,
32
- audit: audit,
33
33
  config: config,
34
34
  doctor: doctor,
35
35
  setup: setup,
@@ -55,5 +55,6 @@ export const HANDLERS = {
55
55
  vpn: vpn,
56
56
  github: github,
57
57
  web: web,
58
+ remote: remote,
58
59
  _web: webServer,
59
60
  };
@@ -0,0 +1,2 @@
1
+ import { type Context } from './context.js';
2
+ export declare function remote(ctx: Context): Promise<number>;
@@ -0,0 +1,49 @@
1
+ // `sandbox remote [user] [--browser]`: another Windows user's program, used from here (docs/design/remote-access.md).
2
+ // Windows sign-in through the owner's pipe gives a one-time token; the menu then runs over the owner's web UI (every
3
+ // action checked against the role the owner gave, and written to their audit log), or --browser opens it signed in.
4
+ import { allows } from '../../app/kernel/index.js';
5
+ import { fail, safeText } from '../../core/util/index.js';
6
+ import { knockOnDoor } from '../../host/remote/index.js';
7
+ import { currentUserName, runningOwner } from '../../host/sharing/index.js';
8
+ import { openBrowser } from '../../host/web/index.js';
9
+ import { RemoteMenu } from '../menu/remote-menu.js';
10
+ import { remoteClient } from '../services/remote-client.js';
11
+ import { App } from '../tui/index.js';
12
+ import { requireOnPc } from './context.js';
13
+ /** Whose program: the user named, else the owner of the running sandbox when it is someone else's. */
14
+ function ownerOf(ctx) {
15
+ const named = ctx.options.positionals[0];
16
+ if (named)
17
+ return { name: named, id: named };
18
+ const owner = runningOwner();
19
+ if (owner.kind === 'other')
20
+ return { name: owner.user, id: owner.record.sid };
21
+ return fail('Whose web UI? No sandbox of another user is running.', 'Name them: sandbox remote <their Windows user name>');
22
+ }
23
+ export async function remote(ctx) {
24
+ requireOnPc('remote');
25
+ const owner = ownerOf(ctx);
26
+ const answer = await knockOnDoor(ctx.storageRoot, owner.id);
27
+ if (!answer.ok)
28
+ return fail(safeText(answer.reason), 'Ask ' +
29
+ safeText(owner.name) +
30
+ ' to turn on web.remote.windowsSignIn, add you to web.remote.users and start the web UI.');
31
+ if (ctx.options.raw.browser === true) {
32
+ openBrowser('http://127.0.0.1:' + answer.port + '/#w=' + answer.token);
33
+ console.log('Opened ' + safeText(owner.name) + "'s web UI in your browser (" + answer.role + '). It runs as them, not you.');
34
+ return 0;
35
+ }
36
+ const client = await remoteClient(answer.port, answer.token);
37
+ const caller = { kind: 'windows', user: currentUserName(), role: answer.role };
38
+ const app = new App();
39
+ try {
40
+ await new RemoteMenu(app, ctx, client, owner.name, caller).start();
41
+ }
42
+ finally {
43
+ app.close();
44
+ client.close();
45
+ }
46
+ if (!allows(caller, 'control'))
47
+ console.log('You had view access: nothing was changed.');
48
+ return 0;
49
+ }
@@ -8,6 +8,9 @@ import { c } from '../../output/index.js';
8
8
  /** Where each running session is on this PC, so it can be reopened after a restart. */
9
9
  export function sessionsToReopen(registry, sessions) {
10
10
  return sessions.flatMap((s) => {
11
+ // Shells are not reopened: a new one would not be the one that ended.
12
+ if (s.kind === 'shell')
13
+ return [];
11
14
  const project = projectForGuestDir([...registry.projects, ...registry.roots.map((r) => ({ ...r }))], s.cwd);
12
15
  return project
13
16
  ? [
@@ -3,10 +3,16 @@
3
3
  // running when no sandbox does. startWebOnLaunch is the sandbox start's hook.
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
+ import { accessStoreOf } from '../../app/features/access/index.js';
6
7
  import { currentServer, START_WAIT_MS, webStatus } from '../../app/features/web/index.js';
7
8
  import { createRpcHost } from '../../app/registry.js';
9
+ import { loadConfig } from '../../core/config/index.js';
8
10
  import { packageVersion } from '../../core/package-info.js';
9
11
  import { sleep, fail } from '../../core/util/index.js';
12
+ import { AuditLog } from '../../host/audit/index.js';
13
+ import { doorPipeName, HostToolsHelper, networkProfile, RemoteGate, TlsStore, } from '../../host/remote/index.js';
14
+ import { SecretStore } from '../../host/secrets/index.js';
15
+ import { currentUserSid } from '../../host/sharing/index.js';
10
16
  import { IDLE_EXIT_MS, openBrowser, WebControl, WebRunner, WebStore } from '../../host/web/index.js';
11
17
  import { spawnDetached } from '../../host/sandbox/index.js';
12
18
  import { WebService } from '../services/index.js';
@@ -53,6 +59,65 @@ export async function startWebOnLaunch(ctx) {
53
59
  export async function web(ctx) {
54
60
  return (await definitionHandler(webStatus)(ctx)) ?? 0;
55
61
  }
62
+ /** The remote settings as in the file now (read again at most every 3 seconds; a broken file keeps the last). */
63
+ function remoteSettings(ctx) {
64
+ let last = ctx.loaded.config.web.remote;
65
+ let readAt = Date.now();
66
+ return () => {
67
+ if (Date.now() - readAt < 3000)
68
+ return last;
69
+ readAt = Date.now();
70
+ try {
71
+ last = loadConfig(ctx.options.config).config.web.remote;
72
+ }
73
+ catch {
74
+ // A settings file being edited or broken: keep what was read last.
75
+ }
76
+ return last;
77
+ };
78
+ }
79
+ /** Remote actions go to the audit log of this data set (k: remote), so the Activity page shows who did what. */
80
+ function remoteAudit(ctx) {
81
+ const { days, maxMb } = ctx.loaded.config.audit;
82
+ const log = new AuditLog(AuditLog.dirFor(ctx.storageRoot, ctx.dataPath), { days, maxMb });
83
+ return (entry) => {
84
+ try {
85
+ log.append([{ t: new Date().toISOString(), k: 'remote', p: entry.what, a: entry.outcome, au: entry.who }]);
86
+ }
87
+ catch {
88
+ // The audit log is a record, never a reason for a request to fail.
89
+ }
90
+ };
91
+ }
92
+ /**
93
+ * The gate for other Windows users and devices (docs/design/remote-access.md). The Windows helper is started when it
94
+ * can be built here: then the owner's own cookie is bound to the owner's connections too, and the sign-in pipe is
95
+ * served (it refuses everyone while web.remote.windowsSignIn is off).
96
+ */
97
+ function remoteGate(ctx) {
98
+ const secrets = SecretStore.standard();
99
+ let ownerSid = '';
100
+ try {
101
+ ownerSid = currentUserSid();
102
+ }
103
+ catch {
104
+ // Unknown: Windows sign-in stays off, the owner signs in with the token as before.
105
+ }
106
+ const helper = ownerSid ? HostToolsHelper.start(ctx.storageRoot, doorPipeName(ownerSid)) : null;
107
+ const audit = remoteAudit(ctx);
108
+ helper?.onProblem((text) => audit({ who: 'the web UI', what: 'Windows sign-in: ' + text, outcome: 'failed' }));
109
+ const store = new WebStore(ctx.storageRoot);
110
+ return new RemoteGate({
111
+ store: accessStoreOf(ctx),
112
+ settings: remoteSettings(ctx),
113
+ ownerSid,
114
+ helper,
115
+ audit,
116
+ tls: new TlsStore(store.dir, secrets),
117
+ profile: (address) => networkProfile(address),
118
+ port: () => ctx.loaded.config.web.port,
119
+ });
120
+ }
56
121
  /** Body of the background process (`sandbox _web`): runs until stopped. */
57
122
  export async function webServer(ctx) {
58
123
  const staticRoot = path.join(ctx.packageRoot, 'dist', 'web');
@@ -66,6 +131,7 @@ export async function webServer(ctx) {
66
131
  staticRoot,
67
132
  version: packageVersion(),
68
133
  idleExitMs: IDLE_EXIT_MS,
134
+ gate: remoteGate(ctx),
69
135
  }).run();
70
136
  process.exit(0);
71
137
  }