dsh-kingdom 0.8.0 → 2.0.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/ASSETS_NOTICE.md +22 -0
- package/LICENSE +661 -29
- package/NOTICE +9 -0
- package/README.md +93 -94
- package/THIRD_PARTY_NOTICES.md +92 -0
- package/cordis.patch.yml +6 -6
- package/docs/V2.0-RELEASE-NOTES.md +30 -0
- package/docs/V2.0-USER-GUIDE.md +74 -0
- package/lib/adapter/dsh-backend.js +327 -24
- package/lib/adapter/dsh-backend.js.map +1 -1
- package/lib/adapter/dsh-cost.js +68 -0
- package/lib/adapter/dsh-cost.js.map +1 -0
- package/lib/adapter/dsh-session-events.js +86 -0
- package/lib/adapter/dsh-session-events.js.map +1 -0
- package/lib/adapter/dsh-usage.js +159 -0
- package/lib/adapter/dsh-usage.js.map +1 -0
- package/lib/capability/admin.js +72 -0
- package/lib/capability/admin.js.map +1 -0
- package/lib/capability/dsh-enforcement.js +143 -34
- package/lib/capability/dsh-enforcement.js.map +1 -1
- package/lib/capability/resolver.js +21 -8
- package/lib/capability/resolver.js.map +1 -1
- package/lib/capability/service.js +169 -26
- package/lib/capability/service.js.map +1 -1
- package/lib/capability/tool-disclosure.js +144 -0
- package/lib/capability/tool-disclosure.js.map +1 -0
- package/lib/core/binding.js +52 -34
- package/lib/core/binding.js.map +1 -1
- package/lib/core/budget.js +304 -0
- package/lib/core/budget.js.map +1 -0
- package/lib/core/collaboration.js +422 -0
- package/lib/core/collaboration.js.map +1 -0
- package/lib/core/cost.js +212 -0
- package/lib/core/cost.js.map +1 -0
- package/lib/core/db.js +197 -4
- package/lib/core/db.js.map +1 -1
- package/lib/core/execution.js +2 -2
- package/lib/core/execution.js.map +1 -1
- package/lib/core/governed-claim.js +46 -0
- package/lib/core/governed-claim.js.map +1 -0
- package/lib/core/governed.js +891 -98
- package/lib/core/governed.js.map +1 -1
- package/lib/core/kingdom.js +78 -70
- package/lib/core/kingdom.js.map +1 -1
- package/lib/core/owner-binding-intent.js +732 -0
- package/lib/core/owner-binding-intent.js.map +1 -0
- package/lib/core/owner-control.js +86 -0
- package/lib/core/owner-control.js.map +1 -0
- package/lib/core/owner-window.js +822 -0
- package/lib/core/owner-window.js.map +1 -0
- package/lib/core/task-service.js +296 -133
- package/lib/core/task-service.js.map +1 -1
- package/lib/core/territory.js +44 -14
- package/lib/core/territory.js.map +1 -1
- package/lib/core/usage.js +39 -0
- package/lib/core/usage.js.map +1 -0
- package/lib/core/workspace-admission.js +225 -0
- package/lib/core/workspace-admission.js.map +1 -0
- package/lib/dispatch/evidence.js +5 -2
- package/lib/dispatch/evidence.js.map +1 -1
- package/lib/dispatch/reconcile.js +3 -9
- package/lib/dispatch/reconcile.js.map +1 -1
- package/lib/dispatch/service.js +464 -64
- package/lib/dispatch/service.js.map +1 -1
- package/lib/gui/assets/characters/chancellor-idle.svg +67 -0
- package/lib/gui/assets/characters/chancellor-sleeping.svg +67 -0
- package/lib/gui/assets/characters/chancellor-thinking.svg +67 -0
- package/lib/gui/assets/characters/chancellor-working.svg +67 -0
- package/lib/gui/assets/characters/knight-redraw-r1-idle.svg +67 -0
- package/lib/gui/assets/characters/knight-redraw-r1-sleeping.svg +67 -0
- package/lib/gui/assets/characters/knight-redraw-r1-thinking.svg +67 -0
- package/lib/gui/assets/characters/knight-redraw-r1-working.svg +67 -0
- package/lib/gui/assets/characters/supervisor-idle.svg +67 -0
- package/lib/gui/assets/characters/supervisor-sleeping.svg +67 -0
- package/lib/gui/assets/characters/supervisor-thinking.svg +67 -0
- package/lib/gui/assets/characters/supervisor-working.svg +67 -0
- package/lib/gui/console-app.js +2330 -0
- package/lib/gui/console-app.js.map +1 -0
- package/lib/gui/contract.js +5 -5
- package/lib/gui/contract.js.map +1 -1
- package/lib/gui/control-contract.js +232 -0
- package/lib/gui/control-contract.js.map +1 -0
- package/lib/gui/local-control.js +423 -0
- package/lib/gui/local-control.js.map +1 -0
- package/lib/gui/owner-control.js +310 -0
- package/lib/gui/owner-control.js.map +1 -0
- package/lib/gui/owner-ui.js +191 -0
- package/lib/gui/owner-ui.js.map +1 -0
- package/lib/gui/server.js +445 -35
- package/lib/gui/server.js.map +1 -1
- package/lib/gui/snapshot.js +1346 -67
- package/lib/gui/snapshot.js.map +1 -1
- package/lib/gui/visual-assets.js +56 -0
- package/lib/gui/visual-assets.js.map +1 -0
- package/lib/gui/visual-system.js +272 -0
- package/lib/gui/visual-system.js.map +1 -0
- package/lib/gui/workbench-ui.js +358 -0
- package/lib/gui/workbench-ui.js.map +1 -0
- package/lib/gui/workbench.js +163 -0
- package/lib/gui/workbench.js.map +1 -0
- package/lib/index.js +1384 -361
- package/lib/index.js.map +1 -1
- package/lib/runner-context-broker.js +1134 -0
- package/lib/runner-context-broker.js.map +1 -0
- package/lib/runtime-cost-observer.js +145 -0
- package/lib/runtime-cost-observer.js.map +1 -0
- package/lib/types/adapter/contract.d.ts +61 -1
- package/lib/types/adapter/dsh-backend.d.ts +32 -13
- package/lib/types/adapter/dsh-cost.d.ts +13 -0
- package/lib/types/adapter/dsh-session-events.d.ts +16 -0
- package/lib/types/adapter/dsh-usage.d.ts +36 -0
- package/lib/types/capability/admin.d.ts +13 -0
- package/lib/types/capability/dsh-enforcement.d.ts +11 -7
- package/lib/types/capability/service.d.ts +10 -3
- package/lib/types/capability/tool-disclosure.d.ts +36 -0
- package/lib/types/core/binding.d.ts +19 -10
- package/lib/types/core/budget.d.ts +81 -0
- package/lib/types/core/collaboration.d.ts +110 -0
- package/lib/types/core/cost.d.ts +99 -0
- package/lib/types/core/db.d.ts +44 -4
- package/lib/types/core/execution.d.ts +1 -1
- package/lib/types/core/governed-claim.d.ts +7 -0
- package/lib/types/core/governed.d.ts +182 -1
- package/lib/types/core/kingdom.d.ts +3 -0
- package/lib/types/core/owner-binding-intent.d.ts +176 -0
- package/lib/types/core/owner-control.d.ts +53 -0
- package/lib/types/core/owner-window.d.ts +226 -0
- package/lib/types/core/task-service.d.ts +60 -13
- package/lib/types/core/territory.d.ts +9 -2
- package/lib/types/core/usage.d.ts +26 -0
- package/lib/types/core/workspace-admission.d.ts +43 -0
- package/lib/types/dispatch/evidence.d.ts +2 -5
- package/lib/types/dispatch/service.d.ts +62 -4
- package/lib/types/gui/console-app.d.ts +137 -0
- package/lib/types/gui/contract.d.ts +396 -16
- package/lib/types/gui/control-contract.d.ts +111 -0
- package/lib/types/gui/local-control.d.ts +120 -0
- package/lib/types/gui/owner-control.d.ts +45 -0
- package/lib/types/gui/owner-ui.d.ts +2 -0
- package/lib/types/gui/server.d.ts +17 -3
- package/lib/types/gui/snapshot.d.ts +33 -11
- package/lib/types/gui/visual-assets.d.ts +27 -0
- package/lib/types/gui/visual-system.d.ts +2 -0
- package/lib/types/gui/workbench-ui.d.ts +5 -0
- package/lib/types/gui/workbench.d.ts +33 -0
- package/lib/types/index.d.ts +157 -6
- package/lib/types/runner-context-broker.d.ts +119 -0
- package/lib/types/runtime-cost-observer.d.ts +22 -0
- package/lib/types/worker/collaboration.d.ts +35 -0
- package/lib/types/worker/dsh-subagent.d.ts +4 -4
- package/lib/types/worker/executor-factory.d.ts +2 -1
- package/lib/types/worker/executor.d.ts +8 -3
- package/lib/types/worker/governed-executor.d.ts +8 -1
- package/lib/worker/collaboration.js +172 -0
- package/lib/worker/collaboration.js.map +1 -0
- package/lib/worker/dsh-subagent.js +4 -4
- package/lib/worker/executor-factory.js.map +1 -1
- package/lib/worker/executor.js +7 -2
- package/lib/worker/executor.js.map +1 -1
- package/lib/worker/governed-executor.js +295 -84
- package/lib/worker/governed-executor.js.map +1 -1
- package/package.json +23 -8
package/lib/index.js
CHANGED
|
@@ -1,21 +1,156 @@
|
|
|
1
1
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
2
|
import z from '@deepseek-ai/schemastery';
|
|
3
|
-
import {
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { hostname } from 'node:os';
|
|
5
5
|
import { KingdomManager } from './core/kingdom.js';
|
|
6
6
|
import { DshRuntimeAdapter } from './adapter/dsh-backend.js';
|
|
7
7
|
import { runGovernedTask } from './worker/governed-executor.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { advanceCollaboration } from './worker/collaboration.js';
|
|
9
|
+
import { proposeCollaborationPlan } from './core/collaboration.js';
|
|
10
|
+
import { settleAndRelease, reconcileGovernedDispatch, discardGovernedRecoveryContexts } from './dispatch/service.js';
|
|
11
|
+
import { submitGovernedClaim } from './core/governed-claim.js';
|
|
12
|
+
import { recordDispatchUsage } from './core/usage.js';
|
|
13
|
+
import { observeDshDispatchUsage } from './adapter/dsh-usage.js';
|
|
14
|
+
import { ToolDisclosureRuntime, DISCOVERY_TOOL_NAME } from './capability/tool-disclosure.js';
|
|
15
|
+
import { installRuntimeCostObservers } from './runtime-cost-observer.js';
|
|
16
|
+
import { setCapabilityCeiling } from './capability/admin.js';
|
|
17
|
+
import { bindRole, listBindings, parseExecutionProfile, rebindSession, resolveBinding, setExecutionProfile, unbindRole, } from './core/binding.js';
|
|
18
|
+
import { createRunnerContextPort } from './core/governed.js';
|
|
19
|
+
import { forgetRunnerContextPort, getRunnerContextPort, revokeRunnerContextBrokerContext, } from './runner-context-broker.js';
|
|
20
|
+
import { asExecutionState, isTerminalExecutionState } from './core/execution.js';
|
|
10
21
|
import { createTerritory, deleteTerritory, listTerritories, setTerritorySupervisor } from './core/territory.js';
|
|
11
|
-
import { abortExecution, assignTask, listTasks, pauseExecution, planTask, reclaimOrphanExecutions, resumeExecution, reviewTask, startTask, } from './core/task-service.js';
|
|
22
|
+
import { abortExecution, assignTask, listTasks, pauseExecution, planTask, reclaimOrphanExecutions, resolveGovernedStartSupervisor, resumeExecution, reviewTask, startTask, } from './core/task-service.js';
|
|
12
23
|
import { buildSnapshot, buildTaskDetail, toEventView } from './gui/snapshot.js';
|
|
13
24
|
import { startGuiServer } from './gui/server.js';
|
|
14
|
-
import {
|
|
25
|
+
import { DuplicateJsonKeyError, GUI_OWNER_ONLY_HTTP_COMMANDS, GUI_SESSION_COMMANDS, GUI_START_SANDBOX_MODES, parseStrictJsonObject, } from './gui/control-contract.js';
|
|
26
|
+
import { LOCAL_CONTROL_LAUNCH_PATH, LocalControlManager } from './gui/local-control.js';
|
|
27
|
+
import { OwnerLocalControlManager } from './gui/owner-control.js';
|
|
28
|
+
import { OwnerDecisionController } from './core/owner-window.js';
|
|
15
29
|
import { resolveWorkerExecution } from './worker/executor-factory.js';
|
|
30
|
+
import { issueOwnerControlCapability, ownerControlAuth, } from './core/owner-control.js';
|
|
31
|
+
import { draftOwnerBindingIntent, draftOwnerBindingIntentFromRejectedWrite, serializeOwnerBindingIntentDraft, } from './core/owner-binding-intent.js';
|
|
16
32
|
export const name = 'dsh-kingdom';
|
|
17
33
|
export const inject = ['tools', 'commands'];
|
|
34
|
+
function exactRuntimeToken(value) {
|
|
35
|
+
return typeof value === 'string'
|
|
36
|
+
&& value.length > 0
|
|
37
|
+
&& value === value.trim()
|
|
38
|
+
&& !/[\s\u0000-\u001f\u007f]/u.test(value);
|
|
39
|
+
}
|
|
40
|
+
function agentSession(agent) {
|
|
41
|
+
if (typeof agent !== 'object' || agent === null)
|
|
42
|
+
return null;
|
|
43
|
+
const candidate = agent;
|
|
44
|
+
if (typeof candidate.session !== 'object' || candidate.session === null)
|
|
45
|
+
return null;
|
|
46
|
+
const session = candidate.session;
|
|
47
|
+
if (!exactRuntimeToken(candidate.id) || !exactRuntimeToken(session.id))
|
|
48
|
+
return null;
|
|
49
|
+
return { agent: candidate, session, sessionId: candidate.id };
|
|
50
|
+
}
|
|
51
|
+
function failClassification(classification, sessionId, reason) {
|
|
52
|
+
return { classification, sessionId, reason };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the exact DSH Tool caller. A non-empty session id is only a target
|
|
56
|
+
* reference; it becomes an ACTIVE Principal after every public identity/life
|
|
57
|
+
* check succeeds. In particular, tool arguments are not accepted here.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveTrustedToolSession(execution, seam) {
|
|
60
|
+
const target = agentSession(execution?.agent);
|
|
61
|
+
if (!target)
|
|
62
|
+
return failClassification('ABSENT', null, '缺少 exact exec.agent.session.id。');
|
|
63
|
+
const agents = seam.agents;
|
|
64
|
+
const sessions = seam.sessions;
|
|
65
|
+
if (!agents || !sessions || typeof agents.currentInitiator !== 'function'
|
|
66
|
+
|| typeof agents.get !== 'function' || typeof sessions.get !== 'function') {
|
|
67
|
+
return failClassification('UNKNOWN', target.sessionId, 'DSH agents/sessions registry seam 不完整。');
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const initiator = agents.currentInitiator();
|
|
71
|
+
if (initiator !== target.agent) {
|
|
72
|
+
return failClassification('FOREIGN', target.sessionId, 'exec.agent 不是当前 DSH current initiator。');
|
|
73
|
+
}
|
|
74
|
+
if (agents.get(target.sessionId) !== target.agent) {
|
|
75
|
+
return failClassification('FOREIGN', target.sessionId, 'agents.get 未返回同一 live Agent 对象。');
|
|
76
|
+
}
|
|
77
|
+
if (typeof agents.list === 'function') {
|
|
78
|
+
const matches = agents.list().filter(item => agentSession(item)?.sessionId === target.sessionId);
|
|
79
|
+
if (matches.length !== 1) {
|
|
80
|
+
return failClassification('MULTIPLE', target.sessionId, 'live Agent registry 未能证明 target 唯一。');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (sessions.get(target.sessionId) !== target.session) {
|
|
84
|
+
return failClassification('FOREIGN', target.sessionId, 'sessions.get 未返回同一 live Session 对象。');
|
|
85
|
+
}
|
|
86
|
+
if (target.agent.id !== target.session.id) {
|
|
87
|
+
return failClassification('FOREIGN', target.sessionId, 'Agent id 与 Session id 不一致。');
|
|
88
|
+
}
|
|
89
|
+
if (target.agent.status === 'expired') {
|
|
90
|
+
return failClassification('EXPIRED', target.sessionId, 'Agent/Session 已过期。');
|
|
91
|
+
}
|
|
92
|
+
if (target.agent.status !== 'running') {
|
|
93
|
+
return failClassification('UNKNOWN', target.sessionId, `Agent status=${String(target.agent.status)} 不是 running。`);
|
|
94
|
+
}
|
|
95
|
+
if (execution?.signal?.aborted !== false) {
|
|
96
|
+
return failClassification(execution?.signal?.aborted === true ? 'ABORTED' : 'UNKNOWN', target.sessionId, execution?.signal?.aborted === true ? 'Tool execution signal 已 aborted。' : '缺少未 aborted 的 Tool execution signal。');
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
classification: 'ACTIVE',
|
|
100
|
+
sessionId: target.sessionId,
|
|
101
|
+
agent: target.agent,
|
|
102
|
+
session: target.session,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return failClassification('UNKNOWN', target.sessionId, `DSH registry lookup 失败:${error instanceof Error ? error.message : String(error)}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Validate a direct Owner Slash target without requiring current-initiator scope. */
|
|
110
|
+
export function validateLiveDirectSession(sessionId, seam) {
|
|
111
|
+
if (!exactRuntimeToken(sessionId)) {
|
|
112
|
+
return { ok: false, classification: 'ABSENT', reason: 'session_id 必须是非空 exact token。' };
|
|
113
|
+
}
|
|
114
|
+
const agents = seam.agents;
|
|
115
|
+
const sessions = seam.sessions;
|
|
116
|
+
if (!agents || !sessions || typeof agents.get !== 'function' || typeof sessions.get !== 'function') {
|
|
117
|
+
return { ok: false, classification: 'UNKNOWN', reason: 'DSH agents/sessions registry seam 不完整。' };
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const agent = agents.get(sessionId);
|
|
121
|
+
const target = agentSession(agent);
|
|
122
|
+
if (!target || target.sessionId !== sessionId || agent !== target.agent) {
|
|
123
|
+
return { ok: false, classification: 'ABSENT', reason: 'session_id 不对应 live Agent。' };
|
|
124
|
+
}
|
|
125
|
+
if (typeof agents.list === 'function') {
|
|
126
|
+
const matches = agents.list().filter(item => agentSession(item)?.sessionId === sessionId);
|
|
127
|
+
if (matches.length !== 1) {
|
|
128
|
+
return { ok: false, classification: 'MULTIPLE', reason: 'live Agent registry 未能证明 session 唯一。' };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (sessions.get(sessionId) !== target.session) {
|
|
132
|
+
return { ok: false, classification: 'FOREIGN', reason: 'sessions.get 未返回绑定的 live Session 对象。' };
|
|
133
|
+
}
|
|
134
|
+
if (target.agent.id !== target.session.id) {
|
|
135
|
+
return { ok: false, classification: 'FOREIGN', reason: 'Agent id 与 Session id 不一致。' };
|
|
136
|
+
}
|
|
137
|
+
if (target.agent.status !== 'idle' && target.agent.status !== 'running') {
|
|
138
|
+
return { ok: false, classification: 'EXPIRED', reason: `Agent status=${String(target.agent.status)} 不可作为 live session。` };
|
|
139
|
+
}
|
|
140
|
+
return { ok: true, sessionId, agent: target.agent, session: target.session };
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
return { ok: false, classification: 'UNKNOWN', reason: `DSH registry lookup 失败:${error instanceof Error ? error.message : String(error)}` };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
18
146
|
export const Config = z.object({
|
|
147
|
+
toolDisclosure: z.object({
|
|
148
|
+
mode: z.union(['off', 'pilot']).default('off'),
|
|
149
|
+
residentTools: z.array(z.string()).default([]),
|
|
150
|
+
phaseTools: z.array(z.string()).default([]),
|
|
151
|
+
maxResults: z.number().default(3),
|
|
152
|
+
maxResultBytes: z.number().default(12000),
|
|
153
|
+
}).default({ mode: 'off', residentTools: [], phaseTools: [], maxResults: 3, maxResultBytes: 12000 }),
|
|
19
154
|
kingdomName: z.string().default('My Kingdom'),
|
|
20
155
|
ownerName: z.string().default(''),
|
|
21
156
|
/** Worker 执行用的 subagent provider(dsh base bundle 默认注册 spawn / fork)。 */
|
|
@@ -25,7 +160,7 @@ export const Config = z.object({
|
|
|
25
160
|
* 设为非零值即启用,只绑定 127.0.0.1。
|
|
26
161
|
*/
|
|
27
162
|
guiPort: z.number().default(0),
|
|
28
|
-
/** 可选 bearer
|
|
163
|
+
/** Role GUI 可选 bearer;独立 Owner 路由使用其直接激活的短期凭据,不升级 Role 权限。 */
|
|
29
164
|
guiToken: z.string().default(''),
|
|
30
165
|
/** CORS 允许的 Origin 列表;默认放开(服务只绑本机回环)。 */
|
|
31
166
|
guiAllowOrigins: z.array(z.string()).default(['*']),
|
|
@@ -43,7 +178,134 @@ export const Config = z.object({
|
|
|
43
178
|
*/
|
|
44
179
|
migrateV4: z.boolean().default(false),
|
|
45
180
|
});
|
|
46
|
-
|
|
181
|
+
/** Smallest capability fact used by the GUI thin slice's governed path. */
|
|
182
|
+
export const MINIMAL_CAPABILITY_JSON = '{"tool:pwsh":true}';
|
|
183
|
+
// R20 public root surface. These helpers carry transport bootstrap only;
|
|
184
|
+
// Product registration/settlement helpers remain private to this module.
|
|
185
|
+
export { connectRunnerContextBroker, createRunnerContextBrokerLaunch, createRunnerContextBrokerProductLifecycle, } from './runner-context-broker.js';
|
|
186
|
+
const GUI_OWNER_ONLY_COMMAND_SET = new Set(GUI_OWNER_ONLY_HTTP_COMMANDS);
|
|
187
|
+
/**
|
|
188
|
+
* Format the direct GUI activation result without accepting any launch secret
|
|
189
|
+
* as presentation data. The ticket is handed only to the local URL opener;
|
|
190
|
+
* command/done persistence, logger output, and domain events receive this
|
|
191
|
+
* clean result instead.
|
|
192
|
+
*/
|
|
193
|
+
export function formatGuiLaunchCommandResult(origin, expiresAt, browserOpened) {
|
|
194
|
+
const cleanUrl = `${origin}${LOCAL_CONTROL_LAUNCH_PATH}`;
|
|
195
|
+
return browserOpened
|
|
196
|
+
? {
|
|
197
|
+
kind: 'success',
|
|
198
|
+
text: `GUI Control Session 已激活:${cleanUrl};expiresAt=${expiresAt}。已请求在本机浏览器打开 Console。`,
|
|
199
|
+
}
|
|
200
|
+
: {
|
|
201
|
+
kind: 'error',
|
|
202
|
+
text: `GUI Control Session 已激活:${cleanUrl};expiresAt=${expiresAt}。本机浏览器自动打开失败;未输出一次性 launch ticket,请重新执行 /kingdom gui 后重试。`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function throwOnGuiSetupFailure(value) {
|
|
206
|
+
if (/^(?:错误:|OWNER_CONTROL_REQUIRED|CONFIG_DENIED|UNKNOWN\/)/u.test(value))
|
|
207
|
+
throw new Error(value);
|
|
208
|
+
}
|
|
209
|
+
/** Reuse an ACTIVE same-name Territory without changing its topology. */
|
|
210
|
+
export function ensureGuiSetupTerritory(store, input, auth) {
|
|
211
|
+
const name = input.name.trim();
|
|
212
|
+
const workspacePath = input.workspacePath?.trim() || null;
|
|
213
|
+
const existing = store.getTerritoryByName(input.kingdomId, name);
|
|
214
|
+
if (existing) {
|
|
215
|
+
if (existing.status !== 'ACTIVE') {
|
|
216
|
+
throw new Error(`错误:setup.basic 不能复用 ${existing.status} 领地「${name}」;不会自动改写拓扑。`);
|
|
217
|
+
}
|
|
218
|
+
if (workspacePath !== null && existing.workspace_path !== workspacePath) {
|
|
219
|
+
throw new Error(`错误:setup.basic 同名领地「${name}」的 workspace 冲突(已有 ${existing.workspace_path ?? '未设置'},请求 ${workspacePath});不会自动改写拓扑。`);
|
|
220
|
+
}
|
|
221
|
+
return existing;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const result = createTerritory(store, {
|
|
225
|
+
kingdomId: input.kingdomId,
|
|
226
|
+
name,
|
|
227
|
+
workspacePath: workspacePath ?? undefined,
|
|
228
|
+
}, auth);
|
|
229
|
+
throwOnGuiSetupFailure(result);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
233
|
+
if (/UNIQUE constraint failed: territories/u.test(detail)) {
|
|
234
|
+
throw new Error(`错误:setup.basic 同名领地「${name}」已有 DELETED/tombstone 记录;不会自动重建或改写拓扑。`);
|
|
235
|
+
}
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
const created = store.getTerritoryByName(input.kingdomId, name);
|
|
239
|
+
if (!created)
|
|
240
|
+
throw new Error('错误:setup.basic 创建后找不到 Territory。');
|
|
241
|
+
return created;
|
|
242
|
+
}
|
|
243
|
+
/** Ensure setup role identity without ever binding a Worker to the activator. */
|
|
244
|
+
export function ensureGuiSetupBinding(store, input, auth) {
|
|
245
|
+
const existing = store.getBindingByRole(input.kingdomId, input.roleType);
|
|
246
|
+
if (!existing) {
|
|
247
|
+
const result = bindRole(store, {
|
|
248
|
+
kingdomId: input.kingdomId,
|
|
249
|
+
roleType: input.roleType,
|
|
250
|
+
roleName: input.roleName,
|
|
251
|
+
sessionId: input.sessionId,
|
|
252
|
+
}, auth);
|
|
253
|
+
throwOnGuiSetupFailure(result);
|
|
254
|
+
const created = store.getBindingByRole(input.kingdomId, input.roleType);
|
|
255
|
+
if (!created)
|
|
256
|
+
throw new Error(`错误:setup.basic 创建 ${input.roleType} 后找不到 binding。`);
|
|
257
|
+
return created;
|
|
258
|
+
}
|
|
259
|
+
if ((input.roleType === 'CHANCELLOR' || input.roleType === 'SUPERVISOR')
|
|
260
|
+
&& existing.session_id !== input.sessionId) {
|
|
261
|
+
const result = rebindSession(store, {
|
|
262
|
+
kingdomId: input.kingdomId,
|
|
263
|
+
bindingId: existing.binding_id,
|
|
264
|
+
sessionId: input.sessionId,
|
|
265
|
+
}, auth);
|
|
266
|
+
throwOnGuiSetupFailure(result);
|
|
267
|
+
return store.getBindingById(existing.binding_id) ?? existing;
|
|
268
|
+
}
|
|
269
|
+
return existing;
|
|
270
|
+
}
|
|
271
|
+
function sameGuiExecutionProfile(current, desired) {
|
|
272
|
+
return (current?.model ?? null) === (desired.model ?? null)
|
|
273
|
+
&& (current?.provider ?? null) === (desired.provider ?? null);
|
|
274
|
+
}
|
|
275
|
+
/** Set the requested Worker profile only when its semantic value changes. */
|
|
276
|
+
export function ensureGuiSetupExecutionProfile(store, input, auth) {
|
|
277
|
+
const binding = store.getBindingById(input.bindingId);
|
|
278
|
+
if (!binding || binding.kingdom_id !== input.kingdomId) {
|
|
279
|
+
throw new Error(`错误:找不到当前王国的 Worker binding ${input.bindingId}。`);
|
|
280
|
+
}
|
|
281
|
+
if (sameGuiExecutionProfile(parseExecutionProfile(binding.execution_profile_json), input.profile)) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const result = setExecutionProfile(store, {
|
|
285
|
+
kingdomId: input.kingdomId,
|
|
286
|
+
bindingId: input.bindingId,
|
|
287
|
+
profile: input.profile,
|
|
288
|
+
}, auth);
|
|
289
|
+
throwOnGuiSetupFailure(result);
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
/** Set Territory scope only when the target Supervisor binding changes. */
|
|
293
|
+
export function ensureGuiSetupTerritorySupervisor(store, input, auth) {
|
|
294
|
+
const territory = store.getTerritoryById(input.territoryId);
|
|
295
|
+
if (!territory || territory.kingdom_id !== input.kingdomId) {
|
|
296
|
+
throw new Error(`错误:领地不存在(id=${input.territoryId})。`);
|
|
297
|
+
}
|
|
298
|
+
if (territory.supervisor_binding_id === input.supervisorBindingId)
|
|
299
|
+
return null;
|
|
300
|
+
const result = setTerritorySupervisor(store, {
|
|
301
|
+
kingdomId: input.kingdomId,
|
|
302
|
+
territoryId: input.territoryId,
|
|
303
|
+
supervisorBindingId: input.supervisorBindingId,
|
|
304
|
+
}, auth);
|
|
305
|
+
throwOnGuiSetupFailure(result);
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
export function apply(ctx, config, dependencies = {}) {
|
|
47
309
|
// 单一王国管理器(自包含 SQLite;KingdomStore 连接生命周期 = 插件生命周期)
|
|
48
310
|
const manager = new KingdomManager({
|
|
49
311
|
kingdomName: config.kingdomName,
|
|
@@ -53,6 +315,21 @@ export function apply(ctx, config) {
|
|
|
53
315
|
// 卸载/重载时关闭 SQLite 连接(disposer 由 fiber 自动收集执行)
|
|
54
316
|
ctx.effect(() => () => manager.close());
|
|
55
317
|
const store = manager.storeHandle;
|
|
318
|
+
const disclosure = new ToolDisclosureRuntime(config.toolDisclosure ?? { mode: 'off' });
|
|
319
|
+
ctx.effect(() => () => disclosure.dispose());
|
|
320
|
+
const costObserver = installRuntimeCostObservers(ctx, store, {
|
|
321
|
+
runtimeInstanceRef: `dsh-${hostname()}`, mode: disclosure.config.mode, disclosureMeta: assembly => disclosure.metadata(assembly),
|
|
322
|
+
});
|
|
323
|
+
ctx.effect(() => () => costObserver.dispose());
|
|
324
|
+
const costRuntime = { toolDisclosureMode: disclosure.config.mode, observerAvailable: costObserver.available };
|
|
325
|
+
if (disclosure.config.mode === 'pilot')
|
|
326
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
327
|
+
name: DISCOVERY_TOOL_NAME,
|
|
328
|
+
description: 'Find currently granted tools by a short name or purpose query. Matching native schemas become visible on the next step; discovery grants no permission.',
|
|
329
|
+
parameters: { query: { type: 'string', required: true, description: 'A short exact tool name or purpose; at most 160 characters.' } },
|
|
330
|
+
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
|
|
331
|
+
async execute(args, exec) { return JSON.stringify(disclosure.find(args, exec)); },
|
|
332
|
+
})), 'dsh-kingdom: authorized tool discovery pilot');
|
|
56
333
|
const requireKingdom = () => {
|
|
57
334
|
const kingdom = store.getDefaultKingdom();
|
|
58
335
|
return kingdom ? kingdom.kingdom_id : null;
|
|
@@ -71,57 +348,422 @@ export function apply(ctx, config) {
|
|
|
71
348
|
+ 'GUI 若提供派发/复核/返工按钮,必须显著标注为「本地可信演示权限」。',
|
|
72
349
|
};
|
|
73
350
|
const commandContext = (kingdomId, principal) => ({ kingdomId, auth: authView, ...principal ? { principal } : {} });
|
|
351
|
+
const dshRegistrySeam = () => ({
|
|
352
|
+
agents: ctx.get('agents'),
|
|
353
|
+
sessions: ctx.get('sessions'),
|
|
354
|
+
});
|
|
355
|
+
const trustedToolPrincipal = (exec) => {
|
|
356
|
+
const resolved = resolveTrustedToolSession(exec, dshRegistrySeam());
|
|
357
|
+
return resolved.classification === 'ACTIVE' ? { sessionId: resolved.sessionId } : undefined;
|
|
358
|
+
};
|
|
359
|
+
const targetSessionRef = (exec) => {
|
|
360
|
+
const target = agentSession(exec.agent);
|
|
361
|
+
return target?.sessionId ?? null;
|
|
362
|
+
};
|
|
363
|
+
const ownerBindingIntentContext = (exec) => {
|
|
364
|
+
const kingdomId = requireKingdom();
|
|
365
|
+
const resolved = resolveTrustedToolSession(exec, dshRegistrySeam());
|
|
366
|
+
return {
|
|
367
|
+
// This is only the target reference echoed by the runtime context. The
|
|
368
|
+
// explicit classification below is the only liveness/authority seam.
|
|
369
|
+
target_session_ref: targetSessionRef(exec),
|
|
370
|
+
target_session_classification: resolved.classification,
|
|
371
|
+
kingdom_id: kingdomId,
|
|
372
|
+
role_bindings: kingdomId
|
|
373
|
+
? store.listBindings(kingdomId).map(binding => ({
|
|
374
|
+
binding_id: binding.binding_id,
|
|
375
|
+
role_type: binding.role_type,
|
|
376
|
+
role_name: binding.role_name,
|
|
377
|
+
session_id: binding.session_id,
|
|
378
|
+
status: binding.status,
|
|
379
|
+
kingdom_id: binding.kingdom_id,
|
|
380
|
+
}))
|
|
381
|
+
: [],
|
|
382
|
+
territories: kingdomId
|
|
383
|
+
? store.listTerritories(kingdomId).map(territory => ({
|
|
384
|
+
territory_id: territory.territory_id,
|
|
385
|
+
name: territory.name,
|
|
386
|
+
status: territory.status,
|
|
387
|
+
kingdom_id: territory.kingdom_id,
|
|
388
|
+
supervisor_binding_id: territory.supervisor_binding_id,
|
|
389
|
+
}))
|
|
390
|
+
: [],
|
|
391
|
+
};
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* Public governed start 永远不接受 declarative 权限:调用者必须由 DSH Runtime
|
|
395
|
+
* 提供可验证的 session,再由 Core 将它解析为该领地的 Supervisor binding。
|
|
396
|
+
*/
|
|
397
|
+
const governedStartCommandContext = (kingdomId, principal) => ({
|
|
398
|
+
kingdomId,
|
|
399
|
+
auth: {
|
|
400
|
+
mode: 'session-bound',
|
|
401
|
+
trustLevel: 'session-verified',
|
|
402
|
+
note: 'kingdom_start_task_governed 只接受由 DSH Runtime session 证明的 Supervisor 调用。',
|
|
403
|
+
},
|
|
404
|
+
...principal ? { principal } : {},
|
|
405
|
+
});
|
|
74
406
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
407
|
+
* The ticket is a launch-only secret. It may cross the local process into
|
|
408
|
+
* the user's browser, but it must never enter CommandResult.text, logger
|
|
409
|
+
* output, or a Kingdom event. The host has no browser service in its public
|
|
410
|
+
* Cordis surface, so use only the platform's existing URL opener seam.
|
|
79
411
|
*/
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
412
|
+
const openLocalConsole = dependencies.openLocalConsole ?? ((url) => {
|
|
413
|
+
try {
|
|
414
|
+
if (process.platform === 'win32') {
|
|
415
|
+
const result = spawnSync('cmd.exe', ['/d', '/c', 'start', '', url], {
|
|
416
|
+
stdio: 'ignore',
|
|
417
|
+
windowsHide: true,
|
|
418
|
+
});
|
|
419
|
+
return result.error === undefined && result.status === 0;
|
|
420
|
+
}
|
|
421
|
+
const command = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
422
|
+
const result = spawnSync(command, [url], { stdio: 'ignore' });
|
|
423
|
+
return result.error === undefined && result.status === 0;
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
/** Grant 仅表达当前已认证 Supervisor 授予的 capability,绝不承载调用者身份。 */
|
|
430
|
+
const parseSupervisorGrant = (grantJson) => {
|
|
431
|
+
try {
|
|
432
|
+
const parsed = JSON.parse(grantJson);
|
|
433
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
434
|
+
return { ok: false, message: 'grant_json 必须是非空 JSON 对象。' };
|
|
435
|
+
}
|
|
436
|
+
const entries = Object.entries(parsed);
|
|
437
|
+
if (entries.length === 0 || entries.some(([, allowed]) => typeof allowed !== 'boolean')) {
|
|
438
|
+
return { ok: false, message: 'grant_json 必须是非空、value 全为 boolean 的 capability Grant 对象。' };
|
|
439
|
+
}
|
|
440
|
+
return { ok: true, grant: Object.fromEntries(entries) };
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
return { ok: false, message: 'grant_json 必须是合法 JSON。' };
|
|
444
|
+
}
|
|
83
445
|
};
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
446
|
+
const collectDispatchUsage = async (dispatchId, authorized = () => true) => {
|
|
447
|
+
const dispatch = store.getDispatch(dispatchId);
|
|
448
|
+
if (!dispatch?.runtime_dispatch_ref)
|
|
449
|
+
return;
|
|
450
|
+
// Resolve the persisted exact reference against Runtime-owned agents; do not mint a branded SessionId.
|
|
451
|
+
const agent = ctx.get('agents')?.list().find(candidate => candidate.id === dispatch.session_ref);
|
|
452
|
+
const observed = await observeDshDispatchUsage(agent?.session, dispatch.runtime_dispatch_ref);
|
|
453
|
+
if (!authorized())
|
|
454
|
+
return;
|
|
455
|
+
recordDispatchUsage(store, dispatchId, observed);
|
|
456
|
+
};
|
|
457
|
+
const runGovernedStart = async (args, caller) => {
|
|
458
|
+
const requestedSandboxMode = args.sandboxMode ?? 'workspace-write';
|
|
459
|
+
if (!GUI_START_SANDBOX_MODES.includes(requestedSandboxMode)) {
|
|
460
|
+
return `INPUT_DENIED [INVALID_SANDBOX_MODE]: sandbox_mode 必须是 ${GUI_START_SANDBOX_MODES.join(' / ')} 之一。`;
|
|
461
|
+
}
|
|
462
|
+
const sandboxMode = requestedSandboxMode;
|
|
463
|
+
const kingdomId = requireKingdom();
|
|
464
|
+
if (!kingdomId)
|
|
465
|
+
return '尚未初始化王国。请先 /kingdom init。';
|
|
466
|
+
const principal = caller.kind === 'tool'
|
|
467
|
+
? trustedToolPrincipal(caller.execution)
|
|
468
|
+
: caller.principal;
|
|
469
|
+
if (!principal) {
|
|
470
|
+
return 'AUTHZ_DENIED [UNAUTHORIZED_PRINCIPAL]: 无法从 DSH Runtime 证明当前调用方的唯一活动 session。';
|
|
471
|
+
}
|
|
472
|
+
// Public governed start 不随全局 authMode 降级:真实 DSH session → Supervisor
|
|
473
|
+
// binding → Territory scope 必须在任何 executor/session/lease 准备前全部通过。
|
|
474
|
+
const supervisor = resolveGovernedStartSupervisor(store, governedStartCommandContext(kingdomId, principal), args.taskId);
|
|
475
|
+
if (!supervisor.ok) {
|
|
476
|
+
return `AUTHZ_DENIED [${supervisor.code}]: ${supervisor.message}`;
|
|
477
|
+
}
|
|
478
|
+
const parsedGrant = parseSupervisorGrant(args.grantJson);
|
|
479
|
+
if (!parsedGrant.ok) {
|
|
480
|
+
return `INPUT_DENIED [INVALID_SUPERVISOR_GRANT]: ${parsedGrant.message}`;
|
|
481
|
+
}
|
|
482
|
+
if (!store.isSchemaV4) {
|
|
483
|
+
return '错误:Schema v4 未迁移(正式 kingdom.db 迁移须经 Formal DB Migration Gate);governed 执行不可用(fail-closed)。';
|
|
484
|
+
}
|
|
485
|
+
const loadedTask = supervisor.task;
|
|
486
|
+
if (loadedTask.status !== 'ASSIGNED' && loadedTask.status !== 'RUNNING') {
|
|
487
|
+
return `错误:任务状态 ${loadedTask.status} 不可 governed 启动(仅 ASSIGNED / REWORK 后的 RUNNING)。`;
|
|
488
|
+
}
|
|
489
|
+
const unsettledGovernedExecution = store.listExecutions(loadedTask.task_id)
|
|
490
|
+
.slice()
|
|
491
|
+
.reverse()
|
|
492
|
+
.find(execution => execution.execution_contract === 'GOVERNED_PERSISTENT'
|
|
493
|
+
&& !isTerminalExecutionState(asExecutionState(execution.state)));
|
|
494
|
+
if (unsettledGovernedExecution) {
|
|
495
|
+
const code = unsettledGovernedExecution.state === 'RECOVERING'
|
|
496
|
+
? 'EXISTING_EXECUTION_RECOVERING'
|
|
497
|
+
: 'EXISTING_EXECUTION_UNSETTLED';
|
|
498
|
+
return `GOVERNED_EXECUTION_DENIED [${code}]: Task ${loadedTask.task_id} 已有未终结的 governed Execution `
|
|
499
|
+
+ `${unsettledGovernedExecution.execution_id}(${unsettledGovernedExecution.state})。`
|
|
500
|
+
+ '请先完成 terminal/reconcile 对账;未访问 Runtime/Session/Lease,Task 保持不变且不会自动重试。';
|
|
501
|
+
}
|
|
502
|
+
const territory = store.getTerritoryById(loadedTask.territory_id);
|
|
503
|
+
if (!territory)
|
|
504
|
+
return '错误:任务领地缺失。';
|
|
505
|
+
const worker = loadedTask.assigned_binding_id;
|
|
506
|
+
if (!worker)
|
|
507
|
+
return '错误:任务未指派 Worker。';
|
|
508
|
+
const initialCeiling = store.getKingdomCapabilityCeiling(kingdomId);
|
|
509
|
+
const initialProfile = store.getBindingById(worker)?.execution_profile_json;
|
|
510
|
+
const stillAuthorized = () => {
|
|
511
|
+
if (caller.kind === 'gui' && caller.signal?.aborted)
|
|
512
|
+
return false;
|
|
513
|
+
const currentPrincipal = caller.kind === 'tool' ? trustedToolPrincipal(caller.execution) : caller.principal;
|
|
514
|
+
if (!currentPrincipal || currentPrincipal.sessionId !== principal.sessionId)
|
|
515
|
+
return false;
|
|
516
|
+
const checked = resolveGovernedStartSupervisor(store, governedStartCommandContext(kingdomId, currentPrincipal), args.taskId);
|
|
517
|
+
return checked.ok && checked.binding.binding_id === supervisor.binding.binding_id
|
|
518
|
+
&& checked.task.assigned_binding_id === worker && checked.task.territory_id === loadedTask.territory_id
|
|
519
|
+
&& checked.task.title === loadedTask.title && checked.task.description === loadedTask.description
|
|
520
|
+
&& checked.task.acceptance_criteria === loadedTask.acceptance_criteria
|
|
521
|
+
&& checked.task.capability_requirement_json === loadedTask.capability_requirement_json
|
|
522
|
+
&& store.getTerritoryById(loadedTask.territory_id)?.workspace_path === territory.workspace_path
|
|
523
|
+
&& store.getKingdomCapabilityCeiling(kingdomId) === initialCeiling
|
|
524
|
+
&& store.getBindingById(worker)?.execution_profile_json === initialProfile;
|
|
525
|
+
};
|
|
526
|
+
const agents = ctx.get('agents');
|
|
527
|
+
if (!agents) {
|
|
528
|
+
return '错误:当前 dsh 组合未提供 agents 服务(Persistent Session 不可用);governed 执行不可用。';
|
|
529
|
+
}
|
|
530
|
+
// Owner V0.8 PRODUCTION-PATH CLOSURE(正式入口 E2E seam):S4 materialize 需要**模块函数** seam
|
|
531
|
+
// (`setSandboxMode = session.append('sandbox/mode')` / `setApprovalPolicy = session.append('approval/policy')`),
|
|
532
|
+
// 而非 ctx 服务对象(SandboxPolicyService/ApprovalService 无这些方法,传服务 → MATERIALIZE_FAILED)。
|
|
533
|
+
// 动态 import(懒解析):失败 → null → materialize 报「缺失」→ DENIED(fail-closed,不崩溃插件)。
|
|
534
|
+
const s4Seam = dependencies.loadS4Policy
|
|
535
|
+
? await dependencies.loadS4Policy()
|
|
536
|
+
: await (async () => {
|
|
537
|
+
try {
|
|
538
|
+
// 变量 specifier:运行时经 dsh loader 解析(动态 seam;静态类型不做解析校验)
|
|
539
|
+
const sbSpec = '@deepseek-ai/dsh-sandbox-policy';
|
|
540
|
+
const apSpec = '@deepseek-ai/dsh-user-approval';
|
|
541
|
+
const sandbox = (await import(sbSpec));
|
|
542
|
+
const approval = (await import(apSpec));
|
|
543
|
+
return {
|
|
544
|
+
sandboxPolicy: { setSandboxMode: sandbox.setSandboxMode },
|
|
545
|
+
approval: { setApprovalPolicy: approval.setApprovalPolicy },
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
return { sandboxPolicy: null, approval: null };
|
|
550
|
+
}
|
|
551
|
+
})();
|
|
552
|
+
if (!stillAuthorized())
|
|
553
|
+
return 'AUTHZ_DENIED [AUTHORITY_CHANGED]: 准备期间身份、任务范围或执行配置变化;未访问 Worker Session。';
|
|
554
|
+
const adapter = new DshRuntimeAdapter({
|
|
555
|
+
runtimeInstanceRef: `dsh-${hostname()}`,
|
|
556
|
+
...(disclosure.config.mode === 'pilot' ? { toolDisclosure: { install: disclosure.install.bind(disclosure) } } : {}),
|
|
557
|
+
provider: config.workerProvider || 'spawn',
|
|
558
|
+
model: null,
|
|
559
|
+
agents,
|
|
560
|
+
permission: ctx.get('permission'),
|
|
561
|
+
sandboxPolicy: s4Seam.sandboxPolicy,
|
|
562
|
+
approval: s4Seam.approval,
|
|
563
|
+
presets: ctx.get('agentPresets'),
|
|
564
|
+
});
|
|
565
|
+
// attempt 编号必须纳入 Lease Ledger(zero-execution 的 gate 拒绝也会消耗 attempt_no,
|
|
566
|
+
// 否则重试会撞 UNIQUE(task_id, attempt_no)——正式入口 E2E 实证)
|
|
567
|
+
const leaseMax = store.listLeases(kingdomId)
|
|
568
|
+
.filter((l) => l.task_id === loadedTask.task_id)
|
|
569
|
+
.reduce((m, l) => Math.max(m, l.attempt_no), 0);
|
|
570
|
+
const attemptNo = Math.max(store.nextAttemptNo(loadedTask.task_id), leaseMax + 1);
|
|
571
|
+
let result;
|
|
572
|
+
try {
|
|
573
|
+
result = await runGovernedTask({
|
|
574
|
+
store, adapter, kingdomId, workerBindingId: worker,
|
|
575
|
+
territoryId: loadedTask.territory_id, cwd: territory.workspace_path ?? process.cwd(),
|
|
576
|
+
taskId: loadedTask.task_id, attemptNo, supervisorBindingId: supervisor.binding.binding_id,
|
|
577
|
+
grant: parsedGrant.grant, requirementJson: store.getTaskCapabilityRequirement(loadedTask.task_id),
|
|
578
|
+
sandboxMode,
|
|
579
|
+
// Owner V0.8 PRODUCTION-PATH CLOSURE A:全局 workerProvider 仅作 provider 回退;
|
|
580
|
+
// Worker 的 provider/model 权威来源 = execution_profile_json(runGovernedTask 内部解析,
|
|
581
|
+
// model 缺失 → fail closed configuration error,不创建 Session、不 dispatch)。
|
|
582
|
+
globalProvider: config.workerProvider || 'spawn',
|
|
583
|
+
stillAuthorized,
|
|
584
|
+
// 真实模型 turn 轮询窗口(正式入口 E2E 实证:默认 100ms×40=4s 太短,turn 未及 terminal):
|
|
585
|
+
// 1s × 60 = 60s;超窗 → fail-closed 返回(进 RECOVERING,由 reconcile 处理)。
|
|
586
|
+
pollIntervalMs: dependencies.governedPolling?.intervalMs ?? 1000,
|
|
587
|
+
maxPolls: dependencies.governedPolling?.maxPolls ?? 60,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
const detail = (error instanceof Error ? error.message : String(error)).slice(0, 256);
|
|
592
|
+
const terminalDispatch = store.listDispatches(kingdomId).some(dispatch => dispatch.task_id === loadedTask.task_id
|
|
593
|
+
&& dispatch.attempt_no === attemptNo
|
|
594
|
+
&& dispatch.state === 'TERMINAL');
|
|
595
|
+
if (terminalDispatch) {
|
|
596
|
+
return `GOVERNED_SETTLEMENT_BLOCKED [UNPROVEN_TERMINAL_RELATION]: ${detail};未写入 Claim/REVIEW。`;
|
|
597
|
+
}
|
|
598
|
+
return `GOVERNED_EXECUTION_DENIED [DISPATCH_EXCEPTION]: ${detail}`;
|
|
599
|
+
}
|
|
600
|
+
if (!result.ok) {
|
|
601
|
+
const observedDispatch = store.listDispatchesForTaskAttempt(loadedTask.task_id, attemptNo)[0];
|
|
602
|
+
if (observedDispatch)
|
|
603
|
+
await collectDispatchUsage(observedDispatch.dispatch_id);
|
|
604
|
+
const prefix = result.reason.startsWith('Capability DENIED') ? 'CAPABILITY_DENIED' : 'GOVERNED_EXECUTION_DENIED';
|
|
605
|
+
if (observedDispatch && store.getDispatch(observedDispatch.dispatch_id)?.state === 'RECOVERING') {
|
|
606
|
+
return `${prefix}: ${result.reason}\n(任务保持 ${loadedTask.status};请由当前 Supervisor 使用 kingdom_reconcile_task 对账原 Dispatch ${observedDispatch.dispatch_id}。对账完成前不得再次启动任务;不会自动降级为 LEGACY_COMPAT。)`;
|
|
607
|
+
}
|
|
608
|
+
return `${prefix}: ${result.reason}\n(任务保持 ${loadedTask.status};请检查 Capability/Runtime 配置后,在同一 governed persistent 入口重试。不会自动降级为 LEGACY_COMPAT。)`;
|
|
609
|
+
}
|
|
610
|
+
// R11 settlement-before-Claim:先从已提交的 canonical
|
|
611
|
+
// Task/Execution/Lease/Dispatch relation 签发 product context,并消费同一
|
|
612
|
+
// opaque handle + version 完成 settlement。若 exact relation、version 或
|
|
613
|
+
// incident 记录无法证明,必须在任何 Claim/Task 写入前 fail-closed。
|
|
614
|
+
const task = store.getTask(loadedTask.task_id);
|
|
615
|
+
if (!task)
|
|
616
|
+
return 'GOVERNED_SETTLEMENT_BLOCKED [RELATION_MISSING]: terminal Task 已消失,未写入 Claim。';
|
|
617
|
+
let settledLease;
|
|
618
|
+
let runnerContext = null;
|
|
619
|
+
try {
|
|
620
|
+
runnerContext = getRunnerContextPort(result.dispatchId)
|
|
621
|
+
?? createRunnerContextPort(store, { dispatchId: result.dispatchId });
|
|
622
|
+
const activeRunnerContext = runnerContext;
|
|
623
|
+
const terminal = activeRunnerContext.read(activeRunnerContext.handle, activeRunnerContext.initialVersion);
|
|
624
|
+
const settled = activeRunnerContext.settle(activeRunnerContext.handle, terminal.version, (context) => {
|
|
625
|
+
// The callback receives the exact product-minted capability. Do not
|
|
626
|
+
// substitute result.* IDs: the port's bounded view is the relation
|
|
627
|
+
// consumed by settlement and the same handle/version is in-flight.
|
|
628
|
+
if (context.handle !== activeRunnerContext.handle || context.version !== terminal.version) {
|
|
629
|
+
throw new Error('R11 RunnerContext settlement handle/version continuity failed');
|
|
630
|
+
}
|
|
631
|
+
return settleAndRelease(store, context.view.leaseId, result.cleanupReceipt, 'governed-terminal-settlement', {
|
|
632
|
+
adapter,
|
|
633
|
+
fence: result.trustFence,
|
|
634
|
+
leaseId: context.view.leaseId,
|
|
635
|
+
sessionRef: context.view.sessionRef,
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
settledLease = settled.value;
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
642
|
+
return `GOVERNED_SETTLEMENT_BLOCKED [UNPROVEN_TERMINAL_RELATION]: ${detail};未写入 Claim/REVIEW。`;
|
|
643
|
+
}
|
|
644
|
+
finally {
|
|
645
|
+
// Ticket/connection revocation is transport cleanup only. It cannot
|
|
646
|
+
// release a Lease or alter Claim/Dispatch governance facts.
|
|
647
|
+
revokeRunnerContextBrokerContext(runnerContext);
|
|
648
|
+
forgetRunnerContextPort(result.dispatchId);
|
|
649
|
+
}
|
|
650
|
+
// Claim 到达(Claim ≠ Fact):结算完成或已明确进入 RECOVERING 后,才落
|
|
651
|
+
// worker_results + Task RUNNING→REVIEW(不是 DONE)。普通 cleanup false/
|
|
652
|
+
// throw/missing 与 post-TX-4 incident 都保留可信 terminal Claim。
|
|
653
|
+
// Owner V0.8 FINAL RELEASE BLOCKER:Claim outcome 按已验证 terminalOutcome 收敛
|
|
654
|
+
// (COMPLETED/FAILED/ABORTED)——禁止 hardcode COMPLETED;FAILED 不得生成 COMPLETED Claim;
|
|
655
|
+
// summary 仅来自真实 assistant 文本,无文本 → 诚实回退占位(不伪造"任务完成"类摘要)。
|
|
656
|
+
const outcome = result.terminalOutcome;
|
|
657
|
+
const summary = result.summary;
|
|
658
|
+
submitGovernedClaim(store, result.dispatchId, summary);
|
|
659
|
+
await collectDispatchUsage(result.dispatchId);
|
|
660
|
+
const recoveryNotice = settledLease?.state === 'RECOVERING'
|
|
661
|
+
? result.cleanupReceipt.status === 'CONFIRMED'
|
|
662
|
+
? `\n注意:terminal settlement 未能安全释放(${settledLease.release_reason ?? 'integrity recovery'}),Lease=RECOVERING;不得复用该 Session 或发起新 Dispatch,须先完成 reconcile/恢复。`
|
|
663
|
+
: `\n注意:enforcement cleanup 未确认(${result.cleanupReceipt.status}),Lease=RECOVERING;不得复用该 Session 或发起新 Dispatch,须先完成 reconcile/恢复。`
|
|
664
|
+
: '';
|
|
665
|
+
return `Governed Worker 已提交第 ${attemptNo} 次尝试的 Claim(outcome=${outcome},长期 Session ${result.sessionRef.slice(-8)}${result.created ? ' 新建' : ' 复用'})。\n`
|
|
666
|
+
+ `摘要:${summary}\n`
|
|
667
|
+
+ `任务「${task.title}」现在处于 **REVIEW**。这是一个待审查的 Claim,尚未成为任务完成事实——请 Supervisor 用 kingdom_review_task 裁定 ACCEPT / REWORK / FAIL。`
|
|
668
|
+
+ recoveryNotice;
|
|
669
|
+
};
|
|
670
|
+
// ── init 引导(首次初始化后给出组织方式与内置 GUI 入口)──
|
|
671
|
+
const guiLine = '直接键入 exact `/kingdom gui` 激活短期本地 control session 并打开内置操作台;不用时执行 `/kingdom gui stop`。';
|
|
672
|
+
const initGuidance = '\n\nOwner Control 已建立:Owner 是稳定的人类本机 principal,OWNER.session_id 保持 null。\n'
|
|
673
|
+
+ 'Owner-only 初始化、Territory、角色/session、Execution Profile 与 Capability Ceiling 写入必须来自 direct `/kingdom` Slash;GUI/HTTP 只保留 session-bound Role 命令。\n'
|
|
674
|
+
+ 'Agent Role Plane 请为 CHANCELLOR / SUPERVISOR / WORKER 使用真实独立 DSH Session;正常执行入口是 kingdom_start_task_governed。\n'
|
|
675
|
+
+ `GUI:${guiLine}`;
|
|
97
676
|
/** 首次初始化返回带引导文案;再次接入保持简洁。 */
|
|
98
677
|
const initResultText = (result) => result.action === 'initialized' ? `${result.detail}${initGuidance}` : result.detail;
|
|
99
|
-
|
|
678
|
+
const ownerToolDenied = (rejection) => {
|
|
679
|
+
if (!rejection) {
|
|
680
|
+
return 'OWNER_CONTROL_REQUIRED: Owner-only 写操作只能通过 direct `/kingdom` Slash 执行;'
|
|
681
|
+
+ 'Agent Tool、未激活的 GUI/HTTP、工具参数和 caller session 不能代表 Owner。';
|
|
682
|
+
}
|
|
683
|
+
return serializeOwnerBindingIntentDraft(draftOwnerBindingIntentFromRejectedWrite({
|
|
684
|
+
code: 'OWNER_CONTROL_REQUIRED',
|
|
685
|
+
operation: rejection.operation,
|
|
686
|
+
request: rejection.request,
|
|
687
|
+
context: ownerBindingIntentContext(rejection.exec),
|
|
688
|
+
}));
|
|
689
|
+
};
|
|
690
|
+
// ── 加载期孤儿处理:按 Execution Contract 保持事实诚实 ──────────
|
|
100
691
|
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
692
|
+
// LEGACY_COMPAT one-shot 随插件生命周期结束,可标记 ABORTED;
|
|
693
|
+
// GOVERNED_PERSISTENT 的 Runtime outcome 在重启后未知,只能进入
|
|
694
|
+
// RECOVERING 并等待显式对账,不能声称 Session 已停止或自动重试。
|
|
104
695
|
{
|
|
105
696
|
const kingdomId = requireKingdom();
|
|
106
697
|
if (kingdomId) {
|
|
107
698
|
const reclaimed = reclaimOrphanExecutions(store, kingdomId);
|
|
108
699
|
if (reclaimed > 0) {
|
|
109
|
-
ctx.logger.info(`dsh-kingdom
|
|
700
|
+
ctx.logger.info(`dsh-kingdom:已按 Execution Contract 处理 ${reclaimed} 个加载期残留 Execution`);
|
|
110
701
|
}
|
|
111
702
|
}
|
|
112
703
|
}
|
|
704
|
+
ctx.effect(() => () => discardGovernedRecoveryContexts(store));
|
|
705
|
+
const runGovernedReconcile = async (args, caller) => {
|
|
706
|
+
const kingdomId = requireKingdom();
|
|
707
|
+
if (!kingdomId)
|
|
708
|
+
return '尚未初始化王国。';
|
|
709
|
+
const resolveCaller = () => {
|
|
710
|
+
if (caller.kind === 'tool')
|
|
711
|
+
return trustedToolPrincipal(caller.execution);
|
|
712
|
+
if (caller.signal?.aborted)
|
|
713
|
+
return undefined;
|
|
714
|
+
if (!caller.principal.sessionId)
|
|
715
|
+
return undefined;
|
|
716
|
+
// GUI has a broker-owned activation, not an active Tool/current-initiator scope.
|
|
717
|
+
const live = validateLiveDirectSession(caller.principal.sessionId, dshRegistrySeam());
|
|
718
|
+
return live.ok && live.agent === caller.agent ? caller.principal : undefined;
|
|
719
|
+
};
|
|
720
|
+
const initialPrincipal = resolveCaller();
|
|
721
|
+
if (!initialPrincipal)
|
|
722
|
+
return 'AUTHZ_DENIED [UNAUTHORIZED_PRINCIPAL]: 无法证明当前 DSH Session。';
|
|
723
|
+
const authorized = resolveGovernedStartSupervisor(store, governedStartCommandContext(kingdomId, initialPrincipal), args.taskId);
|
|
724
|
+
if (!authorized.ok)
|
|
725
|
+
return `AUTHZ_DENIED [${authorized.code}]: ${authorized.message}`;
|
|
726
|
+
const execution = store.latestExecution(args.taskId);
|
|
727
|
+
const dispatch = args.dispatchId ? store.getDispatch(args.dispatchId) : execution && store.listDispatchesForTaskAttempt(args.taskId, execution.attempt_no)
|
|
728
|
+
.find(row => row.execution_id === execution.execution_id);
|
|
729
|
+
if (!dispatch || dispatch.task_id !== args.taskId || dispatch.kingdom_id !== kingdomId)
|
|
730
|
+
return 'RECOVERY_REQUIRED [DISPATCH_MISSING]: 未找到该任务的执行凭据。';
|
|
731
|
+
const stillAuthorized = () => {
|
|
732
|
+
const principal = resolveCaller();
|
|
733
|
+
if (!principal || principal.sessionId !== initialPrincipal.sessionId)
|
|
734
|
+
return false;
|
|
735
|
+
const current = resolveGovernedStartSupervisor(store, governedStartCommandContext(kingdomId, principal), args.taskId);
|
|
736
|
+
return current.ok && current.binding.binding_id === authorized.binding.binding_id;
|
|
737
|
+
};
|
|
738
|
+
const reconciled = await reconcileGovernedDispatch(store, dispatch.dispatch_id, stillAuthorized);
|
|
739
|
+
if (!stillAuthorized())
|
|
740
|
+
return 'AUTHZ_DENIED [AUTHORITY_REVOKED]: 对账期间授权已失效,请重新确认当前任务。';
|
|
741
|
+
await collectDispatchUsage(dispatch.dispatch_id, stillAuthorized);
|
|
742
|
+
if (!stillAuthorized())
|
|
743
|
+
return 'AUTHZ_DENIED [AUTHORITY_REVOKED]: 对账期间授权已失效。';
|
|
744
|
+
if (reconciled.status !== 'TERMINAL')
|
|
745
|
+
return `RECOVERY_REQUIRED [${reconciled.status}]: ${reconciled.reason};未重新派发任务,未释放 Lease。`;
|
|
746
|
+
try {
|
|
747
|
+
const claim = submitGovernedClaim(store, dispatch.dispatch_id, reconciled.summary ?? '(终态证据已确认;最终消息文本当前不可恢复)');
|
|
748
|
+
const lease = store.getLease(dispatch.lease_id);
|
|
749
|
+
const task = store.getTask(args.taskId);
|
|
750
|
+
return `${claim.created ? '对账已补齐' : '已存在'}第 ${dispatch.attempt_no} 次 Claim(${claim.outcome})。\n摘要:${claim.summary}\n任务状态 ${task?.status ?? 'UNKNOWN'};Lease=${lease?.state ?? 'UNKNOWN'}。${lease?.state === 'RECOVERING' ? '清理或结算证据不足,不重试清理、不复用 Session。' : ''}任务完成仍由 Supervisor 审查裁定。`;
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
return `RECOVERY_REQUIRED [CLAIM_BLOCKED]: ${error instanceof Error ? error.message : String(error)}`;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
113
756
|
// ── 工具注册(全部挂 ctx.effect)────────────────────────────
|
|
114
757
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
115
758
|
name: 'kingdom_init',
|
|
116
|
-
description: '
|
|
759
|
+
description: 'Owner-only 写操作已关闭:请由用户直接键入 `/kingdom init`(Agent Tool 调用确定性 OWNER_CONTROL_REQUIRED)',
|
|
117
760
|
parameters: {},
|
|
118
761
|
output: {
|
|
119
762
|
schema: { type: 'string' },
|
|
120
763
|
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
121
764
|
},
|
|
122
765
|
async execute() {
|
|
123
|
-
|
|
124
|
-
return initResultText(result);
|
|
766
|
+
return ownerToolDenied();
|
|
125
767
|
},
|
|
126
768
|
})), 'dsh-kingdom: init tool');
|
|
127
769
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
@@ -138,7 +780,7 @@ export function apply(ctx, config) {
|
|
|
138
780
|
})), 'dsh-kingdom: status tool');
|
|
139
781
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
140
782
|
name: 'kingdom_create_territory',
|
|
141
|
-
description: '
|
|
783
|
+
description: 'Owner-only:Agent Tool 不代行;请使用 `/kingdom territory.create <JSON>`',
|
|
142
784
|
parameters: {
|
|
143
785
|
name: { type: 'string', required: true, description: '领地名,如 RAG 研发领' },
|
|
144
786
|
workspace_path: { type: 'string', description: '工作区绝对路径,默认当前目录' },
|
|
@@ -149,18 +791,7 @@ export function apply(ctx, config) {
|
|
|
149
791
|
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
150
792
|
},
|
|
151
793
|
async execute(args, exec) {
|
|
152
|
-
|
|
153
|
-
if (!kingdomId)
|
|
154
|
-
return '尚未初始化王国。请先 /kingdom init 或说“初始化王国”。';
|
|
155
|
-
return createTerritory(store, {
|
|
156
|
-
kingdomId,
|
|
157
|
-
name: args.name,
|
|
158
|
-
workspacePath: args.workspace_path,
|
|
159
|
-
summary: args.summary,
|
|
160
|
-
}, {
|
|
161
|
-
mode: config.authMode,
|
|
162
|
-
principalSessionId: sessionPrincipal(exec)?.sessionId,
|
|
163
|
-
});
|
|
794
|
+
return ownerToolDenied();
|
|
164
795
|
},
|
|
165
796
|
})), 'dsh-kingdom: create-territory tool');
|
|
166
797
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
@@ -180,7 +811,7 @@ export function apply(ctx, config) {
|
|
|
180
811
|
})), 'dsh-kingdom: list-territories tool');
|
|
181
812
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
182
813
|
name: 'kingdom_delete_territory',
|
|
183
|
-
description: '
|
|
814
|
+
description: 'Owner-only:Agent Tool 不代行;请使用 `/kingdom territory.delete <JSON>`(任务级联语义保持)',
|
|
184
815
|
parameters: {
|
|
185
816
|
territory_id: { type: 'string', description: '领地 id(与 name 二选一,优先)' },
|
|
186
817
|
name: { type: 'string', description: '领地名(与 territory_id 二选一)' },
|
|
@@ -192,26 +823,12 @@ export function apply(ctx, config) {
|
|
|
192
823
|
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
193
824
|
},
|
|
194
825
|
async execute(args, exec) {
|
|
195
|
-
|
|
196
|
-
if (!kingdomId)
|
|
197
|
-
return '尚未初始化王国。请先 /kingdom init。';
|
|
198
|
-
if (!args.territory_id && !args.name)
|
|
199
|
-
return '错误:请提供 territory_id 或 name 指定要删除的领地。';
|
|
200
|
-
return deleteTerritory(store, {
|
|
201
|
-
kingdomId,
|
|
202
|
-
territoryId: args.territory_id,
|
|
203
|
-
name: args.name,
|
|
204
|
-
force: args.force,
|
|
205
|
-
reason: args.reason,
|
|
206
|
-
}, {
|
|
207
|
-
mode: config.authMode,
|
|
208
|
-
principalSessionId: sessionPrincipal(exec)?.sessionId,
|
|
209
|
-
});
|
|
826
|
+
return ownerToolDenied();
|
|
210
827
|
},
|
|
211
828
|
})), 'dsh-kingdom: delete-territory tool');
|
|
212
829
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
213
830
|
name: 'kingdom_set_territory_supervisor',
|
|
214
|
-
description: '
|
|
831
|
+
description: 'Owner-only:Agent Tool 不代行;请使用 `/kingdom territory.supervisor <JSON>`',
|
|
215
832
|
parameters: {
|
|
216
833
|
territory_id: { type: 'string', required: true, description: '领地 id' },
|
|
217
834
|
supervisor_binding_id: { type: 'string', description: 'ACTIVE SUPERVISOR 绑定 id;传 null 解除主理' },
|
|
@@ -221,22 +838,12 @@ export function apply(ctx, config) {
|
|
|
221
838
|
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
222
839
|
},
|
|
223
840
|
async execute(args, exec) {
|
|
224
|
-
|
|
225
|
-
if (!kingdomId)
|
|
226
|
-
return '尚未初始化王国。请先 /kingdom init。';
|
|
227
|
-
return setTerritorySupervisor(store, {
|
|
228
|
-
kingdomId,
|
|
229
|
-
territoryId: args.territory_id,
|
|
230
|
-
supervisorBindingId: args.supervisor_binding_id === undefined ? null : args.supervisor_binding_id,
|
|
231
|
-
}, {
|
|
232
|
-
mode: config.authMode,
|
|
233
|
-
principalSessionId: sessionPrincipal(exec)?.sessionId,
|
|
234
|
-
});
|
|
841
|
+
return ownerToolDenied();
|
|
235
842
|
},
|
|
236
843
|
})), 'dsh-kingdom: set-territory-supervisor tool');
|
|
237
844
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
238
845
|
name: 'kingdom_bind_role',
|
|
239
|
-
description: '
|
|
846
|
+
description: 'Owner-only:Agent Tool 不代行;请使用 `/kingdom role.bind <JSON>`(OWNER 不得绑定 Session)',
|
|
240
847
|
parameters: {
|
|
241
848
|
role_type: { type: 'string', required: true, description: '角色:OWNER/CHANCELLOR/SUPERVISOR/WORKER' },
|
|
242
849
|
role_name: { type: 'string', description: '角色名,如 Worker-01' },
|
|
@@ -250,26 +857,20 @@ export function apply(ctx, config) {
|
|
|
250
857
|
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
251
858
|
},
|
|
252
859
|
async execute(args, exec) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
modelName: args.model_name,
|
|
262
|
-
agentName: args.agent_name,
|
|
263
|
-
sessionMeta: args.session_meta,
|
|
264
|
-
}, {
|
|
265
|
-
mode: config.authMode,
|
|
266
|
-
principalSessionId: sessionPrincipal(exec)?.sessionId,
|
|
860
|
+
return ownerToolDenied({
|
|
861
|
+
operation: 'kingdom_bind_role',
|
|
862
|
+
request: {
|
|
863
|
+
role_type: args.role_type,
|
|
864
|
+
role_name: args.role_name,
|
|
865
|
+
session_id: args.session_id,
|
|
866
|
+
},
|
|
867
|
+
exec,
|
|
267
868
|
});
|
|
268
869
|
},
|
|
269
870
|
})), 'dsh-kingdom: bind-role tool');
|
|
270
871
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
271
872
|
name: 'kingdom_unbind_role',
|
|
272
|
-
description: '
|
|
873
|
+
description: 'Owner-only:Agent Tool 不代行;请使用 `/kingdom role.unbind <JSON>`(OWNER 受保护)',
|
|
273
874
|
parameters: {
|
|
274
875
|
role_type: { type: 'string', description: '角色:CHANCELLOR/SUPERVISOR/WORKER(与 binding_id 二选一)' },
|
|
275
876
|
binding_id: { type: 'string', description: '绑定 id(与 role_type 二选一)' },
|
|
@@ -280,23 +881,12 @@ export function apply(ctx, config) {
|
|
|
280
881
|
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
281
882
|
},
|
|
282
883
|
async execute(args, exec) {
|
|
283
|
-
|
|
284
|
-
if (!kingdomId)
|
|
285
|
-
return '尚未初始化王国。请先 /kingdom init。';
|
|
286
|
-
return unbindRole(store, {
|
|
287
|
-
kingdomId,
|
|
288
|
-
roleType: args.role_type,
|
|
289
|
-
bindingId: args.binding_id,
|
|
290
|
-
reason: args.reason,
|
|
291
|
-
}, {
|
|
292
|
-
mode: config.authMode,
|
|
293
|
-
principalSessionId: sessionPrincipal(exec)?.sessionId,
|
|
294
|
-
});
|
|
884
|
+
return ownerToolDenied();
|
|
295
885
|
},
|
|
296
886
|
})), 'dsh-kingdom: unbind-role tool');
|
|
297
887
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
298
888
|
name: 'kingdom_bind_session',
|
|
299
|
-
description: '
|
|
889
|
+
description: 'Owner-only:Agent Tool 不代行;请使用 `/kingdom role.session <JSON>`(OWNER.session_id 永远不是 Authority)',
|
|
300
890
|
parameters: {
|
|
301
891
|
role_type: { type: 'string', description: '角色:OWNER/CHANCELLOR/SUPERVISOR/WORKER(与 binding_id 二选一)' },
|
|
302
892
|
binding_id: { type: 'string', description: '绑定 id(与 role_type 二选一)' },
|
|
@@ -310,23 +900,35 @@ export function apply(ctx, config) {
|
|
|
310
900
|
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
311
901
|
},
|
|
312
902
|
async execute(args, exec) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
modelName: args.model_name === undefined ? undefined : args.model_name,
|
|
322
|
-
agentName: args.agent_name === undefined ? undefined : args.agent_name,
|
|
323
|
-
sessionMeta: args.session_meta === undefined ? undefined : args.session_meta,
|
|
324
|
-
}, {
|
|
325
|
-
mode: config.authMode,
|
|
326
|
-
principalSessionId: sessionPrincipal(exec)?.sessionId,
|
|
903
|
+
return ownerToolDenied({
|
|
904
|
+
operation: 'kingdom_bind_session',
|
|
905
|
+
request: {
|
|
906
|
+
role_type: args.role_type,
|
|
907
|
+
binding_id: args.binding_id,
|
|
908
|
+
session_id: args.session_id,
|
|
909
|
+
},
|
|
910
|
+
exec,
|
|
327
911
|
});
|
|
328
912
|
},
|
|
329
913
|
})), 'dsh-kingdom: bind-session tool');
|
|
914
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
915
|
+
name: 'kingdom_draft_owner_binding_intent',
|
|
916
|
+
description: '解析 bounded 自然语言 Owner 绑定意图;只返回 zero-write Draft,不执行绑定、不 mint Owner authority,最终须 direct /kingdom Slash 确认',
|
|
917
|
+
parameters: {
|
|
918
|
+
text: { type: 'string', required: true, description: '自然语言意图,例如“把当前会话设为宰相”或“让该会话主管 RAG 研发辖区”' },
|
|
919
|
+
},
|
|
920
|
+
output: {
|
|
921
|
+
schema: { type: 'string' },
|
|
922
|
+
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
923
|
+
},
|
|
924
|
+
async execute(args, exec) {
|
|
925
|
+
const draft = draftOwnerBindingIntent({
|
|
926
|
+
text: args.text,
|
|
927
|
+
context: ownerBindingIntentContext(exec),
|
|
928
|
+
});
|
|
929
|
+
return serializeOwnerBindingIntentDraft(draft);
|
|
930
|
+
},
|
|
931
|
+
})), 'dsh-kingdom: owner-binding-intent draft tool');
|
|
330
932
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
331
933
|
name: 'kingdom_list_bindings',
|
|
332
934
|
description: '列出当前王国的全部角色绑定',
|
|
@@ -344,7 +946,7 @@ export function apply(ctx, config) {
|
|
|
344
946
|
})), 'dsh-kingdom: list-bindings tool');
|
|
345
947
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
346
948
|
name: 'kingdom_set_execution_profile',
|
|
347
|
-
description: '
|
|
949
|
+
description: 'Owner-only:Agent Tool 不代行;请使用 `/kingdom execution-profile <JSON>`',
|
|
348
950
|
parameters: {
|
|
349
951
|
role_type: { type: 'string', description: '角色:OWNER/CHANCELLOR/SUPERVISOR/WORKER(与 binding_id 二选一)' },
|
|
350
952
|
binding_id: { type: 'string', description: '绑定 id(与 role_type 二选一)' },
|
|
@@ -357,34 +959,24 @@ export function apply(ctx, config) {
|
|
|
357
959
|
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
358
960
|
},
|
|
359
961
|
async execute(args, exec) {
|
|
360
|
-
|
|
361
|
-
if (!kingdomId)
|
|
362
|
-
return '尚未初始化王国。请先 /kingdom init。';
|
|
363
|
-
if (!args.role_type && !args.binding_id)
|
|
364
|
-
return '错误:请提供 role_type 或 binding_id 指定要配置的绑定。';
|
|
365
|
-
if (args.clear) {
|
|
366
|
-
return setExecutionProfile(store, { kingdomId, roleType: args.role_type, bindingId: args.binding_id, profile: null }, {
|
|
367
|
-
mode: config.authMode,
|
|
368
|
-
principalSessionId: sessionPrincipal(exec)?.sessionId,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
if (!args.provider && !args.model) {
|
|
372
|
-
return '错误:请至少提供 provider 或 model 之一(或传 clear=true 清空配置)。';
|
|
373
|
-
}
|
|
374
|
-
return setExecutionProfile(store, {
|
|
375
|
-
kingdomId,
|
|
376
|
-
roleType: args.role_type,
|
|
377
|
-
bindingId: args.binding_id,
|
|
378
|
-
profile: {
|
|
379
|
-
...args.provider ? { provider: args.provider } : {},
|
|
380
|
-
...args.model ? { model: args.model } : {},
|
|
381
|
-
},
|
|
382
|
-
}, {
|
|
383
|
-
mode: config.authMode,
|
|
384
|
-
principalSessionId: sessionPrincipal(exec)?.sessionId,
|
|
385
|
-
});
|
|
962
|
+
return ownerToolDenied();
|
|
386
963
|
},
|
|
387
964
|
})), 'dsh-kingdom: set-execution-profile tool');
|
|
965
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
966
|
+
name: 'kingdom_set_capability_ceiling',
|
|
967
|
+
description: 'Owner-only:Agent Tool 不代行;请使用 `/kingdom ceiling <JSON>`;这是 governed persistent 的必要前置。',
|
|
968
|
+
parameters: {
|
|
969
|
+
ceiling_json: { type: 'string', description: 'capability allow-list JSON 对象,例如 {"filesystem.write":true,"tool:pwsh":true};value 必须全为 boolean' },
|
|
970
|
+
clear: { type: 'boolean', description: 'true = 清空 Ceiling(保持 governed execution fail-closed,直到重新配置)' },
|
|
971
|
+
},
|
|
972
|
+
output: {
|
|
973
|
+
schema: { type: 'string' },
|
|
974
|
+
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
975
|
+
},
|
|
976
|
+
async execute(args, exec) {
|
|
977
|
+
return ownerToolDenied();
|
|
978
|
+
},
|
|
979
|
+
})), 'dsh-kingdom: set-capability-ceiling tool');
|
|
388
980
|
// ── Phase 2:Task 治理闭环(5 个工具)──────────────────────────
|
|
389
981
|
//
|
|
390
982
|
// 注意这里**没有** kingdom_report_result 工具。Worker 是 one-shot subagent,
|
|
@@ -399,6 +991,7 @@ export function apply(ctx, config) {
|
|
|
399
991
|
description: { type: 'string', description: '任务详细描述' },
|
|
400
992
|
acceptance_criteria: { type: 'string', description: '验收标准(Supervisor 审查的客观依据,强烈建议填写)' },
|
|
401
993
|
territory_id: { type: 'string', description: '领地 id;王国只有一个领地时可省略' },
|
|
994
|
+
capability_requirement_json: { type: 'string', description: '可选 Capability Requirement JSON;默认使用最小 governed requirement,并与 Task 同事务写入' },
|
|
402
995
|
},
|
|
403
996
|
output: {
|
|
404
997
|
schema: { type: 'string' },
|
|
@@ -408,14 +1001,77 @@ export function apply(ctx, config) {
|
|
|
408
1001
|
const kingdomId = requireKingdom();
|
|
409
1002
|
if (!kingdomId)
|
|
410
1003
|
return '尚未初始化王国。请先 /kingdom init。';
|
|
411
|
-
|
|
1004
|
+
const capabilityRequirementJson = args.capability_requirement_json === undefined
|
|
1005
|
+
? MINIMAL_CAPABILITY_JSON
|
|
1006
|
+
: args.capability_requirement_json;
|
|
1007
|
+
if (capabilityRequirementJson.trim() && !store.isSchemaV4) {
|
|
1008
|
+
return '错误:Schema v4 未迁移;PlanTask capability requirement 不可安全写入,命令拒绝(fail-closed)。';
|
|
1009
|
+
}
|
|
1010
|
+
return planTask(store, commandContext(kingdomId, trustedToolPrincipal(exec)), {
|
|
412
1011
|
title: args.title,
|
|
413
1012
|
description: args.description,
|
|
414
1013
|
acceptanceCriteria: args.acceptance_criteria,
|
|
415
1014
|
territoryId: args.territory_id,
|
|
1015
|
+
capabilityRequirementJson,
|
|
416
1016
|
}).message;
|
|
417
1017
|
},
|
|
418
1018
|
})), 'dsh-kingdom: plan-task tool');
|
|
1019
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
1020
|
+
name: 'kingdom_propose_collaboration',
|
|
1021
|
+
description: '宰相提议一层协作计划:含主整合者最多3位Worker,先由人类按准确版本采纳。小任务仍默认单执行者。',
|
|
1022
|
+
parameters: { plan_json: { type: 'string', required: true, description: 'JSON: parentTaskId,expectedVersion?,mode(EXPERT|TEAM),reason,integratorBindingId,budgetTokens,reserveTokens,items(1..2)。每项key/title/description/acceptanceCriteria/territoryId/workerBindingId/access(READ_ONLY|WRITE)/dependsOn/expectedArtifact;EXPERT只读。' } },
|
|
1023
|
+
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
|
|
1024
|
+
async execute(args, exec) {
|
|
1025
|
+
const kingdomId = requireKingdom(), principal = trustedToolPrincipal(exec);
|
|
1026
|
+
if (!kingdomId || !principal)
|
|
1027
|
+
return 'AUTHZ_DENIED: 需要当前王国的真实宰相 Session。';
|
|
1028
|
+
try {
|
|
1029
|
+
if (typeof args.plan_json !== 'string' || args.plan_json.length > 24000)
|
|
1030
|
+
return 'INPUT_DENIED: 计划过长。';
|
|
1031
|
+
return JSON.stringify(proposeCollaborationPlan(store, governedStartCommandContext(kingdomId, principal), parseStrictJsonObject(args.plan_json)));
|
|
1032
|
+
}
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
return `PLAN_REJECTED: ${error instanceof Error ? error.message : String(error)}`;
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
})), 'dsh-kingdom: propose-collaboration tool');
|
|
1038
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
1039
|
+
name: 'kingdom_advance_collaboration',
|
|
1040
|
+
description: '当前主管推进已获人类采纳的准确计划;串行单项或并行至多两个独立项,自动完成正常指派,沿原Gate/执行/Claim链,仍须主管审查,不自动ACCEPT。',
|
|
1041
|
+
parameters: { plan_id: { type: 'string', required: true, description: '已采纳计划ID' },
|
|
1042
|
+
version: { type: 'number', required: true, description: '准确版本' }, digest: { type: 'string', required: true, description: '完整计划摘要' },
|
|
1043
|
+
strategy: { type: 'string', required: true, description: 'SERIAL单项等待已有结算 / PARALLEL独立子项至多两个' },
|
|
1044
|
+
items_json: { type: 'string', required: true, description: 'JSON数组,每项{taskId,grant:{能力名:boolean}};不携带身份,不自动扩大权限。' } },
|
|
1045
|
+
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
|
|
1046
|
+
async execute(args, exec) {
|
|
1047
|
+
const kingdomId = requireKingdom();
|
|
1048
|
+
if (!kingdomId)
|
|
1049
|
+
return '尚未初始化王国。';
|
|
1050
|
+
try {
|
|
1051
|
+
if (typeof args.items_json !== 'string' || args.items_json.length > 20000)
|
|
1052
|
+
return 'INPUT_DENIED: 批次过长。';
|
|
1053
|
+
const items = parseStrictJsonObject(`{"items":${args.items_json}}`).items;
|
|
1054
|
+
const result = await advanceCollaboration(store, () => {
|
|
1055
|
+
const principal = trustedToolPrincipal(exec);
|
|
1056
|
+
return principal ? governedStartCommandContext(kingdomId, principal) : null;
|
|
1057
|
+
}, { planId: args.plan_id, version: args.version, digest: args.digest, strategy: args.strategy, items }, async (input) => {
|
|
1058
|
+
const previous = store.latestWorkerResult(input.taskId)?.result_id;
|
|
1059
|
+
const message = await runGovernedStart(input, { kind: 'tool', execution: exec });
|
|
1060
|
+
const task = store.getTask(input.taskId), claim = store.latestWorkerResult(input.taskId), execution = store.latestExecution(input.taskId);
|
|
1061
|
+
const dispatch = execution ? store.listDispatchesForTaskAttempt(input.taskId, execution.attempt_no).find(row => row.execution_id === execution.execution_id) : null;
|
|
1062
|
+
const lease = dispatch ? store.getLease(dispatch.lease_id) : null;
|
|
1063
|
+
const ok = task?.status === 'REVIEW' && !!claim && claim.result_id !== previous && claim.outcome === 'COMPLETED'
|
|
1064
|
+
&& execution?.state === 'COMPLETED' && execution.attempt_no === claim.attempt_no && execution.worker_binding_id === claim.worker_binding_id
|
|
1065
|
+
&& dispatch?.state === 'TERMINAL' && dispatch.session_ref === claim.session_id && lease?.state === 'RELEASED';
|
|
1066
|
+
return { ok, message };
|
|
1067
|
+
});
|
|
1068
|
+
return JSON.stringify(result);
|
|
1069
|
+
}
|
|
1070
|
+
catch (error) {
|
|
1071
|
+
return `ADVANCE_REJECTED: ${error instanceof Error ? error.message : String(error)}`;
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
})), 'dsh-kingdom: advance-collaboration tool');
|
|
419
1075
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
420
1076
|
name: 'kingdom_assign_task',
|
|
421
1077
|
description: '把任务派给 Worker 绑定(Supervisor 职权),CREATED → ASSIGNED',
|
|
@@ -431,7 +1087,7 @@ export function apply(ctx, config) {
|
|
|
431
1087
|
const kingdomId = requireKingdom();
|
|
432
1088
|
if (!kingdomId)
|
|
433
1089
|
return '尚未初始化王国。请先 /kingdom init。';
|
|
434
|
-
return assignTask(store, commandContext(kingdomId,
|
|
1090
|
+
return assignTask(store, commandContext(kingdomId, trustedToolPrincipal(exec)), {
|
|
435
1091
|
taskId: args.task_id,
|
|
436
1092
|
workerBindingId: args.worker_binding_id,
|
|
437
1093
|
}).message;
|
|
@@ -439,15 +1095,19 @@ export function apply(ctx, config) {
|
|
|
439
1095
|
})), 'dsh-kingdom: assign-task tool');
|
|
440
1096
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
441
1097
|
name: 'kingdom_start_task',
|
|
442
|
-
description: '
|
|
1098
|
+
description: 'LEGACY_COMPAT(仅显式选择):用旧 one-shot Worker 执行任务并等待结果;不会自动作为 governed persistent 的 fallback。结果只会让任务进入 REVIEW,不会直接完成任务',
|
|
443
1099
|
parameters: {
|
|
444
1100
|
task_id: { type: 'string', required: true, description: '任务 id(状态需为 ASSIGNED,或 REWORK 后的 RUNNING)' },
|
|
1101
|
+
legacy_opt_in: { type: 'boolean', description: '必须显式传 true 选择 LEGACY_COMPAT;省略/false 永远拒绝,persistent 失败不会自动 fallback' },
|
|
445
1102
|
},
|
|
446
1103
|
output: {
|
|
447
1104
|
schema: { type: 'string' },
|
|
448
1105
|
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
449
1106
|
},
|
|
450
1107
|
async execute(args, exec) {
|
|
1108
|
+
if (args.legacy_opt_in !== true) {
|
|
1109
|
+
return 'LEGACY_COMPAT_REQUIRED: kingdom_start_task 是旧 one-shot 兼容入口;必须显式传 legacy_opt_in=true。persistent governed 失败不会自动降级。';
|
|
1110
|
+
}
|
|
451
1111
|
const kingdomId = requireKingdom();
|
|
452
1112
|
if (!kingdomId)
|
|
453
1113
|
return '尚未初始化王国。请先 /kingdom init。';
|
|
@@ -472,16 +1132,16 @@ export function apply(ctx, config) {
|
|
|
472
1132
|
});
|
|
473
1133
|
if (!resolved.ok)
|
|
474
1134
|
return resolved.error;
|
|
475
|
-
const result = await startTask(store, resolved.executor, commandContext(kingdomId,
|
|
1135
|
+
const result = await startTask(store, resolved.executor, commandContext(kingdomId, trustedToolPrincipal(exec)), { taskId: args.task_id });
|
|
476
1136
|
return result.message;
|
|
477
1137
|
},
|
|
478
1138
|
})), 'dsh-kingdom: start-task tool');
|
|
479
1139
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
480
1140
|
name: 'kingdom_start_task_governed',
|
|
481
|
-
description: '
|
|
1141
|
+
description: 'CANONICAL HEADLESS:以 Governed Persistent Worker 执行任务(v0.8):真实 DSH caller session 中的 Supervisor + Territory scope 授权后,Worker 长期 DSH Session + Capability Gate(仅 GRANTED+ENFORCED 才 dispatch)+ Lease/Dispatch/Receipt/Evidence 全链;DENIED/CANNOT_ENFORCE → zero execution。需王国已迁移 Schema v4;Worker 获得并复用长期 Session(REWORK 唤醒同一 Worker)。失败只 fail-closed,不自动降级 LEGACY_COMPAT',
|
|
482
1142
|
parameters: {
|
|
483
1143
|
task_id: { type: 'string', required: true, description: '任务 id(状态需为 ASSIGNED,或 REWORK 后的 RUNNING)' },
|
|
484
|
-
grant_json: { type: 'string', required: true, description: 'Supervisor
|
|
1144
|
+
grant_json: { type: 'string', required: true, description: '当前已认证 Supervisor 的本次 capability Grant JSON(仅表达能力,不代表身份;如 {"tool:pwsh": true, "filesystem.write": true})' },
|
|
485
1145
|
sandbox_mode: { type: 'string', description: 'workspace-write(默认,领地写边界)/ read-only' },
|
|
486
1146
|
},
|
|
487
1147
|
output: {
|
|
@@ -489,135 +1149,25 @@ export function apply(ctx, config) {
|
|
|
489
1149
|
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
490
1150
|
},
|
|
491
1151
|
async execute(args, exec) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
const loadedTask = store.getTask(args.task_id);
|
|
499
|
-
if (!loadedTask)
|
|
500
|
-
return `错误:找不到任务 ${args.task_id}。`;
|
|
501
|
-
if (loadedTask.status !== 'ASSIGNED' && loadedTask.status !== 'RUNNING') {
|
|
502
|
-
return `错误:任务状态 ${loadedTask.status} 不可 governed 启动(仅 ASSIGNED / REWORK 后的 RUNNING)。`;
|
|
503
|
-
}
|
|
504
|
-
const territory = store.getTerritoryById(loadedTask.territory_id);
|
|
505
|
-
if (!territory)
|
|
506
|
-
return '错误:任务领地缺失。';
|
|
507
|
-
const worker = loadedTask.assigned_binding_id;
|
|
508
|
-
if (!worker)
|
|
509
|
-
return '错误:任务未指派 Worker。';
|
|
510
|
-
const agents = ctx.get('agents');
|
|
511
|
-
if (!agents) {
|
|
512
|
-
return '错误:当前 dsh 组合未提供 agents 服务(Persistent Session 不可用);governed 执行不可用。';
|
|
513
|
-
}
|
|
514
|
-
let grant;
|
|
515
|
-
try {
|
|
516
|
-
const parsed = JSON.parse(args.grant_json);
|
|
517
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
518
|
-
throw new Error('shape');
|
|
519
|
-
grant = parsed;
|
|
520
|
-
}
|
|
521
|
-
catch {
|
|
522
|
-
return '错误:grant_json 必须是非空 JSON 对象(Supervisor 权威授权)。';
|
|
523
|
-
}
|
|
524
|
-
const sup = territory.supervisor_binding_id ?? store.getBindingsByRole(kingdomId, 'SUPERVISOR')[0]?.binding_id ?? null;
|
|
525
|
-
// Owner V0.8 PRODUCTION-PATH CLOSURE(正式入口 E2E seam):S4 materialize 需要**模块函数** seam
|
|
526
|
-
// (`setSandboxMode = session.append('sandbox/mode')` / `setApprovalPolicy = session.append('approval/policy')`),
|
|
527
|
-
// 而非 ctx 服务对象(SandboxPolicyService/ApprovalService 无这些方法,传服务 → MATERIALIZE_FAILED)。
|
|
528
|
-
// 动态 import(懒解析):失败 → null → materialize 报「缺失」→ DENIED(fail-closed,不崩溃插件)。
|
|
529
|
-
const s4Seam = await (async () => {
|
|
530
|
-
try {
|
|
531
|
-
// 变量 specifier:运行时经 dsh loader 解析(动态 seam;静态类型不做解析校验)
|
|
532
|
-
const sbSpec = '@deepseek-ai/dsh-sandbox-policy';
|
|
533
|
-
const apSpec = '@deepseek-ai/dsh-user-approval';
|
|
534
|
-
const sandbox = (await import(sbSpec));
|
|
535
|
-
const approval = (await import(apSpec));
|
|
536
|
-
return {
|
|
537
|
-
sandboxPolicy: { setSandboxMode: sandbox.setSandboxMode },
|
|
538
|
-
approval: { setApprovalPolicy: approval.setApprovalPolicy },
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
catch {
|
|
542
|
-
return { sandboxPolicy: null, approval: null };
|
|
543
|
-
}
|
|
544
|
-
})();
|
|
545
|
-
const adapter = new DshRuntimeAdapter({
|
|
546
|
-
runtimeInstanceRef: `dsh-${hostname()}`,
|
|
547
|
-
provider: config.workerProvider || 'spawn',
|
|
548
|
-
model: null,
|
|
549
|
-
agents,
|
|
550
|
-
permission: ctx.get('permission'),
|
|
551
|
-
sandboxPolicy: s4Seam.sandboxPolicy,
|
|
552
|
-
approval: s4Seam.approval,
|
|
553
|
-
presets: ctx.get('agentPresets'),
|
|
554
|
-
});
|
|
555
|
-
// attempt 编号必须纳入 Lease Ledger(zero-execution 的 gate 拒绝也会消耗 attempt_no,
|
|
556
|
-
// 否则重试会撞 UNIQUE(task_id, attempt_no)——正式入口 E2E 实证)
|
|
557
|
-
const leaseMax = store.listLeases(kingdomId)
|
|
558
|
-
.filter((l) => l.task_id === loadedTask.task_id)
|
|
559
|
-
.reduce((m, l) => Math.max(m, l.attempt_no), 0);
|
|
560
|
-
const attemptNo = Math.max(store.nextAttemptNo(loadedTask.task_id), leaseMax + 1);
|
|
561
|
-
const result = await runGovernedTask({
|
|
562
|
-
store, adapter, kingdomId, workerBindingId: worker,
|
|
563
|
-
territoryId: loadedTask.territory_id, cwd: territory.workspace_path ?? process.cwd(),
|
|
564
|
-
taskId: loadedTask.task_id, attemptNo, supervisorBindingId: sup,
|
|
565
|
-
grant, requirementJson: store.getTaskCapabilityRequirement(loadedTask.task_id),
|
|
566
|
-
sandboxMode: args.sandbox_mode === 'read-only' ? 'read-only' : 'workspace-write',
|
|
567
|
-
// Owner V0.8 PRODUCTION-PATH CLOSURE A:全局 workerProvider 仅作 provider 回退;
|
|
568
|
-
// Worker 的 provider/model 权威来源 = execution_profile_json(runGovernedTask 内部解析,
|
|
569
|
-
// model 缺失 → fail closed configuration error,不创建 Session、不 dispatch)。
|
|
570
|
-
globalProvider: config.workerProvider || 'spawn',
|
|
571
|
-
// 真实模型 turn 轮询窗口(正式入口 E2E 实证:默认 100ms×40=4s 太短,turn 未及 terminal):
|
|
572
|
-
// 1s × 60 = 60s;超窗 → fail-closed 返回(进 RECOVERING,由 reconcile 处理)。
|
|
573
|
-
pollIntervalMs: 1000,
|
|
574
|
-
maxPolls: 60,
|
|
575
|
-
});
|
|
576
|
-
if (!result.ok) {
|
|
577
|
-
return `Governed 执行未发生(zero execution):${result.reason}\n(任务保持 ${loadedTask.status};请检查 Capability 配置后重试或改用 kingdom_start_task legacy 路径。)`;
|
|
578
|
-
}
|
|
579
|
-
// Claim 到达(Claim ≠ Fact):落 worker_results + Task RUNNING→REVIEW(不是 DONE)。
|
|
580
|
-
// Owner V0.8 FINAL RELEASE BLOCKER:Claim outcome 按已验证 terminalOutcome 收敛
|
|
581
|
-
// (COMPLETED/FAILED/ABORTED)——禁止 hardcode COMPLETED;FAILED 不得生成 COMPLETED Claim;
|
|
582
|
-
// summary 仅来自真实 assistant 文本,无文本 → 诚实回退占位(不伪造"任务完成"类摘要)。
|
|
583
|
-
const task = store.getTask(loadedTask.task_id);
|
|
584
|
-
const running = task.status === 'ASSIGNED' ? store.transitionTask(task, 'RUNNING') : task;
|
|
585
|
-
const outcome = result.terminalOutcome;
|
|
586
|
-
const summary = result.summary;
|
|
587
|
-
store.insertWorkerResult({
|
|
588
|
-
result_id: randomUUID(),
|
|
589
|
-
task_id: task.task_id,
|
|
590
|
-
attempt_no: attemptNo,
|
|
591
|
-
worker_binding_id: worker,
|
|
592
|
-
session_id: result.sessionRef,
|
|
593
|
-
outcome,
|
|
594
|
-
result_json: JSON.stringify({ outcome, summary }),
|
|
595
|
-
created_at: new Date().toISOString(),
|
|
596
|
-
});
|
|
597
|
-
store.transitionTask(running, 'REVIEW', { result_summary: summary });
|
|
598
|
-
// settlement:cleanup 确认 → release lease
|
|
599
|
-
const lease = store.getLease(result.leaseId);
|
|
600
|
-
if (lease && lease.state !== 'RELEASED') {
|
|
601
|
-
settleAndRelease(store, result.leaseId, true, 'settled-cleanup-ok');
|
|
602
|
-
}
|
|
603
|
-
const nowIso = new Date().toISOString();
|
|
604
|
-
store.appendEvent({
|
|
605
|
-
event_id: randomUUID(), kingdom_id: kingdomId, event_type: 'WORKER_RESULT_SUBMITTED',
|
|
606
|
-
actor_role: 'WORKER', actor_id: worker, target_type: 'task', target_id: task.task_id,
|
|
607
|
-
payload_json: JSON.stringify({ attempt_no: attemptNo, claimed_outcome: outcome, session_id: result.sessionRef, executor: 'dsh-governed:persistent' }),
|
|
608
|
-
created_at: nowIso,
|
|
609
|
-
});
|
|
610
|
-
store.appendEvent({
|
|
611
|
-
event_id: randomUUID(), kingdom_id: kingdomId, event_type: 'SESSION_STOPPED',
|
|
612
|
-
actor_role: 'WORKER', actor_id: worker, target_type: 'execution', target_id: result.executionId,
|
|
613
|
-
payload_json: JSON.stringify({ task_id: task.task_id, attempt_no: attemptNo, reason: outcome === 'COMPLETED' ? 'completed' : outcome.toLowerCase() }),
|
|
614
|
-
created_at: nowIso,
|
|
615
|
-
});
|
|
616
|
-
return `Governed Worker 已提交第 ${attemptNo} 次尝试的 Claim(outcome=${outcome},长期 Session ${result.sessionRef.slice(-8)}${result.created ? ' 新建' : ' 复用'})。\n`
|
|
617
|
-
+ `摘要:${summary}\n`
|
|
618
|
-
+ `任务「${task.title}」现在处于 **REVIEW**。这是一个待审查的 Claim,尚未成为任务完成事实——请 Supervisor 用 kingdom_review_task 裁定 ACCEPT / REWORK / FAIL。`;
|
|
1152
|
+
return runGovernedStart({
|
|
1153
|
+
taskId: args.task_id,
|
|
1154
|
+
grantJson: args.grant_json,
|
|
1155
|
+
sandboxMode: args.sandbox_mode,
|
|
1156
|
+
}, { kind: 'tool', execution: exec });
|
|
619
1157
|
},
|
|
620
1158
|
})), 'dsh-kingdom: governed start-task tool');
|
|
1159
|
+
ctx.effect(() => ctx.tools.register(defineTool({
|
|
1160
|
+
name: 'kingdom_reconcile_task',
|
|
1161
|
+
description: '对账已有任务的 governed Dispatch:仅原进程保留的可信上下文可补齐迟到 Claim;缺凭据或清理证据保持 RECOVERING。不会重发任务或自动 ACCEPT。',
|
|
1162
|
+
parameters: {
|
|
1163
|
+
task_id: { type: 'string', required: true, description: '当前 Supervisor 管辖的任务 id' },
|
|
1164
|
+
dispatch_id: { type: 'string', description: '可选;默认选任务最新 Execution 的原 Dispatch' },
|
|
1165
|
+
},
|
|
1166
|
+
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
|
|
1167
|
+
async execute(args, exec) {
|
|
1168
|
+
return runGovernedReconcile({ taskId: args.task_id, dispatchId: args.dispatch_id }, { kind: 'tool', execution: exec });
|
|
1169
|
+
},
|
|
1170
|
+
})), 'dsh-kingdom: governed reconcile-task tool');
|
|
621
1171
|
ctx.effect(() => ctx.tools.register(defineTool({
|
|
622
1172
|
name: 'kingdom_review_task',
|
|
623
1173
|
description: '审查 Worker 提交的结果并裁定(Supervisor 职权,须在任务领地的主理范围内)。这是任务能变成 DONE 的唯一路径:ACCEPT(→DONE)/ REWORK(→RUNNING 同 Worker)/ FAIL(→FAILED)/ HANDOFF(→RUNNING 转交新 Worker,需 to_binding_id + reason)',
|
|
@@ -635,7 +1185,7 @@ export function apply(ctx, config) {
|
|
|
635
1185
|
const kingdomId = requireKingdom();
|
|
636
1186
|
if (!kingdomId)
|
|
637
1187
|
return '尚未初始化王国。请先 /kingdom init。';
|
|
638
|
-
return reviewTask(store, commandContext(kingdomId,
|
|
1188
|
+
return reviewTask(store, commandContext(kingdomId, trustedToolPrincipal(exec)), {
|
|
639
1189
|
taskId: args.task_id,
|
|
640
1190
|
decision: args.decision,
|
|
641
1191
|
reason: args.reason,
|
|
@@ -685,7 +1235,7 @@ export function apply(ctx, config) {
|
|
|
685
1235
|
const kingdomId = requireKingdom();
|
|
686
1236
|
if (!kingdomId)
|
|
687
1237
|
return { error: 'KINGDOM_NOT_INITIALIZED', message: '尚未初始化王国。请先 /kingdom init。' };
|
|
688
|
-
return buildSnapshot(store, { auth: authView });
|
|
1238
|
+
return buildSnapshot(store, { auth: authView, costRuntime });
|
|
689
1239
|
}), 'dsh-kingdom: snapshot tool');
|
|
690
1240
|
ctx.effect(() => jsonTool('kingdom_task_detail', '返回单个任务的结构化详情 JSON:验收标准、尝试历史、全部 Claim、Supervisor 决策、执行记录、关联事件与允许的下一步动作', { task_id: { type: 'string', required: true, description: '任务 id' } }, (args) => {
|
|
691
1241
|
const kingdomId = requireKingdom();
|
|
@@ -711,7 +1261,7 @@ export function apply(ctx, config) {
|
|
|
711
1261
|
if (!kingdomId)
|
|
712
1262
|
return '尚未初始化王国。请先 /kingdom init。';
|
|
713
1263
|
const input = { executionId: args.execution_id, reason: args.reason };
|
|
714
|
-
const cmd = commandContext(kingdomId,
|
|
1264
|
+
const cmd = commandContext(kingdomId, trustedToolPrincipal(exec));
|
|
715
1265
|
switch (args.action.trim().toLowerCase()) {
|
|
716
1266
|
case 'pause': return pauseExecution(store, cmd, input).message;
|
|
717
1267
|
case 'resume': return resumeExecution(store, cmd, input).message;
|
|
@@ -720,30 +1270,200 @@ export function apply(ctx, config) {
|
|
|
720
1270
|
}
|
|
721
1271
|
},
|
|
722
1272
|
})), 'dsh-kingdom: execution-control tool');
|
|
723
|
-
// ── 本地 GUI
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
1273
|
+
// ── 本地 GUI 通道(direct `/kingdom gui` 激活;配置端口可预启动)────
|
|
1274
|
+
const GUI_HOST = '127.0.0.1';
|
|
1275
|
+
let guiRuntime = null;
|
|
1276
|
+
let guiStartPromise = null;
|
|
1277
|
+
let guiStopRequested = false;
|
|
1278
|
+
const projectionSecurityFor = (readContext) => {
|
|
1279
|
+
const principalSessionId = readContext?.principalSessionId;
|
|
1280
|
+
if (typeof principalSessionId !== 'string' || principalSessionId.length === 0)
|
|
1281
|
+
return {};
|
|
1282
|
+
const kingdomId = requireKingdom();
|
|
1283
|
+
const supervisorBindingIds = new Set(kingdomId
|
|
1284
|
+
? store.getBindingsByRole(kingdomId, 'SUPERVISOR')
|
|
1285
|
+
.filter(binding => binding.status === 'ACTIVE' && binding.session_id === principalSessionId)
|
|
1286
|
+
.map(binding => binding.binding_id)
|
|
1287
|
+
: []);
|
|
1288
|
+
const scope = kingdomId
|
|
1289
|
+
? store.listTerritories(kingdomId)
|
|
1290
|
+
.filter(territory => territory.supervisor_binding_id !== null
|
|
1291
|
+
&& supervisorBindingIds.has(territory.supervisor_binding_id))
|
|
1292
|
+
.map(territory => `territory:${territory.territory_id}`)
|
|
1293
|
+
: [];
|
|
1294
|
+
return {
|
|
1295
|
+
sessionVerified: true,
|
|
1296
|
+
principalSessionId,
|
|
1297
|
+
hostContext: true,
|
|
1298
|
+
commandCoverage: [...GUI_SESSION_COMMANDS],
|
|
1299
|
+
scope,
|
|
1300
|
+
};
|
|
1301
|
+
};
|
|
1302
|
+
const closeGuiRuntime = (runtime) => {
|
|
1303
|
+
runtime.control.revokeAllSessions();
|
|
1304
|
+
runtime.ownerControl.dispose();
|
|
1305
|
+
try {
|
|
1306
|
+
runtime.close();
|
|
1307
|
+
}
|
|
1308
|
+
catch { /* server may already be unavailable */ }
|
|
1309
|
+
if (guiRuntime === runtime)
|
|
1310
|
+
guiRuntime = null;
|
|
1311
|
+
};
|
|
1312
|
+
const ensureGuiServer = () => {
|
|
1313
|
+
if (guiRuntime)
|
|
1314
|
+
return Promise.resolve(guiRuntime);
|
|
1315
|
+
if (guiStartPromise)
|
|
1316
|
+
return guiStartPromise;
|
|
1317
|
+
guiStopRequested = false;
|
|
1318
|
+
let closeServer = null;
|
|
1319
|
+
const pending = new Promise((resolve, reject) => {
|
|
1320
|
+
let listeningAddress = null;
|
|
1321
|
+
let settled = false;
|
|
1322
|
+
const control = new LocalControlManager({
|
|
1323
|
+
host: GUI_HOST,
|
|
1324
|
+
// This remains empty until startGuiServer reports the actual port.
|
|
1325
|
+
// In particular, port=0 can never activate against a guessed origin.
|
|
1326
|
+
expectedOrigin: () => listeningAddress?.origin ?? '',
|
|
1327
|
+
});
|
|
1328
|
+
const ownerController = new OwnerDecisionController(store, {
|
|
1329
|
+
validateTargetSession: async ({ sessionId, signal }) => {
|
|
1330
|
+
if (signal.aborted)
|
|
1331
|
+
return { ok: false, code: 'OWNER_VALIDATION_ABORTED', message: '管理窗口已撤销或校验超时。' };
|
|
1332
|
+
const checked = validateLiveDirectSession(sessionId, dshRegistrySeam());
|
|
1333
|
+
return checked.ok ? { ok: true } : { ok: false, code: 'OWNER_SESSION_UNAVAILABLE', message: '目标会话不是当前唯一、有效的 DSH 会话;请刷新后重新选择。' };
|
|
1334
|
+
},
|
|
1335
|
+
listTargetSessions: async ({ sessionIds, signal }) => {
|
|
1336
|
+
const seam = dshRegistrySeam();
|
|
1337
|
+
return signal.aborted ? [] : sessionIds.filter(id => validateLiveDirectSession(id, seam).ok)
|
|
1338
|
+
.map(id => ({ id, label: `DSH 会话 ${id}` }));
|
|
1339
|
+
},
|
|
1340
|
+
validateExecutionProfile: async ({ profile, signal }) => {
|
|
1341
|
+
const provider = profile?.provider?.trim() || config.workerProvider;
|
|
1342
|
+
const model = profile?.model?.trim();
|
|
1343
|
+
if (!model)
|
|
1344
|
+
return { ok: false, code: 'OWNER_MODEL_REQUIRED', message: '请明确选择模型;保存配置不会证明模型已运行成功。' };
|
|
1345
|
+
const llm = ctx.get('llm');
|
|
1346
|
+
if (!llm || typeof llm.resolveModelInfo !== 'function')
|
|
1347
|
+
return { ok: false, code: 'OWNER_MODEL_VALIDATION_UNAVAILABLE', message: '当前宿主无法核对模型配置,未写入变更。' };
|
|
1348
|
+
try {
|
|
1349
|
+
await llm.resolveModelInfo(provider, model, signal);
|
|
1350
|
+
return signal.aborted ? { ok: false, code: 'OWNER_VALIDATION_ABORTED', message: '管理窗口已撤销或校验超时。' } : { ok: true };
|
|
1351
|
+
}
|
|
1352
|
+
catch {
|
|
1353
|
+
return { ok: false, code: 'OWNER_MODEL_UNAVAILABLE', message: '宿主未能确认该提供方与模型路由,请核对现有模型配置。' };
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
});
|
|
1357
|
+
const ownerControl = new OwnerLocalControlManager({ controller: ownerController, expectedOrigin: () => listeningAddress?.origin ?? '' });
|
|
1358
|
+
const safeClose = () => {
|
|
1359
|
+
try {
|
|
1360
|
+
closeServer?.();
|
|
1361
|
+
}
|
|
1362
|
+
catch { /* startup failure is already reported */ }
|
|
1363
|
+
};
|
|
1364
|
+
closeServer = () => {
|
|
1365
|
+
try {
|
|
1366
|
+
serverClose();
|
|
1367
|
+
}
|
|
1368
|
+
catch { /* server may not have reached listen */ }
|
|
1369
|
+
};
|
|
1370
|
+
// The returned close function is assigned after startGuiServer creates
|
|
1371
|
+
// its Server. Keep the indirection local so callbacks never expose the
|
|
1372
|
+
// ticket or depend on a second launch protocol.
|
|
1373
|
+
let serverClose = () => undefined;
|
|
1374
|
+
const onListening = (address) => {
|
|
1375
|
+
listeningAddress = address;
|
|
1376
|
+
const runtime = {
|
|
1377
|
+
address,
|
|
1378
|
+
control,
|
|
1379
|
+
ownerControl,
|
|
1380
|
+
close: safeClose,
|
|
738
1381
|
};
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1382
|
+
if (guiStopRequested) {
|
|
1383
|
+
control.revokeAllSessions();
|
|
1384
|
+
ownerControl.dispose();
|
|
1385
|
+
safeClose();
|
|
1386
|
+
settled = true;
|
|
1387
|
+
reject(new Error('GUI_STOPPED_DURING_START'));
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
guiRuntime = runtime;
|
|
1391
|
+
settled = true;
|
|
1392
|
+
resolve(runtime);
|
|
1393
|
+
};
|
|
1394
|
+
const onUnavailable = (error) => {
|
|
1395
|
+
if (settled)
|
|
1396
|
+
return;
|
|
1397
|
+
settled = true;
|
|
1398
|
+
control.dispose();
|
|
1399
|
+
ownerControl.dispose();
|
|
1400
|
+
reject(error);
|
|
1401
|
+
};
|
|
1402
|
+
serverClose = startGuiServer({
|
|
1403
|
+
snapshot: (readContext) => buildSnapshot(store, {
|
|
1404
|
+
auth: authView,
|
|
1405
|
+
costRuntime,
|
|
1406
|
+
security: projectionSecurityFor(readContext),
|
|
1407
|
+
}),
|
|
1408
|
+
taskDetail: (taskId, readContext) => {
|
|
1409
|
+
const kingdomId = requireKingdom();
|
|
1410
|
+
return kingdomId
|
|
1411
|
+
? buildTaskDetail(store, kingdomId, taskId, {
|
|
1412
|
+
security: projectionSecurityFor(readContext),
|
|
1413
|
+
})
|
|
1414
|
+
: null;
|
|
1415
|
+
},
|
|
1416
|
+
eventsSince: (afterSeq, limit) => {
|
|
1417
|
+
const kingdomId = requireKingdom();
|
|
1418
|
+
if (!kingdomId)
|
|
1419
|
+
return { revision: 0, events: [] };
|
|
1420
|
+
return {
|
|
1421
|
+
revision: store.revision(kingdomId),
|
|
1422
|
+
events: store.listEventsSince(kingdomId, afterSeq, limit).map(toEventView),
|
|
1423
|
+
};
|
|
1424
|
+
},
|
|
1425
|
+
command: (name, payload, context) => runGuiCommand(name, payload, context),
|
|
1426
|
+
}, {
|
|
1427
|
+
port: config.guiPort,
|
|
1428
|
+
host: GUI_HOST,
|
|
1429
|
+
...config.guiToken ? { token: config.guiToken } : {},
|
|
1430
|
+
allowOrigins: config.guiAllowOrigins,
|
|
1431
|
+
control,
|
|
1432
|
+
ownerControl,
|
|
1433
|
+
onListening,
|
|
1434
|
+
onUnavailable,
|
|
1435
|
+
logger: ctx.logger,
|
|
1436
|
+
});
|
|
1437
|
+
});
|
|
1438
|
+
guiStartPromise = pending;
|
|
1439
|
+
void pending.catch(() => {
|
|
1440
|
+
if (guiStartPromise === pending)
|
|
1441
|
+
guiStartPromise = null;
|
|
1442
|
+
});
|
|
1443
|
+
return pending;
|
|
1444
|
+
};
|
|
1445
|
+
const stopGuiServer = async () => {
|
|
1446
|
+
guiStopRequested = true;
|
|
1447
|
+
const pending = guiStartPromise;
|
|
1448
|
+
if (pending && !guiRuntime) {
|
|
1449
|
+
try {
|
|
1450
|
+
const runtime = await pending;
|
|
1451
|
+
closeGuiRuntime(runtime);
|
|
1452
|
+
}
|
|
1453
|
+
catch {
|
|
1454
|
+
// onUnavailable already failed closed and disposed the broker.
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
else if (guiRuntime) {
|
|
1458
|
+
closeGuiRuntime(guiRuntime);
|
|
1459
|
+
}
|
|
1460
|
+
guiStartPromise = null;
|
|
1461
|
+
};
|
|
1462
|
+
ctx.effect(() => () => { void stopGuiServer(); });
|
|
1463
|
+
if (config.guiPort > 0) {
|
|
1464
|
+
void ensureGuiServer().catch((error) => {
|
|
1465
|
+
ctx.logger.warn(`dsh-kingdom GUI 通道启动失败(${error instanceof Error ? error.message : String(error)})`);
|
|
1466
|
+
});
|
|
747
1467
|
}
|
|
748
1468
|
const guiFailure = (errorCode, message) => ({
|
|
749
1469
|
ok: false,
|
|
@@ -757,10 +1477,17 @@ export function apply(ctx, config) {
|
|
|
757
1477
|
});
|
|
758
1478
|
/** 把 Phase-1 风格(返回字符串)的领地/角色操作包装成 CommandResultView(M2:GUI 写层)。 */
|
|
759
1479
|
const plainResult = (text, kId) => {
|
|
760
|
-
const failed =
|
|
1480
|
+
const failed = /^(?:错误:|OWNER_CONTROL_REQUIRED|CONFIG_DENIED|UNKNOWN\/|INPUT_DENIED|AUTHZ_DENIED|CAPABILITY_DENIED|GOVERNED_EXECUTION_DENIED|GOVERNED_SETTLEMENT_BLOCKED|RECOVERY_REQUIRED)/u.test(text);
|
|
1481
|
+
const errorCode = !failed
|
|
1482
|
+
? null
|
|
1483
|
+
: text.startsWith('RECOVERY_REQUIRED') || text.startsWith('GOVERNED_SETTLEMENT_BLOCKED') ? 'RECOVERY_REQUIRED'
|
|
1484
|
+
: text.startsWith('AUTHZ_DENIED') ? 'SESSION_AUTH_REQUIRED'
|
|
1485
|
+
: text.startsWith('INPUT_DENIED') || text.startsWith('错误:') ? 'INVALID_INPUT'
|
|
1486
|
+
: text.startsWith('OWNER_CONTROL_REQUIRED') || text.startsWith('CONFIG_DENIED') ? 'SESSION_AUTH_REQUIRED'
|
|
1487
|
+
: 'WORKER_EXECUTION_FAILED';
|
|
761
1488
|
return {
|
|
762
1489
|
ok: !failed,
|
|
763
|
-
errorCode
|
|
1490
|
+
errorCode,
|
|
764
1491
|
message: text,
|
|
765
1492
|
task: null,
|
|
766
1493
|
execution: null,
|
|
@@ -770,28 +1497,24 @@ export function apply(ctx, config) {
|
|
|
770
1497
|
};
|
|
771
1498
|
};
|
|
772
1499
|
/** GUI 写命令的分发。GUI 仍然经插件执行命令,绝不直接写 SQLite。 */
|
|
773
|
-
async function runGuiCommand(name, payload) {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
const result = manager.init();
|
|
777
|
-
return plainResult(result.detail, result.kingdomId);
|
|
778
|
-
}
|
|
779
|
-
const kingdomId = requireKingdom();
|
|
780
|
-
if (!kingdomId) {
|
|
781
|
-
return guiFailure('KINGDOM_NOT_INITIALIZED', '尚未初始化王国。请先 /kingdom init。');
|
|
1500
|
+
async function runGuiCommand(name, payload, control) {
|
|
1501
|
+
if (GUI_OWNER_ONLY_COMMAND_SET.has(name)) {
|
|
1502
|
+
return guiFailure('SESSION_AUTH_REQUIRED', `DIRECT_SLASH_REQUIRED: Owner-only action "${name}" 不能由 GUI/HTTP 执行;请由人类 Owner 直接使用 /kingdom Slash。`);
|
|
782
1503
|
}
|
|
783
1504
|
const str = (key) => typeof payload[key] === 'string' ? payload[key] : '';
|
|
784
1505
|
const opt = (key) => typeof payload[key] === 'string' ? payload[key] : undefined;
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
// 网关面永远无 principal。session-bound 下所有写命令 fail-closed(SESSION_AUTH_REQUIRED)。
|
|
788
|
-
const cmd = commandContext(kingdomId, undefined);
|
|
789
|
-
const guard = guiWriteGuard(authView.mode);
|
|
790
|
-
if (!guard.allowed) {
|
|
791
|
-
return guiFailure(guard.code, '该操作需要由已绑定角色的真实 DSH Session 发起。'
|
|
792
|
-
+ 'GUI 当前没有可信 DSH Principal,因此拒绝代行。'
|
|
793
|
-
+ '请在 DSH 会话中调用对应 kingdom_* 工具完成此操作。');
|
|
1506
|
+
if (!control || control.signal.aborted) {
|
|
1507
|
+
return guiFailure('SESSION_AUTH_REQUIRED', 'GUI Control Session 不存在或已撤销;命令未进入 Core。');
|
|
794
1508
|
}
|
|
1509
|
+
const kingdomId = requireKingdom();
|
|
1510
|
+
if (!kingdomId) {
|
|
1511
|
+
return guiFailure('KINGDOM_NOT_INITIALIZED', '尚未初始化王国。请由人类 Owner 先直接执行 `/kingdom init` 与所需 Owner-only 配置。');
|
|
1512
|
+
}
|
|
1513
|
+
// GUI role actions use the exact activation-time session, never a browser
|
|
1514
|
+
// field and never the configured declarative demo mode.
|
|
1515
|
+
const cmd = governedStartCommandContext(kingdomId, {
|
|
1516
|
+
sessionId: control.principalSessionId,
|
|
1517
|
+
});
|
|
795
1518
|
switch (name) {
|
|
796
1519
|
case 'territory.create':
|
|
797
1520
|
return plainResult(createTerritory(store, {
|
|
@@ -836,12 +1559,18 @@ export function apply(ctx, config) {
|
|
|
836
1559
|
sessionMeta: opt('session_meta'),
|
|
837
1560
|
}), kingdomId);
|
|
838
1561
|
case 'plan':
|
|
839
|
-
|
|
1562
|
+
if (!store.isSchemaV4) {
|
|
1563
|
+
return guiFailure('EXECUTOR_UNAVAILABLE', 'Schema v4 未迁移;GUI governed plan capability requirement 不可安全写入,命令拒绝。');
|
|
1564
|
+
}
|
|
1565
|
+
const capabilityRequirementJson = opt('capability_requirement_json') ?? MINIMAL_CAPABILITY_JSON;
|
|
1566
|
+
const planned = planTask(store, cmd, {
|
|
840
1567
|
title: str('title'),
|
|
841
1568
|
description: opt('description'),
|
|
842
1569
|
acceptanceCriteria: opt('acceptance_criteria'),
|
|
843
1570
|
territoryId: opt('territory_id'),
|
|
1571
|
+
capabilityRequirementJson,
|
|
844
1572
|
});
|
|
1573
|
+
return planned;
|
|
845
1574
|
case 'assign':
|
|
846
1575
|
return assignTask(store, cmd, { taskId: str('task_id'), workerBindingId: opt('worker_binding_id') });
|
|
847
1576
|
case 'review':
|
|
@@ -849,6 +1578,7 @@ export function apply(ctx, config) {
|
|
|
849
1578
|
taskId: str('task_id'),
|
|
850
1579
|
decision: str('decision'),
|
|
851
1580
|
reason: opt('reason'),
|
|
1581
|
+
to_binding_id: opt('to_binding_id'),
|
|
852
1582
|
});
|
|
853
1583
|
case 'execution.pause':
|
|
854
1584
|
return pauseExecution(store, cmd, { executionId: str('execution_id'), reason: opt('reason') });
|
|
@@ -856,61 +1586,354 @@ export function apply(ctx, config) {
|
|
|
856
1586
|
return resumeExecution(store, cmd, { executionId: str('execution_id'), reason: opt('reason') });
|
|
857
1587
|
case 'execution.abort':
|
|
858
1588
|
return abortExecution(store, cmd, { executionId: str('execution_id'), reason: opt('reason') });
|
|
1589
|
+
case 'reconcile': {
|
|
1590
|
+
if (control.signal.aborted)
|
|
1591
|
+
return guiFailure('UNAUTHORIZED_PRINCIPAL', '本地控制已关闭。');
|
|
1592
|
+
const reconciled = await runGovernedReconcile({ taskId: str('task_id') }, {
|
|
1593
|
+
kind: 'gui', principal: { sessionId: control.principalSessionId }, agent: control.agent,
|
|
1594
|
+
signal: control.signal,
|
|
1595
|
+
});
|
|
1596
|
+
return plainResult(reconciled, kingdomId);
|
|
1597
|
+
}
|
|
859
1598
|
case 'start':
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1599
|
+
if (!control.principalSessionId.trim()) {
|
|
1600
|
+
return guiFailure('UNAUTHORIZED_PRINCIPAL', '激活时没有 exact agent.session.id;governed start 拒绝执行。');
|
|
1601
|
+
}
|
|
1602
|
+
const callerAgent = control.agent;
|
|
1603
|
+
if (!callerAgent) {
|
|
1604
|
+
return guiFailure('EXECUTOR_UNAVAILABLE', '激活时没有 exact DSH Agent reference;GUI 不伪造 Worker 父 Agent。');
|
|
1605
|
+
}
|
|
1606
|
+
try {
|
|
1607
|
+
const started = await runGovernedStart({
|
|
1608
|
+
taskId: str('task_id'),
|
|
1609
|
+
grantJson: str('grant_json'),
|
|
1610
|
+
sandboxMode: opt('sandbox_mode'),
|
|
1611
|
+
}, {
|
|
1612
|
+
kind: 'gui',
|
|
1613
|
+
principal: { sessionId: control.principalSessionId },
|
|
1614
|
+
agent: callerAgent,
|
|
1615
|
+
});
|
|
1616
|
+
if (control.signal.aborted) {
|
|
1617
|
+
return plainResult('UNKNOWN/RECOVERY_REQUIRED: GUI Control Session 在 governed start 期间被撤销;请先刷新并对账,不自动重试。', kingdomId);
|
|
1618
|
+
}
|
|
1619
|
+
return plainResult(started, kingdomId);
|
|
1620
|
+
}
|
|
1621
|
+
catch (error) {
|
|
1622
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1623
|
+
return plainResult(`UNKNOWN/RECOVERY_REQUIRED: governed start 结果不确定,请先刷新 Projection 对账;不自动重试。(${detail})`, kingdomId);
|
|
1624
|
+
}
|
|
866
1625
|
default:
|
|
867
1626
|
return guiFailure('INVALID_INPUT', `未知命令 "${name}"。`);
|
|
868
1627
|
}
|
|
869
1628
|
}
|
|
870
|
-
|
|
1629
|
+
const parseJsonEnvelope = (rest, allowed, required = []) => {
|
|
1630
|
+
if (!rest)
|
|
1631
|
+
return { ok: false, message: '需要一个 JSON object envelope;不得省略参数。' };
|
|
1632
|
+
let parsed;
|
|
1633
|
+
try {
|
|
1634
|
+
parsed = parseStrictJsonObject(rest);
|
|
1635
|
+
}
|
|
1636
|
+
catch (error) {
|
|
1637
|
+
if (error instanceof DuplicateJsonKeyError) {
|
|
1638
|
+
return { ok: false, message: `JSON envelope 不得包含重复字段 "${error.key}"。` };
|
|
1639
|
+
}
|
|
1640
|
+
return { ok: false, message: 'JSON envelope 非法;必须是单个 JSON object。' };
|
|
1641
|
+
}
|
|
1642
|
+
const value = parsed;
|
|
1643
|
+
const allowedSet = new Set(allowed);
|
|
1644
|
+
const unknown = Object.keys(value).filter(key => !allowedSet.has(key));
|
|
1645
|
+
if (unknown.length > 0)
|
|
1646
|
+
return { ok: false, message: `不允许的字段:${unknown.join(', ')}。` };
|
|
1647
|
+
const missing = required.filter(key => !Object.prototype.hasOwnProperty.call(value, key));
|
|
1648
|
+
if (missing.length > 0)
|
|
1649
|
+
return { ok: false, message: `缺少字段:${missing.join(', ')}。` };
|
|
1650
|
+
return { ok: true, value };
|
|
1651
|
+
};
|
|
1652
|
+
const directCommandParts = (rawInput) => {
|
|
1653
|
+
let line = rawInput.trim();
|
|
1654
|
+
if (line.startsWith('/kingdom'))
|
|
1655
|
+
line = line.slice('/kingdom'.length).trim();
|
|
1656
|
+
const match = line.match(/^(\S+)(?:\s+([\s\S]*))?$/u);
|
|
1657
|
+
return { sub: match?.[1] ?? '', rest: match?.[2] ?? '' };
|
|
1658
|
+
};
|
|
1659
|
+
const ownerWrite = (operation, fn) => {
|
|
1660
|
+
const kingdomId = requireKingdom();
|
|
1661
|
+
if (!kingdomId)
|
|
1662
|
+
return 'OWNER_CONTROL_REQUIRED: 请先直接执行 `/kingdom init` 建立 Owner principal。';
|
|
1663
|
+
try {
|
|
1664
|
+
// Each direct operation gets one IMMEDIATE boundary. Core helpers may
|
|
1665
|
+
// append events inside it; failure rolls back all domain writes.
|
|
1666
|
+
return store.withImmediateTransaction(fn);
|
|
1667
|
+
}
|
|
1668
|
+
catch (error) {
|
|
1669
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1670
|
+
return `UNKNOWN/RECOVERY_REQUIRED: Owner operation ${operation} 的写结果不确定;请先 /kingdom status 对账。(${detail})`;
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
const validateDirectSessionPayload = (operation, value) => {
|
|
1674
|
+
if (value === undefined || value === null)
|
|
1675
|
+
return null;
|
|
1676
|
+
if (typeof value !== 'string')
|
|
1677
|
+
return `INPUT_DENIED [${operation}]: session_id 必须是 string 或 null。`;
|
|
1678
|
+
const seam = dshRegistrySeam();
|
|
1679
|
+
const live = validateLiveDirectSession(value, seam);
|
|
1680
|
+
if (live.ok)
|
|
1681
|
+
return null;
|
|
1682
|
+
return `INPUT_DENIED [SESSION_${live.classification}]: ${live.reason};direct /kingdom ${operation} 未写入。`;
|
|
1683
|
+
};
|
|
1684
|
+
const ownerAuth = () => ownerControlAuth(issueOwnerControlCapability());
|
|
871
1685
|
ctx.effect(() => ctx.commands.register({
|
|
872
1686
|
name: 'kingdom',
|
|
873
|
-
description: '
|
|
874
|
-
input: { hint: 'init | status |
|
|
1687
|
+
description: 'direct Owner Control:初始化、状态、内置 GUI 与精确 JSON 配置;独立 Agent Tool/HTTP payload 不得代行 Owner 写入',
|
|
1688
|
+
input: { hint: 'gui [start|stop] | owner.gui <json> | init | status | ceiling <json> | territory.* <json> | role.* <json> | execution-profile <json> | help' },
|
|
875
1689
|
handler: async (invocation) => {
|
|
876
|
-
const
|
|
877
|
-
const
|
|
878
|
-
switch (sub
|
|
879
|
-
case '
|
|
880
|
-
const
|
|
881
|
-
|
|
1690
|
+
const { sub, rest } = directCommandParts(invocation.rawInput);
|
|
1691
|
+
const auth = ownerAuth();
|
|
1692
|
+
switch (sub) {
|
|
1693
|
+
case 'owner.gui': {
|
|
1694
|
+
const parsed = parseJsonEnvelope(rest, ['kingdomId', 'actions', 'scope', 'ttlMs']);
|
|
1695
|
+
if (!parsed.ok)
|
|
1696
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${parsed.message}` };
|
|
1697
|
+
try {
|
|
1698
|
+
const runtime = await ensureGuiServer();
|
|
1699
|
+
const activation = runtime.ownerControl.activate(issueOwnerControlCapability(), parsed.value);
|
|
1700
|
+
const opened = openLocalConsole(`${runtime.address.origin}${activation.launchPath}?ticket=${encodeURIComponent(activation.launchTicket)}`);
|
|
1701
|
+
return {
|
|
1702
|
+
kind: opened ? 'success' : 'error',
|
|
1703
|
+
text: opened
|
|
1704
|
+
? `人类管理窗口已激活:${runtime.address.origin}/owner;有效期至 ${activation.expiresAt}。网页仅可操作本次授权范围。`
|
|
1705
|
+
: '管理窗口已建立,但浏览器未能打开;未输出一次性票据,请重新直接激活。',
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
catch {
|
|
1709
|
+
return { kind: 'error', text: 'OWNER_GUI_ACTIVATION_DENIED: 管理窗口未能激活。请核对严格 JSON 中的王国、动作、具体范围及期限;没有输出控制凭据。' };
|
|
1710
|
+
}
|
|
882
1711
|
}
|
|
883
|
-
case '
|
|
1712
|
+
case 'gui': {
|
|
1713
|
+
const action = rest.trim().toLowerCase();
|
|
1714
|
+
if (action === 'stop') {
|
|
1715
|
+
await stopGuiServer();
|
|
1716
|
+
return {
|
|
1717
|
+
kind: 'success',
|
|
1718
|
+
text: 'GUI Control Session 已停止;活动控制会话已撤销,本地 GUI server 已关闭(如曾启动)。',
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
if (action !== '' && action !== 'start') {
|
|
1722
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: `/kingdom gui` 只接受 start、stop 或无参数。' };
|
|
1723
|
+
}
|
|
1724
|
+
try {
|
|
1725
|
+
// ensureGuiServer resolves only from the server's onListening
|
|
1726
|
+
// callback, so port=0 uses the actual bound origin.
|
|
1727
|
+
const runtime = await ensureGuiServer();
|
|
1728
|
+
const activation = runtime.control.activate(invocation.agent);
|
|
1729
|
+
const launchUrl = `${runtime.address.origin}${activation.launchPath}?ticket=${encodeURIComponent(activation.launchTicket)}`;
|
|
1730
|
+
const browserOpened = openLocalConsole(launchUrl);
|
|
1731
|
+
return formatGuiLaunchCommandResult(runtime.address.origin, activation.expiresAt, browserOpened);
|
|
1732
|
+
}
|
|
1733
|
+
catch {
|
|
1734
|
+
// Never reflect an exception into command/done if it could contain
|
|
1735
|
+
// a transport value. The launch ticket is intentionally absent.
|
|
1736
|
+
return {
|
|
1737
|
+
kind: 'error',
|
|
1738
|
+
text: 'UNKNOWN/RECOVERY_REQUIRED: GUI Control Session 启动失败;未输出 launch ticket,请检查本地 GUI server 后重试。',
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
case 'init':
|
|
1743
|
+
if (rest)
|
|
1744
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: `/kingdom init` 不接受额外 token。' };
|
|
1745
|
+
try {
|
|
1746
|
+
return { kind: 'success', text: initResultText(manager.init()) };
|
|
1747
|
+
}
|
|
1748
|
+
catch (error) {
|
|
1749
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1750
|
+
return { kind: 'error', text: `UNKNOWN/RECOVERY_REQUIRED: init 写结果不确定,请先对账。(${detail})` };
|
|
1751
|
+
}
|
|
1752
|
+
case 'status':
|
|
1753
|
+
if (rest)
|
|
1754
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: `/kingdom status` 不接受额外 token。' };
|
|
884
1755
|
return { kind: 'success', text: store.statusSummary() };
|
|
1756
|
+
case 'reset':
|
|
1757
|
+
if (rest)
|
|
1758
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: `/kingdom reset` 不接受额外 token。' };
|
|
1759
|
+
try {
|
|
1760
|
+
return { kind: 'success', text: `已重新接入。${manager.rescan().detail}` };
|
|
1761
|
+
}
|
|
1762
|
+
catch (error) {
|
|
1763
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1764
|
+
return { kind: 'error', text: `UNKNOWN/RECOVERY_REQUIRED: reset 读结果不确定,请先对账。(${detail})` };
|
|
1765
|
+
}
|
|
1766
|
+
case 'ceiling': {
|
|
1767
|
+
const parsed = parseJsonEnvelope(rest, ['ceiling', 'ceiling_json', 'clear']);
|
|
1768
|
+
if (!parsed.ok)
|
|
1769
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${parsed.message}` };
|
|
1770
|
+
const value = parsed.value;
|
|
1771
|
+
if (value.clear !== undefined && typeof value.clear !== 'boolean')
|
|
1772
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: clear 必须是 boolean。' };
|
|
1773
|
+
if (value.clear === true && (value.ceiling !== undefined || value.ceiling_json !== undefined))
|
|
1774
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: clear=true 不得同时提供 ceiling。' };
|
|
1775
|
+
let ceilingJson;
|
|
1776
|
+
if (value.clear === true)
|
|
1777
|
+
ceilingJson = null;
|
|
1778
|
+
else if (typeof value.ceiling_json === 'string')
|
|
1779
|
+
ceilingJson = value.ceiling_json;
|
|
1780
|
+
else if (value.ceiling !== undefined) {
|
|
1781
|
+
if (typeof value.ceiling !== 'object' || value.ceiling === null || Array.isArray(value.ceiling))
|
|
1782
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ceiling 必须是 capability→boolean object。' };
|
|
1783
|
+
ceilingJson = JSON.stringify(value.ceiling);
|
|
1784
|
+
}
|
|
1785
|
+
else
|
|
1786
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: 请提供 ceiling object、ceiling_json 或 clear=true。' };
|
|
1787
|
+
const text = ownerWrite('ceiling', () => setCapabilityCeiling(store, { kingdomId: requireKingdom(), ceilingJson }, auth));
|
|
1788
|
+
return { kind: text.startsWith('UNKNOWN/') || text.startsWith('CONFIG_DENIED') ? 'error' : 'success', text };
|
|
1789
|
+
}
|
|
1790
|
+
case 'territory.create': {
|
|
1791
|
+
const parsed = parseJsonEnvelope(rest, ['name', 'workspace_path', 'summary'], ['name']);
|
|
1792
|
+
if (!parsed.ok || typeof parsed.value.name !== 'string')
|
|
1793
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${parsed.ok ? 'name 必须是 string。' : parsed.message}` };
|
|
1794
|
+
const value = parsed.value;
|
|
1795
|
+
if (value.workspace_path !== undefined && typeof value.workspace_path !== 'string')
|
|
1796
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: workspace_path 必须是 string。' };
|
|
1797
|
+
if (value.summary !== undefined && typeof value.summary !== 'string')
|
|
1798
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: summary 必须是 string。' };
|
|
1799
|
+
const text = ownerWrite('territory.create', () => createTerritory(store, { kingdomId: requireKingdom(), name: value.name, workspacePath: value.workspace_path, summary: value.summary }, auth));
|
|
1800
|
+
return { kind: text.startsWith('UNKNOWN/') || text.startsWith('错误:') ? 'error' : 'success', text };
|
|
1801
|
+
}
|
|
1802
|
+
case 'territory.delete': {
|
|
1803
|
+
const parsed = parseJsonEnvelope(rest, ['territory_id', 'name', 'force', 'reason']);
|
|
1804
|
+
if (!parsed.ok)
|
|
1805
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${parsed.message}` };
|
|
1806
|
+
const value = parsed.value;
|
|
1807
|
+
if (typeof value.territory_id !== 'string' && typeof value.name !== 'string')
|
|
1808
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: territory_id/name 二选一。' };
|
|
1809
|
+
if (value.force !== undefined && typeof value.force !== 'boolean')
|
|
1810
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: force 必须是 boolean。' };
|
|
1811
|
+
if (value.reason !== undefined && typeof value.reason !== 'string')
|
|
1812
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: reason 必须是 string。' };
|
|
1813
|
+
const text = ownerWrite('territory.delete', () => deleteTerritory(store, { kingdomId: requireKingdom(), territoryId: value.territory_id, name: value.name, force: value.force, reason: value.reason }, auth));
|
|
1814
|
+
return { kind: text.startsWith('UNKNOWN/') || text.startsWith('错误:') ? 'error' : 'success', text };
|
|
1815
|
+
}
|
|
1816
|
+
case 'territory.supervisor': {
|
|
1817
|
+
const parsed = parseJsonEnvelope(rest, ['territory_id', 'supervisor_binding_id'], ['territory_id', 'supervisor_binding_id']);
|
|
1818
|
+
if (!parsed.ok)
|
|
1819
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${parsed.message}` };
|
|
1820
|
+
const value = parsed.value;
|
|
1821
|
+
if (typeof value.territory_id !== 'string' || (value.supervisor_binding_id !== null && typeof value.supervisor_binding_id !== 'string'))
|
|
1822
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: territory_id/supervisor_binding_id 类型错误。' };
|
|
1823
|
+
const text = ownerWrite('territory.supervisor', () => setTerritorySupervisor(store, { kingdomId: requireKingdom(), territoryId: value.territory_id, supervisorBindingId: value.supervisor_binding_id }, auth));
|
|
1824
|
+
return { kind: text.startsWith('UNKNOWN/') || text.startsWith('错误:') ? 'error' : 'success', text };
|
|
1825
|
+
}
|
|
1826
|
+
case 'role.bind': {
|
|
1827
|
+
const parsed = parseJsonEnvelope(rest, ['role_type', 'role_name', 'session_id', 'model_name', 'agent_name', 'session_meta'], ['role_type']);
|
|
1828
|
+
if (!parsed.ok || typeof parsed.value.role_type !== 'string')
|
|
1829
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${parsed.ok ? 'role_type 必须是 string。' : parsed.message}` };
|
|
1830
|
+
const value = parsed.value;
|
|
1831
|
+
if (value.role_type.toUpperCase() === 'OWNER')
|
|
1832
|
+
return { kind: 'error', text: 'OWNER_CONTROL_REQUIRED: OWNER projection 不得通过 role.bind 改绑为 Session。' };
|
|
1833
|
+
for (const key of ['role_name', 'session_id', 'model_name', 'agent_name', 'session_meta']) {
|
|
1834
|
+
if (value[key] !== undefined && typeof value[key] !== 'string')
|
|
1835
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${key} 必须是 string。` };
|
|
1836
|
+
}
|
|
1837
|
+
const liveSessionError = validateDirectSessionPayload('role.bind', value.session_id);
|
|
1838
|
+
if (liveSessionError)
|
|
1839
|
+
return { kind: 'error', text: liveSessionError };
|
|
1840
|
+
const text = ownerWrite('role.bind', () => bindRole(store, { kingdomId: requireKingdom(), roleType: value.role_type, roleName: value.role_name, sessionId: value.session_id, modelName: value.model_name, agentName: value.agent_name, sessionMeta: value.session_meta }, auth));
|
|
1841
|
+
return { kind: text.startsWith('UNKNOWN/') || text.startsWith('错误:') || text.startsWith('角色 ') && text.includes('已有绑定') ? 'error' : 'success', text };
|
|
1842
|
+
}
|
|
1843
|
+
case 'role.unbind': {
|
|
1844
|
+
const parsed = parseJsonEnvelope(rest, ['role_type', 'binding_id', 'reason']);
|
|
1845
|
+
if (!parsed.ok)
|
|
1846
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${parsed.message}` };
|
|
1847
|
+
const value = parsed.value;
|
|
1848
|
+
if (value.role_type !== undefined && typeof value.role_type !== 'string')
|
|
1849
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: role_type 必须是 string。' };
|
|
1850
|
+
if (value.binding_id !== undefined && typeof value.binding_id !== 'string')
|
|
1851
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: binding_id 必须是 string。' };
|
|
1852
|
+
if (value.reason !== undefined && typeof value.reason !== 'string')
|
|
1853
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: reason 必须是 string。' };
|
|
1854
|
+
if ((value.role_type === undefined) === (value.binding_id === undefined))
|
|
1855
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: role_type/binding_id 必须二选一。' };
|
|
1856
|
+
if (String(value.role_type ?? '').toUpperCase() === 'OWNER')
|
|
1857
|
+
return { kind: 'error', text: 'OWNER_CONTROL_REQUIRED: OWNER projection 不得退任。' };
|
|
1858
|
+
const text = ownerWrite('role.unbind', () => unbindRole(store, { kingdomId: requireKingdom(), roleType: value.role_type, bindingId: value.binding_id, reason: value.reason }, auth));
|
|
1859
|
+
return { kind: text.startsWith('UNKNOWN/') || text.startsWith('错误:') ? 'error' : 'success', text };
|
|
1860
|
+
}
|
|
1861
|
+
case 'role.session': {
|
|
1862
|
+
const parsed = parseJsonEnvelope(rest, ['role_type', 'binding_id', 'session_id', 'model_name', 'agent_name', 'session_meta']);
|
|
1863
|
+
if (!parsed.ok)
|
|
1864
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${parsed.message}` };
|
|
1865
|
+
const value = parsed.value;
|
|
1866
|
+
// parseJsonEnvelope has already enforced the strict object,
|
|
1867
|
+
// duplicate-key, and unknown-field boundary. For this valid
|
|
1868
|
+
// envelope, Owner is a projection rather than a runtime-session
|
|
1869
|
+
// target: resolve explicit role/binding Owner before the remaining
|
|
1870
|
+
// xor/type/live-session proof so mixed requests fail closed alike.
|
|
1871
|
+
const directKingdomId = requireKingdom();
|
|
1872
|
+
const directBinding = directKingdomId && typeof value.binding_id === 'string'
|
|
1873
|
+
? resolveBinding(store, directKingdomId, undefined, value.binding_id)
|
|
1874
|
+
: null;
|
|
1875
|
+
const explicitOwner = typeof value.role_type === 'string'
|
|
1876
|
+
&& value.role_type.trim().toUpperCase() === 'OWNER';
|
|
1877
|
+
if (explicitOwner || directBinding?.role_type.trim().toUpperCase() === 'OWNER') {
|
|
1878
|
+
return { kind: 'error', text: 'OWNER_CONTROL_REQUIRED: OWNER.session_id 永远不作为 Owner Authority。' };
|
|
1879
|
+
}
|
|
1880
|
+
if ((value.role_type === undefined) === (value.binding_id === undefined))
|
|
1881
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: role_type/binding_id 必须二选一。' };
|
|
1882
|
+
for (const key of ['role_type', 'binding_id', 'session_id', 'model_name', 'agent_name', 'session_meta']) {
|
|
1883
|
+
if (value[key] !== undefined && value[key] !== null && typeof value[key] !== 'string')
|
|
1884
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${key} 必须是 string 或 null。` };
|
|
1885
|
+
}
|
|
1886
|
+
const liveSessionError = validateDirectSessionPayload('role.session', value.session_id);
|
|
1887
|
+
if (liveSessionError)
|
|
1888
|
+
return { kind: 'error', text: liveSessionError };
|
|
1889
|
+
const text = ownerWrite('role.session', () => rebindSession(store, { kingdomId: requireKingdom(), roleType: value.role_type, bindingId: value.binding_id, sessionId: value.session_id, modelName: value.model_name, agentName: value.agent_name, sessionMeta: value.session_meta }, auth));
|
|
1890
|
+
return { kind: text.startsWith('UNKNOWN/') || text.startsWith('错误:') ? 'error' : 'success', text };
|
|
885
1891
|
}
|
|
886
|
-
case '
|
|
887
|
-
const
|
|
888
|
-
|
|
1892
|
+
case 'execution-profile': {
|
|
1893
|
+
const parsed = parseJsonEnvelope(rest, ['role_type', 'binding_id', 'provider', 'model', 'clear']);
|
|
1894
|
+
if (!parsed.ok)
|
|
1895
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${parsed.message}` };
|
|
1896
|
+
const value = parsed.value;
|
|
1897
|
+
if ((value.role_type === undefined) === (value.binding_id === undefined))
|
|
1898
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: role_type/binding_id 必须二选一。' };
|
|
1899
|
+
if (value.clear !== undefined && typeof value.clear !== 'boolean')
|
|
1900
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: clear 必须是 boolean。' };
|
|
1901
|
+
for (const key of ['role_type', 'binding_id', 'provider', 'model']) {
|
|
1902
|
+
if (value[key] !== undefined && typeof value[key] !== 'string')
|
|
1903
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: ${key} 必须是 string。` };
|
|
1904
|
+
}
|
|
1905
|
+
if (value.clear !== true && typeof value.provider !== 'string' && typeof value.model !== 'string')
|
|
1906
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: 至少提供 provider/model,或 clear=true。' };
|
|
1907
|
+
const text = ownerWrite('execution-profile', () => setExecutionProfile(store, { kingdomId: requireKingdom(), roleType: value.role_type, bindingId: value.binding_id, profile: value.clear === true ? null : { ...(typeof value.provider === 'string' ? { provider: value.provider } : {}), ...(typeof value.model === 'string' ? { model: value.model } : {}) } }, auth));
|
|
1908
|
+
return { kind: text.startsWith('UNKNOWN/') || text.startsWith('错误:') ? 'error' : 'success', text };
|
|
889
1909
|
}
|
|
890
1910
|
case 'help':
|
|
891
1911
|
case '':
|
|
1912
|
+
if (rest)
|
|
1913
|
+
return { kind: 'error', text: 'INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: help 不接受额外 token。' };
|
|
892
1914
|
return {
|
|
893
1915
|
kind: 'success',
|
|
894
1916
|
text: [
|
|
895
|
-
'dsh-Kingdom
|
|
896
|
-
'/kingdom init
|
|
897
|
-
'/kingdom status
|
|
898
|
-
'/kingdom
|
|
899
|
-
'
|
|
900
|
-
'
|
|
901
|
-
'
|
|
902
|
-
'
|
|
903
|
-
'',
|
|
904
|
-
'
|
|
905
|
-
'
|
|
906
|
-
'
|
|
907
|
-
'',
|
|
908
|
-
'
|
|
909
|
-
'
|
|
1917
|
+
'dsh-Kingdom Owner Control Plane(Owner ≠ Agent ≠ Session)',
|
|
1918
|
+
'/kingdom init 原子初始化/接入;OWNER.session_id 永远为 null',
|
|
1919
|
+
'/kingdom status 查看真实状态(只读)',
|
|
1920
|
+
'/kingdom owner.gui <严格 JSON> 激活具体动作/资源范围的短期人类管理窗口;/owner 页面提供激活格式说明',
|
|
1921
|
+
'/kingdom ceiling {"ceiling":{"tool:pwsh":true}} | {"clear":true}',
|
|
1922
|
+
'/kingdom territory.create {"name":"研发领","workspace_path":"C:/work"}',
|
|
1923
|
+
'/kingdom territory.delete {"territory_id":"...","force":false}',
|
|
1924
|
+
'/kingdom territory.supervisor {"territory_id":"...","supervisor_binding_id":"..."}',
|
|
1925
|
+
'/kingdom role.bind {"role_type":"SUPERVISOR","role_name":"主理","session_id":"真实 DSH session"}',
|
|
1926
|
+
'/kingdom role.unbind {"binding_id":"...","reason":"换届"}',
|
|
1927
|
+
'/kingdom role.session {"binding_id":"...","session_id":"真实 DSH session"}',
|
|
1928
|
+
'/kingdom execution-profile {"binding_id":"...","provider":"spawn","model":"..."}',
|
|
1929
|
+
'以上写命令只接受一个 JSON object;unknown key、额外 token、Owner Session 均拒绝。',
|
|
1930
|
+
'Agent Role Plane:plan → assign → kingdom_start_task_governed(CANONICAL HEADLESS,Persistent Worker) → Claim(REVIEW) → Supervisor ACCEPT/REWORK/FAIL。',
|
|
1931
|
+
'LEGACY_COMPAT:kingdom_start_task 仅在用户明确选择旧 one-shot 兼容模式时显式调用;persistent 失败不会自动 fallback。',
|
|
1932
|
+
'GUI 需先由 direct `/kingdom gui` 激活;HTTP 只作无权 transport,命令仍经同一 Core/Role/Capability gates。',
|
|
910
1933
|
].join('\n'),
|
|
911
1934
|
};
|
|
912
1935
|
default:
|
|
913
|
-
return { kind: 'error', text:
|
|
1936
|
+
return { kind: 'error', text: `INPUT_DENIED [OWNER_COMMAND_GRAMMAR]: 未知子命令 "${sub}"。请使用 /kingdom help。` };
|
|
914
1937
|
}
|
|
915
1938
|
},
|
|
916
1939
|
}), 'dsh-kingdom: /kingdom command');
|