@steerable/agent-shell 0.6.21 → 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 -14
- 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/prompt-builder.d.ts +10 -4
- package/dist/local-backend/prompt-builder.js +13 -12
- package/dist/local-backend/router.d.ts +18 -4
- package/dist/local-backend/router.js +133 -98
- 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/worktree-service.d.ts +1 -1
- package/dist/local-backend/worktree-service.js +6 -6
- package/dist/main.js +43 -26
- package/dist/server/http-server.d.ts +13 -1
- package/dist/server/http-server.js +37 -8
- package/dist/server/index.js +4 -90
- 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-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 +75 -68
- package/dist/storage/index.js +506 -301
- 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();
|
|
@@ -78,8 +112,8 @@ export function createHostRuntime(options) {
|
|
|
78
112
|
// 4.6a/4.6b:跨 turn 后台任务 + git worktree 隔离。任务流由宿主独立驱动
|
|
79
113
|
// (不绑父 turn 生命周期,见 task-service.ts 模块头);终态经广播推到
|
|
80
114
|
// 用户面任务面板。
|
|
81
|
-
const resolveChatProjectRoot = (chatId) => {
|
|
82
|
-
const projectId =
|
|
115
|
+
const resolveChatProjectRoot = async (chatId) => {
|
|
116
|
+
const projectId = (await defaultStore.getChat(chatId))?.projectId;
|
|
83
117
|
if (!projectId)
|
|
84
118
|
return null;
|
|
85
119
|
const project = projectRegistry.get(projectId);
|
|
@@ -91,20 +125,19 @@ export function createHostRuntime(options) {
|
|
|
91
125
|
const packHandles = new Map();
|
|
92
126
|
{
|
|
93
127
|
const packDeps = {
|
|
94
|
-
|
|
128
|
+
packDb,
|
|
95
129
|
listTools: () => toolRouter.listModelSchemas(),
|
|
96
130
|
resolveChatProject: resolveChatProjectRoot,
|
|
97
131
|
broadcast,
|
|
98
132
|
registerTools: (tools) => toolRouter.registerToolContributions(tools),
|
|
99
|
-
recordUsage: (input) =>
|
|
100
|
-
recordInsight: (input) => recordInsightTurn(input),
|
|
133
|
+
recordUsage: (input) => (localBackendRouter?.activeStore ?? defaultStore).recordUsageEvent(input),
|
|
134
|
+
recordInsight: (input) => recordInsightTurn(localBackendRouter?.activeStore ?? defaultStore, input),
|
|
101
135
|
onLog,
|
|
102
136
|
};
|
|
103
137
|
// 包迁移容错应用(3.2):包的 import 链可能经 llm/index 等模块提前
|
|
104
138
|
// 触发 storage 单例构造(ESM 深度优先,active.ts 的注册体后于链上
|
|
105
139
|
// 单例求值)——构造时注册表尚空,此处(装配前)补上已注册包迁移。
|
|
106
140
|
// 幂等(按包 id 去重),注册表为空时零开销。
|
|
107
|
-
localStore.applyPackMigrations();
|
|
108
141
|
for (const [packId, assemble] of getPackAssemblies()) {
|
|
109
142
|
const handle = assemble(packDeps);
|
|
110
143
|
if (handle)
|
|
@@ -113,14 +146,18 @@ export function createHostRuntime(options) {
|
|
|
113
146
|
}
|
|
114
147
|
const worktreeService = new WorktreeService({ resolveProject: resolveChatProjectRoot });
|
|
115
148
|
const taskService = new TaskService({
|
|
116
|
-
store:
|
|
149
|
+
store: () => localBackendRouter?.activeStore ?? defaultStore,
|
|
117
150
|
toolRouter,
|
|
118
151
|
worktreeService,
|
|
119
152
|
resolveChatProject: resolveChatProjectRoot,
|
|
120
153
|
broadcast,
|
|
121
154
|
});
|
|
122
155
|
toolRouter.setTaskServices({ taskService, worktreeService });
|
|
123
|
-
|
|
156
|
+
localBackendRouter = new LocalBackendRouter(toolRouter, {
|
|
157
|
+
store: defaultStore,
|
|
158
|
+
resolveStore: (principal) => principal
|
|
159
|
+
? getScopedStore({ tenantId: principal.tenantId, userId: principal.id })
|
|
160
|
+
: defaultStore,
|
|
124
161
|
taskService,
|
|
125
162
|
broadcast: broadcastMain,
|
|
126
163
|
});
|
|
@@ -137,6 +174,7 @@ export function createHostRuntime(options) {
|
|
|
137
174
|
});
|
|
138
175
|
let started = false;
|
|
139
176
|
return {
|
|
177
|
+
store: defaultStore,
|
|
140
178
|
localExecutor,
|
|
141
179
|
localScriptRegistry,
|
|
142
180
|
terminalManager,
|
|
@@ -150,13 +188,13 @@ export function createHostRuntime(options) {
|
|
|
150
188
|
approvalBridge,
|
|
151
189
|
askUserBridge,
|
|
152
190
|
maybeExecInTerminal,
|
|
153
|
-
start() {
|
|
191
|
+
async start() {
|
|
154
192
|
if (started)
|
|
155
193
|
return;
|
|
156
194
|
started = true;
|
|
157
195
|
// 4.6a:上次进程崩溃/强杀留下的 running 任务不是真相——任务流随进程
|
|
158
196
|
// 一起死了,启动时落成 failed,任务面板据此显示"重启中断"。
|
|
159
|
-
const sweptTasks =
|
|
197
|
+
const sweptTasks = await defaultStore.failRunningTasks(options.taskSweepReason);
|
|
160
198
|
if (sweptTasks > 0) {
|
|
161
199
|
onLog(`[task] swept ${sweptTasks} stale running task(s)`);
|
|
162
200
|
}
|
|
@@ -164,8 +202,9 @@ export function createHostRuntime(options) {
|
|
|
164
202
|
// Not awaited: sidecar boot (python import) must not delay readiness.
|
|
165
203
|
// A turn that races the boot falls back for that turn only.
|
|
166
204
|
void startHostSidecar({
|
|
205
|
+
store: defaultStore,
|
|
167
206
|
toolRouter,
|
|
168
|
-
resolveProjectRoot: (chatId) => localBackendRouter.resolveChatProject(chatId)?.folderPath ?? null,
|
|
207
|
+
resolveProjectRoot: async (chatId) => (await localBackendRouter.resolveChatProject(chatId))?.folderPath ?? null,
|
|
169
208
|
// 项目模式下文件读写被围栏在项目目录内;会话附件目录是额外放行的
|
|
170
209
|
// 只读根,保证用户上传的文件即使在项目会话里也能被 agent 读回。
|
|
171
210
|
resolveAdditionalReadRoots: (chatId) => [getChatAttachmentsDir(chatId)],
|
|
@@ -201,6 +240,7 @@ export function createHostRuntime(options) {
|
|
|
201
240
|
await handle.dispose?.();
|
|
202
241
|
}
|
|
203
242
|
await shutdownHostSidecar();
|
|
243
|
+
await closeStorage();
|
|
204
244
|
},
|
|
205
245
|
};
|
|
206
246
|
}
|
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;
|
|
@@ -48,6 +48,11 @@ export interface BuildPromptOptions {
|
|
|
48
48
|
* 仍然优先——模式级排除(plan 模式的执行类技能)不因勾选而失效。
|
|
49
49
|
*/
|
|
50
50
|
pinnedSkillNames?: Iterable<string>;
|
|
51
|
+
/**
|
|
52
|
+
* 本轮自称。有绑定智能体时用智能体显示名,渲染技能正文 `{agentName}`;
|
|
53
|
+
* 未传则回落产品品牌 `brand.agentName`。
|
|
54
|
+
*/
|
|
55
|
+
identityName?: string | null;
|
|
51
56
|
}
|
|
52
57
|
export interface BuiltPrompt {
|
|
53
58
|
prompt: string;
|
|
@@ -84,11 +89,12 @@ export declare function conditionsFromTools(toolNames: Iterable<string>, autoHas
|
|
|
84
89
|
*/
|
|
85
90
|
export declare function buildSystemPrompt(options?: BuildPromptOptions): Promise<BuiltPrompt>;
|
|
86
91
|
/**
|
|
87
|
-
*
|
|
88
|
-
* `{
|
|
89
|
-
* 中性默认)。eager 系统提示词路径统一注入;包技能同理可用。
|
|
92
|
+
* 品牌/身份占位符:`{brandDisplayName}` / `{brandTagline}` 始终是产品品牌;
|
|
93
|
+
* `{agentName}` 有本轮智能体时用智能体显示名,否则回落 `brand.agentName`。
|
|
90
94
|
*/
|
|
91
|
-
export declare function brandSkillVars(
|
|
95
|
+
export declare function brandSkillVars(options?: {
|
|
96
|
+
identityName?: string | null;
|
|
97
|
+
}): Record<string, string>;
|
|
92
98
|
/** 技能正文渲染:把 `{scripts}` 及额外占位符解析为绝对路径。 */
|
|
93
99
|
export declare function renderSkillContent(m: SkillModule, vars?: Record<string, string>): string;
|
|
94
100
|
/** 用户显式指定技能的优先级指令头,渲染在该技能内容之前。 */
|