windows-development-sandbox 1.12.0 → 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.
- package/README.md +1 -0
- package/config.example.json +11 -1
- package/dist/app/features/access/access.def.d.ts +190 -0
- package/dist/app/features/access/access.def.js +273 -0
- package/dist/app/features/access/access.run.d.ts +79 -0
- package/dist/app/features/access/access.run.js +129 -0
- package/dist/app/features/access/index.d.ts +2 -0
- package/dist/app/features/access/index.js +3 -0
- package/dist/app/features/agents/agent-entry.js +5 -0
- package/dist/app/features/agents/agents.def.js +1 -0
- package/dist/app/features/apps/apps.def.js +1 -0
- package/dist/app/features/audit/audit.def.d.ts +11 -2
- package/dist/app/features/audit/audit.def.js +7 -4
- package/dist/app/features/audit/audit.run.d.ts +3 -0
- package/dist/app/features/defs.d.ts +155 -3
- package/dist/app/features/defs.js +12 -1
- package/dist/app/features/files/files.def.js +1 -0
- package/dist/app/features/health/health.def.js +1 -0
- package/dist/app/features/sandbox/sandbox.def.js +3 -0
- package/dist/app/features/sessions/sessions.def.d.ts +36 -0
- package/dist/app/features/sessions/sessions.def.js +77 -9
- package/dist/app/features/sessions/sessions.run.d.ts +25 -2
- package/dist/app/features/sessions/sessions.run.js +71 -3
- package/dist/app/features/settings/settings.def.d.ts +8 -0
- package/dist/app/features/settings/settings.def.js +17 -0
- package/dist/app/features/web/web.def.d.ts +9 -0
- package/dist/app/features/web/web.def.js +26 -1
- package/dist/app/features/web/web.run.d.ts +3 -0
- package/dist/app/features/web/web.run.js +11 -3
- package/dist/app/kernel/access.d.ts +33 -0
- package/dist/app/kernel/access.js +44 -0
- package/dist/app/kernel/define.d.ts +14 -1
- package/dist/app/kernel/index.d.ts +1 -0
- package/dist/app/kernel/index.js +1 -0
- package/dist/app/kernel/rpc.d.ts +5 -2
- package/dist/app/registry.d.ts +4 -1
- package/dist/app/registry.js +24 -4
- package/dist/cli/commands/registry.js +2 -0
- package/dist/cli/commands/remote.d.ts +2 -0
- package/dist/cli/commands/remote.js +49 -0
- package/dist/cli/commands/sessions/reopen.js +3 -0
- package/dist/cli/commands/web.js +66 -0
- package/dist/cli/manifest.d.ts +63 -4
- package/dist/cli/manifest.js +33 -4
- package/dist/cli/menu/io-menu.d.ts +5 -3
- package/dist/cli/menu/io-menu.js +12 -5
- package/dist/cli/menu/remote-menu.d.ts +19 -0
- package/dist/cli/menu/remote-menu.js +108 -0
- package/dist/cli/menu/views.js +2 -0
- package/dist/cli/operations/handler.js +15 -1
- package/dist/cli/services/remote-client.d.ts +16 -0
- package/dist/cli/services/remote-client.js +141 -0
- package/dist/cli/services/sessions-service.d.ts +5 -0
- package/dist/cli/services/sessions-service.js +17 -1
- package/dist/core/config/defaults.js +11 -1
- package/dist/core/local-config/file.js +16 -2
- package/dist/core/local-config/overrides.js +7 -0
- package/dist/core/run/versions.js +9 -8
- package/dist/core/schema/config.d.ts +22 -3
- package/dist/core/schema/config.js +39 -2
- package/dist/core/schema/web.d.ts +51 -0
- package/dist/core/schema/web.js +46 -1
- package/dist/core/util/fs.d.ts +5 -0
- package/dist/core/util/fs.js +23 -0
- package/dist/core/util/index.d.ts +1 -1
- package/dist/core/util/index.js +1 -1
- package/dist/guest/daemon/daemon.js +2 -0
- package/dist/guest/daemon/idle-shutdown.js +2 -1
- package/dist/guest/daemon/sessions.d.ts +16 -2
- package/dist/guest/daemon/sessions.js +126 -20
- package/dist/guest/git/decision.js +7 -0
- package/dist/guest/sessions/cmdline.d.ts +14 -0
- package/dist/guest/sessions/cmdline.js +10 -0
- package/dist/guest/toolchains/setup.js +3 -0
- package/dist/host/audit/log.d.ts +3 -2
- package/dist/host/audit/log.js +3 -3
- package/dist/host/channel/channel.d.ts +5 -1
- package/dist/host/channel/channel.js +12 -0
- package/dist/host/guard/agent-files.js +7 -1
- package/dist/host/guard/commands.js +13 -1
- package/dist/host/guard/git-config.js +27 -7
- package/dist/host/guard/git-dirs.js +78 -3
- package/dist/host/guard/mark-of-the-web.js +3 -1
- package/dist/host/guard/nested-repos.js +7 -2
- package/dist/host/guard/store.js +1 -0
- package/dist/host/guard/types.d.ts +2 -0
- package/dist/host/launch/mappings.js +11 -1
- package/dist/host/launch/project-scan.js +2 -21
- package/dist/host/launch/stage.js +4 -0
- package/dist/host/life/session-ledger.js +2 -1
- package/dist/host/remote/access-store.d.ts +156 -0
- package/dist/host/remote/access-store.js +308 -0
- package/dist/host/remote/cert.d.ts +20 -0
- package/dist/host/remote/cert.js +103 -0
- package/dist/host/remote/door-client.d.ts +13 -0
- package/dist/host/remote/door-client.js +54 -0
- package/dist/host/remote/firewall.d.ts +44 -0
- package/dist/host/remote/firewall.js +118 -0
- package/dist/host/remote/gate.d.ts +125 -0
- package/dist/host/remote/gate.js +391 -0
- package/dist/host/remote/index.d.ts +8 -0
- package/dist/host/remote/index.js +10 -0
- package/dist/host/remote/lan.d.ts +24 -0
- package/dist/host/remote/lan.js +88 -0
- package/dist/host/remote/tls-store.d.ts +22 -0
- package/dist/host/remote/tls-store.js +63 -0
- package/dist/host/remote/windows-helper.d.ts +67 -0
- package/dist/host/remote/windows-helper.js +129 -0
- package/dist/host/web/bridge.d.ts +5 -1
- package/dist/host/web/bridge.js +6 -2
- package/dist/host/web/browser.js +1 -1
- package/dist/host/web/control.d.ts +4 -1
- package/dist/host/web/control.js +28 -4
- package/dist/host/web/rpc.d.ts +16 -2
- package/dist/host/web/rpc.js +37 -4
- package/dist/host/web/runner.d.ts +4 -0
- package/dist/host/web/runner.js +12 -0
- package/dist/host/web/server.d.ts +18 -2
- package/dist/host/web/server.js +97 -24
- package/dist/host/web/state.d.ts +5 -0
- package/dist/host/web/state.js +8 -0
- package/dist/runtime/agent.js +74 -3
- package/dist/runtime/daemon.js +205 -24
- package/dist/runtime/db.js +74 -3
- package/dist/runtime/gitguard.js +6 -0
- package/dist/runtime/main.js +77 -3
- package/dist/runtime/netproxy.js +74 -3
- package/dist/runtime/view.js +76 -3
- package/dist/selftest/app.test.js +41 -6
- package/dist/selftest/channel.test.js +16 -0
- package/dist/selftest/cmdline.test.js +16 -0
- package/dist/selftest/git-access.test.js +8 -0
- package/dist/selftest/hostile-input.test.js +12 -1
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/live-daemon.test.js +25 -0
- package/dist/selftest/local-config.test.js +30 -0
- package/dist/selftest/remote.test.d.ts +2 -0
- package/dist/selftest/remote.test.js +536 -0
- package/dist/selftest/repo-guard.test.js +56 -2
- package/dist/selftest/resume.test.js +2 -1
- package/dist/selftest/sharing.test.js +9 -2
- package/dist/selftest/support/fixtures.d.ts +13 -0
- package/dist/selftest/support/web-preview.js +63 -0
- package/dist/selftest/web.test.js +2 -0
- package/dist/shared/channel/schema.d.ts +23 -0
- package/dist/shared/channel/schema.js +8 -0
- package/dist/shared/pty/protocol.d.ts +2 -0
- package/dist/shared/pty/protocol.js +2 -0
- package/dist/web/index.html +1 -1
- package/dist/web/main-Z2WYAE2Z.js +221 -0
- package/native/csharp/HostTools/Program.cs +17 -1
- package/native/csharp/HostTools/WebHelper.cs +499 -0
- package/native/scripts/get-network-profile.ps1 +19 -0
- package/native/scripts/set-folder-access.ps1 +11 -0
- package/native/scripts/set-web-firewall.ps1 +103 -0
- package/package.json +1 -1
- package/sandbox/csharp/PtyHost.cs +20 -0
- package/schemas/config.schema.json +65 -0
- package/schemas/sandbox-config.schema.json +0 -18
- package/dist/web/main-UD4QCXZR.js +0 -219
|
@@ -131,6 +131,7 @@ export const lastHealth = defineQuery({
|
|
|
131
131
|
});
|
|
132
132
|
export const checkHealth = defineOperation({
|
|
133
133
|
id: 'health.check',
|
|
134
|
+
access: 'control',
|
|
134
135
|
title: 'Check this PC',
|
|
135
136
|
summary: 'check this PC, the install, your settings and the sandbox (changes nothing)',
|
|
136
137
|
input: z.object({ harness: z.string().optional().describe('agent to check') }),
|
|
@@ -91,6 +91,7 @@ export const sandboxStatus = defineQuery({
|
|
|
91
91
|
});
|
|
92
92
|
export const showWindow = defineOperation({
|
|
93
93
|
id: 'sandbox.showWindow',
|
|
94
|
+
access: 'control',
|
|
94
95
|
title: 'Show the window',
|
|
95
96
|
summary: 'show the sandbox window',
|
|
96
97
|
input: z.object({}),
|
|
@@ -100,6 +101,7 @@ export const showWindow = defineOperation({
|
|
|
100
101
|
});
|
|
101
102
|
export const hideWindow = defineOperation({
|
|
102
103
|
id: 'sandbox.hideWindow',
|
|
104
|
+
access: 'control',
|
|
103
105
|
title: 'Hide the window',
|
|
104
106
|
summary: 'hide the sandbox window (it keeps running)',
|
|
105
107
|
input: z.object({}),
|
|
@@ -109,6 +111,7 @@ export const hideWindow = defineOperation({
|
|
|
109
111
|
});
|
|
110
112
|
export const stopSandbox = defineOperation({
|
|
111
113
|
id: 'sandbox.stop',
|
|
114
|
+
access: 'control',
|
|
112
115
|
title: 'Stop the sandbox…',
|
|
113
116
|
summary: "shut the sandbox down (every project's sessions end)",
|
|
114
117
|
input: z.object({}),
|
|
@@ -5,6 +5,10 @@ export declare const SessionRowSchema: z.ZodObject<{
|
|
|
5
5
|
id: z.ZodString;
|
|
6
6
|
launchId: z.ZodString;
|
|
7
7
|
harness: z.ZodString;
|
|
8
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
9
|
+
shell: "shell";
|
|
10
|
+
agent: "agent";
|
|
11
|
+
}>>;
|
|
8
12
|
cwd: z.ZodString;
|
|
9
13
|
project: z.ZodOptional<z.ZodString>;
|
|
10
14
|
phase: z.ZodEnum<{
|
|
@@ -36,6 +40,10 @@ export declare const SessionsListSchema: z.ZodObject<{
|
|
|
36
40
|
id: z.ZodString;
|
|
37
41
|
launchId: z.ZodString;
|
|
38
42
|
harness: z.ZodString;
|
|
43
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
44
|
+
shell: "shell";
|
|
45
|
+
agent: "agent";
|
|
46
|
+
}>>;
|
|
39
47
|
cwd: z.ZodString;
|
|
40
48
|
project: z.ZodOptional<z.ZodString>;
|
|
41
49
|
phase: z.ZodEnum<{
|
|
@@ -74,6 +82,10 @@ export declare const listSessions: import("../../kernel/define.js").QueryDef<z.Z
|
|
|
74
82
|
id: z.ZodString;
|
|
75
83
|
launchId: z.ZodString;
|
|
76
84
|
harness: z.ZodString;
|
|
85
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
86
|
+
shell: "shell";
|
|
87
|
+
agent: "agent";
|
|
88
|
+
}>>;
|
|
77
89
|
cwd: z.ZodString;
|
|
78
90
|
project: z.ZodOptional<z.ZodString>;
|
|
79
91
|
phase: z.ZodEnum<{
|
|
@@ -102,6 +114,26 @@ export declare const restartSessions: import("../../kernel/define.js").Operation
|
|
|
102
114
|
}, z.core.$strip>, z.ZodObject<{
|
|
103
115
|
message: z.ZodString;
|
|
104
116
|
}, z.core.$strip>, "sessions.restart">;
|
|
117
|
+
/** The shells a shell session can run; auto: PowerShell 7 when the sandbox has it, else Windows PowerShell. */
|
|
118
|
+
export declare const SHELL_CHOICES: readonly ["auto", "pwsh", "powershell", "cmd"];
|
|
119
|
+
export declare const startShell: import("../../kernel/define.js").OperationDef<z.ZodObject<{
|
|
120
|
+
project: z.ZodOptional<z.ZodString>;
|
|
121
|
+
shell: z.ZodDefault<z.ZodEnum<{
|
|
122
|
+
pwsh: "pwsh";
|
|
123
|
+
auto: "auto";
|
|
124
|
+
powershell: "powershell";
|
|
125
|
+
cmd: "cmd";
|
|
126
|
+
}>>;
|
|
127
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
128
|
+
session: z.ZodString;
|
|
129
|
+
shell: z.ZodString;
|
|
130
|
+
message: z.ZodString;
|
|
131
|
+
}, z.core.$strip>, "sessions.shell">;
|
|
132
|
+
export declare const closeSession: import("../../kernel/define.js").OperationDef<z.ZodObject<{
|
|
133
|
+
session: z.ZodString;
|
|
134
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
135
|
+
message: z.ZodString;
|
|
136
|
+
}, z.core.$strip>, "sessions.close">;
|
|
105
137
|
/** The sessions page: one list (a table on the command line), this folder's project first. */
|
|
106
138
|
export declare function sessionsPage(o: SessionsList): Page;
|
|
107
139
|
export declare const sessionsView: import("../../kernel/define.js").ViewDef<{
|
|
@@ -116,6 +148,10 @@ export declare const sessionsView: import("../../kernel/define.js").ViewDef<{
|
|
|
116
148
|
id: z.ZodString;
|
|
117
149
|
launchId: z.ZodString;
|
|
118
150
|
harness: z.ZodString;
|
|
151
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
152
|
+
shell: "shell";
|
|
153
|
+
agent: "agent";
|
|
154
|
+
}>>;
|
|
119
155
|
cwd: z.ZodString;
|
|
120
156
|
project: z.ZodOptional<z.ZodString>;
|
|
121
157
|
phase: z.ZodEnum<{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// The
|
|
2
|
-
// view; sessions.run.ts does the work. A running session links to
|
|
3
|
-
// in the web UI).
|
|
1
|
+
// The sessions of the running sandbox, agents and plain shells: the list (live), restarting the agents, opening a shell
|
|
2
|
+
// and closing a session. Pure: schemas, labels and the view; sessions.run.ts does the work. A running session links to
|
|
3
|
+
// its terminal (attach in the menu and on the command line, the terminal page in the web UI).
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { action, ago, defineOperation, defineQuery, defineView, link, } from '../../kernel/index.js';
|
|
6
6
|
const ViewersSchema = z.object({ viewers: z.number(), remote: z.number() });
|
|
@@ -8,7 +8,9 @@ const ViewersSchema = z.object({ viewers: z.number(), remote: z.number() });
|
|
|
8
8
|
export const SessionRowSchema = z.object({
|
|
9
9
|
id: z.string().describe('The session'),
|
|
10
10
|
launchId: z.string(),
|
|
11
|
-
harness: z.string().describe('The agent'),
|
|
11
|
+
harness: z.string().describe('The agent, or the shell (pwsh, powershell, cmd)'),
|
|
12
|
+
/** A plain shell rather than an agent (absent: an agent). */
|
|
13
|
+
kind: z.enum(['agent', 'shell']).optional(),
|
|
12
14
|
cwd: z.string().describe('Its folder in the sandbox'),
|
|
13
15
|
project: z.string().optional(),
|
|
14
16
|
phase: z.enum(['starting', 'running', 'exited', 'failed']).describe('What it is doing'),
|
|
@@ -50,7 +52,7 @@ export function orderedSessions(list) {
|
|
|
50
52
|
export const listSessions = defineQuery({
|
|
51
53
|
id: 'sessions.list',
|
|
52
54
|
title: 'Sessions',
|
|
53
|
-
summary: 'list
|
|
55
|
+
summary: 'list sessions (agents and shells) and who is attached',
|
|
54
56
|
input: z.object({ wide: z.boolean().default(false).describe("also show each session's folder") }),
|
|
55
57
|
output: SessionsListSchema,
|
|
56
58
|
live: { everyMs: 2500 },
|
|
@@ -65,6 +67,7 @@ export const listSessions = defineQuery({
|
|
|
65
67
|
});
|
|
66
68
|
export const restartSessions = defineOperation({
|
|
67
69
|
id: 'sessions.restart',
|
|
70
|
+
access: 'control',
|
|
68
71
|
title: 'Restart agents',
|
|
69
72
|
summary: "restart this project's running agents in place; they resume their conversation",
|
|
70
73
|
input: z.object({
|
|
@@ -82,19 +85,71 @@ export const restartSessions = defineOperation({
|
|
|
82
85
|
},
|
|
83
86
|
},
|
|
84
87
|
});
|
|
88
|
+
/** Agent and project names as the web UI has always accepted them. */
|
|
89
|
+
const NameText = z.string().regex(/^[A-Za-z0-9_.-]{1,64}$/);
|
|
90
|
+
const SessionId = z.string().regex(/^[A-Za-z0-9_-]{1,64}$/);
|
|
91
|
+
/** The shells a shell session can run; auto: PowerShell 7 when the sandbox has it, else Windows PowerShell. */
|
|
92
|
+
export const SHELL_CHOICES = ['auto', 'pwsh', 'powershell', 'cmd'];
|
|
93
|
+
/** The session the operation opened, as a link to its terminal. */
|
|
94
|
+
function openedSession(o) {
|
|
95
|
+
return { kind: 'terminal', session: o.session };
|
|
96
|
+
}
|
|
97
|
+
export const startShell = defineOperation({
|
|
98
|
+
id: 'sessions.shell',
|
|
99
|
+
title: 'New shell',
|
|
100
|
+
summary: 'open PowerShell or cmd in a project of the running sandbox, as a session you can attach to',
|
|
101
|
+
input: z.object({
|
|
102
|
+
project: NameText.optional().describe("The project of the running sandbox (default: this folder's, else asked)"),
|
|
103
|
+
shell: z
|
|
104
|
+
.enum(SHELL_CHOICES)
|
|
105
|
+
.default('auto')
|
|
106
|
+
.describe('The shell: pwsh, powershell or cmd (default: pwsh if present)'),
|
|
107
|
+
}),
|
|
108
|
+
output: z.object({
|
|
109
|
+
session: z.string().describe('The new session'),
|
|
110
|
+
shell: z.string().describe('The shell it runs'),
|
|
111
|
+
message: z.string().describe('What happened'),
|
|
112
|
+
}),
|
|
113
|
+
// A shell runs anything as you: someone you let in remotely may type into one you opened, never open one.
|
|
114
|
+
access: 'owner',
|
|
115
|
+
text: (o) => o.message,
|
|
116
|
+
opens: openedSession,
|
|
117
|
+
cli: {
|
|
118
|
+
name: 'shell',
|
|
119
|
+
group: 'Sessions',
|
|
120
|
+
positional: ['project'],
|
|
121
|
+
flags: { shell: { flag: '--shell', value: 'name', choices: ['pwsh', 'powershell', 'cmd'] } },
|
|
122
|
+
json: true,
|
|
123
|
+
examples: [
|
|
124
|
+
"sandbox shell PowerShell in this folder's project, in this terminal (Ctrl+] detaches)",
|
|
125
|
+
'sandbox shell api --shell cmd cmd in the project api',
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
export const closeSession = defineOperation({
|
|
130
|
+
id: 'sessions.close',
|
|
131
|
+
title: 'Close',
|
|
132
|
+
summary: 'end a running session (an agent or a shell) and every program it started',
|
|
133
|
+
input: z.object({ session: SessionId.describe('The session to close') }),
|
|
134
|
+
output: z.object({ message: z.string().describe("The sandbox's answer") }),
|
|
135
|
+
access: 'control',
|
|
136
|
+
danger: true,
|
|
137
|
+
text: (o) => o.message,
|
|
138
|
+
cli: { name: 'close', group: 'Sessions', positional: ['session'], examples: ['sandbox close 4f2c9a'] },
|
|
139
|
+
});
|
|
85
140
|
function sessionItem(s, wide) {
|
|
86
141
|
const state = s.phase + (s.exitCode !== undefined ? ' (' + s.exitCode + ')' : '');
|
|
87
142
|
const running = s.phase === 'running';
|
|
88
143
|
return {
|
|
89
144
|
id: s.id,
|
|
90
|
-
label: s.harness + ' ' + (s.projectName ?? '-'),
|
|
145
|
+
label: (s.kind === 'shell' ? s.harness + ' (shell)' : s.harness) + ' ' + (s.projectName ?? '-'),
|
|
91
146
|
hint: state + ' ' + ago(s.startedAt),
|
|
92
147
|
badges: [{ text: s.phase, tone: running ? 'ok' : s.phase === 'failed' ? 'danger' : 'muted' }],
|
|
93
148
|
group: s.here ? "This folder's project" : 'Other sessions',
|
|
94
149
|
marked: s.here,
|
|
95
150
|
icon: s.harness,
|
|
96
151
|
description: running
|
|
97
|
-
? "Opens this session's terminal (in a terminal, Ctrl+] detaches; the session keeps running)."
|
|
152
|
+
? "Opens this session's terminal (in a terminal, Ctrl+] detaches; the session keeps running). x closes it."
|
|
98
153
|
: undefined,
|
|
99
154
|
details: ['Session ' + s.id, 'Folder ' + s.cwd, 'Attached ' + viewerText(s.viewers)],
|
|
100
155
|
cells: {
|
|
@@ -108,6 +163,9 @@ function sessionItem(s, wide) {
|
|
|
108
163
|
started: ago(s.startedAt),
|
|
109
164
|
},
|
|
110
165
|
...(running && s.canOpen ? { primary: link({ kind: 'terminal', session: s.id }, 'Open', { verb: 'attach' }) } : {}),
|
|
166
|
+
...(running
|
|
167
|
+
? { actions: [action(closeSession, { session: s.id }, { key: 'x', verb: 'close', importance: 1 })] }
|
|
168
|
+
: {}),
|
|
111
169
|
...(running ? {} : { disabled: 'This session has ended.' }),
|
|
112
170
|
...(running && !s.canOpen ? { disabled: 'Not reachable from this PC.' } : {}),
|
|
113
171
|
};
|
|
@@ -150,6 +208,16 @@ export function sessionsPage(o) {
|
|
|
150
208
|
],
|
|
151
209
|
actions: [
|
|
152
210
|
...(o.sandboxRunning ? [{ ...start, label: 'New session', key: 'n', verb: 'new session', importance: 1 }] : []),
|
|
211
|
+
...(o.sandboxRunning
|
|
212
|
+
? [
|
|
213
|
+
action(startShell, { shell: 'auto' }, {
|
|
214
|
+
key: 's',
|
|
215
|
+
verb: 'new shell',
|
|
216
|
+
importance: 1,
|
|
217
|
+
description: 'Opens PowerShell in a project of the sandbox, as a session like the agents.',
|
|
218
|
+
}),
|
|
219
|
+
]
|
|
220
|
+
: []),
|
|
153
221
|
...(sessions.length
|
|
154
222
|
? [action(restartSessions, { all: false }, { key: 'r', verb: 'restart agents', importance: 1 })]
|
|
155
223
|
: []),
|
|
@@ -172,8 +240,8 @@ export function sessionsPage(o) {
|
|
|
172
240
|
export const sessionsView = defineView({
|
|
173
241
|
id: 'sessions',
|
|
174
242
|
title: 'Sessions',
|
|
175
|
-
summary: 'The
|
|
243
|
+
summary: 'The sessions of the running sandbox: open one, restart the agents, open a shell, close one.',
|
|
176
244
|
queries: { sessions: listSessions },
|
|
177
|
-
operations: [restartSessions],
|
|
245
|
+
operations: [restartSessions, startShell, closeSession],
|
|
178
246
|
build: ({ sessions }) => sessionsPage(sessions),
|
|
179
247
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Context } from '../../../cli/commands/context.js';
|
|
1
2
|
export declare const listSessionsRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").QueryDef<import("zod").ZodObject<{
|
|
2
3
|
wide: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
3
4
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
@@ -9,6 +10,10 @@ export declare const listSessionsRun: import("../../kernel/define.js").Implement
|
|
|
9
10
|
id: import("zod").ZodString;
|
|
10
11
|
launchId: import("zod").ZodString;
|
|
11
12
|
harness: import("zod").ZodString;
|
|
13
|
+
kind: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
14
|
+
shell: "shell";
|
|
15
|
+
agent: "agent";
|
|
16
|
+
}>>;
|
|
12
17
|
cwd: import("zod").ZodString;
|
|
13
18
|
project: import("zod").ZodOptional<import("zod").ZodString>;
|
|
14
19
|
phase: import("zod").ZodEnum<{
|
|
@@ -30,10 +35,28 @@ export declare const listSessionsRun: import("../../kernel/define.js").Implement
|
|
|
30
35
|
}, import("zod/v4/core").$strip>>>;
|
|
31
36
|
canOpen: import("zod").ZodBoolean;
|
|
32
37
|
}, import("zod/v4/core").$strip>>;
|
|
33
|
-
}, import("zod/v4/core").$strip>, "sessions.list">,
|
|
38
|
+
}, import("zod/v4/core").$strip>, "sessions.list">, Context>;
|
|
34
39
|
export declare const restartSessionsRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{
|
|
35
40
|
harness: import("zod").ZodOptional<import("zod").ZodString>;
|
|
36
41
|
all: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
37
42
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
38
43
|
message: import("zod").ZodString;
|
|
39
|
-
}, import("zod/v4/core").$strip>, "sessions.restart">,
|
|
44
|
+
}, import("zod/v4/core").$strip>, "sessions.restart">, Context>;
|
|
45
|
+
export declare const startShellRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{
|
|
46
|
+
project: import("zod").ZodOptional<import("zod").ZodString>;
|
|
47
|
+
shell: import("zod").ZodDefault<import("zod").ZodEnum<{
|
|
48
|
+
pwsh: "pwsh";
|
|
49
|
+
auto: "auto";
|
|
50
|
+
powershell: "powershell";
|
|
51
|
+
cmd: "cmd";
|
|
52
|
+
}>>;
|
|
53
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
54
|
+
session: import("zod").ZodString;
|
|
55
|
+
shell: import("zod").ZodString;
|
|
56
|
+
message: import("zod").ZodString;
|
|
57
|
+
}, import("zod/v4/core").$strip>, "sessions.shell">, Context>;
|
|
58
|
+
export declare const closeSessionRun: import("../../kernel/define.js").Implementation<import("../../kernel/define.js").OperationDef<import("zod").ZodObject<{
|
|
59
|
+
session: import("zod").ZodString;
|
|
60
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
61
|
+
message: import("zod").ZodString;
|
|
62
|
+
}, import("zod/v4/core").$strip>, "sessions.close">, Context>;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
// The sessions feature's work: the list (as this user may see it under the sharing rules)
|
|
2
|
-
|
|
1
|
+
// The sessions feature's work: the list (as this user may see it under the sharing rules), restarting the agents,
|
|
2
|
+
// opening a shell in a project of the running sandbox and closing a session.
|
|
3
|
+
import { fail, safeText } from '../../../core/util/index.js';
|
|
4
|
+
import { RunDir } from '../../../shared/channel/index.js';
|
|
3
5
|
import { hostEndpoint } from '../../../host/channel/index.js';
|
|
4
6
|
import { isSandboxRunning } from '../../../host/sandbox/index.js';
|
|
5
7
|
import { requireOnPc, requireRunning } from '../../../cli/commands/context.js';
|
|
8
|
+
import { locateHere } from '../../../cli/commands/projects/here.js';
|
|
6
9
|
import { lookAtSandbox, useSharedSandbox } from '../../../cli/commands/shared-use.js';
|
|
7
10
|
import { SessionsService } from '../../../cli/services/index.js';
|
|
11
|
+
import { runningProjects } from '../../../cli/services/web-service.js';
|
|
8
12
|
import { implement, typedConfirm } from '../../context.js';
|
|
9
13
|
import { cancelled } from '../../kernel/index.js';
|
|
10
|
-
import { listSessions, restartSessions } from './sessions.def.js';
|
|
14
|
+
import { closeSession, listSessions, restartSessions, startShell } from './sessions.def.js';
|
|
11
15
|
/** A listed session as the surfaces see it: no attach address or token, text from the sandbox made safe. */
|
|
12
16
|
function sessionRow(s) {
|
|
13
17
|
const { attach: _attach, ...rest } = s;
|
|
@@ -56,3 +60,67 @@ export const restartSessionsRun = implement(restartSessions, async (ctx, input,
|
|
|
56
60
|
io.progress('Restarting the agents' + (scope.guest ? ' of ' + scope.projectName : ''));
|
|
57
61
|
return { message: safeText(await service.restart(input.harness ?? null, scope.guest)) };
|
|
58
62
|
});
|
|
63
|
+
/** Where a new shell opens: the project named, else the one of the current folder, else the only one, else asked. */
|
|
64
|
+
async function shellPlace(ctx, project, io) {
|
|
65
|
+
const projects = runningProjects({ ctx, line: null, limited: false, owner: null });
|
|
66
|
+
if (project !== undefined) {
|
|
67
|
+
const named = projects.find((p) => p.name.toLowerCase() === project.toLowerCase());
|
|
68
|
+
if (!named)
|
|
69
|
+
fail('The running sandbox has no project "' + project + '".', 'sandbox status lists its projects.');
|
|
70
|
+
return { name: named.name, guest: named.guest };
|
|
71
|
+
}
|
|
72
|
+
const here = locateHere(ctx, new RunDir(ctx.dataPath).readSandboxStatus());
|
|
73
|
+
if (here.guestDir)
|
|
74
|
+
return { name: here.project?.name, guest: here.guestDir };
|
|
75
|
+
if (!projects.length)
|
|
76
|
+
fail('The running sandbox has no projects.');
|
|
77
|
+
if (projects.length === 1)
|
|
78
|
+
return projects[0];
|
|
79
|
+
const chosen = await io.choose({
|
|
80
|
+
title: 'Which project?',
|
|
81
|
+
choices: projects.map((p) => ({ value: p.name, label: p.name, description: p.guest })),
|
|
82
|
+
});
|
|
83
|
+
const picked = projects.find((p) => p.name === chosen);
|
|
84
|
+
if (!picked)
|
|
85
|
+
cancelled();
|
|
86
|
+
return picked;
|
|
87
|
+
}
|
|
88
|
+
export const startShellRun = implement(startShell, async (asked, input, io) => {
|
|
89
|
+
requireOnPc('shell');
|
|
90
|
+
requireRunning();
|
|
91
|
+
const ctx = await useSharedSandbox(asked, { kind: 'use' }, typedConfirm(io));
|
|
92
|
+
const place = await shellPlace(ctx, input.project, io);
|
|
93
|
+
io.progress('Opening ' + (input.shell === 'auto' ? 'a shell' : input.shell) + ' in ' + (place.name ?? place.guest));
|
|
94
|
+
const session = await new SessionsService(ctx).openShell(input.shell, place.guest);
|
|
95
|
+
const shell = safeText(session.harness);
|
|
96
|
+
return {
|
|
97
|
+
session: safeText(session.id),
|
|
98
|
+
shell,
|
|
99
|
+
message: 'Opened ' + shell + ' in ' + safeText(place.name ?? place.guest) + ' (session ' + safeText(session.id) + ').',
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
export const closeSessionRun = implement(closeSession, async (asked, input, io) => {
|
|
103
|
+
requireOnPc('close');
|
|
104
|
+
requireRunning();
|
|
105
|
+
const find = (ctx) => new SessionsService(ctx).running(null).find((s) => s.id === input.session);
|
|
106
|
+
// Anyone the sandbox is shared with may close a shell; an agent session is its owner's to end.
|
|
107
|
+
const ctx = await useSharedSandbox(asked, (shared) => find(shared)?.kind === 'shell' ? { kind: 'use' } : { kind: 'owner-only', what: 'close an agent session' }, typedConfirm(io));
|
|
108
|
+
const session = find(ctx);
|
|
109
|
+
if (!session)
|
|
110
|
+
fail('No running session ' + input.session + '.', 'sandbox sessions lists the running ones.');
|
|
111
|
+
const what = safeText(session.harness) + (session.kind === 'shell' ? ' (shell)' : '');
|
|
112
|
+
const yes = await io.confirm({
|
|
113
|
+
title: 'Close ' + what + ' session ' + input.session + '?',
|
|
114
|
+
lines: [
|
|
115
|
+
'It ends, with every program it started.' +
|
|
116
|
+
(session.kind === 'shell' ? '' : ' The agent can pick its conversation up again in a new session.'),
|
|
117
|
+
],
|
|
118
|
+
yes: 'Close',
|
|
119
|
+
danger: true,
|
|
120
|
+
implied: true,
|
|
121
|
+
});
|
|
122
|
+
if (!yes)
|
|
123
|
+
cancelled();
|
|
124
|
+
io.progress('Closing session ' + input.session);
|
|
125
|
+
return { message: safeText(await new SessionsService(ctx).close(input.session)) };
|
|
126
|
+
});
|
|
@@ -94,6 +94,14 @@ export declare const getSetting: import("../../kernel/define.js").QueryDef<z.Zod
|
|
|
94
94
|
key: z.ZodString;
|
|
95
95
|
value: z.ZodUnknown;
|
|
96
96
|
}, z.core.$strip>, "settings.get">;
|
|
97
|
+
/**
|
|
98
|
+
* The settings someone with control (not the owner) may change: how attaching and reopening sessions behave, in your
|
|
99
|
+
* own settings file. Everything else (folders, agents, secrets, network, storage, sharing, the audit log, remote
|
|
100
|
+
* access) changes what the sandbox can reach or who can come in, so it is the owner's (docs/design/remote-access.md).
|
|
101
|
+
*/
|
|
102
|
+
export declare const CONTROL_SETTINGS: readonly string[];
|
|
103
|
+
/** settings.set / settings.reset: control for CONTROL_SETTINGS in the user file, owner for everything else. */
|
|
104
|
+
export declare function controlSettingAccess(input: unknown): 'control' | 'owner';
|
|
97
105
|
export declare const setSetting: import("../../kernel/define.js").OperationDef<z.ZodObject<{
|
|
98
106
|
scope: z.ZodDefault<z.ZodEnum<{
|
|
99
107
|
project: "project";
|
|
@@ -65,8 +65,24 @@ export const getSetting = defineQuery({
|
|
|
65
65
|
surfaces: { except: { menu: 'Settings shows every value', web: 'the Settings page shows every value' } },
|
|
66
66
|
cli: { name: 'config', action: 'get', positional: ['key'] },
|
|
67
67
|
});
|
|
68
|
+
/**
|
|
69
|
+
* The settings someone with control (not the owner) may change: how attaching and reopening sessions behave, in your
|
|
70
|
+
* own settings file. Everything else (folders, agents, secrets, network, storage, sharing, the audit log, remote
|
|
71
|
+
* access) changes what the sandbox can reach or who can come in, so it is the owner's (docs/design/remote-access.md).
|
|
72
|
+
*/
|
|
73
|
+
export const CONTROL_SETTINGS = ['attach.', 'resume.'];
|
|
74
|
+
/** settings.set / settings.reset: control for CONTROL_SETTINGS in the user file, owner for everything else. */
|
|
75
|
+
export function controlSettingAccess(input) {
|
|
76
|
+
const o = (input ?? {});
|
|
77
|
+
const key = typeof o.key === 'string' ? o.key : '';
|
|
78
|
+
const userFile = o.scope === undefined || o.scope === 'user';
|
|
79
|
+
return userFile && CONTROL_SETTINGS.some((prefix) => key.startsWith(prefix) && key.length > prefix.length)
|
|
80
|
+
? 'control'
|
|
81
|
+
: 'owner';
|
|
82
|
+
}
|
|
68
83
|
export const setSetting = defineOperation({
|
|
69
84
|
id: 'settings.set',
|
|
85
|
+
access: controlSettingAccess,
|
|
70
86
|
title: 'Change a setting',
|
|
71
87
|
summary: 'change one setting (text is read like the command line: true/on, numbers, JSON)',
|
|
72
88
|
input: z.object({
|
|
@@ -80,6 +96,7 @@ export const setSetting = defineOperation({
|
|
|
80
96
|
});
|
|
81
97
|
export const resetSetting = defineOperation({
|
|
82
98
|
id: 'settings.reset',
|
|
99
|
+
access: controlSettingAccess,
|
|
83
100
|
title: 'Reset a setting',
|
|
84
101
|
summary: 'put one setting back to its default (in a project file: remove it)',
|
|
85
102
|
input: z.object({ scope: ScopeSchema.default('user'), key: KeySchema }),
|
|
@@ -4,12 +4,18 @@ export declare const WebStatusSchema: z.ZodObject<{
|
|
|
4
4
|
running: z.ZodBoolean;
|
|
5
5
|
port: z.ZodNullable<z.ZodNumber>;
|
|
6
6
|
address: z.ZodNullable<z.ZodString>;
|
|
7
|
+
lanAddress: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
8
|
+
lanFingerprint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
9
|
+
lanProblem: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
7
10
|
}, z.core.$strip>;
|
|
8
11
|
export type WebStatus = z.output<typeof WebStatusSchema>;
|
|
9
12
|
export declare const webStatus: import("../../kernel/define.js").QueryDef<z.ZodObject<{}, z.core.$strip>, z.ZodObject<{
|
|
10
13
|
running: z.ZodBoolean;
|
|
11
14
|
port: z.ZodNullable<z.ZodNumber>;
|
|
12
15
|
address: z.ZodNullable<z.ZodString>;
|
|
16
|
+
lanAddress: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
17
|
+
lanFingerprint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
18
|
+
lanProblem: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
13
19
|
}, z.core.$strip>, "web.status">;
|
|
14
20
|
export declare const startWeb: import("../../kernel/define.js").OperationDef<z.ZodObject<{}, z.core.$strip>, z.ZodObject<{
|
|
15
21
|
port: z.ZodNumber;
|
|
@@ -29,5 +35,8 @@ export declare const webView: import("../../kernel/define.js").ViewDef<{
|
|
|
29
35
|
running: z.ZodBoolean;
|
|
30
36
|
port: z.ZodNullable<z.ZodNumber>;
|
|
31
37
|
address: z.ZodNullable<z.ZodString>;
|
|
38
|
+
lanAddress: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
39
|
+
lanFingerprint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
40
|
+
lanProblem: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
32
41
|
}, z.core.$strip>, "web.status">;
|
|
33
42
|
}, Record<string, never>, "web">;
|
|
@@ -8,6 +8,13 @@ export const WebStatusSchema = z.object({
|
|
|
8
8
|
running: z.boolean().describe('The web UI server answers'),
|
|
9
9
|
port: z.number().nullable().describe('Its port on 127.0.0.1, when it runs'),
|
|
10
10
|
address: z.string().nullable().describe('Its address (without the login token), when it runs'),
|
|
11
|
+
lanAddress: z
|
|
12
|
+
.string()
|
|
13
|
+
.nullable()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Its address on your network for paired devices (LAN mode), when it listens there'),
|
|
16
|
+
lanFingerprint: z.string().nullable().optional().describe("The LAN certificate's SHA-256 fingerprint"),
|
|
17
|
+
lanProblem: z.string().nullable().optional().describe('Why LAN mode is on but not listening'),
|
|
11
18
|
});
|
|
12
19
|
const MessageSchema = z.object({ message: z.string().describe('What happened') });
|
|
13
20
|
export const webStatus = defineQuery({
|
|
@@ -18,7 +25,11 @@ export const webStatus = defineQuery({
|
|
|
18
25
|
output: WebStatusSchema,
|
|
19
26
|
surfaces: NOT_ON_WEB,
|
|
20
27
|
text: (o) => o.running && o.port
|
|
21
|
-
? 'The web UI is running at http://127.0.0.1:' +
|
|
28
|
+
? 'The web UI is running at http://127.0.0.1:' +
|
|
29
|
+
o.port +
|
|
30
|
+
'/ (sandbox web open signs you in).' +
|
|
31
|
+
(o.lanAddress ? '\nOn your network: ' + o.lanAddress + ' (certificate ' + o.lanFingerprint + ')' : '') +
|
|
32
|
+
(o.lanProblem ? '\nLAN mode is on but not listening: ' + o.lanProblem : '')
|
|
22
33
|
: 'The web UI is not running (sandbox web start).',
|
|
23
34
|
cli: { name: 'web', action: 'status', json: true },
|
|
24
35
|
});
|
|
@@ -73,6 +84,20 @@ export function webPage(o) {
|
|
|
73
84
|
tone: o.running ? 'ok' : 'muted',
|
|
74
85
|
},
|
|
75
86
|
...(o.address ? [{ id: 'address', label: 'Address', value: o.address + ' (this PC only)' }] : []),
|
|
87
|
+
...(o.lanAddress
|
|
88
|
+
? [
|
|
89
|
+
{
|
|
90
|
+
id: 'lan',
|
|
91
|
+
label: 'On your network',
|
|
92
|
+
value: o.lanAddress,
|
|
93
|
+
description: 'Type this into the phone, then enter the code from Remote access > Allow a new device.',
|
|
94
|
+
details: o.lanFingerprint ? ['Certificate ' + o.lanFingerprint] : undefined,
|
|
95
|
+
},
|
|
96
|
+
]
|
|
97
|
+
: []),
|
|
98
|
+
...(o.lanProblem
|
|
99
|
+
? [{ id: 'lan-problem', label: 'LAN mode', value: o.lanProblem, tone: 'warn' }]
|
|
100
|
+
: []),
|
|
76
101
|
],
|
|
77
102
|
actions: o.running ? [open, stop] : [start, open],
|
|
78
103
|
notes: [{ text: 'Loopback only, behind a token. Open signs you in.', tone: 'muted' }],
|
|
@@ -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
|
-
|
|
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
|
+
}
|