@steerable/agent-shell 0.6.20 → 0.6.22
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 +34 -0
- package/dist/auth/index.d.ts +38 -0
- package/dist/auth/index.js +20 -0
- package/dist/host/pack-assembly.d.ts +7 -7
- package/dist/host/runtime.d.ts +6 -2
- package/dist/host/runtime.js +54 -15
- package/dist/host/visible-terminal-exec.d.ts +0 -2
- package/dist/host/visible-terminal-exec.js +4 -8
- package/dist/insights/flush.d.ts +5 -4
- package/dist/insights/flush.js +14 -15
- package/dist/insights/record.d.ts +6 -5
- package/dist/insights/record.js +9 -10
- package/dist/llm/index.d.ts +6 -2
- package/dist/llm/index.js +19 -9
- package/dist/local-backend/ai-suggestions.d.ts +32 -0
- package/dist/local-backend/ai-suggestions.js +160 -0
- package/dist/local-backend/coreloop-stream.js +1 -1
- package/dist/local-backend/mentioned-paths.d.ts +28 -0
- package/dist/local-backend/mentioned-paths.js +67 -0
- package/dist/local-backend/prompt-builder.d.ts +10 -4
- package/dist/local-backend/prompt-builder.js +13 -12
- package/dist/local-backend/router.d.ts +26 -4
- package/dist/local-backend/router.js +243 -96
- package/dist/local-backend/skills/00-identity/SKILL.md +1 -1
- package/dist/local-backend/task-service.d.ts +14 -13
- package/dist/local-backend/task-service.js +32 -29
- package/dist/local-backend/turn-files.d.ts +57 -0
- package/dist/local-backend/turn-files.js +285 -0
- package/dist/local-backend/worktree-service.d.ts +1 -1
- package/dist/local-backend/worktree-service.js +6 -6
- package/dist/main.js +45 -26
- package/dist/preload.cjs +30 -10
- package/dist/preload.js +33 -10
- package/dist/server/http-server.d.ts +13 -1
- package/dist/server/http-server.js +45 -8
- package/dist/server/index.js +4 -90
- package/dist/server/sse-bus.d.ts +1 -1
- package/dist/server/start.d.ts +26 -0
- package/dist/server/start.js +111 -0
- package/dist/sidecar/boot.d.ts +3 -1
- package/dist/sidecar/boot.js +6 -7
- package/dist/sidecar/reverse-approval.d.ts +2 -0
- package/dist/sidecar/reverse-approval.js +2 -1
- package/dist/sidecar/reverse-ask-user.d.ts +2 -0
- package/dist/sidecar/reverse-ask-user.js +2 -1
- package/dist/sidecar/reverse-spawn.js +12 -4
- package/dist/sidecar/reverse-tools.d.ts +1 -1
- package/dist/sidecar/reverse-tools.js +1 -1
- package/dist/sidecar/storage-path.js +2 -3
- package/dist/storage/driver.d.ts +57 -0
- package/dist/storage/driver.js +104 -0
- package/dist/storage/index.d.ts +80 -67
- package/dist/storage/index.js +528 -296
- package/dist/storage/pack-migrations.d.ts +2 -4
- package/dist/storage/pack-migrations.js +2 -4
- package/dist/storage/scoped-store.d.ts +125 -0
- package/dist/storage/scoped-store.js +1 -0
- package/dist/storage/sqlite-driver.d.ts +16 -0
- package/dist/storage/sqlite-driver.js +135 -0
- package/dist/storage/write-lease-error.d.ts +11 -0
- package/dist/storage/write-lease-error.js +18 -0
- package/package.json +14 -5
- package/dist/storage/local-store-singleton.d.ts +0 -28
- package/dist/storage/local-store-singleton.js +0 -38
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @steerable/agent-shell
|
|
2
|
+
|
|
3
|
+
Product-neutral Electron and headless HTTP host for Steerable Framework.
|
|
4
|
+
|
|
5
|
+
## BS composition
|
|
6
|
+
|
|
7
|
+
Products can register an `AuthProvider` from `@steerable/agent-shell/auth`
|
|
8
|
+
before starting the host. A registered provider authenticates every
|
|
9
|
+
`/api/v2/*` and `/host/*` request and supplies the request `Principal`.
|
|
10
|
+
Without a provider, the host retains its loopback Host check and
|
|
11
|
+
per-process Bearer token.
|
|
12
|
+
|
|
13
|
+
Use `startBsHost` from `@steerable/agent-shell/server/start` to embed the BS
|
|
14
|
+
host. Its options support host and port selection, a fixed Bearer token, and
|
|
15
|
+
ordered middleware. Middleware returns `pass` to continue or `handled` to
|
|
16
|
+
short-circuit. The returned handle owns shutdown of both HTTP and runtime
|
|
17
|
+
services.
|
|
18
|
+
|
|
19
|
+
## Storage drivers
|
|
20
|
+
|
|
21
|
+
Storage drivers register during product composition and initialize after all
|
|
22
|
+
scenario-pack migrations and seeds are registered. `initializeStorage()` must
|
|
23
|
+
complete before `getScopedStore(scope)` or `getPackDbAccess(scope)` is used.
|
|
24
|
+
The default `SqliteStorageDriver` preserves the local database path, migration
|
|
25
|
+
sequence, seed behavior, and write lease.
|
|
26
|
+
|
|
27
|
+
Host repositories use the async `ScopedStore` API. Scenario packs receive
|
|
28
|
+
`PackDbAccess`, which carries `tenantId` and `userId` and does not expose the
|
|
29
|
+
driver connection. Because arbitrary SQL cannot be rewritten safely,
|
|
30
|
+
pack repositories must include both ownership columns in every statement;
|
|
31
|
+
the SQLite and MySQL access objects reject missing or mismatched ownership,
|
|
32
|
+
and pack boundary checks enforce the same requirement before runtime.
|
|
33
|
+
|
|
34
|
+
License: Apache-2.0.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product-neutral authenticated identity attached to one local-backend request.
|
|
3
|
+
*/
|
|
4
|
+
export interface Principal {
|
|
5
|
+
id: string;
|
|
6
|
+
tenantId: string;
|
|
7
|
+
displayName: string;
|
|
8
|
+
email: string | null;
|
|
9
|
+
roles: readonly string[];
|
|
10
|
+
isAdmin: boolean;
|
|
11
|
+
}
|
|
12
|
+
export type AuthDecision = {
|
|
13
|
+
ok: true;
|
|
14
|
+
principal: Principal;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
status: 401 | 403;
|
|
18
|
+
body?: unknown;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Authentication extension point for BS deployments.
|
|
22
|
+
*
|
|
23
|
+
* Implementations own all interpretation and validation of request headers,
|
|
24
|
+
* including cookies and any identity assertions added by a trusted proxy.
|
|
25
|
+
*/
|
|
26
|
+
export interface AuthProvider {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
authenticate(headers: Readonly<Record<string, string | string[] | undefined>>): Promise<AuthDecision>;
|
|
29
|
+
describeSelf(principal: Principal): Promise<unknown>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Registers the process-wide provider before the BS host starts.
|
|
33
|
+
*
|
|
34
|
+
* @returns A disposer that removes this provider if it is still active.
|
|
35
|
+
*/
|
|
36
|
+
export declare function registerAuthProvider(provider: AuthProvider): () => void;
|
|
37
|
+
/** Returns the provider registered by the product composition root, if any. */
|
|
38
|
+
export declare function getAuthProvider(): AuthProvider | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
let activeProvider = null;
|
|
2
|
+
/**
|
|
3
|
+
* Registers the process-wide provider before the BS host starts.
|
|
4
|
+
*
|
|
5
|
+
* @returns A disposer that removes this provider if it is still active.
|
|
6
|
+
*/
|
|
7
|
+
export function registerAuthProvider(provider) {
|
|
8
|
+
if (activeProvider) {
|
|
9
|
+
throw new Error(`[auth] provider already registered: ${activeProvider.id}`);
|
|
10
|
+
}
|
|
11
|
+
activeProvider = provider;
|
|
12
|
+
return () => {
|
|
13
|
+
if (activeProvider === provider)
|
|
14
|
+
activeProvider = null;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Returns the provider registered by the product composition root, if any. */
|
|
18
|
+
export function getAuthProvider() {
|
|
19
|
+
return activeProvider;
|
|
20
|
+
}
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
* 消费 handle.httpRoutes——包的入口面贡献由宿主循环驱动,宿主代码里
|
|
15
15
|
* 不出现任何包名。
|
|
16
16
|
*/
|
|
17
|
-
import type Database from 'better-sqlite3';
|
|
18
17
|
import type { HttpRouteContribution, IpcContribution, ToolContribution } from '../scenario/pack.js';
|
|
18
|
+
import type { PackDbAccess } from '../storage/driver.js';
|
|
19
19
|
/** 宿主注入包的装配能力面。包不 import 宿主装配模块,全部经回调注入。 */
|
|
20
20
|
export interface PackAssemblyDeps {
|
|
21
|
-
/**
|
|
22
|
-
readonly
|
|
21
|
+
/** Scope-bound database access; the underlying driver connection is hidden. */
|
|
22
|
+
readonly packDb: PackDbAccess;
|
|
23
23
|
/** 模型可见工具面(与普通回合同源)。 */
|
|
24
24
|
readonly listTools: () => {
|
|
25
25
|
name: string;
|
|
@@ -27,10 +27,10 @@ export interface PackAssemblyDeps {
|
|
|
27
27
|
inputSchema: unknown;
|
|
28
28
|
}[];
|
|
29
29
|
/** chatId → 绑定项目(无项目对话返回 null)。与 router.resolveChatProject 同源。 */
|
|
30
|
-
readonly resolveChatProject: (chatId: string) => {
|
|
30
|
+
readonly resolveChatProject: (chatId: string) => Promise<{
|
|
31
31
|
name: string;
|
|
32
32
|
folderPath: string;
|
|
33
|
-
} | null
|
|
33
|
+
} | null>;
|
|
34
34
|
/** 面向全部用户面的事件广播(CS=所有窗口 IPC,BS=SSE 总线)。 */
|
|
35
35
|
readonly broadcast: (channel: string, payload: unknown) => void;
|
|
36
36
|
/** 工具贡献注册口(toolRouter.registerToolContributions 的收窄面)。 */
|
|
@@ -46,7 +46,7 @@ export interface PackAssemblyDeps {
|
|
|
46
46
|
totalTokens: number;
|
|
47
47
|
cachedPromptTokens?: number;
|
|
48
48
|
costUsd?: number | null;
|
|
49
|
-
}) => void
|
|
49
|
+
}) => Promise<void>;
|
|
50
50
|
/** 行为洞察(与主对话同款 turn 事件)。 */
|
|
51
51
|
readonly recordInsight: (input: {
|
|
52
52
|
chatId: string;
|
|
@@ -57,7 +57,7 @@ export interface PackAssemblyDeps {
|
|
|
57
57
|
toolNames?: unknown;
|
|
58
58
|
userText?: string;
|
|
59
59
|
assistantText?: string;
|
|
60
|
-
}) => void
|
|
60
|
+
}) => Promise<void>;
|
|
61
61
|
/** 装配期日志行(进宿主日志)。 */
|
|
62
62
|
readonly onLog: (line: string) => void;
|
|
63
63
|
}
|
package/dist/host/runtime.d.ts
CHANGED
|
@@ -20,10 +20,13 @@ import { ProjectRegistry } from '../project-registry.js';
|
|
|
20
20
|
import { LocalBackendRouter } from '../local-backend/router.js';
|
|
21
21
|
import { WorktreeService } from '../local-backend/worktree-service.js';
|
|
22
22
|
import { TaskService } from '../local-backend/task-service.js';
|
|
23
|
+
import { type TenantScope } from '../storage/driver.js';
|
|
23
24
|
import { createApprovalBridge } from '../sidecar/reverse-approval.js';
|
|
24
25
|
import { createAskUserBridge } from '../sidecar/reverse-ask-user.js';
|
|
25
26
|
import { type PackAssemblyHandle } from './pack-assembly.js';
|
|
26
27
|
export interface HostRuntimeOptions {
|
|
28
|
+
/** Storage ownership for this host process. Defaults to local personal use. */
|
|
29
|
+
scope?: TenantScope;
|
|
27
30
|
/** 面向全部用户面的事件广播(CS=所有窗口 IPC,BS=SSE 总线)。 */
|
|
28
31
|
broadcast: (channel: string, payload: unknown) => void;
|
|
29
32
|
/**
|
|
@@ -42,6 +45,7 @@ export interface HostRuntimeOptions {
|
|
|
42
45
|
taskSweepReason: string;
|
|
43
46
|
}
|
|
44
47
|
export interface HostRuntime {
|
|
48
|
+
store: import('../storage/scoped-store.js').ScopedStore;
|
|
45
49
|
localExecutor: LocalExecutor;
|
|
46
50
|
localScriptRegistry: LocalScriptRegistry;
|
|
47
51
|
terminalManager: TerminalManager;
|
|
@@ -62,8 +66,8 @@ export interface HostRuntime {
|
|
|
62
66
|
* 不阻塞调用方的就绪路径(sidecar python import 慢,竞速的回合由
|
|
63
67
|
* router 回退处理)。
|
|
64
68
|
*/
|
|
65
|
-
start(): void
|
|
69
|
+
start(): Promise<void>;
|
|
66
70
|
/** 关停:杀终端、停 mock、关 sidecar。 */
|
|
67
71
|
shutdown(): Promise<void>;
|
|
68
72
|
}
|
|
69
|
-
export declare function createHostRuntime(options: HostRuntimeOptions): HostRuntime
|
|
73
|
+
export declare function createHostRuntime(options: HostRuntimeOptions): Promise<HostRuntime>;
|
package/dist/host/runtime.js
CHANGED
|
@@ -20,7 +20,7 @@ import { ProjectRegistry } from '../project-registry.js';
|
|
|
20
20
|
import { LocalBackendRouter } from '../local-backend/router.js';
|
|
21
21
|
import { WorktreeService } from '../local-backend/worktree-service.js';
|
|
22
22
|
import { TaskService } from '../local-backend/task-service.js';
|
|
23
|
-
import {
|
|
23
|
+
import { LOCAL_SCOPE, closeStorage, getPackDbAccess, getScopedStore, initializeStorage, } from '../storage/driver.js';
|
|
24
24
|
import { createApprovalBridge } from '../sidecar/reverse-approval.js';
|
|
25
25
|
import { createAskUserBridge } from '../sidecar/reverse-ask-user.js';
|
|
26
26
|
import { startHostSidecar, shutdownHostSidecar } from '../sidecar/boot.js';
|
|
@@ -29,18 +29,52 @@ import { createJsonStore } from '../json-store.js';
|
|
|
29
29
|
import { bindWorkspaceSkillRoots } from '../local-backend/skill-loader.js';
|
|
30
30
|
import { getChatAttachmentsDir } from '../attachments.js';
|
|
31
31
|
import { recordInsightTurn } from '../insights/record.js';
|
|
32
|
+
import { llmService } from '../llm/index.js';
|
|
32
33
|
import { getPackAssemblies, } from './pack-assembly.js';
|
|
33
|
-
export function createHostRuntime(options) {
|
|
34
|
+
export async function createHostRuntime(options) {
|
|
34
35
|
const { broadcast, hasWindow, onLog } = options;
|
|
35
36
|
const broadcastMain = options.broadcastMain ?? broadcast;
|
|
37
|
+
await initializeStorage();
|
|
38
|
+
const scope = options.scope ?? LOCAL_SCOPE;
|
|
39
|
+
const defaultStore = getScopedStore(scope);
|
|
40
|
+
let localBackendRouter;
|
|
41
|
+
const activeScope = () => localBackendRouter?.activeStore.scope ?? scope;
|
|
42
|
+
const packDb = {
|
|
43
|
+
get scope() {
|
|
44
|
+
return activeScope();
|
|
45
|
+
},
|
|
46
|
+
get(sql, params) {
|
|
47
|
+
return getPackDbAccess(activeScope()).get(sql, params);
|
|
48
|
+
},
|
|
49
|
+
all(sql, params) {
|
|
50
|
+
return getPackDbAccess(activeScope()).all(sql, params);
|
|
51
|
+
},
|
|
52
|
+
run(sql, params) {
|
|
53
|
+
return getPackDbAccess(activeScope()).run(sql, params);
|
|
54
|
+
},
|
|
55
|
+
exec(sql) {
|
|
56
|
+
return getPackDbAccess(activeScope()).exec(sql);
|
|
57
|
+
},
|
|
58
|
+
transaction(operation) {
|
|
59
|
+
return getPackDbAccess(activeScope()).transaction(() => operation(packDb));
|
|
60
|
+
},
|
|
61
|
+
};
|
|
36
62
|
const localExecutor = new LocalExecutor();
|
|
37
63
|
// 恢复设置界面里配置的"命令默认超时"(保存时由 local-backend router 即时
|
|
38
64
|
// 生效,这里负责进程重启后的恢复)。
|
|
39
65
|
{
|
|
40
|
-
const persistedLlmSettings =
|
|
66
|
+
const persistedLlmSettings = await defaultStore.getLlmSettings();
|
|
41
67
|
setDefaultExecTimeoutMs(persistedLlmSettings?.execTimeoutSeconds
|
|
42
68
|
? persistedLlmSettings.execTimeoutSeconds * 1000
|
|
43
69
|
: null);
|
|
70
|
+
llmService.initialize(persistedLlmSettings ?? {
|
|
71
|
+
provider: 'ollama',
|
|
72
|
+
model: 'llama3.1:8b',
|
|
73
|
+
baseUrl: 'http://127.0.0.1:11434',
|
|
74
|
+
temperature: 0.3,
|
|
75
|
+
}, {
|
|
76
|
+
setLlmSettings: (settings) => defaultStore.setLlmSettings(settings),
|
|
77
|
+
});
|
|
44
78
|
}
|
|
45
79
|
const localScriptRegistry = new LocalScriptRegistry();
|
|
46
80
|
const terminalManager = new TerminalManager();
|
|
@@ -68,7 +102,6 @@ export function createHostRuntime(options) {
|
|
|
68
102
|
const maybeExecInTerminal = createVisibleTerminalExec({
|
|
69
103
|
localExecutor,
|
|
70
104
|
terminalManager,
|
|
71
|
-
broadcast,
|
|
72
105
|
});
|
|
73
106
|
const toolRouter = new ToolRouter(localExecutor, localScriptRegistry, async (request) => {
|
|
74
107
|
const viaTerminal = await maybeExecInTerminal(request);
|
|
@@ -79,8 +112,8 @@ export function createHostRuntime(options) {
|
|
|
79
112
|
// 4.6a/4.6b:跨 turn 后台任务 + git worktree 隔离。任务流由宿主独立驱动
|
|
80
113
|
// (不绑父 turn 生命周期,见 task-service.ts 模块头);终态经广播推到
|
|
81
114
|
// 用户面任务面板。
|
|
82
|
-
const resolveChatProjectRoot = (chatId) => {
|
|
83
|
-
const projectId =
|
|
115
|
+
const resolveChatProjectRoot = async (chatId) => {
|
|
116
|
+
const projectId = (await defaultStore.getChat(chatId))?.projectId;
|
|
84
117
|
if (!projectId)
|
|
85
118
|
return null;
|
|
86
119
|
const project = projectRegistry.get(projectId);
|
|
@@ -92,20 +125,19 @@ export function createHostRuntime(options) {
|
|
|
92
125
|
const packHandles = new Map();
|
|
93
126
|
{
|
|
94
127
|
const packDeps = {
|
|
95
|
-
|
|
128
|
+
packDb,
|
|
96
129
|
listTools: () => toolRouter.listModelSchemas(),
|
|
97
130
|
resolveChatProject: resolveChatProjectRoot,
|
|
98
131
|
broadcast,
|
|
99
132
|
registerTools: (tools) => toolRouter.registerToolContributions(tools),
|
|
100
|
-
recordUsage: (input) =>
|
|
101
|
-
recordInsight: (input) => recordInsightTurn(input),
|
|
133
|
+
recordUsage: (input) => (localBackendRouter?.activeStore ?? defaultStore).recordUsageEvent(input),
|
|
134
|
+
recordInsight: (input) => recordInsightTurn(localBackendRouter?.activeStore ?? defaultStore, input),
|
|
102
135
|
onLog,
|
|
103
136
|
};
|
|
104
137
|
// 包迁移容错应用(3.2):包的 import 链可能经 llm/index 等模块提前
|
|
105
138
|
// 触发 storage 单例构造(ESM 深度优先,active.ts 的注册体后于链上
|
|
106
139
|
// 单例求值)——构造时注册表尚空,此处(装配前)补上已注册包迁移。
|
|
107
140
|
// 幂等(按包 id 去重),注册表为空时零开销。
|
|
108
|
-
localStore.applyPackMigrations();
|
|
109
141
|
for (const [packId, assemble] of getPackAssemblies()) {
|
|
110
142
|
const handle = assemble(packDeps);
|
|
111
143
|
if (handle)
|
|
@@ -114,14 +146,18 @@ export function createHostRuntime(options) {
|
|
|
114
146
|
}
|
|
115
147
|
const worktreeService = new WorktreeService({ resolveProject: resolveChatProjectRoot });
|
|
116
148
|
const taskService = new TaskService({
|
|
117
|
-
store:
|
|
149
|
+
store: () => localBackendRouter?.activeStore ?? defaultStore,
|
|
118
150
|
toolRouter,
|
|
119
151
|
worktreeService,
|
|
120
152
|
resolveChatProject: resolveChatProjectRoot,
|
|
121
153
|
broadcast,
|
|
122
154
|
});
|
|
123
155
|
toolRouter.setTaskServices({ taskService, worktreeService });
|
|
124
|
-
|
|
156
|
+
localBackendRouter = new LocalBackendRouter(toolRouter, {
|
|
157
|
+
store: defaultStore,
|
|
158
|
+
resolveStore: (principal) => principal
|
|
159
|
+
? getScopedStore({ tenantId: principal.tenantId, userId: principal.id })
|
|
160
|
+
: defaultStore,
|
|
125
161
|
taskService,
|
|
126
162
|
broadcast: broadcastMain,
|
|
127
163
|
});
|
|
@@ -138,6 +174,7 @@ export function createHostRuntime(options) {
|
|
|
138
174
|
});
|
|
139
175
|
let started = false;
|
|
140
176
|
return {
|
|
177
|
+
store: defaultStore,
|
|
141
178
|
localExecutor,
|
|
142
179
|
localScriptRegistry,
|
|
143
180
|
terminalManager,
|
|
@@ -151,13 +188,13 @@ export function createHostRuntime(options) {
|
|
|
151
188
|
approvalBridge,
|
|
152
189
|
askUserBridge,
|
|
153
190
|
maybeExecInTerminal,
|
|
154
|
-
start() {
|
|
191
|
+
async start() {
|
|
155
192
|
if (started)
|
|
156
193
|
return;
|
|
157
194
|
started = true;
|
|
158
195
|
// 4.6a:上次进程崩溃/强杀留下的 running 任务不是真相——任务流随进程
|
|
159
196
|
// 一起死了,启动时落成 failed,任务面板据此显示"重启中断"。
|
|
160
|
-
const sweptTasks =
|
|
197
|
+
const sweptTasks = await defaultStore.failRunningTasks(options.taskSweepReason);
|
|
161
198
|
if (sweptTasks > 0) {
|
|
162
199
|
onLog(`[task] swept ${sweptTasks} stale running task(s)`);
|
|
163
200
|
}
|
|
@@ -165,8 +202,9 @@ export function createHostRuntime(options) {
|
|
|
165
202
|
// Not awaited: sidecar boot (python import) must not delay readiness.
|
|
166
203
|
// A turn that races the boot falls back for that turn only.
|
|
167
204
|
void startHostSidecar({
|
|
205
|
+
store: defaultStore,
|
|
168
206
|
toolRouter,
|
|
169
|
-
resolveProjectRoot: (chatId) => localBackendRouter.resolveChatProject(chatId)?.folderPath ?? null,
|
|
207
|
+
resolveProjectRoot: async (chatId) => (await localBackendRouter.resolveChatProject(chatId))?.folderPath ?? null,
|
|
170
208
|
// 项目模式下文件读写被围栏在项目目录内;会话附件目录是额外放行的
|
|
171
209
|
// 只读根,保证用户上传的文件即使在项目会话里也能被 agent 读回。
|
|
172
210
|
resolveAdditionalReadRoots: (chatId) => [getChatAttachmentsDir(chatId)],
|
|
@@ -202,6 +240,7 @@ export function createHostRuntime(options) {
|
|
|
202
240
|
await handle.dispose?.();
|
|
203
241
|
}
|
|
204
242
|
await shutdownHostSidecar();
|
|
243
|
+
await closeStorage();
|
|
205
244
|
},
|
|
206
245
|
};
|
|
207
246
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { type LocalExecRequest, type LocalExecResult, type LocalExecutor } from '../local-executor.js';
|
|
2
2
|
import type { TerminalManager } from '../terminal-manager.js';
|
|
3
|
-
export type HostBroadcast = (channel: string, payload: unknown) => void;
|
|
4
3
|
export interface VisibleTerminalExecDeps {
|
|
5
4
|
localExecutor: LocalExecutor;
|
|
6
5
|
terminalManager: TerminalManager;
|
|
7
|
-
broadcast: HostBroadcast;
|
|
8
6
|
}
|
|
9
7
|
/**
|
|
10
8
|
* Map a raw PTY shell binary name (e.g. `powershell.exe`, `cmd.exe`, `zsh`) to
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 可见 PTY
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* broadcast 出口(Electron 窗口广播 / BS SSE 总线)。
|
|
2
|
+
* 可见 PTY 执行路由:shell 命令优先打进共享的可见终端,多行/heredoc 等
|
|
3
|
+
* sentinel 抓不住的形态回退 headless。Electron main 与 BS server 共用。
|
|
4
|
+
* 命令进入 PTY 时不通知 renderer 展开面板——终端只由用户手动打开。
|
|
6
5
|
*/
|
|
7
6
|
import log from 'electron-log';
|
|
8
7
|
import { GUI_LAUNCH_WAIT_MS, getConfiguredExecTimeoutMs, isGuiLaunchCommand, rewriteExeCommandIfNeeded, } from '../local-executor.js';
|
|
@@ -32,7 +31,7 @@ export function mapSessionShell(shell) {
|
|
|
32
31
|
* process.
|
|
33
32
|
*/
|
|
34
33
|
export function createVisibleTerminalExec(deps) {
|
|
35
|
-
const { localExecutor, terminalManager
|
|
34
|
+
const { localExecutor, terminalManager } = deps;
|
|
36
35
|
return async function maybeExecInTerminal(req) {
|
|
37
36
|
if (req.command) {
|
|
38
37
|
req.command = rewriteExeCommandIfNeeded(req.command);
|
|
@@ -73,9 +72,6 @@ export function createVisibleTerminalExec(deps) {
|
|
|
73
72
|
timeoutMs = getConfiguredExecTimeoutMs() ?? undefined;
|
|
74
73
|
}
|
|
75
74
|
const session = terminalManager.ensurePrimary({ cwd: req.cwd });
|
|
76
|
-
// Codex 式面板自动展开:命令进入可见 PTY 时通知 renderer 打开内嵌终端
|
|
77
|
-
// 面板。幂等——面板已打开时 renderer 端是 no-op。
|
|
78
|
-
broadcast('terminal:reveal', { sessionId: session.id });
|
|
79
75
|
const resolvedShell = mapSessionShell(session.shell);
|
|
80
76
|
// 项目模式(ToolRouter 会把项目根塞进 req.cwd):可见 PTY 是共享交互
|
|
81
77
|
// 会话,已有 session 不会随 ensurePrimary 改目录——显式 cd 过去。
|
package/dist/insights/flush.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import type { ScopedStore } from '../storage/scoped-store.js';
|
|
1
2
|
export declare function insightsNetworkDisabled(): boolean;
|
|
2
|
-
export declare function buildInsightsExportPayload(): Record<string, unknown
|
|
3
|
+
export declare function buildInsightsExportPayload(store: ScopedStore): Promise<Record<string, unknown>>;
|
|
3
4
|
export declare function postInsightsJson(url: string, body: unknown): Promise<boolean>;
|
|
4
|
-
export declare function flushInsightsOutbox(): Promise<{
|
|
5
|
+
export declare function flushInsightsOutbox(store: ScopedStore): Promise<{
|
|
5
6
|
uploaded: number;
|
|
6
7
|
skipped: number;
|
|
7
8
|
failed: number;
|
|
8
9
|
}>;
|
|
9
|
-
export declare function uploadInsightsBundle(): Promise<boolean>;
|
|
10
|
-
export declare function scheduleInsightsFlush(): void;
|
|
10
|
+
export declare function uploadInsightsBundle(store: ScopedStore): Promise<boolean>;
|
|
11
|
+
export declare function scheduleInsightsFlush(store: ScopedStore): void;
|
package/dist/insights/flush.js
CHANGED
|
@@ -3,7 +3,6 @@ import path from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { getBrand } from '../brand.js';
|
|
5
5
|
import { resolveInsightsApiBase, rowsEligibleForAutoUpload, } from '../storage/insights-settings.js';
|
|
6
|
-
import { localStore } from '../storage/index.js';
|
|
7
6
|
const FLUSH_TIMEOUT_MS = 8_000;
|
|
8
7
|
const MAX_BATCH = 20;
|
|
9
8
|
export function insightsNetworkDisabled() {
|
|
@@ -41,10 +40,10 @@ function clientMeta(settings) {
|
|
|
41
40
|
appVersion: detectAppVersion(),
|
|
42
41
|
};
|
|
43
42
|
}
|
|
44
|
-
export function buildInsightsExportPayload() {
|
|
45
|
-
const settings =
|
|
43
|
+
export async function buildInsightsExportPayload(store) {
|
|
44
|
+
const settings = await store.ensureInsightsSettings();
|
|
46
45
|
return {
|
|
47
|
-
...
|
|
46
|
+
...await store.exportInsightsBundle(),
|
|
48
47
|
...clientMeta(settings),
|
|
49
48
|
};
|
|
50
49
|
}
|
|
@@ -84,12 +83,12 @@ function pathForKind(kind) {
|
|
|
84
83
|
return '/api/v2/agent-insights/turns';
|
|
85
84
|
return '/api/v2/agent-insights/profile';
|
|
86
85
|
}
|
|
87
|
-
export async function flushInsightsOutbox() {
|
|
86
|
+
export async function flushInsightsOutbox(store) {
|
|
88
87
|
if (insightsNetworkDisabled()) {
|
|
89
88
|
return { uploaded: 0, skipped: 0, failed: 0 };
|
|
90
89
|
}
|
|
91
|
-
const settings =
|
|
92
|
-
const pending =
|
|
90
|
+
const settings = await store.ensureInsightsSettings();
|
|
91
|
+
const pending = await store.listInsightOutbox({ uploaded: false, limit: MAX_BATCH });
|
|
93
92
|
const { upload, skipped } = rowsEligibleForAutoUpload(settings, pending);
|
|
94
93
|
let uploaded = 0;
|
|
95
94
|
let failed = 0;
|
|
@@ -105,22 +104,22 @@ export async function flushInsightsOutbox() {
|
|
|
105
104
|
clientEventId: row.id,
|
|
106
105
|
});
|
|
107
106
|
if (ok) {
|
|
108
|
-
|
|
107
|
+
await store.markInsightUploaded(row.id);
|
|
109
108
|
uploaded += 1;
|
|
110
109
|
}
|
|
111
110
|
else {
|
|
112
|
-
|
|
111
|
+
await store.markInsightUploadError(row.id, 'upload_failed');
|
|
113
112
|
failed += 1;
|
|
114
113
|
}
|
|
115
114
|
}
|
|
116
115
|
return { uploaded, skipped, failed };
|
|
117
116
|
}
|
|
118
|
-
export async function uploadInsightsBundle() {
|
|
117
|
+
export async function uploadInsightsBundle(store) {
|
|
119
118
|
if (insightsNetworkDisabled()) {
|
|
120
119
|
return false;
|
|
121
120
|
}
|
|
122
|
-
const settings =
|
|
123
|
-
const bundle = buildInsightsExportPayload();
|
|
121
|
+
const settings = await store.ensureInsightsSettings();
|
|
122
|
+
const bundle = await buildInsightsExportPayload(store);
|
|
124
123
|
const base = resolveInsightsApiBase(settings, process.env.DEEPPATH_INSIGHTS_API_BASE);
|
|
125
124
|
if (!base)
|
|
126
125
|
return false;
|
|
@@ -128,12 +127,12 @@ export async function uploadInsightsBundle() {
|
|
|
128
127
|
if (ok) {
|
|
129
128
|
const records = Array.isArray(bundle.records) ? bundle.records : [];
|
|
130
129
|
for (const row of records)
|
|
131
|
-
|
|
130
|
+
await store.markInsightUploaded(row.id);
|
|
132
131
|
}
|
|
133
132
|
return ok;
|
|
134
133
|
}
|
|
135
|
-
export function scheduleInsightsFlush() {
|
|
136
|
-
void flushInsightsOutbox().catch((err) => {
|
|
134
|
+
export function scheduleInsightsFlush(store) {
|
|
135
|
+
void flushInsightsOutbox(store).catch((err) => {
|
|
137
136
|
console.warn('[insights] flush crashed', err);
|
|
138
137
|
});
|
|
139
138
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function
|
|
1
|
+
import type { ScopedStore } from '../storage/scoped-store.js';
|
|
2
|
+
export declare function recordInsightEvent(store: ScopedStore, eventName: string, properties?: Record<string, unknown>): Promise<void>;
|
|
3
|
+
export declare function recordInsightTurn(store: ScopedStore, input: {
|
|
3
4
|
chatId: string;
|
|
4
5
|
mode?: string;
|
|
5
6
|
modelId?: string | null;
|
|
@@ -8,10 +9,10 @@ export declare function recordInsightTurn(input: {
|
|
|
8
9
|
toolNames?: unknown;
|
|
9
10
|
userText?: string;
|
|
10
11
|
assistantText?: string;
|
|
11
|
-
}): void
|
|
12
|
-
export declare function recordInsightProfile(profile: {
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
export declare function recordInsightProfile(store: ScopedStore, profile: {
|
|
13
14
|
displayName?: string;
|
|
14
15
|
email?: string;
|
|
15
16
|
company?: string;
|
|
16
17
|
note?: string;
|
|
17
|
-
}): void
|
|
18
|
+
}): Promise<void>;
|
package/dist/insights/record.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { redactInsightText, toolNamesOnly } from '../storage/insights-redact.js';
|
|
2
|
-
import { localStore } from '../storage/index.js';
|
|
3
2
|
import { scheduleInsightsFlush } from './flush.js';
|
|
4
|
-
export function recordInsightEvent(eventName, properties = {}) {
|
|
3
|
+
export async function recordInsightEvent(store, eventName, properties = {}) {
|
|
5
4
|
try {
|
|
6
|
-
|
|
7
|
-
scheduleInsightsFlush();
|
|
5
|
+
await store.enqueueInsight('event', { eventName, properties });
|
|
6
|
+
scheduleInsightsFlush(store);
|
|
8
7
|
}
|
|
9
8
|
catch (err) {
|
|
10
9
|
console.warn('[insights] event enqueue failed', err);
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
|
-
export function recordInsightTurn(input) {
|
|
12
|
+
export async function recordInsightTurn(store, input) {
|
|
14
13
|
try {
|
|
15
|
-
|
|
14
|
+
await store.enqueueInsight('turn', {
|
|
16
15
|
chatId: input.chatId,
|
|
17
16
|
mode: input.mode === 'plan' ? 'plan' : 'agent',
|
|
18
17
|
modelId: typeof input.modelId === 'string' ? input.modelId.slice(0, 128) : undefined,
|
|
@@ -22,21 +21,21 @@ export function recordInsightTurn(input) {
|
|
|
22
21
|
userText: redactInsightText(input.userText, 4000),
|
|
23
22
|
assistantText: redactInsightText(input.assistantText, 8000),
|
|
24
23
|
});
|
|
25
|
-
scheduleInsightsFlush();
|
|
24
|
+
scheduleInsightsFlush(store);
|
|
26
25
|
}
|
|
27
26
|
catch (err) {
|
|
28
27
|
console.warn('[insights] turn enqueue failed', err);
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
|
-
export function recordInsightProfile(profile) {
|
|
30
|
+
export async function recordInsightProfile(store, profile) {
|
|
32
31
|
try {
|
|
33
|
-
|
|
32
|
+
await store.enqueueInsight('profile', {
|
|
34
33
|
displayName: redactInsightText(profile.displayName, 128),
|
|
35
34
|
email: redactInsightText(profile.email, 191),
|
|
36
35
|
company: redactInsightText(profile.company, 191),
|
|
37
36
|
note: redactInsightText(profile.note, 500),
|
|
38
37
|
});
|
|
39
|
-
scheduleInsightsFlush();
|
|
38
|
+
scheduleInsightsFlush(store);
|
|
40
39
|
}
|
|
41
40
|
catch (err) {
|
|
42
41
|
console.warn('[insights] profile enqueue failed', err);
|
package/dist/llm/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { LlmSettings, ScopedStore } from '../storage/index.js';
|
|
2
2
|
import { getSidecarSupervisor, setSidecarSupervisor, setSidecarSupervisorPending, whenSidecarSupervisor } from '../sidecar/handle.js';
|
|
3
3
|
export { getSidecarSupervisor, setSidecarSupervisor, setSidecarSupervisorPending, whenSidecarSupervisor };
|
|
4
4
|
import type { LlmGenerateRequest, LlmGenerateResult, LlmGenerateStreamRequest } from './types.js';
|
|
5
5
|
export declare class LlmService {
|
|
6
6
|
private provider;
|
|
7
7
|
private cachedConfigSignature;
|
|
8
|
+
private settings;
|
|
9
|
+
private store;
|
|
10
|
+
/** Initializes the synchronous runtime cache after storage has loaded. */
|
|
11
|
+
initialize(settings: LlmSettings, store: Pick<ScopedStore, 'setLlmSettings'>): void;
|
|
8
12
|
listModels(): Promise<string[]>;
|
|
9
13
|
generate(request: Omit<LlmGenerateRequest, 'model'> & {
|
|
10
14
|
model?: string;
|
|
@@ -16,7 +20,7 @@ export declare class LlmService {
|
|
|
16
20
|
model?: string;
|
|
17
21
|
}): Promise<LlmGenerateResult>;
|
|
18
22
|
getSettings(): LlmSettings;
|
|
19
|
-
setSettings(next: LlmSettings): LlmSettings
|
|
23
|
+
setSettings(next: LlmSettings, store?: Pick<ScopedStore, "setLlmSettings"> | null): Promise<LlmSettings>;
|
|
20
24
|
private getProvider;
|
|
21
25
|
}
|
|
22
26
|
export declare const llmService: LlmService;
|
package/dist/llm/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { localStore } from '../storage/index.js';
|
|
2
1
|
import { sidecarWireProvider } from '../storage/llm-settings.js';
|
|
3
2
|
import { OllamaProvider } from './ollama.js';
|
|
4
3
|
import { OpenAICompatProvider } from './openai-compat.js';
|
|
@@ -11,6 +10,15 @@ export { getSidecarSupervisor, setSidecarSupervisor, setSidecarSupervisorPending
|
|
|
11
10
|
export class LlmService {
|
|
12
11
|
provider = null;
|
|
13
12
|
cachedConfigSignature = '';
|
|
13
|
+
settings = null;
|
|
14
|
+
store = null;
|
|
15
|
+
/** Initializes the synchronous runtime cache after storage has loaded. */
|
|
16
|
+
initialize(settings, store) {
|
|
17
|
+
this.settings = settings;
|
|
18
|
+
this.store = store;
|
|
19
|
+
this.provider = null;
|
|
20
|
+
this.cachedConfigSignature = '';
|
|
21
|
+
}
|
|
14
22
|
async listModels() {
|
|
15
23
|
const provider = this.getProvider();
|
|
16
24
|
return provider.listModels();
|
|
@@ -51,15 +59,17 @@ export class LlmService {
|
|
|
51
59
|
return result;
|
|
52
60
|
}
|
|
53
61
|
getSettings() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
temperature: 0.3,
|
|
59
|
-
};
|
|
62
|
+
if (!this.settings) {
|
|
63
|
+
throw new Error('LlmService must be initialized after storage');
|
|
64
|
+
}
|
|
65
|
+
return this.settings;
|
|
60
66
|
}
|
|
61
|
-
setSettings(next) {
|
|
62
|
-
|
|
67
|
+
async setSettings(next, store = this.store) {
|
|
68
|
+
if (!store) {
|
|
69
|
+
throw new Error('LlmService must be initialized after storage');
|
|
70
|
+
}
|
|
71
|
+
const saved = await store.setLlmSettings(next);
|
|
72
|
+
this.settings = saved;
|
|
63
73
|
this.provider = null;
|
|
64
74
|
this.cachedConfigSignature = '';
|
|
65
75
|
return saved;
|