c0de-agent 1.2.0 → 1.4.0
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/cli/deps.js +4 -1
- package/dist/core/agent.js +15 -8
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.js +2 -1
- package/dist/core/loop.js +16 -1
- package/dist/core/slash.js +73 -0
- package/dist/core/types.d.ts +1 -0
- package/dist/core/workflow.d.ts +14 -3
- package/dist/core/workflow.js +34 -12
- package/dist/core/workflows/builtins.d.ts +7 -0
- package/dist/core/workflows/builtins.js +192 -0
- package/dist/core/workflows/context.d.ts +17 -0
- package/dist/core/workflows/context.js +232 -0
- package/dist/core/workflows/discovery.d.ts +11 -0
- package/dist/core/workflows/discovery.js +69 -0
- package/dist/core/workflows/index.d.ts +7 -0
- package/dist/core/workflows/index.js +5 -0
- package/dist/core/workflows/registry.d.ts +20 -0
- package/dist/core/workflows/registry.js +49 -0
- package/dist/core/workflows/runtime.d.ts +18 -0
- package/dist/core/workflows/runtime.js +70 -0
- package/dist/core/workflows/types.d.ts +95 -0
- package/dist/core/workflows/types.js +1 -0
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.js +1 -1
- package/dist/llm/registry.d.ts +18 -2
- package/dist/llm/registry.js +40 -11
- package/dist/server/app.js +3 -0
- package/dist/server/context.js +11 -0
- package/dist/server/routes/chat.js +13 -3
- package/dist/server/routes/session.js +6 -1
- package/dist/server/routes/workflows.d.ts +5 -0
- package/dist/server/routes/workflows.js +149 -0
- package/dist/server/server.js +18 -2
- package/dist/server/types.d.ts +2 -0
- package/dist/session/branch.d.ts +3 -1
- package/dist/session/branch.js +9 -2
- package/dist/session/session.d.ts +3 -1
- package/dist/session/session.js +12 -1
- package/dist/shared/types/message.d.ts +2 -0
- package/package.json +1 -1
package/dist/llm/registry.js
CHANGED
|
@@ -15,6 +15,44 @@ const registerProvider = (registry, input) => {
|
|
|
15
15
|
models: input.models ?? {},
|
|
16
16
|
});
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Default capabilities for models not explicitly declared in the route's models map.
|
|
20
|
+
*
|
|
21
|
+
* Modern models overwhelmingly support ≥128k context windows; the previous 8192
|
|
22
|
+
* default caused false-positive compaction deadlocks on any provider that
|
|
23
|
+
* didn't declare per-model capabilities. 128k is a conservative floor that
|
|
24
|
+
* matches the most common modern context window.
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_MODEL_CAPABILITIES = {
|
|
27
|
+
contextWindow: 128_000,
|
|
28
|
+
maxOutput: 8192,
|
|
29
|
+
supportsTools: true,
|
|
30
|
+
supportsVision: false,
|
|
31
|
+
supportsThinking: false,
|
|
32
|
+
costPer1kInput: 0,
|
|
33
|
+
costPer1kOutput: 0,
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Merge sparse per-model overrides from config (ModelOverride) into full
|
|
37
|
+
* ModelCapabilities by filling gaps with DEFAULT_MODEL_CAPABILITIES.
|
|
38
|
+
* Strips `enabled` (a UI-only concern) — the registry tracks all models
|
|
39
|
+
* regardless of selector visibility.
|
|
40
|
+
*/
|
|
41
|
+
function overrideToCapabilities(overrides) {
|
|
42
|
+
const result = {};
|
|
43
|
+
for (const [name, o] of Object.entries(overrides)) {
|
|
44
|
+
result[name] = {
|
|
45
|
+
contextWindow: o.contextWindow ?? DEFAULT_MODEL_CAPABILITIES.contextWindow,
|
|
46
|
+
maxOutput: o.maxOutput ?? DEFAULT_MODEL_CAPABILITIES.maxOutput,
|
|
47
|
+
supportsTools: o.supportsTools ?? DEFAULT_MODEL_CAPABILITIES.supportsTools,
|
|
48
|
+
supportsVision: o.supportsVision ?? DEFAULT_MODEL_CAPABILITIES.supportsVision,
|
|
49
|
+
supportsThinking: o.supportsThinking ?? DEFAULT_MODEL_CAPABILITIES.supportsThinking,
|
|
50
|
+
costPer1kInput: o.costPer1kInput ?? DEFAULT_MODEL_CAPABILITIES.costPer1kInput,
|
|
51
|
+
costPer1kOutput: o.costPer1kOutput ?? DEFAULT_MODEL_CAPABILITIES.costPer1kOutput,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
18
56
|
/**
|
|
19
57
|
* Resolve a (provider, modelId) pair into a route + typed model.
|
|
20
58
|
* Throws NoRoute when the provider is unknown.
|
|
@@ -29,16 +67,7 @@ const resolveRoute = (registry, provider, modelId) => {
|
|
|
29
67
|
model: modelId,
|
|
30
68
|
});
|
|
31
69
|
}
|
|
32
|
-
const capabilities = route.models[modelId] ??
|
|
33
|
-
{
|
|
34
|
-
contextWindow: 8192,
|
|
35
|
-
maxOutput: 4096,
|
|
36
|
-
supportsTools: true,
|
|
37
|
-
supportsVision: false,
|
|
38
|
-
supportsThinking: false,
|
|
39
|
-
costPer1kInput: 0,
|
|
40
|
-
costPer1kOutput: 0,
|
|
41
|
-
};
|
|
70
|
+
const capabilities = route.models[modelId] ?? DEFAULT_MODEL_CAPABILITIES;
|
|
42
71
|
return {
|
|
43
72
|
route,
|
|
44
73
|
model: makeModel(modelId, provider, {
|
|
@@ -104,4 +133,4 @@ const builtinCapabilities = {
|
|
|
104
133
|
},
|
|
105
134
|
},
|
|
106
135
|
};
|
|
107
|
-
export { builtinCapabilities, createRegistry, registerProvider, resolveModelByRole, resolveRoute, setRole, };
|
|
136
|
+
export { builtinCapabilities, createRegistry, DEFAULT_MODEL_CAPABILITIES, overrideToCapabilities, registerProvider, resolveModelByRole, resolveRoute, setRole, };
|
package/dist/server/app.js
CHANGED
|
@@ -21,6 +21,7 @@ import { createSessionRoute } from './routes/session.js';
|
|
|
21
21
|
import { createTerminalRoute } from './routes/terminal.js';
|
|
22
22
|
import { createToolRoute } from './routes/tool.js';
|
|
23
23
|
import { createUpdateRoute } from './routes/update.js';
|
|
24
|
+
import { createWorkflowsRoute } from './routes/workflows.js';
|
|
24
25
|
/** 创建完整的 Hono 应用,挂载所有路由 + 中间件。 */
|
|
25
26
|
function createApp(ctx) {
|
|
26
27
|
const app = new Hono();
|
|
@@ -46,6 +47,7 @@ function createApp(ctx) {
|
|
|
46
47
|
app.route('/api/permissions', createPermissionsRoute(ctx));
|
|
47
48
|
app.route('/api/files', createFilesRoute(ctx));
|
|
48
49
|
app.route('/api/terminal', createTerminalRoute(ctx));
|
|
50
|
+
app.route('/api/workflows', createWorkflowsRoute(ctx));
|
|
49
51
|
// 根路径
|
|
50
52
|
app.get('/', (c) => c.json({
|
|
51
53
|
name: 'c0de-agent',
|
|
@@ -66,6 +68,7 @@ function createApp(ctx) {
|
|
|
66
68
|
'/api/permissions',
|
|
67
69
|
'/api/files',
|
|
68
70
|
'/api/terminal',
|
|
71
|
+
'/api/workflows',
|
|
69
72
|
],
|
|
70
73
|
}));
|
|
71
74
|
// 静态文件服务(生产环境 dist-web/ 存在时启用)
|
package/dist/server/context.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/server/context.ts
|
|
2
2
|
import { BUILTIN_AGENTS, createAgentRegistry } from '../core/agents/index.js';
|
|
3
3
|
import { DEFAULT_CONFIG, mergeConfig } from '../core/config.js';
|
|
4
|
+
import { BUILTIN_WORKFLOWS, createWorkflowRegistry } from '../core/workflows/index.js';
|
|
4
5
|
import { createHookRunner, createPluginRegistry } from '../plugins/index.js';
|
|
5
6
|
import { createDefaultRegistry, createDefaultURLRegistry } from '../tools/index.js';
|
|
6
7
|
import { createUpdateScheduler } from '../update/index.js';
|
|
@@ -21,6 +22,7 @@ function createServerContext(opts) {
|
|
|
21
22
|
reg.register(def);
|
|
22
23
|
return reg;
|
|
23
24
|
})();
|
|
25
|
+
let _workflowRegistry;
|
|
24
26
|
return {
|
|
25
27
|
db: opts.db,
|
|
26
28
|
config,
|
|
@@ -33,6 +35,15 @@ function createServerContext(opts) {
|
|
|
33
35
|
permissionStore: createPermissionStore(),
|
|
34
36
|
permissionMode: config.permission.defaultMode,
|
|
35
37
|
agentRegistry,
|
|
38
|
+
// 工作流注册表:惰性初始化,只含内置(项目级 discovery 由 bootstrap 或 API 触发热加载)。
|
|
39
|
+
get workflowRegistry() {
|
|
40
|
+
if (!_workflowRegistry) {
|
|
41
|
+
_workflowRegistry = createWorkflowRegistry();
|
|
42
|
+
for (const wf of BUILTIN_WORKFLOWS)
|
|
43
|
+
_workflowRegistry.register(wf);
|
|
44
|
+
}
|
|
45
|
+
return _workflowRegistry;
|
|
46
|
+
},
|
|
36
47
|
// 测试上下文:默认 scheduler 不启动(enabled=false 由调用方控制)。
|
|
37
48
|
updateScheduler: createUpdateScheduler({
|
|
38
49
|
checkFn: async () => ({
|
|
@@ -5,7 +5,7 @@ import { createAgent, runAgent } from '../../core/agent.js';
|
|
|
5
5
|
import { compactContext } from '../../core/loop.js';
|
|
6
6
|
import { createSlashRegistry, parseSlashInput } from '../../core/slash.js';
|
|
7
7
|
import { injectSteering } from '../../core/steering.js';
|
|
8
|
-
import {
|
|
8
|
+
import { buildWorkflowNotice, containsWorkflow } from '../../core/workflow.js';
|
|
9
9
|
import { getProject } from '../../project/project.js';
|
|
10
10
|
import { getLLMSegments, getSession, updateSessionLastRun } from '../../session/session.js';
|
|
11
11
|
import { upsertFileSnapshot } from '../../session/snapshot.js';
|
|
@@ -53,7 +53,10 @@ function createChatRoute(ctx) {
|
|
|
53
53
|
cwd,
|
|
54
54
|
config: ctx.config,
|
|
55
55
|
// 内置斜杠命令(/clear、/fork、/config)仅需 db + config;
|
|
56
|
-
//
|
|
56
|
+
// 但 /workflow run 会走 executeWorkflow → buildWorkflowContext → runSubAgent,
|
|
57
|
+
// 该路径需要 agentRegistry 来派生子 agent,因此必须注入。
|
|
58
|
+
// permission/toolRegistry 用 autoAllow 凑齐类型(子命令不触发交互权限)。
|
|
59
|
+
workflowRegistry: ctx.workflowRegistry,
|
|
57
60
|
deps: {
|
|
58
61
|
db: ctx.db,
|
|
59
62
|
config: ctx.config,
|
|
@@ -61,6 +64,7 @@ function createChatRoute(ctx) {
|
|
|
61
64
|
permission: autoAllowChecker,
|
|
62
65
|
toolRegistry: ctx.toolRegistry,
|
|
63
66
|
llmRegistry: ctx.llmRegistry,
|
|
67
|
+
agentRegistry: ctx.agentRegistry,
|
|
64
68
|
},
|
|
65
69
|
};
|
|
66
70
|
return streamSSE(c, async (stream) => {
|
|
@@ -258,7 +262,13 @@ function createChatRoute(ctx) {
|
|
|
258
262
|
// workflowz 关键词检测:用户消息包含独立关键词时注入工作流通知(steering),
|
|
259
263
|
// 引导模型用 task 工具批量 fan-out 做确定性多子 agent 分解。
|
|
260
264
|
if (containsWorkflow(message)) {
|
|
261
|
-
|
|
265
|
+
const wfList = ctx.workflowRegistry
|
|
266
|
+
? ctx.workflowRegistry.list().map((w) => ({
|
|
267
|
+
name: w.meta.name,
|
|
268
|
+
description: w.meta.description,
|
|
269
|
+
}))
|
|
270
|
+
: [];
|
|
271
|
+
injectSteering(state, buildWorkflowNotice(wfList));
|
|
262
272
|
}
|
|
263
273
|
// 客户端断开时中止 agent
|
|
264
274
|
stream.onAbort(() => {
|
|
@@ -4,7 +4,7 @@ import { fromDirectory } from '../../project/index.js';
|
|
|
4
4
|
import { archiveOriginalEntries } from '../../session/archive.js';
|
|
5
5
|
import { forkSession, getBranches, getTree } from '../../session/branch.js';
|
|
6
6
|
import { deleteEntriesByIds, getMessages, insertEntry } from '../../session/message.js';
|
|
7
|
-
import { createSession, deleteSession, getLLMSegments, getSession, listSessions, listSessionsByProject, } from '../../session/session.js';
|
|
7
|
+
import { createSession, deleteSession, getLLMSegments, getSession, listSessions, listSessionsByProject, touchLastOpened, } from '../../session/session.js';
|
|
8
8
|
import { applyShakeRegions, collectShakeRegions, DEFAULT_SHAKE_CONFIG, toRegionViews, } from '../../session/shake.js';
|
|
9
9
|
import { estimateMessageTokens } from '../../session/token.js';
|
|
10
10
|
import { generateId } from '../../shared/index.js';
|
|
@@ -194,6 +194,11 @@ function createSessionRoute(ctx) {
|
|
|
194
194
|
}
|
|
195
195
|
return c.json({ shaken: selected.length, archiveId });
|
|
196
196
|
});
|
|
197
|
+
// 记录会话打开(更新 metadata.lastOpenedAt,用于会话列表按最近打开排序)
|
|
198
|
+
app.post('/:id/open', async (c) => {
|
|
199
|
+
await touchLastOpened(ctx.db, c.req.param('id'));
|
|
200
|
+
return c.json({ ok: true });
|
|
201
|
+
});
|
|
197
202
|
// 获取分支
|
|
198
203
|
app.get('/:id/branches', async (c) => {
|
|
199
204
|
const branches = await getBranches(ctx.db, c.req.param('id'));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import type { ServerContext } from '../types.js';
|
|
3
|
+
/** 创建工作流 REST API 路由。 */
|
|
4
|
+
declare function createWorkflowsRoute(ctx: ServerContext): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
5
|
+
export { createWorkflowsRoute };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { unlink } from 'node:fs/promises';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { streamSSE } from 'hono/streaming';
|
|
4
|
+
import { createAgent } from '../../core/agent.js';
|
|
5
|
+
import { executeWorkflow } from '../../core/workflows/runtime.js';
|
|
6
|
+
import { createSession } from '../../session/session.js';
|
|
7
|
+
import { autoAllowChecker } from '../../tools/permission.js';
|
|
8
|
+
import { apiError } from '../middleware/error.js';
|
|
9
|
+
/** 创建工作流 REST API 路由。 */
|
|
10
|
+
function createWorkflowsRoute(ctx) {
|
|
11
|
+
const app = new Hono();
|
|
12
|
+
// GET / — 列出所有工作流
|
|
13
|
+
app.get('/', (c) => {
|
|
14
|
+
const registry = ctx.workflowRegistry;
|
|
15
|
+
if (!registry) {
|
|
16
|
+
return c.json({ workflows: [] });
|
|
17
|
+
}
|
|
18
|
+
const workflows = registry.list().map((entry) => ({
|
|
19
|
+
name: entry.meta.name,
|
|
20
|
+
description: entry.meta.description,
|
|
21
|
+
argsHint: entry.meta.argsHint,
|
|
22
|
+
phases: entry.meta.phases,
|
|
23
|
+
source: entry.source,
|
|
24
|
+
}));
|
|
25
|
+
return c.json({ workflows });
|
|
26
|
+
});
|
|
27
|
+
// GET /:name — 元数据 + 源码
|
|
28
|
+
app.get('/:name', (c) => {
|
|
29
|
+
const name = c.req.param('name');
|
|
30
|
+
const registry = ctx.workflowRegistry;
|
|
31
|
+
if (!registry) {
|
|
32
|
+
return apiError(c, 500, 'NOT_INITIALIZED', 'Workflow registry not initialized');
|
|
33
|
+
}
|
|
34
|
+
const entry = registry.get(name);
|
|
35
|
+
if (!entry) {
|
|
36
|
+
return apiError(c, 404, 'NOT_FOUND', `Workflow "${name}" not found`);
|
|
37
|
+
}
|
|
38
|
+
return c.json({
|
|
39
|
+
name: entry.meta.name,
|
|
40
|
+
description: entry.meta.description,
|
|
41
|
+
argsHint: entry.meta.argsHint,
|
|
42
|
+
phases: entry.meta.phases,
|
|
43
|
+
source: entry.source,
|
|
44
|
+
sourceCode: entry.sourceCode,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
// POST /:name/run — 执行工作流(SSE 推送进度)
|
|
48
|
+
app.post('/:name/run', async (c) => {
|
|
49
|
+
const name = c.req.param('name');
|
|
50
|
+
const registry = ctx.workflowRegistry;
|
|
51
|
+
if (!registry) {
|
|
52
|
+
return apiError(c, 500, 'NOT_INITIALIZED', 'Workflow registry not initialized');
|
|
53
|
+
}
|
|
54
|
+
const entry = registry.get(name);
|
|
55
|
+
if (!entry) {
|
|
56
|
+
return apiError(c, 404, 'NOT_FOUND', `Workflow "${name}" not found`);
|
|
57
|
+
}
|
|
58
|
+
const body = await c.req.json().catch(() => ({}));
|
|
59
|
+
const args = body.args ?? '';
|
|
60
|
+
const agentConfig = {
|
|
61
|
+
provider: ctx.config.defaultProvider,
|
|
62
|
+
model: ctx.config.defaultModel,
|
|
63
|
+
tools: [],
|
|
64
|
+
plugins: ctx.config.plugins.enabled,
|
|
65
|
+
agentName: 'default',
|
|
66
|
+
};
|
|
67
|
+
const session = await createSession(ctx.db, `workflow:${name}`, undefined, 'workflow');
|
|
68
|
+
const parent = await createAgent(session, agentConfig, {
|
|
69
|
+
db: ctx.db,
|
|
70
|
+
llmRegistry: ctx.llmRegistry,
|
|
71
|
+
toolRegistry: ctx.toolRegistry,
|
|
72
|
+
permission: autoAllowChecker,
|
|
73
|
+
config: ctx.config,
|
|
74
|
+
cwd: ctx.cwd,
|
|
75
|
+
agentRegistry: ctx.agentRegistry,
|
|
76
|
+
});
|
|
77
|
+
return streamSSE(c, async (stream) => {
|
|
78
|
+
const deps = {
|
|
79
|
+
db: ctx.db,
|
|
80
|
+
llmRegistry: ctx.llmRegistry,
|
|
81
|
+
toolRegistry: ctx.toolRegistry,
|
|
82
|
+
permission: autoAllowChecker,
|
|
83
|
+
config: ctx.config,
|
|
84
|
+
cwd: ctx.cwd,
|
|
85
|
+
agentRegistry: ctx.agentRegistry,
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
const result = await executeWorkflow({
|
|
89
|
+
registry,
|
|
90
|
+
name,
|
|
91
|
+
args,
|
|
92
|
+
deps,
|
|
93
|
+
parent,
|
|
94
|
+
onProgress: async (message, detail) => {
|
|
95
|
+
await stream.writeSSE({
|
|
96
|
+
event: 'progress',
|
|
97
|
+
data: JSON.stringify({ message, detail }),
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
await stream.writeSSE({
|
|
102
|
+
event: 'result',
|
|
103
|
+
data: JSON.stringify(result),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
await stream.writeSSE({
|
|
108
|
+
event: 'error',
|
|
109
|
+
data: JSON.stringify({
|
|
110
|
+
_tag: 'error',
|
|
111
|
+
message: e instanceof Error ? e.message : String(e),
|
|
112
|
+
}),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
// DELETE /:name — 删除(仅非 builtin)
|
|
118
|
+
app.delete('/:name', async (c) => {
|
|
119
|
+
const name = c.req.param('name');
|
|
120
|
+
// 名称格式校验(涉及文件系统操作前阻断路径穿越)
|
|
121
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
122
|
+
return apiError(c, 400, 'BAD_REQUEST', `Invalid workflow name "${name}"`);
|
|
123
|
+
}
|
|
124
|
+
const registry = ctx.workflowRegistry;
|
|
125
|
+
if (!registry) {
|
|
126
|
+
return apiError(c, 500, 'NOT_INITIALIZED', 'Workflow registry not initialized');
|
|
127
|
+
}
|
|
128
|
+
const entry = registry.get(name);
|
|
129
|
+
if (!entry) {
|
|
130
|
+
return apiError(c, 404, 'NOT_FOUND', `Workflow "${name}" not found`);
|
|
131
|
+
}
|
|
132
|
+
if (entry.source === 'builtin') {
|
|
133
|
+
return apiError(c, 400, 'BAD_REQUEST', 'Cannot delete builtin workflow');
|
|
134
|
+
}
|
|
135
|
+
// 先从磁盘删除文件(若存在),失败则告知用户且不清理 registry 以保持状态一致
|
|
136
|
+
if (entry.filePath) {
|
|
137
|
+
try {
|
|
138
|
+
await unlink(entry.filePath);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return apiError(c, 500, 'DELETE_FAILED', `Failed to delete workflow file for "${name}"`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
registry.delete(name);
|
|
145
|
+
return c.json({ ok: true });
|
|
146
|
+
});
|
|
147
|
+
return app;
|
|
148
|
+
}
|
|
149
|
+
export { createWorkflowsRoute };
|
package/dist/server/server.js
CHANGED
|
@@ -7,15 +7,16 @@ import { WebSocketServer } from 'ws';
|
|
|
7
7
|
import { BUILTIN_AGENTS, createAgentRegistry } from '../core/agents/index.js';
|
|
8
8
|
import { loadConfig } from '../core/config.js';
|
|
9
9
|
import { decryptSecret } from '../core/secret.js';
|
|
10
|
+
import { BUILTIN_WORKFLOWS, createWorkflowRegistry } from '../core/workflows/index.js';
|
|
10
11
|
import { createDB, migrateDB } from '../db/index.js';
|
|
11
|
-
import { createRegistry, registerProvider } from '../llm/registry.js';
|
|
12
|
+
import { createRegistry, overrideToCapabilities, registerProvider, } from '../llm/registry.js';
|
|
12
13
|
import { initPlugins } from '../plugins/index.js';
|
|
13
14
|
import { createDefaultRegistry, createDefaultURLRegistry } from '../tools/index.js';
|
|
14
15
|
import { checkForUpdate, createHandoffServer, createUpdateScheduler, requestHandoff, restoreSessions, } from '../update/index.js';
|
|
15
16
|
import { createAgentManager } from './agent-manager.js';
|
|
16
|
-
import { PTYManager } from './terminal/pty-manager.js';
|
|
17
17
|
import { createApp } from './app.js';
|
|
18
18
|
import { createPermissionStore } from './permission/store.js';
|
|
19
|
+
import { PTYManager } from './terminal/pty-manager.js';
|
|
19
20
|
/** 把 config.providers 注册到新建的 LLM registry(修复此前空 registry 的遗漏)。 */
|
|
20
21
|
function buildRegistryFromConfig(config) {
|
|
21
22
|
const registry = createRegistry();
|
|
@@ -36,6 +37,9 @@ function registerProviderFromConfig(registry, p) {
|
|
|
36
37
|
baseURL: p.baseURL,
|
|
37
38
|
apiKey: p.apiKey ? decryptSecret(p.apiKey) : p.apiKey,
|
|
38
39
|
...(path ? { path } : {}),
|
|
40
|
+
// 传递用户配置的 per-model capabilities(contextWindow 等),
|
|
41
|
+
// 否则 resolveRoute 回退到 DEFAULT_MODEL_CAPABILITIES,可能导致预算过小。
|
|
42
|
+
...(p.models ? { models: overrideToCapabilities(p.models) } : {}),
|
|
39
43
|
});
|
|
40
44
|
}
|
|
41
45
|
/**
|
|
@@ -79,6 +83,8 @@ async function buildServerContext(db, opts = {}) {
|
|
|
79
83
|
toolRegistry,
|
|
80
84
|
llmRegistry,
|
|
81
85
|
});
|
|
86
|
+
// workflowRegistry 惰性初始化 backing field(见下方 ctx getter)。
|
|
87
|
+
let _workflowRegistry;
|
|
82
88
|
const ctx = {
|
|
83
89
|
db,
|
|
84
90
|
config,
|
|
@@ -97,6 +103,16 @@ async function buildServerContext(db, opts = {}) {
|
|
|
97
103
|
reg.register(def);
|
|
98
104
|
return reg;
|
|
99
105
|
})(),
|
|
106
|
+
// 工作流注册表:惰性初始化,只含内置(项目级 discovery 由 bootstrap 或 API 触发热加载)。
|
|
107
|
+
// 与 context.ts 的 createServerContext 保持一致的模式。
|
|
108
|
+
get workflowRegistry() {
|
|
109
|
+
if (!_workflowRegistry) {
|
|
110
|
+
_workflowRegistry = createWorkflowRegistry();
|
|
111
|
+
for (const wf of BUILTIN_WORKFLOWS)
|
|
112
|
+
_workflowRegistry.register(wf);
|
|
113
|
+
}
|
|
114
|
+
return _workflowRegistry;
|
|
115
|
+
},
|
|
100
116
|
// spec §18.1 后台版本检查调度器;config.update.enabled 控制是否启动。
|
|
101
117
|
updateScheduler: createUpdateScheduler({
|
|
102
118
|
checkFn: opts.checkForUpdateFn ?? checkForUpdate,
|
package/dist/server/types.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ type ServerContext = {
|
|
|
30
30
|
permissionMode: 'default' | 'auto';
|
|
31
31
|
/** Agent 类型注册表(spec: multi-agent-design)。注入 agent loop 的 runSubAgent。 */
|
|
32
32
|
agentRegistry: AgentRegistry;
|
|
33
|
+
/** 工作流注册表(spec: dynamic-workflow-design)。注入 /workflow slash 命令和 workflowz steering。 */
|
|
34
|
+
workflowRegistry?: import('../core/workflows/registry.js').WorkflowRegistry;
|
|
33
35
|
/** 后台版本检查调度器(spec §18.1);/api/update 读取其缓存结果。 */
|
|
34
36
|
updateScheduler: UpdateScheduler;
|
|
35
37
|
/** Handoff HTTP 端点(spec §18.3);热更新时新实例 POST /handoff 触发优雅退出。 undefined 表示未启用。 */
|
package/dist/session/branch.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { Session, SessionTreeNode } from './types.js';
|
|
|
4
4
|
declare function forkSession(handle: DB, sessionId: string, messageIndex: number): Promise<Session>;
|
|
5
5
|
/** Get direct child sessions (branches) of a session. */
|
|
6
6
|
declare function getBranches(handle: DB, sessionId: string): Promise<Session[]>;
|
|
7
|
-
/** Build a full session tree from root sessions down.
|
|
7
|
+
/** Build a full session tree from root sessions down.
|
|
8
|
+
* 每层按 metadata.lastOpenedAt 降序(fallback updatedAt、createdAt)。
|
|
9
|
+
*/
|
|
8
10
|
declare function getTree(handle: DB): Promise<SessionTreeNode[]>;
|
|
9
11
|
export { forkSession, getBranches, getTree };
|
package/dist/session/branch.js
CHANGED
|
@@ -44,7 +44,9 @@ async function getBranches(handle, sessionId) {
|
|
|
44
44
|
const rows = await handle.db.select().from(sessions).where(eq(sessions.parentId, sessionId));
|
|
45
45
|
return rows.map(rowToSession);
|
|
46
46
|
}
|
|
47
|
-
/** Build a full session tree from root sessions down.
|
|
47
|
+
/** Build a full session tree from root sessions down.
|
|
48
|
+
* 每层按 metadata.lastOpenedAt 降序(fallback updatedAt、createdAt)。
|
|
49
|
+
*/
|
|
48
50
|
async function getTree(handle) {
|
|
49
51
|
const rows = await handle.db.select().from(sessions);
|
|
50
52
|
const byParent = new Map();
|
|
@@ -54,7 +56,12 @@ async function getTree(handle) {
|
|
|
54
56
|
list.push(session);
|
|
55
57
|
byParent.set(session.parentId, list);
|
|
56
58
|
}
|
|
57
|
-
|
|
59
|
+
// 排序键:lastOpenedAt > updatedAt > createdAt(均为 epoch ms)
|
|
60
|
+
const sortKey = (s) => s.metadata.lastOpenedAt ?? s.updatedAt ?? s.createdAt ?? 0;
|
|
61
|
+
const build = (parentId) => (byParent.get(parentId) ?? [])
|
|
62
|
+
.slice()
|
|
63
|
+
.sort((a, b) => sortKey(b) - sortKey(a))
|
|
64
|
+
.map((session) => ({
|
|
58
65
|
session,
|
|
59
66
|
children: build(session.id),
|
|
60
67
|
}));
|
|
@@ -17,6 +17,8 @@ declare function deleteSession(handle: DB, id: string): Promise<void>;
|
|
|
17
17
|
declare function updateSessionTitle(handle: DB, id: string, title: string): Promise<void>;
|
|
18
18
|
/** Bump updatedAt to now (used after appending messages). */
|
|
19
19
|
declare function touchSession(handle: DB, id: string): Promise<void>;
|
|
20
|
+
/** 记录会话上次打开时间(用于会话列表按最近打开排序)。 */
|
|
21
|
+
declare function touchLastOpened(handle: DB, id: string): Promise<void>;
|
|
20
22
|
/** 规格化工具集并计算前缀指纹。tools 顺序不影响指纹(按 name 排序)。 */
|
|
21
23
|
export declare function segmentFingerprint(systemPrompt: string, tools: ChatTool[]): string;
|
|
22
24
|
/**
|
|
@@ -34,4 +36,4 @@ export declare function saveLLMSegments(handle: DB, id: string, segments: LLMSeg
|
|
|
34
36
|
/** 更新会话 metadata.lastRun(agent run 开始/结束时写入;重启后检测中断用)。 */
|
|
35
37
|
declare function updateSessionLastRun(handle: DB, id: string, lastRun: LastRun): Promise<void>;
|
|
36
38
|
declare function listSessionsByProject(handle: DB, projectId: string): Promise<Session[]>;
|
|
37
|
-
export { createSession, deleteSession, getSession, listSessions, listSessionsByProject, touchSession, updateSessionLastRun, updateSessionTitle, };
|
|
39
|
+
export { createSession, deleteSession, getSession, listSessions, listSessionsByProject, touchLastOpened, touchSession, updateSessionLastRun, updateSessionTitle, };
|
package/dist/session/session.js
CHANGED
|
@@ -50,6 +50,17 @@ async function updateSessionTitle(handle, id, title) {
|
|
|
50
50
|
async function touchSession(handle, id) {
|
|
51
51
|
await handle.db.update(sessions).set({ updatedAt: new Date() }).where(eq(sessions.id, id));
|
|
52
52
|
}
|
|
53
|
+
/** 记录会话上次打开时间(用于会话列表按最近打开排序)。 */
|
|
54
|
+
async function touchLastOpened(handle, id) {
|
|
55
|
+
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
56
|
+
if (!row)
|
|
57
|
+
return;
|
|
58
|
+
const meta = (row.metadata ?? {});
|
|
59
|
+
await handle.db
|
|
60
|
+
.update(sessions)
|
|
61
|
+
.set({ metadata: { ...meta, lastOpenedAt: Date.now() } })
|
|
62
|
+
.where(eq(sessions.id, id));
|
|
63
|
+
}
|
|
53
64
|
/** 规格化工具集并计算前缀指纹。tools 顺序不影响指纹(按 name 排序)。 */
|
|
54
65
|
export function segmentFingerprint(systemPrompt, tools) {
|
|
55
66
|
const norm = JSON.stringify({
|
|
@@ -141,4 +152,4 @@ async function listSessionsByProject(handle, projectId) {
|
|
|
141
152
|
const rows = await handle.db.select().from(sessions).where(eq(sessions.projectId, projectId));
|
|
142
153
|
return rows.map(rowToSession);
|
|
143
154
|
}
|
|
144
|
-
export { createSession, deleteSession, getSession, listSessions, listSessionsByProject, touchSession, updateSessionLastRun, updateSessionTitle, };
|
|
155
|
+
export { createSession, deleteSession, getSession, listSessions, listSessionsByProject, touchLastOpened, touchSession, updateSessionLastRun, updateSessionTitle, };
|
|
@@ -52,6 +52,8 @@ type SessionMetadata = {
|
|
|
52
52
|
segments?: LLMSegment[];
|
|
53
53
|
/** 上次 agent run 状态;status='running' 且进程无活跃 run → 被中断。 */
|
|
54
54
|
lastRun?: LastRun;
|
|
55
|
+
/** 上次打开时间戳(ms),用于会话列表按最近打开排序。 */
|
|
56
|
+
lastOpenedAt?: number;
|
|
55
57
|
};
|
|
56
58
|
/** A conversation session (may have a parent for branching). */
|
|
57
59
|
type Session = {
|