@roj-ai/kompjutr-platform 0.2.11 → 0.2.12

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 CHANGED
@@ -1,10 +1,10 @@
1
1
  # @roj-ai/kompjutr-platform
2
2
 
3
3
  Run the Roj SDK against a [Kompjutr](https://www.npmjs.com/package/@kompjutr/do)
4
- workspace. This adapter uses `@kompjutr/do` **0.1.2** from npm.
4
+ workspace. This adapter uses `@kompjutr/do` **0.1.3** from npm.
5
5
 
6
6
  ```bash
7
- bun add @roj-ai/sdk @roj-ai/kompjutr-platform @kompjutr/do@0.1.2
7
+ bun add @roj-ai/sdk @roj-ai/kompjutr-platform @kompjutr/do@0.1.3
8
8
  ```
9
9
 
10
10
  ## Connect a workspace
@@ -47,17 +47,39 @@ resume after eviction; the default scheduler uses in-memory timers.
47
47
  a native process runner. Process spawning and native dev servers are unsupported;
48
48
  hosts can supply a service executor through SDK bootstrap overrides.
49
49
  - `shellConfinement` defaults to `none`. Set it to `host` only when the host
50
- isolates the entire workspace. It does not create a per-directory sandbox.
51
- Shell path grants are unsupported.
52
- - Known limitation: the host shell runner does not implement the virtual
53
- `/home/user/session` and `/home/user/workspace` namespace exposed by
54
- preset-level `sandboxed: true`. Support requires a native upstream namespace
55
- shared by filesystem resolution, shell execution and Git; this work is deferred.
56
- Existing Bun/bwrap virtual paths are unchanged.
50
+ isolates the entire workspace. Set it to `paths` when one workspace holds
51
+ several sessions' files; see [Path confinement](#path-confinement).
57
52
  - Session logs, LLM call logs and events are stored as SQL rows. Pass the
58
53
  `KompjutrEventStore` explicitly to bootstrap to use SQL event persistence.
59
54
  - The host owns authentication, workspace isolation and storage lifetime.
60
55
 
56
+ ## Path confinement
57
+
58
+ With `shellConfinement: 'paths'`, every shell run sees only the grants the SDK
59
+ sends for it. Presets keep the virtual `/home/user/session` and
60
+ `/home/user/workspace` paths of `sandboxed: true`; each grant maps a virtual
61
+ directory onto a real workspace directory.
62
+
63
+ - The shell runs over a `createMountedFilesystem` view built per run. Paths
64
+ outside every grant do not exist (`ENOENT`), and `ro` grants reject writes.
65
+ - The view fails closed on symlinks: an absolute target, a target above its
66
+ source, or a target in another mount reads as absent. A kernel bind mount
67
+ would follow it.
68
+ - Grant sources resolve to their real directory first. Grant paths must not
69
+ nest, and every source must be an existing directory, or the run is rejected.
70
+ - A run without grants is unconfined, as on the Bun platform.
71
+ - Git keeps the real filesystem, so checkouts and dirty tracking stay real-path
72
+ based. The `git` command translates the cwd and absolute path operands to
73
+ real paths and prints real paths back as virtual ones (`rev-parse
74
+ --show-toplevel`, `init`, `clone` and error messages). It refuses absolute path operands
75
+ outside the grants, `init` and `clone` targets outside a writable grant, and a
76
+ repository whose root no writable grant shows.
77
+ - Git needs no `.git` or `.gitconfig` grants: its data lives in SQL tables, not
78
+ in a `.git` directory. Do not place a checkout at `/` or above a session's
79
+ directories.
80
+ - Confinement covers files, not Git data. Checkouts of one store still share
81
+ refs, objects and config across sessions.
82
+
61
83
  ## LLM call storage
62
84
 
63
85
  `KompjutrLLMCallLog` stores the exact request, response, metrics and error
@@ -0,0 +1,13 @@
1
+ import type { GitCliRunner } from '@kompjutr/do';
2
+ import type { Filesystem } from '@kompjutr/do/fs';
3
+ import type { GrantMounts } from './mounts.js';
4
+ /**
5
+ * Runs Git for a shell confined to a mounted view.
6
+ *
7
+ * Git keeps the real filesystem, because checkout routing and dirty tracking work
8
+ * on real paths. This runner moves the shell's virtual cwd and absolute path
9
+ * operands into real space, refuses what no writable mount shows, and maps the
10
+ * real paths Git prints back to virtual ones.
11
+ */
12
+ export declare function createConfinedGitRunner(git: GitCliRunner, view: Filesystem, mounts: GrantMounts): GitCliRunner;
13
+ //# sourceMappingURL=confined-git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confined-git.d.ts","sourceRoot":"","sources":["../src/confined-git.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,cAAc,CAAA;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,aAAa,CAAA;AAa3D;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,GAAG,YAAY,CAwB9G"}
@@ -0,0 +1,129 @@
1
+ import { posix } from 'node:path';
2
+ /** Commands whose every absolute operand is a pathspec. */
3
+ const PATHSPEC_OPERANDS = new Set(['status', 'ls-files', 'add', 'restore']);
4
+ /** Commands that read pathspecs only after `--`. */
5
+ const PATHSPEC_AFTER_SEPARATOR = new Set(['diff', 'log', 'reset', 'checkout']);
6
+ /** `git clone` options that take their value as the next argument. */
7
+ const CLONE_VALUED_OPTIONS = new Set(['--depth', '--branch', '-b', '--origin', '-o']);
8
+ /** Unquoted paths in Git's messages: after whitespace or a quote, up to the next one. */
9
+ const MESSAGE_PATH = /(?<=^|[\s'"])\/[^\s'"]*/g;
10
+ /**
11
+ * Runs Git for a shell confined to a mounted view.
12
+ *
13
+ * Git keeps the real filesystem, because checkout routing and dirty tracking work
14
+ * on real paths. This runner moves the shell's virtual cwd and absolute path
15
+ * operands into real space, refuses what no writable mount shows, and maps the
16
+ * real paths Git prints back to virtual ones.
17
+ */
18
+ export function createConfinedGitRunner(git, view, mounts) {
19
+ return {
20
+ async runCli(input, options) {
21
+ const virtualCwd = view.realpath(input.cwd ?? '/');
22
+ const cwd = mounts.toReal(virtualCwd);
23
+ if (cwd === undefined)
24
+ return refused(`'${virtualCwd}' is outside the mounted paths`);
25
+ const translated = await translate(git, view, mounts, input.argv, virtualCwd, cwd);
26
+ if (!translated.ok)
27
+ return refused(translated.message);
28
+ const result = await git.runCli({ ...input, argv: translated.argv, cwd: cwd.real }, options);
29
+ const toVirtual = (text) => text.replace(MESSAGE_PATH, (match) => {
30
+ // `path: message` puts a colon after the path.
31
+ const path = match.endsWith(':') ? match.slice(0, -1) : match;
32
+ return `${mounts.toVirtual(path, cwd.mount) ?? path}${match.slice(path.length)}`;
33
+ });
34
+ return {
35
+ ...result,
36
+ stdout: printsRealPaths(input.argv) ? toVirtual(result.stdout) : result.stdout,
37
+ stderr: toVirtual(result.stderr),
38
+ };
39
+ },
40
+ };
41
+ }
42
+ async function translate(git, view, mounts, argv, virtualCwd, cwd) {
43
+ const command = argv[0];
44
+ if (command === 'init' || command === 'clone') {
45
+ const index = command === 'init' ? initDirectoryIndex(argv) : cloneDirectoryIndex(argv);
46
+ const directory = index === undefined ? undefined : argv[index];
47
+ if (index === undefined || directory === undefined) {
48
+ // Both create in or directly under the cwd.
49
+ return cwd.mount.mode === 'rw' ? { ok: true, argv } : { ok: false, message: `'${virtualCwd}' is read-only` };
50
+ }
51
+ const target = writableTarget(view, mounts, posix.resolve(virtualCwd, directory));
52
+ if (target === undefined)
53
+ return { ok: false, message: `'${directory}' is outside the writable mounted paths` };
54
+ const translatedArgv = [...argv];
55
+ translatedArgv[index] = target;
56
+ return { ok: true, argv: translatedArgv };
57
+ }
58
+ const translatedArgv = [...argv];
59
+ for (const index of pathspecIndexes(argv)) {
60
+ const pathspec = argv[index];
61
+ if (pathspec === undefined || !pathspec.startsWith('/'))
62
+ continue;
63
+ const real = mounts.toReal(posix.resolve(pathspec));
64
+ if (real === undefined)
65
+ return { ok: false, message: `'${pathspec}' is outside the mounted paths` };
66
+ translatedArgv[index] = real.real;
67
+ }
68
+ const repository = await git.runCli({ argv: ['rev-parse', '--show-toplevel'], cwd: cwd.real });
69
+ // A cwd outside any repository fails the command itself with Git's own message.
70
+ if (repository.exitCode === 0 && !mounts.isWritable(repository.stdout.trimEnd())) {
71
+ return { ok: false, message: `the repository at '${virtualCwd}' is not inside a writable mounted path` };
72
+ }
73
+ return { ok: true, argv: translatedArgv };
74
+ }
75
+ /**
76
+ * The real path a new repository would take, when a writable mount shows it.
77
+ * The existing part resolves through the view, so a symlink leaving its mount fails here
78
+ * instead of being followed by Git in real space.
79
+ */
80
+ function writableTarget(view, mounts, virtualTarget) {
81
+ let existing = virtualTarget;
82
+ const missing = [];
83
+ while (view.statTarget(existing) === null) {
84
+ if (existing === '/' || view.stat(existing) !== null)
85
+ return undefined;
86
+ missing.unshift(posix.basename(existing));
87
+ existing = posix.dirname(existing);
88
+ }
89
+ const target = mounts.toReal(posix.join(view.realpath(existing), ...missing));
90
+ return target?.mount.mode === 'rw' ? target.real : undefined;
91
+ }
92
+ function initDirectoryIndex(argv) {
93
+ const index = argv.findIndex((argument, position) => position > 0 && !argument.startsWith('-'));
94
+ return index === -1 ? undefined : index;
95
+ }
96
+ function cloneDirectoryIndex(argv) {
97
+ const operands = [];
98
+ for (let index = 1; index < argv.length; index++) {
99
+ const argument = argv[index];
100
+ if (argument === undefined)
101
+ break;
102
+ if (CLONE_VALUED_OPTIONS.has(argument))
103
+ index++;
104
+ else if (!argument.startsWith('-'))
105
+ operands.push(index);
106
+ }
107
+ return operands[1];
108
+ }
109
+ function pathspecIndexes(argv) {
110
+ const command = argv[0] ?? '';
111
+ const separator = argv.indexOf('--', 1);
112
+ const indexes = [];
113
+ for (let index = 1; index < argv.length; index++) {
114
+ if (index === separator)
115
+ continue;
116
+ if (PATHSPEC_OPERANDS.has(command) || (PATHSPEC_AFTER_SEPARATOR.has(command) && separator !== -1 && index > separator)) {
117
+ indexes.push(index);
118
+ }
119
+ }
120
+ return indexes;
121
+ }
122
+ /** Commands whose stdout names a real path rather than repository content. */
123
+ function printsRealPaths(argv) {
124
+ return argv[0] === 'init' || argv[0] === 'clone' || (argv[0] === 'rev-parse' && argv[1] === '--show-toplevel');
125
+ }
126
+ function refused(message) {
127
+ return { stdout: '', stderr: `fatal: ${message}\n`, exitCode: 128, truncated: false };
128
+ }
129
+ //# sourceMappingURL=confined-git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confined-git.js","sourceRoot":"","sources":["../src/confined-git.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * rows rather than files on a disk — inside a Durable Object, or anywhere else
6
6
  * that can hand kompjutr a SQL storage.
7
7
  */
8
- import type { Platform, Scheduler } from '@roj-ai/sdk/platform';
8
+ import type { Platform, Scheduler, ShellConfinement } from '@roj-ai/sdk/platform';
9
9
  import type { Workspace } from '@kompjutr/do';
10
10
  import type { KompjutrLLMCallLogOptions } from './llm-call-log.js';
11
11
  export interface KompjutrPlatformOptions {
@@ -19,8 +19,12 @@ export interface KompjutrPlatformOptions {
19
19
  scheduler?: Scheduler;
20
20
  /** Retention and column ceiling for the LLM call log. */
21
21
  llmCallLog?: KompjutrLLMCallLogOptions;
22
- /** How the workspace itself is isolated. Defaults to `none`. */
23
- shellConfinement?: 'host' | 'none';
22
+ /**
23
+ * How shell commands are confined. `paths` mounts each run's grants as its
24
+ * only filesystem; `host` means the workspace holds one session's files.
25
+ * Defaults to `none`.
26
+ */
27
+ shellConfinement?: ShellConfinement;
24
28
  }
25
29
  export declare function createKompjutrPlatform(workspace: Workspace, options?: KompjutrPlatformOptions): Platform;
26
30
  export { KompjutrEventStore } from './event-store.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAE/D,OAAO,KAAK,EAAO,SAAS,EAAE,MAAM,cAAc,CAAA;AAKlD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA;AAKlE,MAAM,WAAW,uBAAuB;IACvC,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,yBAAyB,CAAA;IACtC,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAClC;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,GAAE,uBAA4B,GAAG,QAAQ,CA2B5G;AAWD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAClD,YAAY,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,YAAY,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AACtD,YAAY,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAEjF,OAAO,KAAK,EAAO,SAAS,EAAE,MAAM,cAAc,CAAA;AAKlD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA;AAKlE,MAAM,WAAW,uBAAuB;IACvC,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,yBAAyB,CAAA;IACtC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;CACnC;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,GAAE,uBAA4B,GAAG,QAAQ,CA2B5G;AAWD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAClD,YAAY,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,YAAY,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AACtD,YAAY,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAG3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD,OAAO,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAiBtD,MAAM,UAAU,sBAAsB,CAAC,SAAoB,EAAE,UAAmC,EAAE;IACjG,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAA;IACvC,2EAA2E;IAC3E,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAEvD,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,CAAC,CAAA;IAEnC,OAAO;QACN,EAAE,EAAE,wBAAwB,CAAC;YAC5B,MAAM,EAAE,SAAS,CAAC,EAAE;YACpB,UAAU,EAAE,SAAS,CAAC,UAAU;SAChC,CAAC;QACF,OAAO,EAAE,8BAA8B,EAAE;QACzC,KAAK,EAAE,yBAAyB,CAAC;YAChC,UAAU,EAAE,SAAS,CAAC,UAAU;YAChC,GAAG;YACH,WAAW,EAAE,OAAO,CAAC,gBAAgB;SACrC,CAAC;QACF,GAAG,EAAE,GAAG,IAAI,uBAAuB,CAAC,GAAG,CAAC;QACxC,2EAA2E;QAC3E,0EAA0E;QAC1E,0EAA0E;QAC1E,UAAU,EAAE,IAAI,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,UAAU,EAAE,IAAI,kBAAkB,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC;QACpE,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,oBAAoB,EAAE;QACtD,MAAM;KACN,CAAA;AACF,CAAC;AAED,mGAAmG;AACnG,SAAS,YAAY,CAAC,SAAoB;IACzC,IAAI,CAAC;QACJ,OAAO,SAAS,CAAC,GAAG,CAAA;IACrB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAA;IACjB,CAAC;AACF,CAAC;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAElD,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD,OAAO,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAG3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD,OAAO,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAqBtD,MAAM,UAAU,sBAAsB,CAAC,SAAoB,EAAE,UAAmC,EAAE;IACjG,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAA;IACvC,2EAA2E;IAC3E,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAEvD,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,CAAC,CAAA;IAEnC,OAAO;QACN,EAAE,EAAE,wBAAwB,CAAC;YAC5B,MAAM,EAAE,SAAS,CAAC,EAAE;YACpB,UAAU,EAAE,SAAS,CAAC,UAAU;SAChC,CAAC;QACF,OAAO,EAAE,8BAA8B,EAAE;QACzC,KAAK,EAAE,yBAAyB,CAAC;YAChC,UAAU,EAAE,SAAS,CAAC,UAAU;YAChC,GAAG;YACH,WAAW,EAAE,OAAO,CAAC,gBAAgB;SACrC,CAAC;QACF,GAAG,EAAE,GAAG,IAAI,uBAAuB,CAAC,GAAG,CAAC;QACxC,2EAA2E;QAC3E,0EAA0E;QAC1E,0EAA0E;QAC1E,UAAU,EAAE,IAAI,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,UAAU,EAAE,IAAI,kBAAkB,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC;QACpE,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,oBAAoB,EAAE;QACtD,MAAM;KACN,CAAA;AACF,CAAC;AAED,mGAAmG;AACnG,SAAS,YAAY,CAAC,SAAoB;IACzC,IAAI,CAAC;QACJ,OAAO,SAAS,CAAC,GAAG,CAAA;IACrB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAA;IACjB,CAAC;AACF,CAAC;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAElD,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD,OAAO,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,22 @@
1
+ import type { Filesystem, MountSpec } from '@kompjutr/do/fs';
2
+ import type { ShellGrant } from '@roj-ai/sdk/platform';
3
+ /** A real workspace path, and the mount that shows it. */
4
+ export interface MountedPath {
5
+ mount: MountSpec;
6
+ real: string;
7
+ }
8
+ /**
9
+ * One run's grants as mounts, plus the prefix translation between the paths a
10
+ * command sees and the workspace's real ones, for consumers that bypass the view.
11
+ */
12
+ export declare class GrantMounts {
13
+ readonly specs: readonly MountSpec[];
14
+ constructor(filesystem: Filesystem, grants: readonly ShellGrant[]);
15
+ /** The real path behind a canonical virtual path, or undefined outside every mount. */
16
+ toReal(virtualPath: string): MountedPath | undefined;
17
+ /** A virtual path showing `realPath`, from `preferred` when it shows it, otherwise from the narrowest source. */
18
+ toVirtual(realPath: string, preferred?: MountSpec): string | undefined;
19
+ /** Whether a writable mount shows `realPath`. */
20
+ isWritable(realPath: string): boolean;
21
+ }
22
+ //# sourceMappingURL=mounts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mounts.d.ts","sourceRoot":"","sources":["../src/mounts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD,0DAA0D;AAC1D,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,SAAS,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACZ;AAED;;;GAGG;AACH,qBAAa,WAAW;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;gBAExB,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,UAAU,EAAE;IASjE,uFAAuF;IACvF,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAQpD,iHAAiH;IACjH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS;IAUtE,iDAAiD;IACjD,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAGrC"}
package/dist/mounts.js ADDED
@@ -0,0 +1,55 @@
1
+ import { posix } from 'node:path';
2
+ /**
3
+ * One run's grants as mounts, plus the prefix translation between the paths a
4
+ * command sees and the workspace's real ones, for consumers that bypass the view.
5
+ */
6
+ export class GrantMounts {
7
+ specs;
8
+ constructor(filesystem, grants) {
9
+ this.specs = grants.map((grant) => ({
10
+ path: posix.resolve(grant.path),
11
+ // A bind mount follows symlinks in its source; the view accepts only the resolved directory.
12
+ source: filesystem.realpath(posix.resolve(grant.source ?? grant.path)),
13
+ mode: grant.mode,
14
+ }));
15
+ }
16
+ /** The real path behind a canonical virtual path, or undefined outside every mount. */
17
+ toReal(virtualPath) {
18
+ for (const mount of this.specs) {
19
+ const rest = suffixUnder(mount.path, virtualPath);
20
+ if (rest !== undefined)
21
+ return { mount, real: joinSuffix(mount.source, rest) };
22
+ }
23
+ return undefined;
24
+ }
25
+ /** A virtual path showing `realPath`, from `preferred` when it shows it, otherwise from the narrowest source. */
26
+ toVirtual(realPath, preferred) {
27
+ const candidates = [...this.specs].sort((left, right) => right.source.length - left.source.length);
28
+ if (preferred !== undefined)
29
+ candidates.unshift(preferred);
30
+ for (const mount of candidates) {
31
+ const rest = suffixUnder(mount.source, realPath);
32
+ if (rest !== undefined)
33
+ return joinSuffix(mount.path, rest);
34
+ }
35
+ return undefined;
36
+ }
37
+ /** Whether a writable mount shows `realPath`. */
38
+ isWritable(realPath) {
39
+ return this.specs.some((mount) => mount.mode === 'rw' && suffixUnder(mount.source, realPath) !== undefined);
40
+ }
41
+ }
42
+ /** `''` when `path` is `root`, `/rest` when below it, undefined otherwise. */
43
+ function suffixUnder(root, path) {
44
+ if (path === root)
45
+ return '';
46
+ if (root === '/')
47
+ return path.startsWith('/') ? path : undefined;
48
+ return path.startsWith(`${root}/`) ? path.slice(root.length) : undefined;
49
+ }
50
+ function joinSuffix(root, suffix) {
51
+ if (suffix === '')
52
+ return root;
53
+ return root === '/' ? suffix : `${root}${suffix}`;
54
+ }
55
+ //# sourceMappingURL=mounts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mounts.js","sourceRoot":"","sources":["../src/mounts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAUjC;;;GAGG;AACH,MAAM,OAAO,WAAW;IACd,KAAK,CAAsB;IAEpC,YAAY,UAAsB,EAAE,MAA6B;QAChE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;YAC/B,6FAA6F;YAC7F,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC;YACtE,IAAI,EAAE,KAAK,CAAC,IAAI;SAChB,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,uFAAuF;IACvF,MAAM,CAAC,WAAmB;QACzB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAChC,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;YACjD,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAA;QAC/E,CAAC;QACD,OAAO,SAAS,CAAA;IACjB,CAAC;IAED,iHAAiH;IACjH,SAAS,CAAC,QAAgB,EAAE,SAAqB;QAChD,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAClG,IAAI,SAAS,KAAK,SAAS;YAAE,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;QAC1D,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;YAChC,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;YAChD,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAC5D,CAAC;QACD,OAAO,SAAS,CAAA;IACjB,CAAC;IAED,iDAAiD;IACjD,UAAU,CAAC,QAAgB;QAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,SAAS,CAAC,CAAA;IAC5G,CAAC;CACD;AAED,8EAA8E;AAC9E,SAAS,WAAW,CAAC,IAAY,EAAE,IAAY;IAC9C,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,EAAE,CAAA;IAC5B,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;IAChE,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACzE,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,MAAc;IAC/C,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAC9B,OAAO,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,MAAM,EAAE,CAAA;AAClD,CAAC"}
package/dist/shell.d.ts CHANGED
@@ -1,11 +1,14 @@
1
- import type { ShellRunner } from '@roj-ai/sdk/platform';
1
+ import type { ShellConfinement, ShellRunner } from '@roj-ai/sdk/platform';
2
2
  import type { Filesystem, Git } from '@kompjutr/do';
3
3
  export interface KompjutrShellRunnerOptions {
4
4
  filesystem: Filesystem;
5
5
  /** Adds the `git` command when the workspace was configured with Git. */
6
6
  git?: Git;
7
- /** `host` is valid only when this workspace contains one session's files. */
8
- confinement?: 'host' | 'none';
7
+ /**
8
+ * `paths` shows each run only its grants; `host` is valid only when this
9
+ * workspace contains one session's files. Defaults to `none`.
10
+ */
11
+ confinement?: ShellConfinement;
9
12
  }
10
13
  export declare function createKompjutrShellRunner(options: KompjutrShellRunnerOptions): ShellRunner;
11
14
  //# sourceMappingURL=shell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAInD,MAAM,WAAW,0BAA0B;IAC1C,UAAU,EAAE,UAAU,CAAA;IACtB,yEAAyE;IACzE,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC7B;AAMD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,WAAW,CA+B1F"}
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAc,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACrF,OAAO,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAOnD,MAAM,WAAW,0BAA0B;IAC1C,UAAU,EAAE,UAAU,CAAA;IACtB,yEAAyE;IACzE,GAAG,CAAC,EAAE,GAAG,CAAA;IACT;;;OAGG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAA;CAC9B;AAaD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,WAAW,CAqC1F"}
package/dist/shell.js CHANGED
@@ -1,21 +1,30 @@
1
+ import { createMountedFilesystem } from '@kompjutr/do/fs';
1
2
  import { createGitCommand } from '@kompjutr/do/git-shell';
2
3
  import { createShell } from '@kompjutr/do/shell';
4
+ import { createConfinedGitRunner } from './confined-git.js';
5
+ import { GrantMounts } from './mounts.js';
3
6
  function quoteShellWord(value) {
4
7
  return `'${value.replaceAll("'", "'\\''")}'`;
5
8
  }
6
9
  export function createKompjutrShellRunner(options) {
7
- const commands = options.git === undefined ? undefined : new Map([['git', createGitCommand(options.git)]]);
10
+ const confinement = options.confinement ?? 'none';
11
+ const unconfined = {
12
+ filesystem: options.filesystem,
13
+ commands: options.git === undefined ? undefined : new Map([['git', createGitCommand(options.git)]]),
14
+ };
8
15
  return {
9
- confinement: options.confinement ?? 'none',
16
+ confinement,
10
17
  supportsTimeout: false,
11
18
  async run(runOptions) {
12
19
  if (!runOptions.cwd.startsWith('/'))
13
20
  throw new Error('kompjutr shell requires an absolute cwd');
14
- if ((runOptions.grants?.length ?? 0) > 0) {
15
- throw new Error('kompjutr shell does not support path grants');
21
+ if (confinement !== 'paths' && (runOptions.grants?.length ?? 0) > 0) {
22
+ throw new Error('kompjutr shell does not support path grants without paths confinement');
16
23
  }
24
+ // Without grants the caller asks for no confinement, as with the Bun sandbox.
25
+ const scope = confinement === 'paths' && runOptions.grants !== undefined ? confinedScope(options, runOptions.grants) : unconfined;
17
26
  // ShellSession caches cwd per instance, even though instances share the default SQL row.
18
- const shell = createShell({ fs: options.filesystem, commands });
27
+ const shell = createShell({ fs: scope.filesystem, commands: scope.commands });
19
28
  const cwdResult = await shell.run(`cd ${quoteShellWord(runOptions.cwd)}`);
20
29
  const result = cwdResult.exitCode === 0
21
30
  ? await shell.run(runOptions.command, {
@@ -33,4 +42,15 @@ export function createKompjutrShellRunner(options) {
33
42
  },
34
43
  };
35
44
  }
45
+ /** A fail-closed view of the run's grants, with Git translated between that view and real space. */
46
+ function confinedScope(options, grants) {
47
+ const mounts = new GrantMounts(options.filesystem, grants);
48
+ const view = createMountedFilesystem(options.filesystem, mounts.specs);
49
+ if (options.git === undefined)
50
+ return { filesystem: view };
51
+ return {
52
+ filesystem: view,
53
+ commands: new Map([['git', createGitCommand(createConfinedGitRunner(options.git, view, mounts))]]),
54
+ };
55
+ }
36
56
  //# sourceMappingURL=shell.js.map
package/dist/shell.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"shell.js","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAUhD,SAAS,cAAc,CAAC,KAAa;IACpC,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAA;AAC7C,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAmC;IAC5E,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAE1G,OAAO;QACN,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,MAAM;QAC1C,eAAe,EAAE,KAAK;QACtB,KAAK,CAAC,GAAG,CAAC,UAAU;YACnB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;YAC/F,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC1C,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;YAC/D,CAAC;YAED,yFAAyF;YACzF,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC/D,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACzE,MAAM,MAAM,GACX,SAAS,CAAC,QAAQ,KAAK,CAAC;gBACvB,CAAC,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE;oBACpC,GAAG,EAAE,UAAU,CAAC,GAAG;oBACnB,KAAK,EAAE,UAAU,CAAC,KAAK;iBACvB,CAAC;gBACH,CAAC,CAAC,SAAS,CAAA;YACb,OAAO;gBACN,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,KAAK;gBACf,SAAS,EAAE,MAAM,CAAC,SAAS;aAC3B,CAAA;QACF,CAAC;KACD,CAAA;AACF,CAAC"}
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/testing/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,IAAI,WAAW,EAAyB,MAAM,YAAY,CAAA;AAC3E,OAAO,KAAK,EAAE,wBAAwB,EAAiB,cAAc,EAAE,MAAM,cAAc,CAAA;AAE3F,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,MAAM,CAAA;AAuEtC,qBAAa,gBAAiB,YAAW,wBAAwB;;IAChE,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAA;IAE5B,8EAA8E;IAC9E,cAAc,SAAI;IAClB,6FAA6F;IAC7F,QAAQ,SAAI;gBAIA,IAAI,SAAa;IAkB7B,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC;IAgBvC,aAAa,IAAI,IAAI;IAKrB,KAAK,IAAI,IAAI;CAGb"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/testing/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,IAAI,WAAW,EAAyB,MAAM,YAAY,CAAA;AAC3E,OAAO,KAAK,EAAE,wBAAwB,EAAiB,cAAc,EAAE,MAAM,cAAc,CAAA;AAE3F,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,MAAM,CAAA;AAuEtC,qBAAa,gBAAiB,YAAW,wBAAwB;;IAChE,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAA;IAE5B,8EAA8E;IAC9E,cAAc,SAAI;IAClB,6FAA6F;IAC7F,QAAQ,SAAI;gBAIA,IAAI,SAAa;IAmB7B,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC;IAgBvC,aAAa,IAAI,IAAI;IAKrB,KAAK,IAAI,IAAI;CAGb"}
@@ -95,7 +95,8 @@ export class BunSqliteStorage {
95
95
  throw new Error(`too many SQL variables: ${bindings.length} exceeds the ${MAX_BIND_PARAMETERS} a Durable Object binds`);
96
96
  }
97
97
  this.statementCount++;
98
- const statement = this.db.query(query);
98
+ // Not `query()`: it caches by SQL text, and a nested cursor over the same SQL would share one statement.
99
+ const statement = this.db.prepare(query);
99
100
  const rows = statement.iterate(...bindings.map(toBinding));
100
101
  return new Cursor(rows, () => {
101
102
  this.rowCount++;
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/testing/storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/testing/storage.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roj-ai/kompjutr-platform",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -43,8 +43,8 @@
43
43
  "type-check": "tsc --noEmit"
44
44
  },
45
45
  "dependencies": {
46
- "@kompjutr/do": "0.1.2",
47
- "@roj-ai/sdk": "^0.2.11"
46
+ "@kompjutr/do": "0.1.3",
47
+ "@roj-ai/sdk": "^0.2.12"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/bun": "^1.3.3",
@@ -0,0 +1,144 @@
1
+ import { posix } from 'node:path'
2
+ import type { GitCliResult, GitCliRunner } from '@kompjutr/do'
3
+ import type { Filesystem } from '@kompjutr/do/fs'
4
+ import type { GrantMounts, MountedPath } from './mounts.js'
5
+
6
+ /** Commands whose every absolute operand is a pathspec. */
7
+ const PATHSPEC_OPERANDS = new Set(['status', 'ls-files', 'add', 'restore'])
8
+ /** Commands that read pathspecs only after `--`. */
9
+ const PATHSPEC_AFTER_SEPARATOR = new Set(['diff', 'log', 'reset', 'checkout'])
10
+ /** `git clone` options that take their value as the next argument. */
11
+ const CLONE_VALUED_OPTIONS = new Set(['--depth', '--branch', '-b', '--origin', '-o'])
12
+ /** Unquoted paths in Git's messages: after whitespace or a quote, up to the next one. */
13
+ const MESSAGE_PATH = /(?<=^|[\s'"])\/[^\s'"]*/g
14
+
15
+ type Translated = { ok: true; argv: readonly string[] } | { ok: false; message: string }
16
+
17
+ /**
18
+ * Runs Git for a shell confined to a mounted view.
19
+ *
20
+ * Git keeps the real filesystem, because checkout routing and dirty tracking work
21
+ * on real paths. This runner moves the shell's virtual cwd and absolute path
22
+ * operands into real space, refuses what no writable mount shows, and maps the
23
+ * real paths Git prints back to virtual ones.
24
+ */
25
+ export function createConfinedGitRunner(git: GitCliRunner, view: Filesystem, mounts: GrantMounts): GitCliRunner {
26
+ return {
27
+ async runCli(input, options) {
28
+ const virtualCwd = view.realpath(input.cwd ?? '/')
29
+ const cwd = mounts.toReal(virtualCwd)
30
+ if (cwd === undefined) return refused(`'${virtualCwd}' is outside the mounted paths`)
31
+
32
+ const translated = await translate(git, view, mounts, input.argv, virtualCwd, cwd)
33
+ if (!translated.ok) return refused(translated.message)
34
+
35
+ const result = await git.runCli({ ...input, argv: translated.argv, cwd: cwd.real }, options)
36
+ const toVirtual = (text: string): string =>
37
+ text.replace(MESSAGE_PATH, (match) => {
38
+ // `path: message` puts a colon after the path.
39
+ const path = match.endsWith(':') ? match.slice(0, -1) : match
40
+ return `${mounts.toVirtual(path, cwd.mount) ?? path}${match.slice(path.length)}`
41
+ })
42
+ return {
43
+ ...result,
44
+ stdout: printsRealPaths(input.argv) ? toVirtual(result.stdout) : result.stdout,
45
+ stderr: toVirtual(result.stderr),
46
+ }
47
+ },
48
+ }
49
+ }
50
+
51
+ async function translate(
52
+ git: GitCliRunner,
53
+ view: Filesystem,
54
+ mounts: GrantMounts,
55
+ argv: readonly string[],
56
+ virtualCwd: string,
57
+ cwd: MountedPath,
58
+ ): Promise<Translated> {
59
+ const command = argv[0]
60
+ if (command === 'init' || command === 'clone') {
61
+ const index = command === 'init' ? initDirectoryIndex(argv) : cloneDirectoryIndex(argv)
62
+ const directory = index === undefined ? undefined : argv[index]
63
+ if (index === undefined || directory === undefined) {
64
+ // Both create in or directly under the cwd.
65
+ return cwd.mount.mode === 'rw' ? { ok: true, argv } : { ok: false, message: `'${virtualCwd}' is read-only` }
66
+ }
67
+ const target = writableTarget(view, mounts, posix.resolve(virtualCwd, directory))
68
+ if (target === undefined) return { ok: false, message: `'${directory}' is outside the writable mounted paths` }
69
+ const translatedArgv = [...argv]
70
+ translatedArgv[index] = target
71
+ return { ok: true, argv: translatedArgv }
72
+ }
73
+
74
+ const translatedArgv = [...argv]
75
+ for (const index of pathspecIndexes(argv)) {
76
+ const pathspec = argv[index]
77
+ if (pathspec === undefined || !pathspec.startsWith('/')) continue
78
+ const real = mounts.toReal(posix.resolve(pathspec))
79
+ if (real === undefined) return { ok: false, message: `'${pathspec}' is outside the mounted paths` }
80
+ translatedArgv[index] = real.real
81
+ }
82
+
83
+ const repository = await git.runCli({ argv: ['rev-parse', '--show-toplevel'], cwd: cwd.real })
84
+ // A cwd outside any repository fails the command itself with Git's own message.
85
+ if (repository.exitCode === 0 && !mounts.isWritable(repository.stdout.trimEnd())) {
86
+ return { ok: false, message: `the repository at '${virtualCwd}' is not inside a writable mounted path` }
87
+ }
88
+ return { ok: true, argv: translatedArgv }
89
+ }
90
+
91
+ /**
92
+ * The real path a new repository would take, when a writable mount shows it.
93
+ * The existing part resolves through the view, so a symlink leaving its mount fails here
94
+ * instead of being followed by Git in real space.
95
+ */
96
+ function writableTarget(view: Filesystem, mounts: GrantMounts, virtualTarget: string): string | undefined {
97
+ let existing = virtualTarget
98
+ const missing: string[] = []
99
+ while (view.statTarget(existing) === null) {
100
+ if (existing === '/' || view.stat(existing) !== null) return undefined
101
+ missing.unshift(posix.basename(existing))
102
+ existing = posix.dirname(existing)
103
+ }
104
+ const target = mounts.toReal(posix.join(view.realpath(existing), ...missing))
105
+ return target?.mount.mode === 'rw' ? target.real : undefined
106
+ }
107
+
108
+ function initDirectoryIndex(argv: readonly string[]): number | undefined {
109
+ const index = argv.findIndex((argument, position) => position > 0 && !argument.startsWith('-'))
110
+ return index === -1 ? undefined : index
111
+ }
112
+
113
+ function cloneDirectoryIndex(argv: readonly string[]): number | undefined {
114
+ const operands: number[] = []
115
+ for (let index = 1; index < argv.length; index++) {
116
+ const argument = argv[index]
117
+ if (argument === undefined) break
118
+ if (CLONE_VALUED_OPTIONS.has(argument)) index++
119
+ else if (!argument.startsWith('-')) operands.push(index)
120
+ }
121
+ return operands[1]
122
+ }
123
+
124
+ function pathspecIndexes(argv: readonly string[]): number[] {
125
+ const command = argv[0] ?? ''
126
+ const separator = argv.indexOf('--', 1)
127
+ const indexes: number[] = []
128
+ for (let index = 1; index < argv.length; index++) {
129
+ if (index === separator) continue
130
+ if (PATHSPEC_OPERANDS.has(command) || (PATHSPEC_AFTER_SEPARATOR.has(command) && separator !== -1 && index > separator)) {
131
+ indexes.push(index)
132
+ }
133
+ }
134
+ return indexes
135
+ }
136
+
137
+ /** Commands whose stdout names a real path rather than repository content. */
138
+ function printsRealPaths(argv: readonly string[]): boolean {
139
+ return argv[0] === 'init' || argv[0] === 'clone' || (argv[0] === 'rev-parse' && argv[1] === '--show-toplevel')
140
+ }
141
+
142
+ function refused(message: string): GitCliResult {
143
+ return { stdout: '', stderr: `fatal: ${message}\n`, exitCode: 128, truncated: false }
144
+ }
package/src/index.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * that can hand kompjutr a SQL storage.
7
7
  */
8
8
 
9
- import type { Platform, Scheduler } from '@roj-ai/sdk/platform'
9
+ import type { Platform, Scheduler, ShellConfinement } from '@roj-ai/sdk/platform'
10
10
  import { createTimerScheduler } from '@roj-ai/sdk/platform'
11
11
  import type { Git, Workspace } from '@kompjutr/do'
12
12
  import { KompjutrEventStore } from './event-store.js'
@@ -29,8 +29,12 @@ export interface KompjutrPlatformOptions {
29
29
  scheduler?: Scheduler
30
30
  /** Retention and column ceiling for the LLM call log. */
31
31
  llmCallLog?: KompjutrLLMCallLogOptions
32
- /** How the workspace itself is isolated. Defaults to `none`. */
33
- shellConfinement?: 'host' | 'none'
32
+ /**
33
+ * How shell commands are confined. `paths` mounts each run's grants as its
34
+ * only filesystem; `host` means the workspace holds one session's files.
35
+ * Defaults to `none`.
36
+ */
37
+ shellConfinement?: ShellConfinement
34
38
  }
35
39
 
36
40
  export function createKompjutrPlatform(workspace: Workspace, options: KompjutrPlatformOptions = {}): Platform {
package/src/mounts.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { posix } from 'node:path'
2
+ import type { Filesystem, MountSpec } from '@kompjutr/do/fs'
3
+ import type { ShellGrant } from '@roj-ai/sdk/platform'
4
+
5
+ /** A real workspace path, and the mount that shows it. */
6
+ export interface MountedPath {
7
+ mount: MountSpec
8
+ real: string
9
+ }
10
+
11
+ /**
12
+ * One run's grants as mounts, plus the prefix translation between the paths a
13
+ * command sees and the workspace's real ones, for consumers that bypass the view.
14
+ */
15
+ export class GrantMounts {
16
+ readonly specs: readonly MountSpec[]
17
+
18
+ constructor(filesystem: Filesystem, grants: readonly ShellGrant[]) {
19
+ this.specs = grants.map((grant) => ({
20
+ path: posix.resolve(grant.path),
21
+ // A bind mount follows symlinks in its source; the view accepts only the resolved directory.
22
+ source: filesystem.realpath(posix.resolve(grant.source ?? grant.path)),
23
+ mode: grant.mode,
24
+ }))
25
+ }
26
+
27
+ /** The real path behind a canonical virtual path, or undefined outside every mount. */
28
+ toReal(virtualPath: string): MountedPath | undefined {
29
+ for (const mount of this.specs) {
30
+ const rest = suffixUnder(mount.path, virtualPath)
31
+ if (rest !== undefined) return { mount, real: joinSuffix(mount.source, rest) }
32
+ }
33
+ return undefined
34
+ }
35
+
36
+ /** A virtual path showing `realPath`, from `preferred` when it shows it, otherwise from the narrowest source. */
37
+ toVirtual(realPath: string, preferred?: MountSpec): string | undefined {
38
+ const candidates = [...this.specs].sort((left, right) => right.source.length - left.source.length)
39
+ if (preferred !== undefined) candidates.unshift(preferred)
40
+ for (const mount of candidates) {
41
+ const rest = suffixUnder(mount.source, realPath)
42
+ if (rest !== undefined) return joinSuffix(mount.path, rest)
43
+ }
44
+ return undefined
45
+ }
46
+
47
+ /** Whether a writable mount shows `realPath`. */
48
+ isWritable(realPath: string): boolean {
49
+ return this.specs.some((mount) => mount.mode === 'rw' && suffixUnder(mount.source, realPath) !== undefined)
50
+ }
51
+ }
52
+
53
+ /** `''` when `path` is `root`, `/rest` when below it, undefined otherwise. */
54
+ function suffixUnder(root: string, path: string): string | undefined {
55
+ if (path === root) return ''
56
+ if (root === '/') return path.startsWith('/') ? path : undefined
57
+ return path.startsWith(`${root}/`) ? path.slice(root.length) : undefined
58
+ }
59
+
60
+ function joinSuffix(root: string, suffix: string): string {
61
+ if (suffix === '') return root
62
+ return root === '/' ? suffix : `${root}${suffix}`
63
+ }
package/src/shell.ts CHANGED
@@ -1,14 +1,27 @@
1
- import type { ShellRunner } from '@roj-ai/sdk/platform'
1
+ import type { ShellConfinement, ShellGrant, ShellRunner } from '@roj-ai/sdk/platform'
2
2
  import type { Filesystem, Git } from '@kompjutr/do'
3
+ import { createMountedFilesystem } from '@kompjutr/do/fs'
3
4
  import { createGitCommand } from '@kompjutr/do/git-shell'
4
5
  import { createShell } from '@kompjutr/do/shell'
6
+ import { createConfinedGitRunner } from './confined-git.js'
7
+ import { GrantMounts } from './mounts.js'
5
8
 
6
9
  export interface KompjutrShellRunnerOptions {
7
10
  filesystem: Filesystem
8
11
  /** Adds the `git` command when the workspace was configured with Git. */
9
12
  git?: Git
10
- /** `host` is valid only when this workspace contains one session's files. */
11
- confinement?: 'host' | 'none'
13
+ /**
14
+ * `paths` shows each run only its grants; `host` is valid only when this
15
+ * workspace contains one session's files. Defaults to `none`.
16
+ */
17
+ confinement?: ShellConfinement
18
+ }
19
+
20
+ type ShellCommands = NonNullable<Parameters<typeof createShell>[0]['commands']>
21
+
22
+ interface ShellScope {
23
+ filesystem: Filesystem
24
+ commands?: ShellCommands
12
25
  }
13
26
 
14
27
  function quoteShellWord(value: string): string {
@@ -16,19 +29,25 @@ function quoteShellWord(value: string): string {
16
29
  }
17
30
 
18
31
  export function createKompjutrShellRunner(options: KompjutrShellRunnerOptions): ShellRunner {
19
- const commands = options.git === undefined ? undefined : new Map([['git', createGitCommand(options.git)]])
32
+ const confinement = options.confinement ?? 'none'
33
+ const unconfined: ShellScope = {
34
+ filesystem: options.filesystem,
35
+ commands: options.git === undefined ? undefined : new Map([['git', createGitCommand(options.git)]]),
36
+ }
20
37
 
21
38
  return {
22
- confinement: options.confinement ?? 'none',
39
+ confinement,
23
40
  supportsTimeout: false,
24
41
  async run(runOptions) {
25
42
  if (!runOptions.cwd.startsWith('/')) throw new Error('kompjutr shell requires an absolute cwd')
26
- if ((runOptions.grants?.length ?? 0) > 0) {
27
- throw new Error('kompjutr shell does not support path grants')
43
+ if (confinement !== 'paths' && (runOptions.grants?.length ?? 0) > 0) {
44
+ throw new Error('kompjutr shell does not support path grants without paths confinement')
28
45
  }
29
46
 
47
+ // Without grants the caller asks for no confinement, as with the Bun sandbox.
48
+ const scope = confinement === 'paths' && runOptions.grants !== undefined ? confinedScope(options, runOptions.grants) : unconfined
30
49
  // ShellSession caches cwd per instance, even though instances share the default SQL row.
31
- const shell = createShell({ fs: options.filesystem, commands })
50
+ const shell = createShell({ fs: scope.filesystem, commands: scope.commands })
32
51
  const cwdResult = await shell.run(`cd ${quoteShellWord(runOptions.cwd)}`)
33
52
  const result =
34
53
  cwdResult.exitCode === 0
@@ -47,3 +66,14 @@ export function createKompjutrShellRunner(options: KompjutrShellRunnerOptions):
47
66
  },
48
67
  }
49
68
  }
69
+
70
+ /** A fail-closed view of the run's grants, with Git translated between that view and real space. */
71
+ function confinedScope(options: KompjutrShellRunnerOptions, grants: readonly ShellGrant[]): ShellScope {
72
+ const mounts = new GrantMounts(options.filesystem, grants)
73
+ const view = createMountedFilesystem(options.filesystem, mounts.specs)
74
+ if (options.git === undefined) return { filesystem: view }
75
+ return {
76
+ filesystem: view,
77
+ commands: new Map([['git', createGitCommand(createConfinedGitRunner(options.git, view, mounts))]]),
78
+ }
79
+ }
@@ -105,7 +105,8 @@ export class BunSqliteStorage implements DurableObjectStorageLike {
105
105
  throw new Error(`too many SQL variables: ${bindings.length} exceeds the ${MAX_BIND_PARAMETERS} a Durable Object binds`)
106
106
  }
107
107
  this.statementCount++
108
- const statement = this.db.query<Row, SQLQueryBindings[]>(query)
108
+ // Not `query()`: it caches by SQL text, and a nested cursor over the same SQL would share one statement.
109
+ const statement = this.db.prepare<Row, SQLQueryBindings[]>(query)
109
110
  const rows = statement.iterate(...bindings.map(toBinding))
110
111
  return new Cursor<Row>(rows, () => {
111
112
  this.rowCount++