@code-yeongyu/senpi-codemode 2026.10.27-2 → 2026.10.30-1
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/CHANGELOG.md +34 -0
- package/package.json +4 -4
- package/src/bridge/http-server.ts +18 -5
- package/src/bridge/permission-check.ts +76 -0
- package/src/bridge/protocol.ts +7 -0
- package/src/kernels/jl/kernel.ts +3 -0
- package/src/kernels/js/context-manager.ts +26 -8
- package/src/kernels/js/inline-worker-entry.js +1 -23
- package/src/kernels/js/inline-worker.ts +7 -2
- package/src/kernels/js/interpose-bun.js +220 -0
- package/src/kernels/js/interpose-capabilities.js +29 -0
- package/src/kernels/js/interpose-code.js +8 -0
- package/src/kernels/js/interpose-fs.js +124 -0
- package/src/kernels/js/interpose-modules.js +107 -0
- package/src/kernels/js/interpose-paths.js +80 -0
- package/src/kernels/js/interpose-process.js +97 -0
- package/src/kernels/js/interpose-resources.js +59 -0
- package/src/kernels/js/interpose-sqlite.js +104 -0
- package/src/kernels/js/interpose.js +86 -0
- package/src/kernels/js/kernel-contract.ts +31 -0
- package/src/kernels/js/kernel-tools-pump.js +4 -2
- package/src/kernels/js/local-module-loader.ts +9 -4
- package/src/kernels/js/permission-authority.d.mts +10 -0
- package/src/kernels/js/permission-authority.mjs +11 -0
- package/src/kernels/js/permission-core.md +114 -0
- package/src/kernels/js/permission-host.ts +388 -0
- package/src/kernels/js/permission-runtime.js +205 -0
- package/src/kernels/js/permission-sessions.ts +46 -0
- package/src/kernels/js/permission-slots.d.mts +51 -0
- package/src/kernels/js/permission-slots.mjs +133 -0
- package/src/kernels/js/permission-worker-entry.js +1 -0
- package/src/kernels/js/permission-worker-frames.ts +55 -0
- package/src/kernels/js/process-tree.js +9 -1
- package/src/kernels/js/rewrite-imports.ts +44 -0
- package/src/kernels/js/windows-tree-kill.js +10 -2
- package/src/kernels/js/worker-core.d.ts +2 -0
- package/src/kernels/js/worker-core.js +5 -2
- package/src/kernels/js/worker-cwd.js +14 -8
- package/src/kernels/js/worker-entry.js +1 -23
- package/src/kernels/js/worker-file-helpers.js +58 -0
- package/src/kernels/js/worker-host.ts +24 -6
- package/src/kernels/js/worker-package-resolve.js +10 -2
- package/src/kernels/js/worker-runtime.js +5 -65
- package/src/kernels/js/worker-slot.ts +5 -1
- package/src/kernels/js/worker-startup.ts +7 -5
- package/src/kernels/js/worker-webview.js +17 -16
- package/src/kernels/py/interpose.py +765 -0
- package/src/kernels/py/kernel-contract.ts +4 -0
- package/src/kernels/py/kernel-tools-host.ts +5 -1
- package/src/kernels/py/kernel.ts +45 -5
- package/src/kernels/py/path_identity.py +162 -0
- package/src/kernels/py/path_identity_posix.py +74 -0
- package/src/kernels/py/path_identity_windows.py +93 -0
- package/src/kernels/py/permission-core.md +103 -0
- package/src/kernels/py/permission-host.ts +198 -0
- package/src/kernels/py/permission_bridge.py +145 -0
- package/src/kernels/py/prelude.py +40 -5
- package/src/kernels/py/transport.ts +15 -8
- package/src/kernels/rb/kernel.ts +3 -0
- package/src/kernels/shared/invocation-authority.ts +59 -0
- package/src/kernels/shared/kernel-tools-pump.ts +22 -1
- package/src/kernels/shared/path-identity.d.mts +42 -0
- package/src/kernels/shared/path-identity.mjs +149 -0
- package/src/kernels/shared/subprocess-contract.ts +2 -0
- package/src/kernels/shared/subprocess-kernel.ts +28 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const PATH_IDENTITY_VERSION: number;
|
|
2
|
+
|
|
3
|
+
export interface PathIdentityStats {
|
|
4
|
+
readonly dev: bigint;
|
|
5
|
+
readonly ino: bigint;
|
|
6
|
+
isSymbolicLink(): boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PathIdentityPrimitives {
|
|
10
|
+
readonly realpathNative: (value: string) => string;
|
|
11
|
+
readonly lstat: (value: string, options?: { bigint: true }) => PathIdentityStats;
|
|
12
|
+
readonly readlink: (value: string) => string;
|
|
13
|
+
readonly pathApi: {
|
|
14
|
+
readonly join: (...parts: string[]) => string;
|
|
15
|
+
readonly dirname: (value: string) => string;
|
|
16
|
+
readonly basename: (value: string) => string;
|
|
17
|
+
readonly isAbsolute: (value: string) => boolean;
|
|
18
|
+
readonly sep: string;
|
|
19
|
+
readonly win32Resolve: (...parts: string[]) => string;
|
|
20
|
+
};
|
|
21
|
+
readonly platform: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CanonicalPath {
|
|
25
|
+
readonly path: string;
|
|
26
|
+
readonly foldCase: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CanonicalRule {
|
|
30
|
+
readonly prefix: string | undefined;
|
|
31
|
+
readonly foldCase: boolean;
|
|
32
|
+
readonly suffix: string;
|
|
33
|
+
readonly wildcardOnly: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface PathIdentity {
|
|
37
|
+
canonical(executed: string, cwd?: string): CanonicalPath;
|
|
38
|
+
canonicalRule(pattern: string, cwd?: string): CanonicalRule | undefined;
|
|
39
|
+
volumeFoldCase(existingPath: string): boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createPathIdentity(primitives: PathIdentityPrimitives): PathIdentity;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Path identity for permission decisions: one canonical form per file, computed from the native
|
|
2
|
+
// realpath of the deepest existing ancestor plus the not-yet-existing tail, dangling links followed.
|
|
3
|
+
// Plain JS with injected primitives: the kernel thread captures its originals before the cwd
|
|
4
|
+
// patches replace `path.resolve` on the shared builtin, so nothing here may reach a live builtin.
|
|
5
|
+
// The host side keeps a TypeScript twin of this algorithm; both carry PATH_IDENTITY_VERSION and a
|
|
6
|
+
// shared table test pins them to the same answers. Bump the version on any change to the climb,
|
|
7
|
+
// the fold mapping or the normalization rule: a host that sees another version ignores the kernel
|
|
8
|
+
// value and resolves the executed path itself.
|
|
9
|
+
|
|
10
|
+
export const PATH_IDENTITY_VERSION = 1;
|
|
11
|
+
|
|
12
|
+
const MISSING = new Set(["ENOENT", "ENOTDIR"]);
|
|
13
|
+
const MAX_DANGLING_HOPS = 40;
|
|
14
|
+
|
|
15
|
+
function failure(code) {
|
|
16
|
+
return Object.assign(new Error(code), { code });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Strip trailing dots and spaces from every component, as Win32 path normalization does. */
|
|
20
|
+
function stripWin32Components(value, sep) {
|
|
21
|
+
return value
|
|
22
|
+
.split(sep)
|
|
23
|
+
.map((part, index) => (index === 0 ? part : part.replace(/[. ]+$/u, "")))
|
|
24
|
+
.join(sep);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createPathIdentity({ realpathNative, lstat, readlink, pathApi, platform }) {
|
|
28
|
+
const { join, dirname, basename, isAbsolute, sep, win32Resolve } = pathApi;
|
|
29
|
+
const normalizeForm = (value) => (platform === "darwin" ? value.normalize("NFC") : value);
|
|
30
|
+
const foldDefault = platform === "darwin" || platform === "win32";
|
|
31
|
+
// Per-volume answer to "does this volume treat case variants as one name", keyed by device.
|
|
32
|
+
const foldByDevice = new Map();
|
|
33
|
+
|
|
34
|
+
const anchor = (value, cwd) => {
|
|
35
|
+
if (platform === "win32") {
|
|
36
|
+
return stripWin32Components(win32Resolve(cwd === undefined ? value : cwd, cwd === undefined ? "" : value), sep);
|
|
37
|
+
}
|
|
38
|
+
if (isAbsolute(value)) return value;
|
|
39
|
+
if (cwd === undefined) throw failure("ERELATIVE");
|
|
40
|
+
return cwd + sep + value;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** The deepest existing ancestor's native realpath, dangling links followed, plus the missing tail. */
|
|
44
|
+
const climb = (base) => {
|
|
45
|
+
let existing = base;
|
|
46
|
+
const missing = [];
|
|
47
|
+
let hops = 0;
|
|
48
|
+
let followedDangling = false;
|
|
49
|
+
for (;;) {
|
|
50
|
+
let real;
|
|
51
|
+
try {
|
|
52
|
+
real = realpathNative(existing);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const code = error?.code;
|
|
55
|
+
if (!MISSING.has(code)) throw failure(code ?? "EUNKNOWN");
|
|
56
|
+
let info;
|
|
57
|
+
try {
|
|
58
|
+
info = lstat(existing);
|
|
59
|
+
} catch (inner) {
|
|
60
|
+
const innerCode = inner?.code;
|
|
61
|
+
if (!MISSING.has(innerCode)) throw failure(innerCode ?? "EUNKNOWN");
|
|
62
|
+
info = undefined;
|
|
63
|
+
}
|
|
64
|
+
if (info?.isSymbolicLink()) {
|
|
65
|
+
hops += 1;
|
|
66
|
+
if (hops > MAX_DANGLING_HOPS) throw failure("ELOOP");
|
|
67
|
+
const target = readlink(existing);
|
|
68
|
+
existing = isAbsolute(target) ? target : join(dirname(existing), target);
|
|
69
|
+
followedDangling = true;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (followedDangling && missing.length > 0) throw failure("EDANGLING");
|
|
73
|
+
missing.unshift(basename(existing));
|
|
74
|
+
const parent = dirname(existing);
|
|
75
|
+
if (parent === existing) throw failure(code);
|
|
76
|
+
existing = parent;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
return { real, path: missing.length ? join(real, ...missing) : real };
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const volumeFoldCase = (existingPath) => {
|
|
84
|
+
let device;
|
|
85
|
+
try {
|
|
86
|
+
device = lstat(existingPath, { bigint: true }).dev;
|
|
87
|
+
} catch {
|
|
88
|
+
return foldDefault;
|
|
89
|
+
}
|
|
90
|
+
const cached = foldByDevice.get(device);
|
|
91
|
+
if (cached !== undefined) return cached;
|
|
92
|
+
const answer = probeFoldCase(existingPath, device);
|
|
93
|
+
foldByDevice.set(device, answer);
|
|
94
|
+
return answer;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const probeFoldCase = (existingPath, device) => {
|
|
98
|
+
let probe = existingPath;
|
|
99
|
+
for (;;) {
|
|
100
|
+
const name = basename(probe);
|
|
101
|
+
const index = [...name].findIndex((char) => char.toLowerCase() !== char.toUpperCase());
|
|
102
|
+
if (index >= 0) {
|
|
103
|
+
const chars = [...name];
|
|
104
|
+
const char = chars[index];
|
|
105
|
+
chars[index] = char === char.toLowerCase() ? char.toUpperCase() : char.toLowerCase();
|
|
106
|
+
const swapped = join(dirname(probe), chars.join(""));
|
|
107
|
+
try {
|
|
108
|
+
const original = lstat(probe, { bigint: true });
|
|
109
|
+
const variant = lstat(swapped, { bigint: true });
|
|
110
|
+
return original.dev === variant.dev && original.ino === variant.ino;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const parent = dirname(probe);
|
|
116
|
+
if (parent === probe) return foldDefault;
|
|
117
|
+
probe = parent;
|
|
118
|
+
if (!sameDevice(probe, device)) return foldDefault;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const sameDevice = (value, device) => {
|
|
123
|
+
try {
|
|
124
|
+
return lstat(value, { bigint: true }).dev === device;
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const canonical = (executed, cwd) => {
|
|
131
|
+
const { real, path } = climb(anchor(executed, cwd));
|
|
132
|
+
return { path: normalizeForm(path), foldCase: volumeFoldCase(real) };
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const canonicalRule = (pattern, cwd) => {
|
|
136
|
+
if (pattern === "*") return undefined;
|
|
137
|
+
const wildcard = pattern.search(/[*?]/u);
|
|
138
|
+
if (wildcard < 0) {
|
|
139
|
+
const { path, foldCase } = canonical(pattern, cwd);
|
|
140
|
+
return { prefix: path, foldCase, suffix: "", wildcardOnly: false };
|
|
141
|
+
}
|
|
142
|
+
const boundary = Math.max(pattern.lastIndexOf("/", wildcard), pattern.lastIndexOf(sep, wildcard));
|
|
143
|
+
if (boundary < 0) return { prefix: undefined, foldCase: foldDefault, suffix: normalizeForm(pattern), wildcardOnly: true };
|
|
144
|
+
const { path, foldCase } = canonical(pattern.slice(0, boundary) || sep, cwd);
|
|
145
|
+
return { prefix: path, foldCase, suffix: normalizeForm(pattern.slice(boundary)), wildcardOnly: false };
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return { canonical, canonicalRule, volumeFoldCase };
|
|
149
|
+
}
|
|
@@ -6,6 +6,7 @@ import type { KernelLifecycle } from "./kernel-death.ts";
|
|
|
6
6
|
import type { FootprintReader } from "./kernel-memory-host.ts";
|
|
7
7
|
import type { SubprocessSpawn } from "./subprocess-process.ts";
|
|
8
8
|
import type { SubprocessStartupOptions } from "./subprocess-startup.ts";
|
|
9
|
+
import type { InvocationPermissions } from "./invocation-authority.ts";
|
|
9
10
|
|
|
10
11
|
export type KernelRunInput = EvalKernelRunInput;
|
|
11
12
|
|
|
@@ -19,6 +20,7 @@ export interface SubprocessKernelMemory {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export interface SubprocessKernelOptions extends KernelLifecycle {
|
|
23
|
+
readonly permissions?: InvocationPermissions;
|
|
22
24
|
readonly command: string;
|
|
23
25
|
readonly args: readonly string[];
|
|
24
26
|
readonly cwd?: string;
|
|
@@ -70,6 +70,8 @@ export class SubprocessKernel {
|
|
|
70
70
|
|
|
71
71
|
run(input: KernelRunInput): Promise<KernelResult> {
|
|
72
72
|
if (this.closed) throw new KernelClosedError();
|
|
73
|
+
const refusal = this.permissionRefusal(input.cellId);
|
|
74
|
+
if (refusal) return Promise.resolve(refusal);
|
|
73
75
|
const run = this.runs.enqueue(input);
|
|
74
76
|
this.pumpRuns();
|
|
75
77
|
return run;
|
|
@@ -169,6 +171,13 @@ export class SubprocessKernel {
|
|
|
169
171
|
if (this.closed || this.runs.active || !process || process.isRetiring || !this.processReady) return;
|
|
170
172
|
const run = this.runs.startNext(performance.now());
|
|
171
173
|
if (!run) return;
|
|
174
|
+
const refusal = this.permissionRefusal(run.input.cellId);
|
|
175
|
+
if (refusal) {
|
|
176
|
+
this.runs.releaseActive(run);
|
|
177
|
+
this.runs.settle(run, refusal);
|
|
178
|
+
this.pumpRuns();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
172
181
|
const timeoutMs = run.input.timeoutMs;
|
|
173
182
|
if (timeoutMs !== undefined) run.timer = setTimeout(() => this.handleTimeout(run, timeoutMs), timeoutMs);
|
|
174
183
|
try {
|
|
@@ -178,6 +187,25 @@ export class SubprocessKernel {
|
|
|
178
187
|
}
|
|
179
188
|
}
|
|
180
189
|
|
|
190
|
+
private permissionRefusal(cellId: string): KernelResult | undefined {
|
|
191
|
+
const state = this.options.permissions?.state();
|
|
192
|
+
if (!state?.active) return undefined;
|
|
193
|
+
const rule = state.rules[0];
|
|
194
|
+
return {
|
|
195
|
+
type: "result",
|
|
196
|
+
cellId,
|
|
197
|
+
ok: false,
|
|
198
|
+
durationMs: 0,
|
|
199
|
+
error: {
|
|
200
|
+
code: "cell_permission_denied",
|
|
201
|
+
message: rule
|
|
202
|
+
? `This kernel cannot enforce ${rule.permission} ${rule.pattern} (${rule.layer}); use Python or JavaScript`
|
|
203
|
+
: "Permission authority unavailable",
|
|
204
|
+
...(rule ? { permissionRule: rule } : {}),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
181
209
|
private handleTimeout(run: PendingRun, timeoutMs: number): void {
|
|
182
210
|
if (this.runs.active !== run || run.settled) return;
|
|
183
211
|
const process = this.process;
|