shennian 0.3.85 → 0.3.86
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/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +28 -28
- package/dist/src/agents/adapter.js +19 -1
- package/dist/src/agents/claude-managed-permissions.js +248 -1
- package/dist/src/agents/claude.js +393 -8
- package/dist/src/agents/codex-managed-permissions.js +294 -1
- package/dist/src/agents/codex-managed-room-tools.js +249 -1
- package/dist/src/agents/codex-utils.js +200 -7
- package/dist/src/agents/codex.js +1352 -17
- package/dist/src/agents/config-status.js +226 -1
- package/dist/src/agents/cursor-managed-permissions.js +299 -2
- package/dist/src/agents/cursor.js +314 -1
- package/dist/src/agents/custom.js +418 -4
- package/dist/src/agents/external-channel-instructions.js +94 -11
- package/dist/src/agents/gemini.js +180 -1
- package/dist/src/agents/manager.d.ts +2 -0
- package/dist/src/agents/manager.js +250 -18
- package/dist/src/agents/model-registry/cache.js +37 -1
- package/dist/src/agents/model-registry/discovery.js +188 -2
- package/dist/src/agents/model-registry/parsers.js +521 -4
- package/dist/src/agents/model-registry/runner.js +30 -1
- package/dist/src/agents/model-registry/service.js +115 -1
- package/dist/src/agents/model-registry/types.js +8 -1
- package/dist/src/agents/model-registry.js +19 -1
- package/dist/src/agents/native-capabilities.js +237 -1
- package/dist/src/agents/openclaw.js +293 -2
- package/dist/src/agents/opencode-managed-permissions.js +187 -2
- package/dist/src/agents/opencode.js +298 -1
- package/dist/src/agents/platform-instructions.js +46 -17
- package/dist/src/agents/runtime-compatibility.js +44 -1
- package/dist/src/agents/workbuddy-managed-permissions.js +329 -2
- package/dist/src/agents/workbuddy.js +444 -3
- package/dist/src/commands/room.js +2 -2
- package/dist/src/fs/boundary.js +185 -1
- package/dist/src/fs/handler.js +130 -1
- package/dist/src/fs/security.js +32 -1
- package/dist/src/fs/text-decoder.js +110 -1
- package/dist/src/index.js +587 -3
- package/dist/src/manager/prompt.js +34 -29
- package/dist/src/manager/registry.js +317 -1
- package/dist/src/manager/retired-external-channel-compat.js +40 -1
- package/dist/src/manager/runtime.d.ts +4 -0
- package/dist/src/manager/runtime.js +1514 -23
- package/dist/src/native-fusion/codex-parser.js +660 -4
- package/dist/src/native-fusion/codex-user-input.js +102 -5
- package/dist/src/native-fusion/config.js +5 -1
- package/dist/src/native-fusion/file-watcher.js +191 -1
- package/dist/src/native-fusion/local-repository.js +569 -2
- package/dist/src/native-fusion/opencode-parser.js +130 -3
- package/dist/src/native-fusion/parsers.js +140 -3
- package/dist/src/native-fusion/service.js +739 -2
- package/dist/src/native-fusion/session-header.js +101 -2
- package/dist/src/native-fusion/state.js +104 -2
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/personal-sync/machine-authority.js +34 -1
- package/dist/src/room/activation-audit.js +45 -2
- package/dist/src/room/activation-policy.js +30 -1
- package/dist/src/room/activation-service.d.ts +1 -0
- package/dist/src/room/activation-service.js +408 -8
- package/dist/src/room/invite-bootstrap.js +156 -1
- package/dist/src/room/managed-context.d.ts +1 -1
- package/dist/src/room/managed-context.js +1 -1
- package/dist/src/room/managed-prompt-composer.d.ts +1 -1
- package/dist/src/room/managed-prompt-composer.js +6 -5
- package/dist/src/room/managed-room-binding.js +167 -1
- package/dist/src/room/managed-room-ipc.d.ts +2 -2
- package/dist/src/room/managed-room-ipc.js +2 -2
- package/dist/src/room/mirror-sync-service.js +183 -1
- package/dist/src/room/room-action-client.d.ts +11 -0
- package/dist/src/room/room-action-client.js +1 -1
- package/dist/src/room/room-binding-client.d.ts +1 -0
- package/dist/src/room/room-binding-client.js +151 -1
- package/dist/src/security/managed-agent-policy.d.ts +3 -2
- package/dist/src/security/managed-agent-policy.js +1 -1
- package/dist/src/session/archive-zip.js +220 -1
- package/dist/src/session/deletion-sync-state.js +136 -2
- package/dist/src/session/handlers/agent-capabilities.js +14 -1
- package/dist/src/session/handlers/agent-config.js +150 -1
- package/dist/src/session/handlers/agents.js +55 -1
- package/dist/src/session/handlers/chat.d.ts +1 -1
- package/dist/src/session/handlers/chat.js +1030 -2
- package/dist/src/session/handlers/control.js +61 -1
- package/dist/src/session/handlers/fs.js +594 -1
- package/dist/src/session/handlers/message-status.js +15 -1
- package/dist/src/session/handlers/remote-access.js +43 -1
- package/dist/src/session/handlers/room-managed.js +101 -1
- package/dist/src/session/handlers/session-refresh.js +35 -1
- package/dist/src/session/handlers/title.js +79 -1
- package/dist/src/session/handlers/tool-detail.js +149 -1
- package/dist/src/session/handlers/voice-window.js +62 -1
- package/dist/src/session/history-subscriptions.js +470 -1
- package/dist/src/session/manager.js +658 -1
- package/dist/src/session/native-cleanup-outbox.js +287 -2
- package/dist/src/session/native-session-lifecycle.js +327 -6
- package/dist/src/session/queue.d.ts +5 -0
- package/dist/src/session/queue.js +8 -8
- package/dist/src/upgrade/scheduler.js +126 -1
- package/node_modules/@shennian/wire/dist/agent-workspace.js +1 -1
- package/package.json +1 -1
|
@@ -1,32 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
// @arch docs/features/manager-agent.md
|
|
2
|
+
// @test src/__tests__/manager-inbox.test.ts
|
|
3
|
+
export const MANAGER_SYSTEM_PROMPT = `你是项目经理,是当前项目的管理者。
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
5
|
+
你的职责:
|
|
6
|
+
- 理解用户目标。
|
|
7
|
+
- 拆解任务。
|
|
8
|
+
- 创建、指派、观察和停止同一项目目录下的 worker Agent session。
|
|
9
|
+
- 汇总 worker 结果。
|
|
10
|
+
- 判断是否需要继续等待、调整安排、询问用户或验收。
|
|
11
|
+
- 在项目 .shennian/ 目录下维护必要的计划、记录和项目记忆。
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- sessions read
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
13
|
+
你的边界:
|
|
14
|
+
- 不要把自己当作主要执行者。
|
|
15
|
+
- 不要直接编辑业务代码,除非用户明确要求你亲自执行。
|
|
16
|
+
- 可以读取文件、搜索项目和检查上下文,以便做判断。
|
|
17
|
+
- 需要修改代码、运行测试、调研方案时,优先创建或指派 worker。
|
|
18
|
+
- 每次收到新任务、补充要求、纠偏或外部消息时,先用 sessions list 查看当前同项目 worker;如果可能相关,再用 sessions read 读取必要摘要后判断是否复用。
|
|
19
|
+
- 如果已有 worker 正在处理同一目标、同一功能区、同一文件范围或同一问题链路,优先用 sessions send 把新要求发给这个 worker;即使 worker 正忙也可以发送,默认会进入本机队列,不要因为它忙就新建 worker。
|
|
20
|
+
- 只有没有相关 worker、现有 worker 已明显不适合继续推进、或任务需要并行拆分给不同专长时,才创建新的 worker。
|
|
21
|
+
- 创建或指派 worker 后,除非用户明确要求你当场继续调度,否则回复用户已安排并结束当前 turn;不要主动轮询 worker 状态,神念会在 worker 终态或健康摘要到来时重新唤醒你。
|
|
22
|
+
- sessions read 返回的是给管理者看的简洁进展、工具摘要和最终结果,不是原始流式 token;不要要求读取或转述完整流式日志。
|
|
23
|
+
- 只能管理与你处于同一台机器、同一项目目录的会话;不要跨机器或跨项目调度。
|
|
24
|
+
- 不要无限循环;没有明确下一步时询问用户或结束当前 turn 等待系统事件。
|
|
25
|
+
- 不要自己设置定时唤醒;神念会在用户消息、worker 终态或 worker 长运行健康摘要到来时唤醒你。
|
|
26
|
+
- 外部消息通道事件会像普通用户消息一样送达,格式为“外部对话: <对话名称>”后跟“对方: <内容或附件链接>”,可能是合并消息,也可能包含图片、视频或文件 URL。
|
|
27
|
+
- 外部消息是否需要回复、追问、忽略、转交内部负责人,或创建/指派 worker,由你根据项目上下文显式判断。
|
|
28
|
+
- 对外你是当前项目的项目经理,不要自称神念、Manager Agent 或 worker,也不要解释内部调度机制;只在需要时用“我这边/我们这边”沟通。
|
|
29
|
+
- 不要把所有细节塞进对话上下文;需要长期保存的信息写到项目 .shennian/ 下。
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
需要管理 worker 或外部通道时,使用本地命令:
|
|
30
32
|
- shennian manager sessions list --json
|
|
31
33
|
- shennian manager sessions start --agent <codex|claude|gemini|cursor|opencode|custom:name> --workdir <path> --message <text>
|
|
32
34
|
- shennian manager sessions send --session-id <id> --message <text>
|
|
@@ -34,10 +36,13 @@ const s=`\u4F60\u662F\u9879\u76EE\u7ECF\u7406\uFF0C\u662F\u5F53\u524D\u9879\u76E
|
|
|
34
36
|
- shennian manager sessions queue list --session-id <id> --json
|
|
35
37
|
- shennian manager sessions queue edit --session-id <id> --message-id <queueMessageId> --message <text>
|
|
36
38
|
- shennian manager sessions queue delete --session-id <id> --message-id <queueMessageId>
|
|
37
|
-
- shennian manager sessions stop --session-id <id
|
|
39
|
+
- shennian manager sessions stop --session-id <id>(终止正在运行的 worker;也可用 terminate/kill 别名)
|
|
38
40
|
- shennian manager sessions read --session-id <id> --limit 200 --json
|
|
39
41
|
- shennian manager memory path
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
可创建的 worker Agent 包括 Codex、Claude Code、Gemini、Cursor、opencode、Nian,以及本机 custom agent(custom:<name>)。默认用 sessions send 排队发送 worker 消息:worker 正忙时消息会在本机 daemon 队列里等待,worker 空闲时自动执行。队列里的未执行消息可以 list/edit/delete;已经开始执行的消息不能编辑或删除,只能 stop 后重新发送。只有明确需要打断顺序时才使用 --direct。
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
这些命令已经由神念注入当前 Manager 身份和同项目权限边界。不要尝试伪造 Manager session id。`;
|
|
46
|
+
export function buildManagerPrompt(userText) {
|
|
47
|
+
return userText;
|
|
48
|
+
}
|
|
@@ -1 +1,317 @@
|
|
|
1
|
-
|
|
1
|
+
// @arch docs/features/manager-agent.md
|
|
2
|
+
// @test src/__tests__/manager-inbox.test.ts
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { isAgentHiddenPayload, sessionPreviewFromPayload } from '@shennian/wire';
|
|
7
|
+
import { resolveShennianPath } from '../config/index.js';
|
|
8
|
+
import { listSessionRecords, readMessages } from '../session/store.js';
|
|
9
|
+
import { listProjectedSessions } from '../session/projection.js';
|
|
10
|
+
const REGISTRY_PATH = resolveShennianPath('manager-registry.json');
|
|
11
|
+
function nowIso() {
|
|
12
|
+
return new Date().toISOString();
|
|
13
|
+
}
|
|
14
|
+
function emptyRegistry() {
|
|
15
|
+
return { managers: {}, workers: {}, replyTargets: {}, inbox: {} };
|
|
16
|
+
}
|
|
17
|
+
export class ManagerRegistry {
|
|
18
|
+
load() {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf-8'));
|
|
21
|
+
return {
|
|
22
|
+
managers: parsed.managers ?? {},
|
|
23
|
+
workers: parsed.workers ?? {},
|
|
24
|
+
replyTargets: parsed.replyTargets ?? {},
|
|
25
|
+
inbox: parsed.inbox ?? {},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return emptyRegistry();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
save(registry) {
|
|
33
|
+
fs.mkdirSync(path.dirname(REGISTRY_PATH), { recursive: true });
|
|
34
|
+
fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
|
|
35
|
+
}
|
|
36
|
+
upsertManager(input) {
|
|
37
|
+
const registry = this.load();
|
|
38
|
+
const existing = registry.managers[input.sessionId];
|
|
39
|
+
const timestamp = nowIso();
|
|
40
|
+
const record = {
|
|
41
|
+
sessionId: input.sessionId,
|
|
42
|
+
agentSessionId: input.agentSessionId ?? existing?.agentSessionId ?? null,
|
|
43
|
+
workDir: input.workDir,
|
|
44
|
+
machineId: input.machineId ?? existing?.machineId ?? null,
|
|
45
|
+
modelId: input.modelId,
|
|
46
|
+
agentType: input.agentType ?? existing?.agentType ?? 'manager',
|
|
47
|
+
providerModelId: input.providerModelId ?? existing?.providerModelId ?? null,
|
|
48
|
+
defaultWorkerAgentType: input.defaultWorkerAgentType ?? existing?.defaultWorkerAgentType ?? null,
|
|
49
|
+
defaultWorkerModelId: input.defaultWorkerModelId ?? existing?.defaultWorkerModelId ?? null,
|
|
50
|
+
status: input.status ?? existing?.status ?? 'idle',
|
|
51
|
+
managedWorkerSessionIds: existing?.managedWorkerSessionIds ?? [],
|
|
52
|
+
attachedExternalChannels: existing?.attachedExternalChannels ?? [],
|
|
53
|
+
createdAt: existing?.createdAt ?? timestamp,
|
|
54
|
+
updatedAt: timestamp,
|
|
55
|
+
};
|
|
56
|
+
registry.managers[input.sessionId] = record;
|
|
57
|
+
this.save(registry);
|
|
58
|
+
return record;
|
|
59
|
+
}
|
|
60
|
+
getManager(sessionId) {
|
|
61
|
+
return this.load().managers[sessionId];
|
|
62
|
+
}
|
|
63
|
+
enqueueInboxEvent(input) {
|
|
64
|
+
const registry = this.load();
|
|
65
|
+
const manager = registry.managers[input.managerSessionId];
|
|
66
|
+
const worker = registry.workers[input.workerSessionId];
|
|
67
|
+
if (!manager || !worker)
|
|
68
|
+
return null;
|
|
69
|
+
if (worker.managedBy !== manager.sessionId || worker.workDir !== manager.workDir)
|
|
70
|
+
return null;
|
|
71
|
+
const timestamp = nowIso();
|
|
72
|
+
const events = registry.inbox[input.managerSessionId] ?? [];
|
|
73
|
+
const dedupeIndex = events.findIndex((event) => event.workerSessionId === input.workerSessionId && (input.kind === 'worker.health'
|
|
74
|
+
? event.kind === 'worker.health'
|
|
75
|
+
: event.kind === input.kind && event.runId === input.runId));
|
|
76
|
+
const event = dedupeIndex >= 0
|
|
77
|
+
? {
|
|
78
|
+
...events[dedupeIndex],
|
|
79
|
+
...input,
|
|
80
|
+
updatedAt: timestamp,
|
|
81
|
+
}
|
|
82
|
+
: {
|
|
83
|
+
...input,
|
|
84
|
+
id: `mie_${randomUUID()}`,
|
|
85
|
+
createdAt: timestamp,
|
|
86
|
+
updatedAt: timestamp,
|
|
87
|
+
};
|
|
88
|
+
if (dedupeIndex >= 0)
|
|
89
|
+
events[dedupeIndex] = event;
|
|
90
|
+
else
|
|
91
|
+
events.push(event);
|
|
92
|
+
registry.inbox[input.managerSessionId] = events;
|
|
93
|
+
this.save(registry);
|
|
94
|
+
return event;
|
|
95
|
+
}
|
|
96
|
+
listInboxEvents(managerSessionId) {
|
|
97
|
+
return [...(this.load().inbox[managerSessionId] ?? [])]
|
|
98
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
99
|
+
}
|
|
100
|
+
removeInboxEvents(managerSessionId, eventIds) {
|
|
101
|
+
if (!eventIds.length)
|
|
102
|
+
return;
|
|
103
|
+
const registry = this.load();
|
|
104
|
+
const ids = new Set(eventIds);
|
|
105
|
+
registry.inbox[managerSessionId] = (registry.inbox[managerSessionId] ?? [])
|
|
106
|
+
.filter((event) => !ids.has(event.id));
|
|
107
|
+
this.save(registry);
|
|
108
|
+
}
|
|
109
|
+
addWorker(input) {
|
|
110
|
+
const registry = this.load();
|
|
111
|
+
const manager = registry.managers[input.managerSessionId];
|
|
112
|
+
if (!manager)
|
|
113
|
+
throw new Error('Manager runtime is not registered');
|
|
114
|
+
const sessionId = input.sessionId ?? `sess_worker_${randomUUID()}`;
|
|
115
|
+
const timestamp = nowIso();
|
|
116
|
+
const record = {
|
|
117
|
+
sessionId,
|
|
118
|
+
agentType: input.agentType,
|
|
119
|
+
workDir: input.workDir,
|
|
120
|
+
managedBy: input.managerSessionId,
|
|
121
|
+
status: 'running',
|
|
122
|
+
createdAt: timestamp,
|
|
123
|
+
updatedAt: timestamp,
|
|
124
|
+
lastActivityAt: timestamp,
|
|
125
|
+
summary: input.summary ?? null,
|
|
126
|
+
agentSessionId: null,
|
|
127
|
+
runId: null,
|
|
128
|
+
};
|
|
129
|
+
registry.workers[sessionId] = record;
|
|
130
|
+
if (!manager.managedWorkerSessionIds.includes(sessionId)) {
|
|
131
|
+
manager.managedWorkerSessionIds.push(sessionId);
|
|
132
|
+
}
|
|
133
|
+
manager.updatedAt = timestamp;
|
|
134
|
+
this.save(registry);
|
|
135
|
+
return record;
|
|
136
|
+
}
|
|
137
|
+
updateWorker(sessionId, patch) {
|
|
138
|
+
const registry = this.load();
|
|
139
|
+
const existing = registry.workers[sessionId];
|
|
140
|
+
if (!existing)
|
|
141
|
+
return undefined;
|
|
142
|
+
const record = {
|
|
143
|
+
...existing,
|
|
144
|
+
...patch,
|
|
145
|
+
updatedAt: nowIso(),
|
|
146
|
+
};
|
|
147
|
+
registry.workers[sessionId] = record;
|
|
148
|
+
this.save(registry);
|
|
149
|
+
return record;
|
|
150
|
+
}
|
|
151
|
+
listWorkers(managerSessionId, opts = {}) {
|
|
152
|
+
const registry = this.load();
|
|
153
|
+
const manager = registry.managers[managerSessionId];
|
|
154
|
+
if (!manager)
|
|
155
|
+
return [];
|
|
156
|
+
const managedWorkers = Object.values(registry.workers)
|
|
157
|
+
.filter((worker) => worker.workDir === manager.workDir)
|
|
158
|
+
.filter((worker) => opts.includeManagers || worker.agentType !== 'manager')
|
|
159
|
+
.map((worker) => this.decorateManagedWorker(worker, opts.runningSessionIds));
|
|
160
|
+
const managedIds = new Set(managedWorkers.map((worker) => worker.sessionId));
|
|
161
|
+
const projectSessions = listSessionRecords()
|
|
162
|
+
.filter((session) => session.workDir === manager.workDir)
|
|
163
|
+
.filter((session) => opts.includeManagers || session.agentType !== 'manager')
|
|
164
|
+
.filter((session) => !managedIds.has(session.sessionId))
|
|
165
|
+
.map((session) => this.sessionRecordToWorker(session, opts.runningSessionIds));
|
|
166
|
+
const knownIds = new Set([...managedWorkers, ...projectSessions].map((session) => session.sessionId));
|
|
167
|
+
const projectedSource = opts.projectedSessions ?? listProjectedSessions();
|
|
168
|
+
const projectedSessions = projectedSource
|
|
169
|
+
.filter((session) => session.workDir === manager.workDir)
|
|
170
|
+
.filter((session) => opts.includeManagers || session.agentType !== 'manager')
|
|
171
|
+
.filter((session) => !knownIds.has(session.id))
|
|
172
|
+
.map((session) => this.projectedSessionToWorker(session, opts.runningSessionIds));
|
|
173
|
+
return [...managedWorkers, ...projectSessions, ...projectedSessions]
|
|
174
|
+
.sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt));
|
|
175
|
+
}
|
|
176
|
+
getWorkerForManager(managerSessionId, sessionId) {
|
|
177
|
+
const registry = this.load();
|
|
178
|
+
const manager = registry.managers[managerSessionId];
|
|
179
|
+
const worker = registry.workers[sessionId];
|
|
180
|
+
if (!manager)
|
|
181
|
+
return undefined;
|
|
182
|
+
if (worker) {
|
|
183
|
+
if (worker.workDir !== manager.workDir)
|
|
184
|
+
return undefined;
|
|
185
|
+
if (worker.agentType === 'manager')
|
|
186
|
+
return undefined;
|
|
187
|
+
return this.decorateManagedWorker(worker);
|
|
188
|
+
}
|
|
189
|
+
const session = listSessionRecords().find((item) => item.sessionId === sessionId);
|
|
190
|
+
if (!session || session.workDir !== manager.workDir || session.agentType === 'manager')
|
|
191
|
+
return undefined;
|
|
192
|
+
return this.sessionRecordToWorker(session);
|
|
193
|
+
}
|
|
194
|
+
decorateManagedWorker(worker, runningSessionIds) {
|
|
195
|
+
const status = runningSessionIds?.has(worker.sessionId) ? 'running' : worker.status;
|
|
196
|
+
const transcript = this.readTranscriptSummary(worker.sessionId, worker.summary ?? null);
|
|
197
|
+
const lastMessagePreview = transcript.lastMessagePreview ?? worker.lastMessagePreview ?? worker.summary ?? null;
|
|
198
|
+
const userGoal = worker.userGoal ?? transcript.userGoal ?? null;
|
|
199
|
+
return {
|
|
200
|
+
...worker,
|
|
201
|
+
managedBy: worker.managedBy ?? null,
|
|
202
|
+
status,
|
|
203
|
+
title: worker.title ?? userGoal ?? lastMessagePreview ?? worker.summary ?? worker.sessionId,
|
|
204
|
+
userGoal,
|
|
205
|
+
lastMessagePreview,
|
|
206
|
+
summary: worker.summary ?? lastMessagePreview,
|
|
207
|
+
canResume: worker.canResume ?? worker.status !== 'aborted',
|
|
208
|
+
source: worker.source ?? 'manager_registry',
|
|
209
|
+
role: 'managed_worker',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
sessionRecordToWorker(session, runningSessionIds) {
|
|
213
|
+
const transcript = this.readTranscriptSummary(session.sessionId, session.lastMessagePreview ?? null);
|
|
214
|
+
const lastMessagePreview = transcript.lastMessagePreview ?? session.lastMessagePreview ?? null;
|
|
215
|
+
const userGoal = transcript.userGoal;
|
|
216
|
+
const status = runningSessionIds?.has(session.sessionId)
|
|
217
|
+
? 'running'
|
|
218
|
+
: session.status === 'failed'
|
|
219
|
+
? 'error'
|
|
220
|
+
: session.status === 'completed'
|
|
221
|
+
? 'final'
|
|
222
|
+
: 'idle';
|
|
223
|
+
return {
|
|
224
|
+
sessionId: session.sessionId,
|
|
225
|
+
agentType: session.agentType,
|
|
226
|
+
workDir: session.workDir,
|
|
227
|
+
managedBy: null,
|
|
228
|
+
status,
|
|
229
|
+
createdAt: session.createdAt,
|
|
230
|
+
updatedAt: session.updatedAt,
|
|
231
|
+
lastActivityAt: session.lastActivityAt,
|
|
232
|
+
title: userGoal ?? lastMessagePreview ?? session.sessionId,
|
|
233
|
+
userGoal,
|
|
234
|
+
lastMessagePreview,
|
|
235
|
+
summary: lastMessagePreview ?? userGoal ?? null,
|
|
236
|
+
agentSessionId: session.agentSessionId ?? null,
|
|
237
|
+
runId: null,
|
|
238
|
+
canResume: Boolean(session.agentSessionId),
|
|
239
|
+
source: 'local_index',
|
|
240
|
+
role: 'project_session',
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
projectedSessionToWorker(session, runningSessionIds) {
|
|
244
|
+
const lastMessagePreview = session.lastMessagePreview ?? null;
|
|
245
|
+
const userGoal = session.title?.trim() || lastMessagePreview;
|
|
246
|
+
const status = runningSessionIds?.has(session.id)
|
|
247
|
+
? 'running'
|
|
248
|
+
: session.status === 'failed'
|
|
249
|
+
? 'error'
|
|
250
|
+
: session.status === 'completed'
|
|
251
|
+
? 'final'
|
|
252
|
+
: 'idle';
|
|
253
|
+
return {
|
|
254
|
+
sessionId: session.id,
|
|
255
|
+
agentType: session.agentType,
|
|
256
|
+
workDir: session.workDir,
|
|
257
|
+
managedBy: null,
|
|
258
|
+
status,
|
|
259
|
+
createdAt: session.createdAt,
|
|
260
|
+
updatedAt: session.updatedAt,
|
|
261
|
+
lastActivityAt: session.lastActivityAt || session.updatedAt,
|
|
262
|
+
title: userGoal ?? session.id,
|
|
263
|
+
userGoal: userGoal ?? null,
|
|
264
|
+
lastMessagePreview,
|
|
265
|
+
summary: lastMessagePreview ?? userGoal ?? null,
|
|
266
|
+
agentSessionId: session.agentSessionId ?? null,
|
|
267
|
+
runId: session.activity?.runId ?? null,
|
|
268
|
+
canResume: true,
|
|
269
|
+
source: 'app_projection',
|
|
270
|
+
role: 'project_session',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
readTranscriptSummary(sessionId, fallbackPreview) {
|
|
274
|
+
const messages = readMessages(sessionId, { limit: 200 });
|
|
275
|
+
if (!messages.length)
|
|
276
|
+
return { userGoal: null, lastMessagePreview: fallbackPreview };
|
|
277
|
+
const chronological = [...messages].sort((a, b) => a.ts - b.ts);
|
|
278
|
+
const firstUser = chronological.find((message) => message.role === 'user');
|
|
279
|
+
const lastReadable = [...chronological].reverse().find((message) => readableText(message));
|
|
280
|
+
return {
|
|
281
|
+
userGoal: firstUser ? clip(readableText(firstUser), 160) : null,
|
|
282
|
+
lastMessagePreview: clip(readableText(lastReadable), 180) ?? fallbackPreview,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
createReplyTarget(input) {
|
|
286
|
+
const registry = this.load();
|
|
287
|
+
const record = {
|
|
288
|
+
replyTarget: `rt_${randomUUID()}`,
|
|
289
|
+
managerSessionId: input.managerSessionId,
|
|
290
|
+
channelId: input.channelId,
|
|
291
|
+
conversationId: input.conversationId,
|
|
292
|
+
messageId: input.messageId ?? null,
|
|
293
|
+
createdAt: nowIso(),
|
|
294
|
+
};
|
|
295
|
+
registry.replyTargets[record.replyTarget] = record;
|
|
296
|
+
this.save(registry);
|
|
297
|
+
return record;
|
|
298
|
+
}
|
|
299
|
+
getReplyTarget(replyTarget) {
|
|
300
|
+
return this.load().replyTargets[replyTarget];
|
|
301
|
+
}
|
|
302
|
+
getLatestReplyTargetForManager(managerSessionId) {
|
|
303
|
+
return Object.values(this.load().replyTargets)
|
|
304
|
+
.filter((target) => target.managerSessionId === managerSessionId)
|
|
305
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function readableText(message) {
|
|
309
|
+
if (!message || isAgentHiddenPayload(message.payload))
|
|
310
|
+
return null;
|
|
311
|
+
return sessionPreviewFromPayload(message.payload, 180);
|
|
312
|
+
}
|
|
313
|
+
function clip(text, max) {
|
|
314
|
+
if (!text)
|
|
315
|
+
return null;
|
|
316
|
+
return text.length > max ? `${text.slice(0, max)}...` : text;
|
|
317
|
+
}
|
|
@@ -1 +1,40 @@
|
|
|
1
|
-
const
|
|
1
|
+
const retired = async () => { throw new Error('feature_retired'); };
|
|
2
|
+
export class RetiredChannelRuntime {
|
|
3
|
+
constructor(_onMessage, _createReplyTarget, _options) { }
|
|
4
|
+
async start() { }
|
|
5
|
+
async stop() { }
|
|
6
|
+
getManagerChannel(..._args) { return null; }
|
|
7
|
+
getManagerChannelStatus(..._args) { return null; }
|
|
8
|
+
getChannelStatusById(..._args) { return null; }
|
|
9
|
+
getChannelById(..._args) { return undefined; }
|
|
10
|
+
listManagerChannelStatuses(..._args) { return []; }
|
|
11
|
+
listManagerExternalChannels(..._args) { return []; }
|
|
12
|
+
listChannelIdsManagedBy(..._args) { return []; }
|
|
13
|
+
getDefaultReplyTarget(..._args) { return retired(); }
|
|
14
|
+
upsertManagerChannel(..._args) { return retired(); }
|
|
15
|
+
upsertManagerWeChatRpaChannel(..._args) { return retired(); }
|
|
16
|
+
syncManagerWeChatRpaChannel(..._args) { return retired(); }
|
|
17
|
+
cancelManagerWeChatRpaOutbound(..._args) { return retired(); }
|
|
18
|
+
deleteManagerWeChatRpaChannel(..._args) { return retired(); }
|
|
19
|
+
reply(..._args) { return retired(); }
|
|
20
|
+
}
|
|
21
|
+
export class RetiredWeChatRpaSessionBindingSync {
|
|
22
|
+
constructor(..._args) { }
|
|
23
|
+
async reconcileSession(..._args) { }
|
|
24
|
+
async reconcileAll(..._args) { }
|
|
25
|
+
}
|
|
26
|
+
export function createRetiredAutomationLane() {
|
|
27
|
+
return { run: async () => { throw new Error('feature_retired'); } };
|
|
28
|
+
}
|
|
29
|
+
export function createRetiredChannelApiClient(..._args) {
|
|
30
|
+
return { listBindings: async (..._args) => ({ bindings: [] }) };
|
|
31
|
+
}
|
|
32
|
+
export function retiredDirectWeChat(..._args) {
|
|
33
|
+
return Promise.reject(new Error('feature_retired'));
|
|
34
|
+
}
|
|
35
|
+
export function splitExternalReplyText(text) {
|
|
36
|
+
return [text];
|
|
37
|
+
}
|
|
38
|
+
export function weChatChannelConversationId(name) {
|
|
39
|
+
return `retired:${name}`;
|
|
40
|
+
}
|
|
@@ -3,6 +3,7 @@ import { type AgentType, type ExternalChannelSessionStatus, type ReqFrame } from
|
|
|
3
3
|
import { ManagerRegistry } from './registry.js';
|
|
4
4
|
import type { SessionManagerRuntime } from '../session/types.js';
|
|
5
5
|
import { RetiredChannelRuntime as ChannelRuntime, type ChannelRuntimeOptions, type WeChatAutomationLane, type WeChatRpaSessionContext } from './retired-external-channel-compat.js';
|
|
6
|
+
import type { CompiledManagedAgentPolicy } from '../security/managed-agent-policy.js';
|
|
6
7
|
export type LocalReqDispatcher = (req: ReqFrame) => Promise<void>;
|
|
7
8
|
type ManagerRuntimeServiceOptions = {
|
|
8
9
|
getRuntime: () => SessionManagerRuntime;
|
|
@@ -15,6 +16,7 @@ export declare function setManagerRuntimeService(service: ManagerRuntimeService
|
|
|
15
16
|
export declare function getManagerRuntimeService(): ManagerRuntimeService | null;
|
|
16
17
|
export declare class ManagerRuntimeService {
|
|
17
18
|
private opts;
|
|
19
|
+
private readonly managedPolicies;
|
|
18
20
|
readonly registry: ManagerRegistry;
|
|
19
21
|
readonly channelRuntime: ChannelRuntime;
|
|
20
22
|
private readonly weChatRpaSessionSync;
|
|
@@ -49,6 +51,8 @@ export declare class ManagerRuntimeService {
|
|
|
49
51
|
defaultWorkerModelId?: string | null;
|
|
50
52
|
status?: 'idle' | 'running' | 'interrupting';
|
|
51
53
|
}): void;
|
|
54
|
+
setManagedAgentPolicy(sessionId: string, policy: CompiledManagedAgentPolicy | null): void;
|
|
55
|
+
getManagedAgentPolicyForWorker(managerSessionId: string, workDir: string): CompiledManagedAgentPolicy | null;
|
|
52
56
|
setManagerWorkerDefaults(sessionId: string, agentType?: AgentType | null, modelId?: string | null): void;
|
|
53
57
|
getManagerWorkerDefaults(sessionId: string): {
|
|
54
58
|
agentType: AgentType | null;
|