shennian 0.2.57 → 0.2.60
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/dist/src/agents/pi.js +2 -2
- package/dist/src/fs/boundary.d.ts +21 -0
- package/dist/src/fs/boundary.js +121 -0
- package/dist/src/manager/registry.js +1 -1
- package/dist/src/manager/runtime.js +18 -3
- package/dist/src/session/handlers/fs.js +30 -30
- package/dist/src/session/manager.d.ts +2 -1
- package/dist/src/session/manager.js +6 -28
- package/dist/src/session/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/src/agents/pi.js
CHANGED
|
@@ -471,12 +471,12 @@ export class PiAdapter extends AgentAdapter {
|
|
|
471
471
|
return;
|
|
472
472
|
this.terminalState = 'error';
|
|
473
473
|
const message = err instanceof Error ? err.message : String(err);
|
|
474
|
-
if (message.includes('429') || message.includes('daily_quota_exceeded')) {
|
|
474
|
+
if (message.includes('429') || message.includes('daily_quota_exceeded') || message.includes('nian_quota_exceeded')) {
|
|
475
475
|
this.emit('agentEvent', {
|
|
476
476
|
state: 'error',
|
|
477
477
|
runId,
|
|
478
478
|
seq: ++this.seq,
|
|
479
|
-
message: '
|
|
479
|
+
message: 'Nian 今日额度已用完,次日自动恢复。',
|
|
480
480
|
});
|
|
481
481
|
}
|
|
482
482
|
else {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type FsPathFlavor = 'win32' | 'posix';
|
|
2
|
+
export type AuthorizedFsRoot = {
|
|
3
|
+
raw: string;
|
|
4
|
+
normalized: string;
|
|
5
|
+
boundary: string;
|
|
6
|
+
real: string | null;
|
|
7
|
+
flavor: FsPathFlavor;
|
|
8
|
+
file: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type ResolveAuthorizedPathResult = {
|
|
11
|
+
ok: true;
|
|
12
|
+
path: string;
|
|
13
|
+
root: AuthorizedFsRoot;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
error: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function resolveSessionWorkDir(input: string): string;
|
|
19
|
+
export declare function createAuthorizedFsRoot(rawRoot: string): AuthorizedFsRoot;
|
|
20
|
+
export declare function resolveAuthorizedPath(rawPath: string, root: AuthorizedFsRoot): ResolveAuthorizedPathResult;
|
|
21
|
+
export declare function isAuthorizedRootPath(pathValue: string, root: AuthorizedFsRoot): boolean;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// @arch docs/architecture/cli/daemon.md#文件系统权限边界
|
|
2
|
+
// @test src/__tests__/fs-boundary.test.ts
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
const WINDOWS_ABSOLUTE_RE = /^[A-Za-z]:([\\/]|$)/;
|
|
7
|
+
const WINDOWS_UNC_RE = /^\\\\[^\\]+\\[^\\]+/;
|
|
8
|
+
const WINDOWS_DRIVE_RELATIVE_RE = /^[A-Za-z]:(?![\\/])/;
|
|
9
|
+
function isWindowsAbsolutePath(input) {
|
|
10
|
+
return WINDOWS_ABSOLUTE_RE.test(input) || WINDOWS_UNC_RE.test(input);
|
|
11
|
+
}
|
|
12
|
+
function hasWindowsDriveRelativePrefix(input) {
|
|
13
|
+
return WINDOWS_DRIVE_RELATIVE_RE.test(input);
|
|
14
|
+
}
|
|
15
|
+
function normalizeWindowsAbsolutePath(input) {
|
|
16
|
+
return input.replace(/^[/\\]([A-Za-z]:[\\/].*)$/, '$1');
|
|
17
|
+
}
|
|
18
|
+
function pathFlavor(input) {
|
|
19
|
+
return isWindowsAbsolutePath(normalizeWindowsAbsolutePath(input)) ? 'win32' : 'posix';
|
|
20
|
+
}
|
|
21
|
+
function pathApi(flavor) {
|
|
22
|
+
return flavor === 'win32' ? path.win32 : path.posix;
|
|
23
|
+
}
|
|
24
|
+
function normalizeForCompare(input, flavor) {
|
|
25
|
+
const api = pathApi(flavor);
|
|
26
|
+
const normalized = api.normalize(input);
|
|
27
|
+
const trimmed = normalized.length > api.parse(normalized).root.length
|
|
28
|
+
? normalized.replace(/[\\/]+$/, '')
|
|
29
|
+
: normalized;
|
|
30
|
+
return flavor === 'win32' ? trimmed.toLowerCase() : trimmed;
|
|
31
|
+
}
|
|
32
|
+
function isWithinOrEqual(candidate, root, flavor) {
|
|
33
|
+
const api = pathApi(flavor);
|
|
34
|
+
const normalizedCandidate = normalizeForCompare(candidate, flavor);
|
|
35
|
+
const normalizedRoot = normalizeForCompare(root, flavor);
|
|
36
|
+
if (normalizedCandidate === normalizedRoot)
|
|
37
|
+
return true;
|
|
38
|
+
const relative = api.relative(normalizedRoot, normalizedCandidate);
|
|
39
|
+
return Boolean(relative) && !relative.startsWith('..') && !api.isAbsolute(relative);
|
|
40
|
+
}
|
|
41
|
+
function safeRealpath(pathValue) {
|
|
42
|
+
try {
|
|
43
|
+
return fs.realpathSync.native(pathValue);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function resolveSessionWorkDir(input) {
|
|
50
|
+
if (!input)
|
|
51
|
+
return os.homedir();
|
|
52
|
+
const normalizedInput = normalizeWindowsAbsolutePath(input);
|
|
53
|
+
const flavor = pathFlavor(normalizedInput);
|
|
54
|
+
const api = pathApi(flavor);
|
|
55
|
+
if (normalizedInput.startsWith('~')) {
|
|
56
|
+
return path.join(os.homedir(), normalizedInput.slice(1).replace(/^[/\\]+/, ''));
|
|
57
|
+
}
|
|
58
|
+
if (process.platform === 'win32') {
|
|
59
|
+
const normalized = normalizedInput.replace(/\\/g, '/');
|
|
60
|
+
if (normalized === '/tmp' || normalized.startsWith('/tmp/')) {
|
|
61
|
+
const suffix = normalized.slice('/tmp'.length).replace(/^\/+/, '');
|
|
62
|
+
return suffix ? path.win32.join(os.tmpdir(), ...suffix.split('/')) : os.tmpdir();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (isWindowsAbsolutePath(normalizedInput)) {
|
|
66
|
+
return path.win32.resolve(normalizedInput);
|
|
67
|
+
}
|
|
68
|
+
return api.resolve(normalizedInput);
|
|
69
|
+
}
|
|
70
|
+
export function createAuthorizedFsRoot(rawRoot) {
|
|
71
|
+
const normalized = resolveSessionWorkDir(rawRoot);
|
|
72
|
+
const flavor = pathFlavor(normalized);
|
|
73
|
+
const stat = safeStat(normalized);
|
|
74
|
+
const file = typeof stat?.isFile === 'function' ? stat.isFile() : false;
|
|
75
|
+
const boundary = file ? pathApi(flavor).dirname(normalized) : normalized;
|
|
76
|
+
return {
|
|
77
|
+
raw: rawRoot,
|
|
78
|
+
normalized,
|
|
79
|
+
boundary,
|
|
80
|
+
real: safeRealpath(boundary),
|
|
81
|
+
flavor,
|
|
82
|
+
file,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function resolveAuthorizedPath(rawPath, root) {
|
|
86
|
+
const input = String(rawPath || '').trim() || root.normalized;
|
|
87
|
+
const normalizedInput = normalizeWindowsAbsolutePath(input);
|
|
88
|
+
const api = pathApi(root.flavor);
|
|
89
|
+
if (hasWindowsDriveRelativePrefix(normalizedInput)) {
|
|
90
|
+
return { ok: false, error: `Access denied: ${rawPath}` };
|
|
91
|
+
}
|
|
92
|
+
const candidate = api.isAbsolute(normalizedInput)
|
|
93
|
+
? api.resolve(normalizedInput)
|
|
94
|
+
: api.resolve(root.boundary, normalizedInput);
|
|
95
|
+
if (!isWithinOrEqual(candidate, root.boundary, root.flavor)) {
|
|
96
|
+
return { ok: false, error: `Access denied: ${rawPath}` };
|
|
97
|
+
}
|
|
98
|
+
if (root.file && normalizeForCompare(candidate, root.flavor) !== normalizeForCompare(root.normalized, root.flavor)) {
|
|
99
|
+
return { ok: false, error: `Access denied: ${rawPath}` };
|
|
100
|
+
}
|
|
101
|
+
const realCandidate = safeRealpath(candidate);
|
|
102
|
+
if (realCandidate) {
|
|
103
|
+
const realRoot = root.real ?? safeRealpath(root.boundary);
|
|
104
|
+
if (realRoot && !isWithinOrEqual(realCandidate, realRoot, pathFlavor(realRoot))) {
|
|
105
|
+
return { ok: false, error: `Access denied: ${rawPath}` };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { ok: true, path: candidate, root };
|
|
109
|
+
}
|
|
110
|
+
export function isAuthorizedRootPath(pathValue, root) {
|
|
111
|
+
return isWithinOrEqual(pathValue, root.boundary, root.flavor) &&
|
|
112
|
+
normalizeForCompare(pathValue, root.flavor) === normalizeForCompare(root.boundary, root.flavor);
|
|
113
|
+
}
|
|
114
|
+
function safeStat(pathValue) {
|
|
115
|
+
try {
|
|
116
|
+
return fs.statSync(pathValue);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -257,7 +257,7 @@ export class ManagerRegistry {
|
|
|
257
257
|
function readableText(message) {
|
|
258
258
|
if (!message || isToolPayload(message.payload))
|
|
259
259
|
return null;
|
|
260
|
-
const text = extractPayloadText(message.payload).replace(/\
|
|
260
|
+
const text = extractPayloadText(message.payload).replace(/\r\n/g, '\n').trim();
|
|
261
261
|
return text || null;
|
|
262
262
|
}
|
|
263
263
|
function clip(text, max) {
|
|
@@ -29,6 +29,16 @@ function runIdFromMessageId(id) {
|
|
|
29
29
|
const match = /^agent-(.+)-\d+$/.exec(id);
|
|
30
30
|
return match?.[1] ?? null;
|
|
31
31
|
}
|
|
32
|
+
function seqFromMessageId(id) {
|
|
33
|
+
const match = /^agent-.+-(\d+)$/.exec(id);
|
|
34
|
+
if (!match)
|
|
35
|
+
return null;
|
|
36
|
+
const seq = Number(match[1]);
|
|
37
|
+
return Number.isInteger(seq) && seq >= 0 ? seq : null;
|
|
38
|
+
}
|
|
39
|
+
function normalizeMarkdownForWorkerSummary(text) {
|
|
40
|
+
return text.replace(/\r\n/g, '\n').trim();
|
|
41
|
+
}
|
|
32
42
|
function toolSummary(payload) {
|
|
33
43
|
try {
|
|
34
44
|
const parsed = JSON.parse(payload);
|
|
@@ -62,6 +72,7 @@ function compactWorkerTranscript(rawMessages, limit) {
|
|
|
62
72
|
const compacted = [];
|
|
63
73
|
let buffer = null;
|
|
64
74
|
let bufferRunId = null;
|
|
75
|
+
let bufferSeq = null;
|
|
65
76
|
let bufferText = '';
|
|
66
77
|
const flush = () => {
|
|
67
78
|
if (!buffer)
|
|
@@ -76,6 +87,7 @@ function compactWorkerTranscript(rawMessages, limit) {
|
|
|
76
87
|
}
|
|
77
88
|
buffer = null;
|
|
78
89
|
bufferRunId = null;
|
|
90
|
+
bufferSeq = null;
|
|
79
91
|
bufferText = '';
|
|
80
92
|
};
|
|
81
93
|
for (const message of chronological) {
|
|
@@ -96,14 +108,17 @@ function compactWorkerTranscript(rawMessages, limit) {
|
|
|
96
108
|
if (!text.trim())
|
|
97
109
|
continue;
|
|
98
110
|
const runId = runIdFromMessageId(message.id);
|
|
99
|
-
|
|
111
|
+
const seq = seqFromMessageId(message.id);
|
|
112
|
+
if (buffer && buffer.role === message.role && bufferRunId === runId && runId && seq !== null && bufferSeq !== null && seq === bufferSeq + 1) {
|
|
100
113
|
bufferText += text;
|
|
101
114
|
buffer.ts = message.ts;
|
|
115
|
+
bufferSeq = seq;
|
|
102
116
|
}
|
|
103
117
|
else {
|
|
104
118
|
flush();
|
|
105
119
|
buffer = message;
|
|
106
120
|
bufferRunId = runId;
|
|
121
|
+
bufferSeq = seq;
|
|
107
122
|
bufferText = text;
|
|
108
123
|
}
|
|
109
124
|
}
|
|
@@ -261,14 +276,14 @@ export class ManagerRuntimeService {
|
|
|
261
276
|
if (event.state === 'delta' && event.text && !event.thinking) {
|
|
262
277
|
const nextText = (this.workerTextAcc.get(textKey) ?? '') + event.text;
|
|
263
278
|
this.workerTextAcc.set(textKey, nextText);
|
|
264
|
-
const normalized = nextText
|
|
279
|
+
const normalized = normalizeMarkdownForWorkerSummary(nextText);
|
|
265
280
|
if (normalized) {
|
|
266
281
|
patch.summary = normalized.length > 160 ? `${normalized.slice(0, 160)}...` : normalized;
|
|
267
282
|
}
|
|
268
283
|
}
|
|
269
284
|
if (event.state === 'final' || event.state === 'error' || event.state === 'aborted') {
|
|
270
285
|
patch.status = event.state;
|
|
271
|
-
const accumulated = this.workerTextAcc.get(textKey)
|
|
286
|
+
const accumulated = normalizeMarkdownForWorkerSummary(this.workerTextAcc.get(textKey) ?? '');
|
|
272
287
|
if (accumulated) {
|
|
273
288
|
patch.summary = accumulated.length > 240 ? `${accumulated.slice(0, 240)}...` : accumulated;
|
|
274
289
|
}
|
|
@@ -7,38 +7,19 @@ const FILE_SYSTEM_ROOTS_PATH = '__roots__';
|
|
|
7
7
|
function isWindowsAbsolutePath(pathValue) {
|
|
8
8
|
return /^[A-Za-z]:([\\/]|$)/.test(pathValue) || /^\\\\[^\\]+\\[^\\]+/.test(pathValue);
|
|
9
9
|
}
|
|
10
|
-
function listFileSystemRoots() {
|
|
11
|
-
if (os.platform() === 'win32') {
|
|
12
|
-
const entries = [];
|
|
13
|
-
for (let code = 65; code <= 90; code += 1) {
|
|
14
|
-
const drive = `${String.fromCharCode(code)}:\\`;
|
|
15
|
-
if (!fs.existsSync(drive))
|
|
16
|
-
continue;
|
|
17
|
-
entries.push({
|
|
18
|
-
name: drive,
|
|
19
|
-
path: drive,
|
|
20
|
-
isDir: true,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
return { path: FILE_SYSTEM_ROOTS_PATH, entries };
|
|
24
|
-
}
|
|
25
|
-
return {
|
|
26
|
-
path: FILE_SYSTEM_ROOTS_PATH,
|
|
27
|
-
entries: [{ name: '/', path: '/', isDir: true }],
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
10
|
export async function handleFsLs(runtime, req) {
|
|
31
11
|
const requestedPath = req.params.path || os.homedir();
|
|
12
|
+
const rootPath = req.params.rootPath || requestedPath;
|
|
32
13
|
if (requestedPath === FILE_SYSTEM_ROOTS_PATH) {
|
|
33
|
-
runtime.client.sendRes({
|
|
34
|
-
type: 'res',
|
|
35
|
-
id: req.id,
|
|
36
|
-
ok: true,
|
|
37
|
-
payload: listFileSystemRoots(),
|
|
38
|
-
});
|
|
14
|
+
runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'Access denied: filesystem roots are outside the authorized directory' });
|
|
39
15
|
return;
|
|
40
16
|
}
|
|
41
|
-
const
|
|
17
|
+
const resolved = runtime.resolveAuthorizedPath(requestedPath, rootPath);
|
|
18
|
+
if (!resolved.ok) {
|
|
19
|
+
runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const dirPath = resolved.path;
|
|
42
23
|
try {
|
|
43
24
|
const raw = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
44
25
|
const joinPath = isWindowsAbsolutePath(dirPath) ? path.win32.join : path.join;
|
|
@@ -65,7 +46,14 @@ export async function handleFsLs(runtime, req) {
|
|
|
65
46
|
}
|
|
66
47
|
}
|
|
67
48
|
export async function handleFsRead(runtime, req) {
|
|
68
|
-
const
|
|
49
|
+
const requestedPath = req.params.path;
|
|
50
|
+
const rootPath = req.params.rootPath || requestedPath;
|
|
51
|
+
const resolved = runtime.resolveAuthorizedPath(requestedPath, rootPath);
|
|
52
|
+
if (!resolved.ok) {
|
|
53
|
+
runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const filePath = resolved.path;
|
|
69
57
|
const encoding = req.params.encoding || 'utf8';
|
|
70
58
|
const offset = req.params.offset;
|
|
71
59
|
const length = req.params.length;
|
|
@@ -148,7 +136,13 @@ export async function handleFsTransfer(runtime, req) {
|
|
|
148
136
|
return;
|
|
149
137
|
}
|
|
150
138
|
try {
|
|
151
|
-
const
|
|
139
|
+
const rootPath = req.params.rootPath || targetPath || os.homedir();
|
|
140
|
+
const resolved = runtime.resolveAuthorizedPath(targetPath || rootPath, rootPath);
|
|
141
|
+
if (!resolved.ok) {
|
|
142
|
+
runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const baseDir = resolved.path;
|
|
152
146
|
const uploadDir = direct ? baseDir : path.join(baseDir, '.uploads');
|
|
153
147
|
if (!direct)
|
|
154
148
|
fs.mkdirSync(uploadDir, { recursive: true });
|
|
@@ -168,7 +162,13 @@ export async function handleFsTransferStart(runtime, req) {
|
|
|
168
162
|
return;
|
|
169
163
|
}
|
|
170
164
|
try {
|
|
171
|
-
const
|
|
165
|
+
const rootPath = req.params.rootPath || targetPath || os.homedir();
|
|
166
|
+
const resolved = runtime.resolveAuthorizedPath(targetPath || rootPath, rootPath);
|
|
167
|
+
if (!resolved.ok) {
|
|
168
|
+
runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const baseDir = resolved.path;
|
|
172
172
|
const destinationDir = direct ? baseDir : path.join(baseDir, '.uploads');
|
|
173
173
|
if (!direct)
|
|
174
174
|
fs.mkdirSync(destinationDir, { recursive: true });
|
|
@@ -8,7 +8,7 @@ import '../agents/cursor.js';
|
|
|
8
8
|
import '../agents/opencode.js';
|
|
9
9
|
import '../agents/pi.js';
|
|
10
10
|
import '../agents/manager.js';
|
|
11
|
-
export
|
|
11
|
+
export { resolveSessionWorkDir } from '../fs/boundary.js';
|
|
12
12
|
export declare class SessionManager {
|
|
13
13
|
private client;
|
|
14
14
|
private nativeFusion;
|
|
@@ -28,5 +28,6 @@ export declare class SessionManager {
|
|
|
28
28
|
handleReq(req: ReqFrame): Promise<void>;
|
|
29
29
|
private evictIdleSessions;
|
|
30
30
|
private resolvePath;
|
|
31
|
+
private resolveAuthorizedPath;
|
|
31
32
|
cleanup(): void;
|
|
32
33
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// @arch docs/architecture/cli/daemon.md#会话管理
|
|
2
2
|
// @test src/__tests__/session-store.test.ts
|
|
3
3
|
// @test src/__tests__/model-switching.test.ts
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import os from 'node:os';
|
|
6
4
|
import { getRegisteredAgents, unregisterAgent } from '../agents/adapter.js';
|
|
7
5
|
import { loadConfig } from '../config/index.js';
|
|
8
6
|
import { handleUpgradeStart, handleUpgradeStatus } from '../commands/upgrade.js';
|
|
@@ -14,6 +12,7 @@ import { cleanupPendingTransfers, handleFsLs, handleFsRead, handleFsTransfer, ha
|
|
|
14
12
|
import { handleRegionProbe, handleRegionSwitch, handleUpgradeSetPolicy } from './handlers/control.js';
|
|
15
13
|
import { ManagerRuntimeService, setManagerRuntimeService } from '../manager/runtime.js';
|
|
16
14
|
import { ChatQueueManager } from './queue.js';
|
|
15
|
+
import { createAuthorizedFsRoot, resolveAuthorizedPath, resolveSessionWorkDir } from '../fs/boundary.js';
|
|
17
16
|
// Side-effect imports to register built-in agent adapters.
|
|
18
17
|
import '../agents/claude.js';
|
|
19
18
|
import '../agents/codex.js';
|
|
@@ -26,32 +25,7 @@ import '../agents/pi.js';
|
|
|
26
25
|
import '../agents/manager.js';
|
|
27
26
|
import { registerCustomAgent } from '../agents/custom.js';
|
|
28
27
|
const MAX_SESSIONS = 50;
|
|
29
|
-
|
|
30
|
-
return /^[A-Za-z]:([\\/]|$)/.test(input) || /^\\\\[^\\]+\\[^\\]+/.test(input);
|
|
31
|
-
}
|
|
32
|
-
function normalizeWindowsAbsolutePath(input) {
|
|
33
|
-
return input.replace(/^[/\\]([A-Za-z]:[\\/].*)$/, '$1');
|
|
34
|
-
}
|
|
35
|
-
export function resolveSessionWorkDir(input) {
|
|
36
|
-
if (!input)
|
|
37
|
-
return os.homedir();
|
|
38
|
-
const normalizedInput = normalizeWindowsAbsolutePath(input);
|
|
39
|
-
const joinPath = process.platform === 'win32' ? path.win32.join : path.join;
|
|
40
|
-
if (normalizedInput.startsWith('~')) {
|
|
41
|
-
return joinPath(os.homedir(), normalizedInput.slice(1).replace(/^[/\\]+/, ''));
|
|
42
|
-
}
|
|
43
|
-
if (process.platform === 'win32') {
|
|
44
|
-
const normalized = normalizedInput.replace(/\\/g, '/');
|
|
45
|
-
if (normalized === '/tmp' || normalized.startsWith('/tmp/')) {
|
|
46
|
-
const suffix = normalized.slice('/tmp'.length).replace(/^\/+/, '');
|
|
47
|
-
return suffix ? path.win32.join(os.tmpdir(), ...suffix.split('/')) : os.tmpdir();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (isWindowsAbsolutePath(normalizedInput)) {
|
|
51
|
-
return path.win32.resolve(normalizedInput);
|
|
52
|
-
}
|
|
53
|
-
return path.resolve(normalizedInput);
|
|
54
|
-
}
|
|
28
|
+
export { resolveSessionWorkDir } from '../fs/boundary.js';
|
|
55
29
|
export class SessionManager {
|
|
56
30
|
client;
|
|
57
31
|
nativeFusion;
|
|
@@ -88,6 +62,7 @@ export class SessionManager {
|
|
|
88
62
|
processedReqIds: this.processedReqIds,
|
|
89
63
|
reloadCustomAgents: () => this.reloadCustomAgents(),
|
|
90
64
|
resolvePath: (pathValue) => this.resolvePath(pathValue),
|
|
65
|
+
resolveAuthorizedPath: (pathValue, rootPath) => this.resolveAuthorizedPath(pathValue, rootPath),
|
|
91
66
|
runTextAcc: this.runTextAcc,
|
|
92
67
|
sessions: this.sessions,
|
|
93
68
|
evictIdleSessions: () => this.evictIdleSessions(),
|
|
@@ -223,6 +198,9 @@ export class SessionManager {
|
|
|
223
198
|
resolvePath(p) {
|
|
224
199
|
return resolveSessionWorkDir(p);
|
|
225
200
|
}
|
|
201
|
+
resolveAuthorizedPath(p, rootPath) {
|
|
202
|
+
return resolveAuthorizedPath(p, createAuthorizedFsRoot(rootPath));
|
|
203
|
+
}
|
|
226
204
|
cleanup() {
|
|
227
205
|
for (const [, session] of this.sessions) {
|
|
228
206
|
session.adapter.stop().catch(() => { });
|
|
@@ -3,6 +3,7 @@ import type { AgentAdapter } from '../agents/adapter.js';
|
|
|
3
3
|
import type { CliRelayClient } from '../relay/client.js';
|
|
4
4
|
import type { NativeSessionFusionService } from '../native-fusion/service.js';
|
|
5
5
|
import type { ManagerRuntimeService } from '../manager/runtime.js';
|
|
6
|
+
import type { ResolveAuthorizedPathResult } from '../fs/boundary.js';
|
|
6
7
|
export type ActiveSession = {
|
|
7
8
|
adapter: AgentAdapter;
|
|
8
9
|
workDir: string;
|
|
@@ -39,6 +40,7 @@ export type SessionManagerRuntime = {
|
|
|
39
40
|
processedReqIds: Set<string>;
|
|
40
41
|
reloadCustomAgents: () => void;
|
|
41
42
|
resolvePath: (pathValue: string) => string;
|
|
43
|
+
resolveAuthorizedPath: (pathValue: string, rootPath: string) => ResolveAuthorizedPathResult;
|
|
42
44
|
runTextAcc: Map<string, string>;
|
|
43
45
|
sessions: Map<string, ActiveSession>;
|
|
44
46
|
evictIdleSessions: () => void;
|