windows-development-sandbox 1.11.6 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/config.example.json +4 -3
- package/dist/app/features/apps/apps-rows.d.ts +4 -1
- package/dist/app/features/apps/apps-rows.js +7 -2
- package/dist/app/features/apps/apps.def.d.ts +19 -0
- package/dist/app/features/apps/apps.def.js +49 -12
- package/dist/app/features/apps/apps.run.d.ts +2 -0
- package/dist/app/features/apps/apps.run.js +1 -1
- package/dist/app/features/audit/audit.def.d.ts +341 -0
- package/dist/app/features/audit/audit.def.js +454 -0
- package/dist/app/features/audit/audit.run.d.ts +107 -0
- package/dist/app/features/audit/audit.run.js +133 -0
- package/dist/app/features/audit/index.d.ts +2 -0
- package/dist/app/features/audit/index.js +3 -0
- package/dist/app/features/defs.d.ts +194 -1
- package/dist/app/features/defs.js +4 -0
- package/dist/app/features/projects/projects.def.js +1 -1
- package/dist/app/features/settings/settings.run.d.ts +3 -0
- package/dist/app/features/settings/settings.run.js +16 -2
- package/dist/app/features/updates/update-lines.d.ts +1 -1
- package/dist/app/features/updates/update-lines.js +3 -7
- package/dist/app/features/updates/updates.def.js +1 -1
- package/dist/app/kernel/define.d.ts +12 -1
- package/dist/app/registry.js +2 -0
- package/dist/cli/args.d.ts +0 -3
- package/dist/cli/args.js +3 -4
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/launch/prepare.js +1 -1
- package/dist/cli/commands/maintenance/index.d.ts +0 -1
- package/dist/cli/commands/maintenance/index.js +0 -1
- package/dist/cli/commands/registry.js +1 -2
- package/dist/cli/manifest.d.ts +0 -41
- package/dist/cli/manifest.js +0 -14
- package/dist/cli/menu/pages/apps-page.js +2 -15
- package/dist/cli/menu/pages/maintenance-page.d.ts +4 -0
- package/dist/cli/menu/pages/maintenance-page.js +51 -8
- package/dist/cli/menu/views.js +2 -0
- package/dist/core/config/defaults.js +1 -1
- package/dist/core/schema/apps.d.ts +9 -0
- package/dist/core/schema/apps.js +9 -0
- package/dist/core/schema/config.d.ts +1 -0
- package/dist/core/schema/config.js +3 -3
- package/dist/core/schema/sandbox.d.ts +1 -0
- package/dist/core/schema/sandbox.js +11 -3
- package/dist/guest/apps/app-installer.js +50 -4
- package/dist/guest/apps/index.d.ts +1 -1
- package/dist/guest/apps/index.js +1 -1
- package/dist/guest/apps/sources.js +1 -1
- package/dist/guest/audit/parse.d.ts +32 -2
- package/dist/guest/audit/parse.js +43 -5
- package/dist/guest/audit/process-audit.d.ts +3 -1
- package/dist/guest/audit/process-audit.js +9 -6
- package/dist/guest/boot/boot.js +12 -3
- package/dist/guest/boot/guide.d.ts +1 -1
- package/dist/guest/boot/guide.js +27 -3
- package/dist/guest/daemon/sessions.d.ts +7 -0
- package/dist/guest/daemon/sessions.js +27 -2
- package/dist/guest/entries/daemon.js +2 -1
- package/dist/guest/setup/addon-mcp.d.ts +2 -0
- package/dist/guest/setup/addon-mcp.js +49 -0
- package/dist/guest/setup/agent-addons.d.ts +9 -0
- package/dist/guest/setup/agent-addons.js +22 -0
- package/dist/guest/setup/headroom.d.ts +7 -0
- package/dist/guest/setup/headroom.js +60 -0
- package/dist/guest/setup/managed-policy.d.ts +2 -1
- package/dist/guest/setup/managed-policy.js +15 -6
- package/dist/guest/toolchains/plan.js +8 -3
- package/dist/host/audit/attribute.d.ts +46 -0
- package/dist/host/audit/attribute.js +76 -0
- package/dist/host/audit/collect.d.ts +29 -3
- package/dist/host/audit/collect.js +256 -61
- package/dist/host/audit/git-commits.d.ts +29 -0
- package/dist/host/audit/git-commits.js +157 -0
- package/dist/host/audit/index.d.ts +5 -0
- package/dist/host/audit/index.js +6 -0
- package/dist/host/audit/log.d.ts +44 -7
- package/dist/host/audit/log.js +148 -45
- package/dist/host/audit/redact.d.ts +2 -0
- package/dist/host/audit/redact.js +67 -0
- package/dist/host/update/decision.d.ts +3 -2
- package/dist/host/update/decision.js +2 -3
- package/dist/host/update/updater.d.ts +4 -1
- package/dist/host/update/updater.js +11 -6
- package/dist/runtime/agent.js +110 -15
- package/dist/runtime/daemon.js +569 -316
- package/dist/runtime/db.js +110 -15
- package/dist/runtime/main.js +1011 -672
- package/dist/runtime/netproxy.js +110 -15
- package/dist/runtime/view.js +110 -15
- package/dist/selftest/app.test.js +25 -1
- package/dist/selftest/audit.test.js +354 -47
- package/dist/selftest/hcs.test.js +35 -0
- package/dist/selftest/menu-screens.js +1 -1
- package/dist/selftest/menu.test.js +3 -3
- package/dist/selftest/support/engine-golden.js +9 -0
- package/dist/selftest/support/fixtures.d.ts +1 -0
- package/dist/selftest/support/web-preview.js +94 -0
- package/dist/selftest/toolchains.test.js +147 -2
- package/dist/selftest/update-service.test.js +0 -7
- package/dist/selftest/update.test.js +4 -4
- package/dist/shared/apps/agent-addons.d.ts +64 -0
- package/dist/shared/apps/agent-addons.js +95 -0
- package/dist/shared/apps/catalogue.d.ts +33 -2
- package/dist/shared/apps/catalogue.js +102 -11
- package/dist/shared/apps/index.d.ts +3 -2
- package/dist/shared/apps/index.js +3 -2
- package/dist/shared/apps/selection.d.ts +2 -0
- package/dist/shared/apps/selection.js +7 -0
- package/dist/sync/watcher.js +9 -1
- package/dist/web/index.html +2 -2
- package/dist/web/{main-RQFN4WBY.css → main-UAQPI5XD.css} +1 -1
- package/dist/web/{main-QL7KHCEE.js → main-UD4QCXZR.js} +66 -66
- package/native/csharp/HcsHost/AssemblyInfo.cs +5 -3
- package/native/csharp/HcsHost/Hcs.cs +9 -0
- package/native/csharp/HcsHost/Program.cs +26 -1
- package/native/csharp/HcsHost/Share.cs +173 -0
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/package.json +1 -1
- package/sandbox/addons/ccusage/package-lock.json +111 -0
- package/sandbox/addons/ccusage/package.json +7 -0
- package/sandbox/addons/context7/package-lock.json +1313 -0
- package/sandbox/addons/context7/package.json +7 -0
- package/sandbox/addons/headroom/requirements.in +1 -0
- package/sandbox/addons/headroom/requirements.txt +2922 -0
- package/sandbox/addons/playwright-mcp/package-lock.json +56 -0
- package/sandbox/addons/playwright-mcp/package.json +7 -0
- package/schemas/config.schema.json +13 -4
- package/schemas/sandbox-config.schema.json +2 -2
- package/dist/cli/commands/maintenance/audit.d.ts +0 -3
- package/dist/cli/commands/maintenance/audit.js +0 -58
|
@@ -8,7 +8,7 @@ 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 {
|
|
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';
|
|
@@ -29,7 +29,6 @@ export const HANDLERS = {
|
|
|
29
29
|
projects: definitionHandler(listProjects),
|
|
30
30
|
sync: sync,
|
|
31
31
|
logs: logs,
|
|
32
|
-
audit: audit,
|
|
33
32
|
config: config,
|
|
34
33
|
doctor: doctor,
|
|
35
34
|
setup: setup,
|
package/dist/cli/manifest.d.ts
CHANGED
|
@@ -499,47 +499,6 @@ declare const COMMAND_LIST: readonly [{
|
|
|
499
499
|
readonly group: "Data";
|
|
500
500
|
readonly args: readonly [];
|
|
501
501
|
readonly options: readonly [];
|
|
502
|
-
}, {
|
|
503
|
-
readonly name: "audit";
|
|
504
|
-
readonly summary: "what happened in your sandboxes: programs started, files changed, sessions, repo guard (turn it on: sandbox config set audit.enabled true)";
|
|
505
|
-
readonly group: "Health";
|
|
506
|
-
readonly json: true;
|
|
507
|
-
readonly args: readonly [];
|
|
508
|
-
readonly options: readonly [{
|
|
509
|
-
readonly flag: "--days";
|
|
510
|
-
readonly type: "number";
|
|
511
|
-
readonly value: "n";
|
|
512
|
-
readonly description: "the last n days (default: today)";
|
|
513
|
-
readonly default: 1;
|
|
514
|
-
}, {
|
|
515
|
-
flag: string;
|
|
516
|
-
type: "string" | "number" | "boolean" | "list";
|
|
517
|
-
description: string;
|
|
518
|
-
short?: string | undefined;
|
|
519
|
-
value?: string | undefined;
|
|
520
|
-
label?: string | undefined;
|
|
521
|
-
default?: string | number | undefined;
|
|
522
|
-
choices?: string[] | "harnesses" | "sessions" | "accounts" | undefined;
|
|
523
|
-
advanced?: boolean | undefined;
|
|
524
|
-
when?: string | undefined;
|
|
525
|
-
noMenu?: boolean | undefined;
|
|
526
|
-
group?: string | undefined;
|
|
527
|
-
shownAs?: string | undefined;
|
|
528
|
-
}, {
|
|
529
|
-
flag: string;
|
|
530
|
-
type: "string" | "number" | "boolean" | "list";
|
|
531
|
-
description: string;
|
|
532
|
-
short?: string | undefined;
|
|
533
|
-
value?: string | undefined;
|
|
534
|
-
label?: string | undefined;
|
|
535
|
-
default?: string | number | undefined;
|
|
536
|
-
choices?: string[] | "harnesses" | "sessions" | "accounts" | undefined;
|
|
537
|
-
advanced?: boolean | undefined;
|
|
538
|
-
when?: string | undefined;
|
|
539
|
-
noMenu?: boolean | undefined;
|
|
540
|
-
group?: string | undefined;
|
|
541
|
-
shownAs?: string | undefined;
|
|
542
|
-
}];
|
|
543
502
|
}, {
|
|
544
503
|
readonly name: "logs";
|
|
545
504
|
readonly summary: "print the newest sandbox log";
|
package/dist/cli/manifest.js
CHANGED
|
@@ -386,20 +386,6 @@ const COMMAND_LIST = [
|
|
|
386
386
|
json,
|
|
387
387
|
},
|
|
388
388
|
{ name: 'sync', summary: 'copy conversation history back to this PC now', group: 'Data', args: [], options: [] },
|
|
389
|
-
{
|
|
390
|
-
name: 'audit',
|
|
391
|
-
summary: 'what happened in your sandboxes: programs started, files changed, sessions, repo guard (turn it on: sandbox config set audit.enabled true)',
|
|
392
|
-
group: 'Health',
|
|
393
|
-
json,
|
|
394
|
-
args: [],
|
|
395
|
-
options: [
|
|
396
|
-
{ flag: '--days', type: 'number', value: 'n', description: 'the last n days (default: today)', default: 1 },
|
|
397
|
-
text('--kind', 'kind', 'only proc, file, session or guard events', {
|
|
398
|
-
choices: ['proc', 'file', 'session', 'guard'],
|
|
399
|
-
}),
|
|
400
|
-
text('--grep', 'text', 'only events containing this text'),
|
|
401
|
-
],
|
|
402
|
-
},
|
|
403
389
|
{
|
|
404
390
|
name: 'logs',
|
|
405
391
|
summary: 'print the newest sandbox log',
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// (apps.toggle), so the staging stays here: "r" runs the feature's request operation, "c" stages your own programs.
|
|
5
5
|
import _ from 'lodash';
|
|
6
6
|
import { errMsg } from '../../../core/util/index.js';
|
|
7
|
-
import { APP_GROUPS, appIsOn, appRows, customRows, CUSTOM_KEY, entriesOf, INSTALL, ISSUES_URL, requestApp, REQUEST_APP, SAVED, } from '../../../app/features/apps/index.js';
|
|
7
|
+
import { APP_GROUPS, appDetails, appIsOn, appRows, customRows, CUSTOM_KEY, entriesOf, INSTALL, ISSUES_URL, requestApp, REQUEST_APP, SAVED, } from '../../../app/features/apps/index.js';
|
|
8
8
|
import { action } from '../../../app/kernel/index.js';
|
|
9
9
|
import { Theme } from '../../tui/index.js';
|
|
10
10
|
import { ValueEditor } from '../settings/value-editor.js';
|
|
@@ -37,20 +37,7 @@ export function appsCategories(file) {
|
|
|
37
37
|
];
|
|
38
38
|
}
|
|
39
39
|
function appItem(row) {
|
|
40
|
-
const notes =
|
|
41
|
-
Theme.muted('Download ') +
|
|
42
|
-
'~' +
|
|
43
|
-
row.size +
|
|
44
|
-
' once, kept in C:\\AgentData\\tools\\' +
|
|
45
|
-
(row.setting ? '' : 'apps\\') +
|
|
46
|
-
row.id,
|
|
47
|
-
Theme.muted('Starts ') + (row.cli ? 'from a terminal (on PATH)' : 'from the desktop or Start > Sandbox apps'),
|
|
48
|
-
...(row.mesa ? [Theme.muted('Graphics ') + 'OpenGL on the CPU (Mesa) is added for it'] : []),
|
|
49
|
-
...(row.fullGitOnly
|
|
50
|
-
? [Theme.warn('Needs full git access (Settings > Projects & setup > Git): it pushes with its own git.')]
|
|
51
|
-
: []),
|
|
52
|
-
...(row.setting ? [Theme.muted('Setting ') + row.setting] : []),
|
|
53
|
-
];
|
|
40
|
+
const notes = appDetails(row).map((d) => d.warn ? Theme.warn(d.text) : (d.label ? Theme.muted(d.label.padEnd(10)) : '') + d.text);
|
|
54
41
|
return {
|
|
55
42
|
value: row.id,
|
|
56
43
|
label: row.name,
|
|
@@ -3,6 +3,7 @@ import { type Engines, type Leftovers } from '../../../app/features/engine/index
|
|
|
3
3
|
import { type HealthLast } from '../../../app/features/health/index.js';
|
|
4
4
|
import { type ContainedStatus, type StorageShow } from '../../../app/features/storage/index.js';
|
|
5
5
|
import { type LogFiles } from '../../../app/features/logs/index.js';
|
|
6
|
+
import { type AuditList } from '../../../app/features/audit/index.js';
|
|
6
7
|
import { type UpdatesStatus } from '../../../app/features/updates/updates.def.js';
|
|
7
8
|
import { type WebStatus } from '../../../app/features/web/index.js';
|
|
8
9
|
import type { Context } from '../../commands/context.js';
|
|
@@ -31,6 +32,8 @@ export interface MaintenanceModel {
|
|
|
31
32
|
readonly engine: Engines | null;
|
|
32
33
|
/** HCS systems left running, read with the engines. */
|
|
33
34
|
readonly leftovers?: Leftovers | null;
|
|
35
|
+
/** The audit log today (the audit view's query; null: not read yet or unreadable). */
|
|
36
|
+
readonly audit?: AuditList | null;
|
|
34
37
|
/** The web UI's server (the web feature's status): running or not, and its address. */
|
|
35
38
|
readonly web: WebStatus;
|
|
36
39
|
/** The commands listed under "More commands". */
|
|
@@ -52,6 +55,7 @@ export declare class MaintenancePage extends ListPage<string> {
|
|
|
52
55
|
private engine;
|
|
53
56
|
private leftovers;
|
|
54
57
|
private web;
|
|
58
|
+
private audit;
|
|
55
59
|
private readonly operations;
|
|
56
60
|
/** The newer version the last check found (null: none, or not looked yet). */
|
|
57
61
|
private latest;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Section 7: keeping things in shape. Updates (the program and your saved settings), Data & storage (the data folder,
|
|
2
|
-
// what is stored, clean-up), Health (doctor, repair and this PC's setup together), the
|
|
3
|
-
// commands. Each is a category on the left and loads when you open it.
|
|
2
|
+
// what is stored, clean-up), Health (doctor, repair and this PC's setup together), Activity (the audit log), the logs,
|
|
3
|
+
// and the less common commands. Each is a category on the left and loads when you open it.
|
|
4
4
|
import { dataPage, showData } from '../../../app/features/data/index.js';
|
|
5
5
|
import { readQuery } from '../../../app/registry.js';
|
|
6
6
|
import { errMsg } from '../../../core/util/index.js';
|
|
@@ -8,6 +8,7 @@ import { enginePage, engineLeftovers, engineStatus, } from '../../../app/feature
|
|
|
8
8
|
import { healthPage, lastHealth } from '../../../app/features/health/index.js';
|
|
9
9
|
import { containedStatus, showStorage, storagePage, } from '../../../app/features/storage/index.js';
|
|
10
10
|
import { CRASH, listLogs, logsPage } from '../../../app/features/logs/index.js';
|
|
11
|
+
import { auditArgv, auditPage, listAudit } from '../../../app/features/audit/index.js';
|
|
11
12
|
import { updatesPage, updatesStatus } from '../../../app/features/updates/updates.def.js';
|
|
12
13
|
import { webPage, webStatus } from '../../../app/features/web/index.js';
|
|
13
14
|
import { commandSpec } from '../../manifest.js';
|
|
@@ -32,6 +33,11 @@ const CATEGORIES = [
|
|
|
32
33
|
label: 'Health',
|
|
33
34
|
description: 'Checks this PC and your settings, repairs problems, and sets up Windows Sandbox.',
|
|
34
35
|
},
|
|
36
|
+
{
|
|
37
|
+
id: 'activity',
|
|
38
|
+
label: 'Activity',
|
|
39
|
+
description: 'What your agents ran, changed and committed in the last 24 hours (the audit log). Enter on a session shows all of it.',
|
|
40
|
+
},
|
|
35
41
|
{ id: 'logs', label: 'Logs', description: "The sandbox's own logs." },
|
|
36
42
|
{
|
|
37
43
|
id: 'web',
|
|
@@ -45,14 +51,13 @@ const CATEGORIES = [
|
|
|
45
51
|
},
|
|
46
52
|
{ id: 'more', label: 'More commands', description: 'Less common commands.' },
|
|
47
53
|
];
|
|
48
|
-
const MORE_COMMANDS = ['sync', '
|
|
54
|
+
const MORE_COMMANDS = ['sync', 'config', 'generate', 'exec', 'agent-commands'];
|
|
49
55
|
const NO_LOGS = { folder: '', files: [], crash: false };
|
|
50
56
|
/** Until the status is read: the server counts as not running. */
|
|
51
57
|
const NO_WEB = { running: false, port: null, address: null };
|
|
52
58
|
/** Plain names for the commands under More commands. */
|
|
53
59
|
const MORE_LABELS = {
|
|
54
60
|
sync: 'Copy history back now',
|
|
55
|
-
audit: 'What happened in the sandbox',
|
|
56
61
|
config: 'Your settings file',
|
|
57
62
|
generate: 'Preview a start',
|
|
58
63
|
exec: 'Run a command in the sandbox',
|
|
@@ -107,6 +112,10 @@ const NO_UPDATES = {
|
|
|
107
112
|
function logsMenu(files) {
|
|
108
113
|
return menuView({ ...logsPage(files), empty: undefined }, { category: 'logs', refresh: false });
|
|
109
114
|
}
|
|
115
|
+
/** The audit view (src/app/features/audit) as the Activity category's rows and keys (none until it was read). */
|
|
116
|
+
function auditMenu(m) {
|
|
117
|
+
return m.audit ? menuView(auditPage(m.audit), { category: 'activity', prefix: 'audit:', refresh: false }) : undefined;
|
|
118
|
+
}
|
|
110
119
|
/** The Web UI view (src/app/features/web) as the menu's rows, keys and Enter labels, for the Web UI category. */
|
|
111
120
|
function webMenu(m) {
|
|
112
121
|
return menuView(webPage(m.web), { category: 'web', prefix: 'web:', refresh: false });
|
|
@@ -120,6 +129,7 @@ export function maintenanceOptions(m) {
|
|
|
120
129
|
const updates = updatesMenu(m);
|
|
121
130
|
const engine = engineMenu(m);
|
|
122
131
|
const web = webMenu(m);
|
|
132
|
+
const audit = auditMenu(m);
|
|
123
133
|
const more = m.more.map((c) => ({
|
|
124
134
|
value: 'more:' + c.name,
|
|
125
135
|
label: MORE_LABELS[c.name] ?? Text.humanize(c.name),
|
|
@@ -129,7 +139,16 @@ export function maintenanceOptions(m) {
|
|
|
129
139
|
return {
|
|
130
140
|
path: [],
|
|
131
141
|
categories: CATEGORIES,
|
|
132
|
-
items: [
|
|
142
|
+
items: [
|
|
143
|
+
...updates.items,
|
|
144
|
+
...dataItems(m),
|
|
145
|
+
...health.items,
|
|
146
|
+
...(audit?.items ?? []),
|
|
147
|
+
...logs,
|
|
148
|
+
...web.items,
|
|
149
|
+
...engine.items,
|
|
150
|
+
...more,
|
|
151
|
+
],
|
|
133
152
|
empty: 'Nothing here yet.',
|
|
134
153
|
actions: maintenanceActions(m),
|
|
135
154
|
enterLabel: (category, id) => category === 'health'
|
|
@@ -140,9 +159,11 @@ export function maintenanceOptions(m) {
|
|
|
140
159
|
? engine.enterLabel(category, id)
|
|
141
160
|
: category === 'web'
|
|
142
161
|
? web.enterLabel(category, id)
|
|
143
|
-
:
|
|
144
|
-
?
|
|
145
|
-
:
|
|
162
|
+
: category === 'activity'
|
|
163
|
+
? (audit?.enterLabel(category, id) ?? '')
|
|
164
|
+
: (category === 'data'
|
|
165
|
+
? storageMenu(m.storage)?.enterLabel(category, id) || dataMenuView(m.data)?.enterLabel(category, id)
|
|
166
|
+
: undefined) || enterLabel(id ?? ''),
|
|
146
167
|
};
|
|
147
168
|
}
|
|
148
169
|
/**
|
|
@@ -201,6 +222,8 @@ function maintenanceActions(m) {
|
|
|
201
222
|
return [{ key: 'l', label: m.engine ? 'check again' : 'check' }, ...engineMenu(m).actions];
|
|
202
223
|
case 'web':
|
|
203
224
|
return webMenu(m).actions;
|
|
225
|
+
case 'activity':
|
|
226
|
+
return auditMenu(m)?.actions ?? [];
|
|
204
227
|
default:
|
|
205
228
|
return [];
|
|
206
229
|
}
|
|
@@ -217,6 +240,7 @@ export class MaintenancePage extends ListPage {
|
|
|
217
240
|
engine = null;
|
|
218
241
|
leftovers = null;
|
|
219
242
|
web = NO_WEB;
|
|
243
|
+
audit = null;
|
|
220
244
|
operations;
|
|
221
245
|
/** The newer version the last check found (null: none, or not looked yet). */
|
|
222
246
|
latest = null;
|
|
@@ -235,6 +259,7 @@ export class MaintenancePage extends ListPage {
|
|
|
235
259
|
engine: this.engine,
|
|
236
260
|
leftovers: this.leftovers,
|
|
237
261
|
web: this.web,
|
|
262
|
+
audit: this.audit,
|
|
238
263
|
more: MORE_COMMANDS.flatMap((name) => {
|
|
239
264
|
const spec = commandSpec(name);
|
|
240
265
|
return spec ? [{ name, summary: spec.summary }] : [];
|
|
@@ -259,6 +284,13 @@ export class MaintenancePage extends ListPage {
|
|
|
259
284
|
}
|
|
260
285
|
this.last = (await readQuery(lastHealth, this.ctx)).last;
|
|
261
286
|
this.updates = await readQuery(updatesStatus, this.ctx);
|
|
287
|
+
try {
|
|
288
|
+
this.audit = await readQuery(listAudit, this.ctx, { limit: 200 });
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
// An unreadable log: the category says there is nothing yet.
|
|
292
|
+
this.audit = null;
|
|
293
|
+
}
|
|
262
294
|
try {
|
|
263
295
|
this.web = await readQuery(webStatus, this.ctx);
|
|
264
296
|
}
|
|
@@ -306,6 +338,17 @@ export class MaintenancePage extends ListPage {
|
|
|
306
338
|
return await this.operations.perform(chosen.action, [this.label], (text) => this.busy(text));
|
|
307
339
|
return {};
|
|
308
340
|
}
|
|
341
|
+
// Activity: a session's row shows its events on the normal screen (sandbox audit --session ...); turning the
|
|
342
|
+
// log on runs the settings operation.
|
|
343
|
+
if (this.category === 'activity') {
|
|
344
|
+
const chosen = auditMenu(this.model())?.resolve(result);
|
|
345
|
+
if (chosen?.kind !== 'action')
|
|
346
|
+
return {};
|
|
347
|
+
const target = chosen.action.kind === 'link' ? chosen.action.target : undefined;
|
|
348
|
+
if (target?.kind === 'view')
|
|
349
|
+
return { exit: { kind: 'run', argv: auditArgv(target.params) } };
|
|
350
|
+
return await this.operations.perform(chosen.action, [this.label], (text) => this.busy(text));
|
|
351
|
+
}
|
|
309
352
|
if (key === 'm' || id === 'data:measure')
|
|
310
353
|
return await this.measure();
|
|
311
354
|
// Only the open category's rows and keys: `f` and Enter mean something else in Health.
|
package/dist/cli/menu/views.js
CHANGED
|
@@ -7,6 +7,7 @@ import { accountsView } from '../../app/features/accounts/index.js';
|
|
|
7
7
|
import { appsView } from '../../app/features/apps/index.js';
|
|
8
8
|
import { healthView } from '../../app/features/health/index.js';
|
|
9
9
|
import { logsView } from '../../app/features/logs/index.js';
|
|
10
|
+
import { auditView } from '../../app/features/audit/index.js';
|
|
10
11
|
import { projectsView } from '../../app/features/projects/index.js';
|
|
11
12
|
import { sandboxView } from '../../app/features/sandbox/index.js';
|
|
12
13
|
import { sessionsView } from '../../app/features/sessions/index.js';
|
|
@@ -29,6 +30,7 @@ export const MENU_VIEWS = [
|
|
|
29
30
|
{ view: dataView, section: 'Maintenance' },
|
|
30
31
|
{ view: storageView, section: 'Maintenance' },
|
|
31
32
|
{ view: logsView, section: 'Maintenance' },
|
|
33
|
+
{ view: auditView, section: 'Maintenance' },
|
|
32
34
|
{ view: updatesView, section: 'Maintenance' },
|
|
33
35
|
{ view: engineView, section: 'Maintenance' },
|
|
34
36
|
{ view: webView, section: 'Maintenance' },
|
|
@@ -3,7 +3,7 @@ export const DEFAULTS = {
|
|
|
3
3
|
guestWorkspace: 'auto',
|
|
4
4
|
readOnly: false,
|
|
5
5
|
workspaceFilter: { enabled: false, exclude: [], include: [] },
|
|
6
|
-
audit: { enabled:
|
|
6
|
+
audit: { enabled: true, days: 7, maxMb: 100, processes: true, files: true, commits: true },
|
|
7
7
|
mappings: [],
|
|
8
8
|
sandbox: {
|
|
9
9
|
engine: 'windows-sandbox',
|
|
@@ -2,6 +2,9 @@ import { z } from 'zod';
|
|
|
2
2
|
export declare const AppIdSchema: z.ZodEnum<{
|
|
3
3
|
[x: string]: string;
|
|
4
4
|
}>;
|
|
5
|
+
export declare const ProjectAppIdSchema: z.ZodEnum<{
|
|
6
|
+
[x: string]: string;
|
|
7
|
+
}>;
|
|
5
8
|
export declare const CustomAppIdSchema: z.ZodString;
|
|
6
9
|
export declare const CustomAppSchema: z.ZodObject<{
|
|
7
10
|
name: z.ZodString;
|
|
@@ -18,6 +21,12 @@ export declare const CustomAppSchema: z.ZodObject<{
|
|
|
18
21
|
exe: z.ZodString;
|
|
19
22
|
cli: z.ZodOptional<z.ZodBoolean>;
|
|
20
23
|
}, z.core.$strict>;
|
|
24
|
+
/** What a project file may say about apps: catalogue apps it adds to yours (no add-ons that are yours only). */
|
|
25
|
+
export declare const ProjectAppsSchema: z.ZodObject<{
|
|
26
|
+
install: z.ZodArray<z.ZodEnum<{
|
|
27
|
+
[x: string]: string;
|
|
28
|
+
}>>;
|
|
29
|
+
}, z.core.$strict>;
|
|
21
30
|
export declare const AppsSchema: z.ZodObject<{
|
|
22
31
|
install: z.ZodArray<z.ZodEnum<{
|
|
23
32
|
[x: string]: string;
|
package/dist/core/schema/apps.js
CHANGED
|
@@ -3,6 +3,9 @@ import { z } from 'zod';
|
|
|
3
3
|
import { APPS } from '../../shared/apps/index.js';
|
|
4
4
|
const CATALOGUE_IDS = APPS.filter((a) => !a.setting).map((a) => a.id);
|
|
5
5
|
export const AppIdSchema = z.enum(CATALOGUE_IDS);
|
|
6
|
+
/** The apps a project's .sandbox-config.json may add: not the add-ons that are yours only (they change the agents). */
|
|
7
|
+
const PROJECT_IDS = APPS.filter((a) => !a.setting && !a.userOnly).map((a) => a.id);
|
|
8
|
+
export const ProjectAppIdSchema = z.enum(PROJECT_IDS);
|
|
6
9
|
export const CustomAppIdSchema = z.string().regex(/^[a-z0-9][a-z0-9-]{0,40}$/, 'lower-case letters, digits and - only');
|
|
7
10
|
const compiles = (pattern) => {
|
|
8
11
|
try {
|
|
@@ -51,6 +54,12 @@ export const CustomAppSchema = z
|
|
|
51
54
|
.refine((a) => (a.github && a.asset ? !a.url : Boolean(a.url) && !a.github && !a.asset), {
|
|
52
55
|
message: 'give github + asset, or url (not both)',
|
|
53
56
|
});
|
|
57
|
+
/** What a project file may say about apps: catalogue apps it adds to yours (no add-ons that are yours only). */
|
|
58
|
+
export const ProjectAppsSchema = z.strictObject({
|
|
59
|
+
install: z
|
|
60
|
+
.array(ProjectAppIdSchema)
|
|
61
|
+
.describe('Apps from the catalogue this project adds to yours (agent add-ons such as rtk, Headroom and the MCP servers are set in your own settings only).'),
|
|
62
|
+
});
|
|
54
63
|
export const AppsSchema = z.strictObject({
|
|
55
64
|
install: z
|
|
56
65
|
.array(AppIdSchema)
|
|
@@ -8,7 +8,7 @@ import { HarnessSchema } from './harness.js';
|
|
|
8
8
|
import { AgentCommandsSchema, AttachSchema, ClaudeProfileSchema, SecretsSchema } from './profile.js';
|
|
9
9
|
import { AuditSchema, DownloadsSchema, SharingSchema, GitSchema, GuestWorkspaceSchema, MappingSchema, NetworkSchema, ProvisioningSchema, SandboxSchema, WorkspaceFilterSchema, } from './sandbox.js';
|
|
10
10
|
import { ToolchainsSchema } from './toolchains.js';
|
|
11
|
-
import { AppsSchema } from './apps.js';
|
|
11
|
+
import { AppsSchema, ProjectAppsSchema } from './apps.js';
|
|
12
12
|
import { ProjectsSchema } from './projects.js';
|
|
13
13
|
import { ResumeSchema } from './resume.js';
|
|
14
14
|
import { WebSchema } from './web.js';
|
|
@@ -115,8 +115,8 @@ export const LocalOverridesSchema = z
|
|
|
115
115
|
attach: AttachSchema.partial(),
|
|
116
116
|
personalization: toggle('Copy your desktop look into the sandbox.').partial(),
|
|
117
117
|
// Catalogue apps only (a vetted list): your own programs (apps.custom) download anything, so a repo cannot add
|
|
118
|
-
// them.
|
|
119
|
-
apps:
|
|
118
|
+
// them, nor agent add-ons that are yours only (rtk hooks every agent command, Headroom sits in its API path).
|
|
119
|
+
apps: ProjectAppsSchema.partial(),
|
|
120
120
|
claudeProfile: ClaudeProfileSchema.pick({
|
|
121
121
|
enabled: true,
|
|
122
122
|
overwrite: true,
|
|
@@ -108,8 +108,13 @@ export const NetworkSchema = z.strictObject({
|
|
|
108
108
|
export const AuditSchema = z.strictObject({
|
|
109
109
|
enabled: z
|
|
110
110
|
.boolean()
|
|
111
|
-
.describe('Keep an audit log on this PC of what
|
|
112
|
-
days: z
|
|
111
|
+
.describe('Keep an audit log on this PC of what your agents did: programs started (with their command lines, secrets hidden), files changed in the projects, git commits, sessions, and what the repo guard caught, each linked to the agent session that did it where that can be known. The sandbox cannot change it. Read it with sandbox audit or on the Activity page.'),
|
|
112
|
+
days: z
|
|
113
|
+
.number()
|
|
114
|
+
.int()
|
|
115
|
+
.min(1)
|
|
116
|
+
.max(365)
|
|
117
|
+
.describe('Days the audit log is kept (1 to 30; a larger number keeps 30); older days are deleted when a sandbox starts and once a day.'),
|
|
113
118
|
maxMb: z
|
|
114
119
|
.number()
|
|
115
120
|
.int()
|
|
@@ -119,7 +124,10 @@ export const AuditSchema = z.strictObject({
|
|
|
119
124
|
processes: z
|
|
120
125
|
.boolean()
|
|
121
126
|
.describe('Record every program started in the sandbox with its command line (Windows process auditing; repeated runs are counted, not listed).'),
|
|
122
|
-
files: z.boolean().describe('Record the files changed in the
|
|
127
|
+
files: z.boolean().describe('Record the files changed in the projects while the sandbox runs.'),
|
|
128
|
+
commits: z
|
|
129
|
+
.boolean()
|
|
130
|
+
.describe("Record the git commits made in the projects while the sandbox runs (read from each project's .git folder on this PC)."),
|
|
123
131
|
});
|
|
124
132
|
export const WorkspaceFilterSchema = z.strictObject({
|
|
125
133
|
enabled: z
|
|
@@ -5,12 +5,13 @@ import path from 'node:path';
|
|
|
5
5
|
import { appsToInstall } from '../../shared/apps/index.js';
|
|
6
6
|
import { downloader, moveIntoPlace, stagingFolder } from '../../shared/download/index.js';
|
|
7
7
|
import { safeRemove } from '../../shared/io/safe-remove.js';
|
|
8
|
-
import { dryRun } from '../runtime/paths.js';
|
|
8
|
+
import { dryRun, launchDir } from '../runtime/paths.js';
|
|
9
9
|
import { execAsync } from '../runtime/process.js';
|
|
10
10
|
import { exists, mkdirp } from '../runtime/fs.js';
|
|
11
11
|
import { log, warn } from '../runtime/log.js';
|
|
12
12
|
import { errMsg } from '../../core/util/errors.js';
|
|
13
13
|
import { tool } from '../toolchains/tool-env.js';
|
|
14
|
+
import { uvVars } from '../toolchains/runtimes.js';
|
|
14
15
|
import { afterInstall } from './after-install.js';
|
|
15
16
|
import { findExe } from './find-exe.js';
|
|
16
17
|
import { shortcuts } from './shortcuts.js';
|
|
@@ -58,9 +59,10 @@ export class AppInstaller {
|
|
|
58
59
|
if (!exe)
|
|
59
60
|
throw new Error(entry.exe + ' not found in the download');
|
|
60
61
|
await afterInstall(entry, exe);
|
|
61
|
-
|
|
62
|
+
// A proxy or MCP server (agent add-ons) is started by the sandbox or the agent: no PATH entry, no shortcut.
|
|
63
|
+
if (!entry.service && (entry.cli || entry.id === 'sysinternals'))
|
|
62
64
|
this.env.prepend.push(path.dirname(exe));
|
|
63
|
-
if (!entry.cli)
|
|
65
|
+
if (!entry.cli && !entry.service)
|
|
64
66
|
this.gui.push({ name: entry.name, exe });
|
|
65
67
|
this.installed.push(entry.name);
|
|
66
68
|
}
|
|
@@ -74,7 +76,11 @@ export class AppInstaller {
|
|
|
74
76
|
const work = stagingFolder(dest);
|
|
75
77
|
const out = path.join(work, 'out');
|
|
76
78
|
try {
|
|
77
|
-
if ('
|
|
79
|
+
if ('locked' in entry.source) {
|
|
80
|
+
log('Installing ' + entry.name + ' ' + entry.source.version + ' (pinned, every file checked) ...');
|
|
81
|
+
await installLocked(entry.id, entry.source.locked, mkdirp(out));
|
|
82
|
+
}
|
|
83
|
+
else if ('npm' in entry.source) {
|
|
78
84
|
const npmCli = path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
79
85
|
log('Installing ' + entry.name + ' (npm) ...');
|
|
80
86
|
await execAsync(process.execPath, [
|
|
@@ -103,6 +109,46 @@ export class AppInstaller {
|
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
}
|
|
112
|
+
/** The Python the uv lock files were made for (sandbox\addons\<id>\requirements.txt). */
|
|
113
|
+
const LOCKED_PYTHON = '3.13';
|
|
114
|
+
/**
|
|
115
|
+
* Installs a pinned package from its lock file in the launch folder (sandbox\addons\<id>) into `out`. npm: `npm ci`
|
|
116
|
+
* checks every tarball against the lock file's integrity, and runs no install scripts. uv: a relocatable venv (the
|
|
117
|
+
* folder moves into place afterwards) and `--require-hashes`, so every wheel must match its SHA-256 in the file; no
|
|
118
|
+
* source builds.
|
|
119
|
+
*/
|
|
120
|
+
async function installLocked(id, kind, out) {
|
|
121
|
+
const lock = path.join(launchDir, 'sandbox', 'addons', id);
|
|
122
|
+
if (kind === 'npm') {
|
|
123
|
+
for (const f of ['package.json', 'package-lock.json'])
|
|
124
|
+
fs.copyFileSync(path.join(lock, f), path.join(out, f));
|
|
125
|
+
const npmCli = path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
126
|
+
await execAsync(process.execPath, [npmCli, 'ci', '--ignore-scripts', '--no-audit', '--no-fund', '--loglevel=error'], { cwd: out });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const uv = findExe(tool('uv'), 'uv.exe');
|
|
130
|
+
if (!uv)
|
|
131
|
+
throw new Error('uv is not installed (the uv step failed)');
|
|
132
|
+
const env = { ...process.env, ...uvVars() };
|
|
133
|
+
const venv = path.join(out, 'venv');
|
|
134
|
+
await execAsync(uv, ['venv', '--relocatable', '--python', LOCKED_PYTHON, '--python-preference', 'only-managed', venv], {
|
|
135
|
+
env,
|
|
136
|
+
});
|
|
137
|
+
await execAsync(uv, [
|
|
138
|
+
'pip',
|
|
139
|
+
'install',
|
|
140
|
+
'--python',
|
|
141
|
+
path.join(venv, 'Scripts', 'python.exe'),
|
|
142
|
+
'--require-hashes',
|
|
143
|
+
'--no-deps',
|
|
144
|
+
'--only-binary',
|
|
145
|
+
':all:',
|
|
146
|
+
'--link-mode',
|
|
147
|
+
'copy',
|
|
148
|
+
'-r',
|
|
149
|
+
path.join(lock, 'requirements.txt'),
|
|
150
|
+
], { env });
|
|
151
|
+
}
|
|
106
152
|
/** Installs apps.install and apps.custom (skipped ones are listed in the summary). */
|
|
107
153
|
export async function setupApps(env, apps, o) {
|
|
108
154
|
const { entries, skipped } = appsToInstall(apps, o.gitAccess);
|
package/dist/guest/apps/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Installs the apps you chose (apps.install from the catalogue, apps.custom of your own) into C:\AgentData\tools\apps.
|
|
2
2
|
// Installers are never run: archives are unpacked, NSIS installers opened with 7-Zip and MSIs extracted (msiexec /a),
|
|
3
3
|
// so nothing is registered and nothing is lost when the sandbox closes.
|
|
4
|
-
export { appsNeedMesa } from '../../shared/apps/index.js';
|
|
4
|
+
export { appsNeedMesa, appsNeedUv } from '../../shared/apps/index.js';
|
|
5
5
|
export { setupApps } from './app-installer.js';
|
|
6
6
|
export { findExe } from './find-exe.js';
|
|
@@ -9,7 +9,7 @@ export async function resolve(entry) {
|
|
|
9
9
|
}
|
|
10
10
|
if ('url' in s) {
|
|
11
11
|
const name = decodeURIComponent(new URL(s.url).pathname.split('/').pop() || entry.id);
|
|
12
|
-
return { url: s.url, fileName: /\.\w{2,5}$/.test(name) ? name : name + '.zip' };
|
|
12
|
+
return { url: s.url, fileName: /\.\w{2,5}$/.test(name) ? name : name + '.zip', sha256: s.sha256 };
|
|
13
13
|
}
|
|
14
14
|
if ('resolve' in s && RESOLVERS[s.resolve])
|
|
15
15
|
return RESOLVERS[s.resolve]();
|
|
@@ -1,7 +1,37 @@
|
|
|
1
1
|
/** Background programs of Windows itself: never what an agent ran. */
|
|
2
2
|
export declare const NOISE: Set<string>;
|
|
3
|
-
/**
|
|
4
|
-
export
|
|
3
|
+
/** A session's ptyhost: everything it starts (and what those start) is that session's. */
|
|
4
|
+
export interface SessionRoot {
|
|
5
|
+
readonly session: string;
|
|
6
|
+
readonly pid: number;
|
|
7
|
+
/** When the ptyhost started and ended (ms); a reused process id outside this window is not the session's. */
|
|
8
|
+
readonly from: number;
|
|
9
|
+
readonly to?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Follows the process tree through the events themselves: a program belongs to a session when its parent is that
|
|
13
|
+
* session's ptyhost, or a program that belongs to it. Every new process id is recorded (as no session's when its
|
|
14
|
+
* parent is not one's), so an id Windows gives to another process later never inherits a session. A program whose
|
|
15
|
+
* parent was never seen is no session's: nothing is guessed.
|
|
16
|
+
*/
|
|
17
|
+
export declare class Attributor {
|
|
18
|
+
private readonly owner;
|
|
19
|
+
/** The session of a new process (pid, started by ppid at time t), or undefined. */
|
|
20
|
+
attribute(e: {
|
|
21
|
+
readonly pid: number;
|
|
22
|
+
readonly ppid: number;
|
|
23
|
+
readonly t: number;
|
|
24
|
+
}, roots: readonly SessionRoot[]): string | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The events in wevtutil's XML output as audit lines, with the newest record id seen. With an attributor and the
|
|
28
|
+
* sessions' roots, each line says which session started it (s), when that is known. Every event goes through the
|
|
29
|
+
* attributor, Windows' own background programs too (they are left out of the lines only afterwards).
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseProcessEvents(xml: string, attribution?: {
|
|
32
|
+
readonly attributor: Attributor;
|
|
33
|
+
readonly roots: readonly SessionRoot[];
|
|
34
|
+
}): {
|
|
5
35
|
lines: string[];
|
|
6
36
|
last: number;
|
|
7
37
|
};
|