@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 +31 -9
- package/dist/confined-git.d.ts +13 -0
- package/dist/confined-git.d.ts.map +1 -0
- package/dist/confined-git.js +129 -0
- package/dist/confined-git.js.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mounts.d.ts +22 -0
- package/dist/mounts.d.ts.map +1 -0
- package/dist/mounts.js +55 -0
- package/dist/mounts.js.map +1 -0
- package/dist/shell.d.ts +6 -3
- package/dist/shell.d.ts.map +1 -1
- package/dist/shell.js +25 -5
- package/dist/shell.js.map +1 -1
- package/dist/testing/storage.d.ts.map +1 -1
- package/dist/testing/storage.js +2 -1
- package/dist/testing/storage.js.map +1 -1
- package/package.json +3 -3
- package/src/confined-git.ts +144 -0
- package/src/index.ts +7 -3
- package/src/mounts.ts +63 -0
- package/src/shell.ts +38 -8
- package/src/testing/storage.ts +2 -1
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.
|
|
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.
|
|
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.
|
|
51
|
-
|
|
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
|
-
/**
|
|
23
|
-
|
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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"}
|
package/dist/mounts.d.ts
ADDED
|
@@ -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
|
-
/**
|
|
8
|
-
|
|
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
|
package/dist/shell.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
|
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:
|
|
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;
|
|
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;
|
|
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"}
|
package/dist/testing/storage.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
47
|
-
"@roj-ai/sdk": "^0.2.
|
|
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
|
-
/**
|
|
33
|
-
|
|
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
|
-
/**
|
|
11
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
+
}
|
package/src/testing/storage.ts
CHANGED
|
@@ -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
|
-
|
|
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++
|