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
@@ -0,0 +1,129 @@
1
+ // Remote access's work (docs/design/remote-access.md): the access list in <storage>\web\access.json (shared with the
2
+ // running web server, which reads it again when it changes and closes the pages of whoever was signed out), the
3
+ // remote settings, the firewall rule and the network check of LAN mode. The one-time code is returned once and only
4
+ // its hash is kept.
5
+ import { requireOnPc } from '../../../cli/commands/context.js';
6
+ import { fail } from '../../../core/util/index.js';
7
+ import { AccessStore, addRuleRequest, applyFirewall, chooseLanAddress, lanUrl, networkProblem, networkProfile, removeRuleRequest, showCode, } from '../../../host/remote/index.js';
8
+ import { SecretStore } from '../../../host/secrets/index.js';
9
+ import { protectPrivateFolder } from '../../../host/sharing/index.js';
10
+ import { selfTestRunning } from '../../../host/test-guard.js';
11
+ import { WebStore } from '../../../host/web/index.js';
12
+ import { implement } from '../../context.js';
13
+ import { ConfigFile } from '../settings/index.js';
14
+ import { accessStatus, allowDevice, lanOff, lanOn, revokeAccess, revokeAllAccess } from './access.def.js';
15
+ /** The access list of this user (the folder is the web state's private one). */
16
+ export function accessStoreOf(ctx) {
17
+ return AccessStore.at(ctx.storageRoot, SecretStore.standard(), {
18
+ // The self-test never changes folder permissions; its storage is a temp folder.
19
+ protect: selfTestRunning() ? undefined : (dir) => protectPrivateFolder(dir),
20
+ });
21
+ }
22
+ function lanStateOf(ctx) {
23
+ const lan = new WebStore(ctx.storageRoot).read()?.lan;
24
+ return { url: lan?.url ?? null, fingerprint: lan?.fingerprint ?? null, problem: lan?.problem ?? null };
25
+ }
26
+ function setLan(ctx, enabled) {
27
+ const outcome = ConfigFile.user(ctx.loaded.configPath).set(['web', 'remote', 'lan', 'enabled'], enabled);
28
+ if (typeof outcome === 'object')
29
+ fail('Could not change web.remote.lan.enabled: ' + outcome.problems.join('; '));
30
+ }
31
+ export const accessStatusRun = implement(accessStatus, (ctx) => {
32
+ requireOnPc('web');
33
+ const remote = ctx.loaded.config.web.remote;
34
+ const data = accessStoreOf(ctx).read();
35
+ const now = Date.now();
36
+ const alive = (iso) => Date.parse(iso) > now;
37
+ const lan = lanStateOf(ctx);
38
+ return {
39
+ lan: {
40
+ enabled: remote.lan.enabled,
41
+ running: remote.lan.enabled && lan.url !== null,
42
+ address: remote.lan.enabled ? lan.url : null,
43
+ fingerprint: remote.lan.enabled ? lan.fingerprint : null,
44
+ problem: remote.lan.enabled ? lan.problem : null,
45
+ network: data.network,
46
+ },
47
+ windowsSignIn: remote.windowsSignIn,
48
+ users: remote.users.map((u) => ({ name: u.name, role: u.role })),
49
+ rememberDays: remote.rememberDays,
50
+ devices: data.devices
51
+ .filter((d) => alive(d.expiresAt))
52
+ .map((d) => ({
53
+ id: d.id,
54
+ name: d.name,
55
+ role: d.role,
56
+ address: d.address,
57
+ createdAt: d.createdAt,
58
+ expiresAt: d.expiresAt,
59
+ lastSeen: d.lastSeen ?? null,
60
+ })),
61
+ windows: data.windows
62
+ .filter((w) => alive(w.expiresAt))
63
+ .map((w) => ({ id: w.id, user: w.user, createdAt: w.createdAt, expiresAt: w.expiresAt })),
64
+ pending: data.code && alive(data.code.expiresAt) ? { role: data.code.role, expiresAt: data.code.expiresAt } : null,
65
+ codeVoided: data.codeVoided,
66
+ };
67
+ });
68
+ export const allowDeviceRun = implement(allowDevice, (ctx, input) => {
69
+ requireOnPc('web');
70
+ if (!ctx.loaded.config.web.remote.lan.enabled)
71
+ fail('LAN mode is off, so no device on your network can reach the web UI.', 'Turn it on first: sandbox web lan-on');
72
+ const { code, expiresAt } = accessStoreOf(ctx).newDeviceCode(input.role);
73
+ const lan = lanStateOf(ctx);
74
+ return { code: showCode(code), expiresAt, role: input.role, address: lan.url, fingerprint: lan.fingerprint };
75
+ });
76
+ export const revokeAccessRun = implement(revokeAccess, (ctx, input) => {
77
+ requireOnPc('web');
78
+ if (!accessStoreOf(ctx).revoke(input.id))
79
+ fail('There is no device or sign-in ' + input.id + '.', 'See sandbox web devices.');
80
+ return { message: 'Signed out. Its open pages close within half a minute.' };
81
+ });
82
+ export const revokeAllAccessRun = implement(revokeAllAccess, async (ctx, _input, io) => {
83
+ requireOnPc('web');
84
+ const ok = await io.confirm({
85
+ title: 'Sign out every device and Windows user now?',
86
+ danger: true,
87
+ yes: 'Sign out all',
88
+ });
89
+ if (!ok)
90
+ return { message: 'Nothing changed.' };
91
+ accessStoreOf(ctx).revokeAll('everything');
92
+ return { message: 'Everyone is signed out, and a code not used yet is void.' };
93
+ });
94
+ export const lanOnRun = implement(lanOn, async (ctx, _input, io) => {
95
+ requireOnPc('web');
96
+ const lan = ctx.loaded.config.web.remote.lan;
97
+ const bound = chooseLanAddress(lan.address);
98
+ io.progress('Checking the network');
99
+ const profile = await networkProfile(bound.address);
100
+ const problem = networkProblem(profile, null);
101
+ if (problem)
102
+ fail(problem, 'LAN mode only runs on a network you marked Private (Windows Settings > Network > your network).');
103
+ io.progress('Adding the firewall rule (Windows asks for permission)');
104
+ await applyFirewall(addRuleRequest(lan.port, bound.address));
105
+ accessStoreOf(ctx).setNetwork(profile.name);
106
+ setLan(ctx, true);
107
+ const address = lanUrl(bound.address, lan.port);
108
+ return {
109
+ address,
110
+ message: 'LAN mode is on for the network "' +
111
+ profile.name +
112
+ '": devices open ' +
113
+ address +
114
+ ' (the running web UI starts listening within half a minute). Next: Allow a new device.',
115
+ };
116
+ });
117
+ export const lanOffRun = implement(lanOff, async (ctx, _input, io) => {
118
+ requireOnPc('web');
119
+ const ok = await io.confirm({ title: 'Turn off LAN mode and sign out every device?', danger: true, yes: 'Turn off' });
120
+ if (!ok)
121
+ return { message: 'Nothing changed.' };
122
+ setLan(ctx, false);
123
+ const store = accessStoreOf(ctx);
124
+ store.revokeAll('devices');
125
+ store.setNetwork(null);
126
+ io.progress('Removing the firewall rule (Windows asks for permission)');
127
+ await applyFirewall(removeRuleRequest());
128
+ return { message: 'LAN mode is off: the firewall rule is gone and every device is signed out.' };
129
+ });
@@ -0,0 +1,2 @@
1
+ export * from './access.def.js';
2
+ export * from './access.run.js';
@@ -0,0 +1,3 @@
1
+ // Remote access: devices on your network and other Windows users of this PC (docs/design/remote-access.md).
2
+ export * from './access.def.js';
3
+ export * from './access.run.js';
@@ -2,6 +2,8 @@
2
2
  // adding and removing one change. Node side (it checks names with the config schema); the agents run writes them.
3
3
  import { DEFAULTS } from '../../../core/config/index.js';
4
4
  import { HarnessSchema, NameSchema, describeIssues } from '../../../core/schema/index.js';
5
+ /** Names an agent cannot have. */
6
+ const RESERVED = ['shell', 'pwsh', 'powershell', 'cmd', 'close'];
5
7
  /** Nothing wrong with the name (true), or what is wrong. Pure. */
6
8
  export function agentNameProblem(name, taken) {
7
9
  const typed = name.trim();
@@ -12,6 +14,9 @@ export function agentNameProblem(name, taken) {
12
14
  return 'Use letters, numbers, - and _ only.';
13
15
  if (taken.some((t) => t.toLowerCase() === typed.toLowerCase()))
14
16
  return typed + ' is an agent already.';
17
+ // Shell sessions are listed by these names, and sandbox shell / close are commands.
18
+ if (RESERVED.includes(typed.toLowerCase()))
19
+ return typed + ' is taken by the sandbox; choose another name.';
15
20
  return true;
16
21
  }
17
22
  /** The command a package usually provides: "@scope/my-agent@latest" gives "my-agent". Pure. */
@@ -25,6 +25,7 @@ export const listAgents = defineQuery({
25
25
  });
26
26
  export const startAgent = defineOperation({
27
27
  id: 'agents.start',
28
+ access: 'control',
28
29
  title: 'Start an agent',
29
30
  summary: 'start an agent in a project of the running sandbox',
30
31
  input: z.object({
@@ -21,7 +21,10 @@ export interface AppsSource {
21
21
  }
22
22
  /** The files as they are on disk. */
23
23
  export declare const SAVED: AppsSource;
24
- /** The catalogue entries a file can change (a project cannot change an app that has a setting of its own). */
24
+ /**
25
+ * The catalogue entries a file can change (a project cannot change an app that has a setting of its own, nor an agent
26
+ * add-on that is yours only).
27
+ */
25
28
  export declare function entriesOf(file: ConfigFile): AppEntry[];
26
29
  /** Is the entry on in this source? */
27
30
  export declare function appIsOn(entry: AppEntry, file: ConfigFile, source: AppsSource): boolean;
@@ -17,9 +17,12 @@ export const SAVED = {
17
17
  setting: (file, key) => _.get(file.userConfig(), key),
18
18
  custom: (file) => (file.isProject ? {} : file.userConfig().apps.custom),
19
19
  };
20
- /** The catalogue entries a file can change (a project cannot change an app that has a setting of its own). */
20
+ /**
21
+ * The catalogue entries a file can change (a project cannot change an app that has a setting of its own, nor an agent
22
+ * add-on that is yours only).
23
+ */
21
24
  export function entriesOf(file) {
22
- return APPS.filter((a) => !(file.isProject && a.setting));
25
+ return APPS.filter((a) => !(file.isProject && (a.setting || a.userOnly)));
23
26
  }
24
27
  /** Downloaded already (into this data folder), so the next sandbox starts it without a download. */
25
28
  function installed(entry, dataPath) {
@@ -57,6 +60,8 @@ export function appRows(file, dataPath, source = SAVED) {
57
60
  cli: Boolean(entry.cli),
58
61
  mesa: entry.needs?.includes('mesa') ?? false,
59
62
  fullGitOnly: Boolean(entry.fullGitOnly),
63
+ ...(entry.plugsInto ? { plugsInto: entry.plugsInto } : {}),
64
+ ...(entry.service ? { service: true } : {}),
60
65
  ...settingParts(entry, file, source),
61
66
  installed: installed(entry, dataPath),
62
67
  on: appIsOn(entry, file, source),
@@ -32,6 +32,8 @@ export declare const AppRowSchema: z.ZodObject<{
32
32
  cli: z.ZodBoolean;
33
33
  mesa: z.ZodBoolean;
34
34
  fullGitOnly: z.ZodBoolean;
35
+ plugsInto: z.ZodOptional<z.ZodString>;
36
+ service: z.ZodOptional<z.ZodBoolean>;
35
37
  setting: z.ZodOptional<z.ZodString>;
36
38
  settingField: z.ZodOptional<z.ZodType<Field, unknown, z.core.$ZodTypeInternals<Field, unknown>>>;
37
39
  settingValue: z.ZodOptional<z.ZodUnknown>;
@@ -63,6 +65,8 @@ export declare const AppsListSchema: z.ZodObject<{
63
65
  cli: z.ZodBoolean;
64
66
  mesa: z.ZodBoolean;
65
67
  fullGitOnly: z.ZodBoolean;
68
+ plugsInto: z.ZodOptional<z.ZodString>;
69
+ service: z.ZodOptional<z.ZodBoolean>;
66
70
  setting: z.ZodOptional<z.ZodString>;
67
71
  settingField: z.ZodOptional<z.ZodType<Field, unknown, z.core.$ZodTypeInternals<Field, unknown>>>;
68
72
  settingValue: z.ZodOptional<z.ZodUnknown>;
@@ -100,6 +104,8 @@ export declare const listApps: import("../../kernel/define.js").QueryDef<z.ZodOb
100
104
  cli: z.ZodBoolean;
101
105
  mesa: z.ZodBoolean;
102
106
  fullGitOnly: z.ZodBoolean;
107
+ plugsInto: z.ZodOptional<z.ZodString>;
108
+ service: z.ZodOptional<z.ZodBoolean>;
103
109
  setting: z.ZodOptional<z.ZodString>;
104
110
  settingField: z.ZodOptional<z.ZodType<Field, unknown, z.core.$ZodTypeInternals<Field, unknown>>>;
105
111
  settingValue: z.ZodOptional<z.ZodUnknown>;
@@ -137,6 +143,17 @@ export declare const setCustomApps: import("../../kernel/define.js").OperationDe
137
143
  export declare const requestApp: import("../../kernel/define.js").OperationDef<z.ZodObject<{}, z.core.$strip>, z.ZodObject<{
138
144
  url: z.ZodString;
139
145
  }, z.core.$strip>, "apps.request">;
146
+ /** One line under an app: a short label ('' for a warning on its own) and its text. */
147
+ export interface AppDetail {
148
+ readonly label: string;
149
+ readonly text: string;
150
+ readonly warn?: boolean;
151
+ }
152
+ /**
153
+ * The lines under an app: its download, how it starts, what it plugs into, what it needs. A setting-backed entry with
154
+ * nothing to download (claude-mem) has no size, so no download lines. The menu draws the same lines with colours.
155
+ */
156
+ export declare function appDetails(row: AppRow): AppDetail[];
140
157
  /** The apps page of one scope: a category per group (with a switch per app), your own programs and their actions. */
141
158
  export declare function appsPage(o: AppsList): Page;
142
159
  export declare const appsView: import("../../kernel/define.js").ViewDef<{
@@ -161,6 +178,8 @@ export declare const appsView: import("../../kernel/define.js").ViewDef<{
161
178
  cli: z.ZodBoolean;
162
179
  mesa: z.ZodBoolean;
163
180
  fullGitOnly: z.ZodBoolean;
181
+ plugsInto: z.ZodOptional<z.ZodString>;
182
+ service: z.ZodOptional<z.ZodBoolean>;
164
183
  setting: z.ZodOptional<z.ZodString>;
165
184
  settingField: z.ZodOptional<z.ZodType<Field, unknown, z.core.$ZodTypeInternals<Field, unknown>>>;
166
185
  settingValue: z.ZodOptional<z.ZodUnknown>;
@@ -10,6 +10,12 @@ export const AppsScopeSchema = z
10
10
  .describe("Your own settings (all projects) or this folder's .sandbox-config.json (this project)");
11
11
  /** The groups the page shows; the menu and the web list the apps by these, so the navigation stays short. */
12
12
  export const APP_GROUPS = [
13
+ {
14
+ id: 'agents',
15
+ label: 'Agent add-ons',
16
+ description: 'Plug-ins for the agents: rtk and Headroom save tokens, claude-mem gives Claude a memory, ccusage reports token use, Playwright, Context7 and REA add MCP servers. Only your own settings turn them on (except REA).',
17
+ members: ['agents'],
18
+ },
13
19
  {
14
20
  id: 'editors',
15
21
  label: 'Editors & docs',
@@ -79,6 +85,8 @@ export const AppRowSchema = z.object({
79
85
  cli: z.boolean().describe('A command-line tool (on PATH), not a desktop app'),
80
86
  mesa: z.boolean().describe('Needs OpenGL on the CPU (Mesa)'),
81
87
  fullGitOnly: z.boolean().describe('Only with full git access'),
88
+ plugsInto: z.string().optional().describe('Which agents an add-on plugs into, and how'),
89
+ service: z.boolean().optional().describe('Started by the sandbox or the agent (a proxy, an MCP server)'),
82
90
  setting: z.string().optional().describe('The setting it stands for (VS Code, Windows Terminal)'),
83
91
  /** That setting's own Field and value (on, off or auto), so it is changed with its own control. */
84
92
  settingField: FieldSchema.optional(),
@@ -137,6 +145,7 @@ export const setCustomApps = defineOperation({
137
145
  });
138
146
  export const requestApp = defineOperation({
139
147
  id: 'apps.request',
148
+ access: 'control',
140
149
  title: 'Request an app…',
141
150
  summary: 'open a prefilled GitHub request for an app that is not in the list',
142
151
  input: z.object({}),
@@ -144,23 +153,52 @@ export const requestApp = defineOperation({
144
153
  text: (o) => 'Opened the request form in your browser: ' + o.url,
145
154
  surfaces: { except: { cli: 'a request is filed on GitHub; the address is in docs' } },
146
155
  });
147
- /** The lines under an app: its download, how it starts, what it needs. Plain text, like the CLI's. */
148
- function details(row) {
156
+ /**
157
+ * The lines under an app: its download, how it starts, what it plugs into, what it needs. A setting-backed entry with
158
+ * nothing to download (claude-mem) has no size, so no download lines. The menu draws the same lines with colours.
159
+ */
160
+ export function appDetails(row) {
161
+ const downloads = row.size !== '';
149
162
  return [
150
- 'Download ~' +
151
- row.size +
152
- ' once, kept in C:\\AgentData\\tools\\' +
153
- (row.setting ? '' : 'apps\\') +
154
- row.id +
155
- (row.installed ? ' (downloaded already)' : ''),
156
- 'Starts ' + (row.cli ? 'from a terminal (on PATH)' : 'from the desktop or Start > Sandbox apps'),
157
- ...(row.mesa ? ['Graphics OpenGL on the CPU (Mesa) is added for it'] : []),
163
+ ...(downloads
164
+ ? [
165
+ {
166
+ label: 'Download',
167
+ text: '~' +
168
+ row.size +
169
+ ' once, kept in C:\\AgentData\\tools\\' +
170
+ (row.setting ? '' : 'apps\\') +
171
+ row.id +
172
+ (row.installed ? ' (downloaded already)' : ''),
173
+ },
174
+ {
175
+ label: 'Starts',
176
+ text: row.service
177
+ ? 'by the sandbox or the agent (no shortcut, not on PATH)'
178
+ : row.cli
179
+ ? 'from a terminal (on PATH)'
180
+ : 'from the desktop or Start > Sandbox apps',
181
+ },
182
+ ]
183
+ : []),
184
+ ...(row.plugsInto ? [{ label: 'Plugs in', text: row.plugsInto }] : []),
185
+ ...(row.mesa ? [{ label: 'Graphics', text: 'OpenGL on the CPU (Mesa) is added for it' }] : []),
158
186
  ...(row.fullGitOnly
159
- ? ['Needs full git access (Settings > Projects & setup > Git): it pushes with its own git.']
187
+ ? [
188
+ {
189
+ label: '',
190
+ text: 'Needs full git access (Settings > Projects & setup > Git): it pushes with its own git.',
191
+ warn: true,
192
+ },
193
+ ]
160
194
  : []),
161
- ...(row.setting ? ['Setting ' + row.setting] : []),
195
+ ...(row.setting ? [{ label: 'Setting', text: row.setting }] : []),
162
196
  ];
163
197
  }
198
+ /** The lines as plain text, like the CLI's. */
199
+ function details(row) {
200
+ return appDetails(row).map((d) => (d.label ? d.label.padEnd(10) : '') + d.text);
201
+ }
164
202
  /** The switch that turns an app on or off: one boolean field, changed in place. */
165
203
  function switchField(row) {
166
204
  return { name: 'on', key: ['apps', 'install'], kind: 'boolean', label: row.name, optional: false };
@@ -19,6 +19,8 @@ export declare const listAppsRun: import("../../kernel/define.js").Implementatio
19
19
  cli: import("zod").ZodBoolean;
20
20
  mesa: import("zod").ZodBoolean;
21
21
  fullGitOnly: import("zod").ZodBoolean;
22
+ plugsInto: import("zod").ZodOptional<import("zod").ZodString>;
23
+ service: import("zod").ZodOptional<import("zod").ZodBoolean>;
22
24
  setting: import("zod").ZodOptional<import("zod").ZodString>;
23
25
  settingField: import("zod").ZodOptional<import("zod").ZodType<import("../../kernel/fields.js").Field, unknown, import("zod/v4/core").$ZodTypeInternals<import("../../kernel/fields.js").Field, unknown>>>;
24
26
  settingValue: import("zod").ZodOptional<import("zod").ZodUnknown>;
@@ -26,7 +26,7 @@ export const listAppsRun = implement(listApps, (ctx, input) => {
26
26
  export const toggleAppRun = implement(toggleApp, (ctx, input) => {
27
27
  const file = settingsFiles(ctx)[input.scope];
28
28
  const entry = APPS.find((a) => a.id === input.id);
29
- if (!entry || (file.isProject && entry.setting))
29
+ if (!entry || (file.isProject && (entry.setting || entry.userOnly)))
30
30
  fail('"' + input.id + '" is not an app this scope can change.', 'Pick one of the apps the Apps page lists.');
31
31
  const name = entry.name;
32
32
  // A setting-backed app (VS Code, Windows Terminal) is its setting; a catalogue app is one id in apps.install.