@dsh-workbench/shared 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/dsh-client.d.ts +20 -0
- package/dist/api/dsh.d.ts +10 -0
- package/dist/host/tool-approval.d.ts +10 -1
- package/dist/host/tool-approval.js +24 -169
- package/dist/host/unit-face.d.ts +2 -1
- package/dist/i18n/en.js +1 -0
- package/dist/i18n/index.d.ts +2 -1
- package/dist/i18n/zh.d.ts +1 -0
- package/dist/i18n/zh.js +1 -0
- package/package.json +1 -1
- package/src/api/dsh-client.ts +20 -0
- package/src/api/dsh.ts +5 -0
- package/src/host/tool-approval.ts +41 -187
- package/src/host/unit-face.ts +2 -1
- package/src/i18n/en.ts +1 -0
- package/src/i18n/zh.ts +1 -0
package/dist/api/dsh-client.d.ts
CHANGED
|
@@ -104,6 +104,7 @@ export interface DshWorkspaceWritten {
|
|
|
104
104
|
readonly title?: string;
|
|
105
105
|
}
|
|
106
106
|
export interface DshWorkspaces {
|
|
107
|
+
readonly list: DshObservable<WorkspaceSnapshot>;
|
|
107
108
|
create(request: {
|
|
108
109
|
path: string;
|
|
109
110
|
}): Promise<DshWorkspaceWritten | undefined>;
|
|
@@ -159,6 +160,12 @@ export interface DshSessionsService {
|
|
|
159
160
|
getSnapshot(): SessionsSnapshot;
|
|
160
161
|
};
|
|
161
162
|
scope(sessionId: string): unknown | undefined;
|
|
163
|
+
create?(request: {
|
|
164
|
+
readonly workspaceId?: string;
|
|
165
|
+
readonly sessionId?: string;
|
|
166
|
+
readonly cwd?: string;
|
|
167
|
+
readonly agentPreset?: string;
|
|
168
|
+
}): Promise<unknown>;
|
|
162
169
|
}
|
|
163
170
|
export interface DshObservable<T> {
|
|
164
171
|
getSnapshot(): T;
|
|
@@ -171,6 +178,18 @@ export interface DshSidebarRight {
|
|
|
171
178
|
openResource(address: string, options?: unknown): void;
|
|
172
179
|
openTab(kind: string, options?: unknown): void;
|
|
173
180
|
}
|
|
181
|
+
export interface DshSessionsRemote {
|
|
182
|
+
create(request: {
|
|
183
|
+
readonly workspaceId?: string;
|
|
184
|
+
readonly cwd?: string;
|
|
185
|
+
readonly agentPreset?: string;
|
|
186
|
+
}): Promise<{
|
|
187
|
+
readonly sessionId: string;
|
|
188
|
+
}>;
|
|
189
|
+
}
|
|
190
|
+
export interface DshClientRemote {
|
|
191
|
+
readonly sessions: DshSessionsRemote;
|
|
192
|
+
}
|
|
174
193
|
export interface DshClientContext {
|
|
175
194
|
get<T = unknown>(name: string): T | undefined;
|
|
176
195
|
provide(name: string, value: unknown): void;
|
|
@@ -181,4 +200,5 @@ export interface DshClientContext {
|
|
|
181
200
|
readonly uiWorkspace: DshUiWorkspace;
|
|
182
201
|
readonly workspaces: DshWorkspaces;
|
|
183
202
|
readonly shortcuts: DshShortcutCatalog;
|
|
203
|
+
readonly remote: DshClientRemote;
|
|
184
204
|
}
|
package/dist/api/dsh.d.ts
CHANGED
|
@@ -91,6 +91,7 @@ export interface DshHostContext {
|
|
|
91
91
|
get<T = unknown>(name: string): T | undefined;
|
|
92
92
|
provide(name: string, value: unknown): () => void;
|
|
93
93
|
effect(effect: () => void | (() => void), label?: string): () => void;
|
|
94
|
+
inject(names: readonly string[], effect: (scoped: DshHostContext) => void): () => void;
|
|
94
95
|
readonly logger?: DshLogger;
|
|
95
96
|
readonly webServer: {
|
|
96
97
|
register(route: DshWebRoute): () => void;
|
|
@@ -128,7 +129,16 @@ export interface DshWorkspaceRegistry {
|
|
|
128
129
|
path?: string;
|
|
129
130
|
title?: string;
|
|
130
131
|
} | undefined;
|
|
132
|
+
resolveByPath(path: string): Promise<{
|
|
133
|
+
readonly id: unknown;
|
|
134
|
+
} | undefined>;
|
|
135
|
+
create(path: string, title?: string): Promise<{
|
|
136
|
+
id?: string;
|
|
137
|
+
path?: string;
|
|
138
|
+
title?: string;
|
|
139
|
+
} | undefined>;
|
|
131
140
|
}
|
|
141
|
+
export type DshHomePath = (relative: string) => string;
|
|
132
142
|
export interface DshToolDefinition {
|
|
133
143
|
name: string;
|
|
134
144
|
description: string;
|
|
@@ -21,9 +21,18 @@ export interface ApprovalCall {
|
|
|
21
21
|
readonly arguments?: unknown;
|
|
22
22
|
readonly workspace?: string | undefined;
|
|
23
23
|
}
|
|
24
|
+
export type ApprovalVerdict = {
|
|
25
|
+
readonly kind: 'read';
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: 'ask';
|
|
28
|
+
readonly reason: string;
|
|
29
|
+
readonly question: ApprovalDetail;
|
|
30
|
+
readonly fallback: ApprovalDetail;
|
|
31
|
+
};
|
|
32
|
+
export type ApprovalPolicy = (call: ApprovalCall) => ApprovalVerdict | undefined;
|
|
24
33
|
export interface ApprovalContext {
|
|
25
|
-
readonly titleOf?: ((id: string) => string | undefined) | undefined;
|
|
26
34
|
readonly writes?: boolean | undefined;
|
|
35
|
+
readonly policy?: ApprovalPolicy | undefined;
|
|
27
36
|
readonly detailOf?: ((call: ApprovalCall) => ApprovalFacts | undefined | Promise<ApprovalFacts | undefined>) | undefined;
|
|
28
37
|
}
|
|
29
38
|
export declare function approvalFor(call: ApprovalCall, context?: ApprovalContext): Promise<AskDecision | undefined>;
|
|
@@ -1,190 +1,45 @@
|
|
|
1
|
-
function
|
|
2
|
-
|
|
3
|
-
return texts.generic;
|
|
4
|
-
return { en: `${detail.en} — ${texts.question.en}`, zh: `${detail.zh} —— ${texts.question.zh}` };
|
|
1
|
+
function messageOf(error) {
|
|
2
|
+
return error instanceof Error ? error.message : String(error);
|
|
5
3
|
}
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const value = raw;
|
|
4
|
+
function compose(detail, verdict) {
|
|
5
|
+
if (detail === undefined || typeof detail.zh !== 'string' || detail.zh.trim() === '') {
|
|
6
|
+
return verdict.fallback;
|
|
7
|
+
}
|
|
11
8
|
return {
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
en: `${detail.en} — ${verdict.question.en}`,
|
|
10
|
+
zh: `${detail.zh} —— ${verdict.question.zh}`,
|
|
14
11
|
};
|
|
15
12
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const UNKNOWN = { kind: 'unknown' };
|
|
19
|
-
function ask(spec) {
|
|
20
|
-
return { kind: 'ask', spec };
|
|
21
|
-
}
|
|
22
|
-
function confirmed(call) {
|
|
23
|
-
const raw = call.arguments;
|
|
24
|
-
if (typeof raw !== 'object' || raw === null)
|
|
13
|
+
export async function approvalFor(call, context = {}) {
|
|
14
|
+
if (context.writes !== true)
|
|
25
15
|
return undefined;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
function effectOf(call, context) {
|
|
30
|
-
const name = call.name ?? '';
|
|
31
|
-
const { action, id } = actionOf(call);
|
|
32
|
-
const readAction = action !== undefined && action !== '' && READ_ONLY_ACTIONS.has(action);
|
|
33
|
-
const what = () => {
|
|
34
|
-
if (id === undefined || id === '')
|
|
35
|
-
return '一个模块';
|
|
36
|
-
return `「${context.titleOf?.(id) ?? id}」`;
|
|
37
|
-
};
|
|
38
|
-
if (name === 'workbench_cmdb_release') {
|
|
39
|
-
if (confirmed(call) !== true)
|
|
40
|
-
return READ;
|
|
41
|
-
return ask({
|
|
42
|
-
reason: 'confirm=true releases a version to a real environment',
|
|
43
|
-
generic: {
|
|
44
|
-
en: 'The agent wants to deploy a version for real (confirm: true) — it deploys on the platform (non-production). Allow?',
|
|
45
|
-
zh: 'agent 想在非生产环境上**真的部署一个版本**(confirm: true)—— 会在平台上部署。允许吗?',
|
|
46
|
-
},
|
|
47
|
-
question: {
|
|
48
|
-
en: 'it deploys on the platform for real (non-production). Allow?',
|
|
49
|
-
zh: '会在平台上真的部署一次(非生产环境)。允许吗?',
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
if (name === 'workbench_coding_issue_comment' || name === 'workbench_coding_issue_transfer') {
|
|
54
|
-
if (confirmed(call) !== true)
|
|
55
|
-
return READ;
|
|
56
|
-
const comment = name === 'workbench_coding_issue_comment';
|
|
57
|
-
return ask({
|
|
58
|
-
reason: comment
|
|
59
|
-
? 'confirm=true posts a comment on the platform'
|
|
60
|
-
: 'confirm=true moves an issue to another status on the platform',
|
|
61
|
-
generic: comment
|
|
62
|
-
? {
|
|
63
|
-
en: 'The agent wants to post a comment on the platform for real (confirm: true) — it writes on the platform. Allow?',
|
|
64
|
-
zh: 'agent 想**真的给一条缺陷加评论**(confirm: true)—— 会写平台数据。允许吗?',
|
|
65
|
-
}
|
|
66
|
-
: {
|
|
67
|
-
en: 'The agent wants to move an issue to another status for real (confirm: true) — it writes on the platform. Allow?',
|
|
68
|
-
zh: 'agent 想**真的把一条缺陷流转到别的状态**(confirm: true)—— 会写平台数据。允许吗?',
|
|
69
|
-
},
|
|
70
|
-
question: comment
|
|
71
|
-
? {
|
|
72
|
-
en: 'it posts that comment on the platform for real. Allow?',
|
|
73
|
-
zh: '会在平台上真的发表这条评论。允许吗?',
|
|
74
|
-
}
|
|
75
|
-
: {
|
|
76
|
-
en: 'it changes that issue status on the platform for real. Allow?',
|
|
77
|
-
zh: '会在平台上真的改这条缺陷的状态。允许吗?',
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
if (name === 'workbench_modules') {
|
|
82
|
-
if (readAction)
|
|
83
|
-
return READ;
|
|
84
|
-
if (action === 'update-dependency') {
|
|
85
|
-
return ask({
|
|
86
|
-
reason: `workbench_modules update-dependency ${id ?? ''} upgrades a shared dependency on this machine`,
|
|
87
|
-
generic: {
|
|
88
|
-
en: `The agent wants to upgrade the shared dependency ${what()} (it changes what is installed on this machine). Allow?`,
|
|
89
|
-
zh: `agent 想升级共用依赖 ${what()}(会改这台机器上装的东西)。允许吗?`,
|
|
90
|
-
},
|
|
91
|
-
question: {
|
|
92
|
-
en: 'it upgrades that shared dependency on this machine. Allow?',
|
|
93
|
-
zh: `会升级共用依赖 ${what()}。允许吗?`,
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
const verb = action === 'install' ? '装' : action === 'uninstall' ? '卸载' : action === 'update' ? '更新' : '装 / 更新 / 卸载';
|
|
98
|
-
return ask({
|
|
99
|
-
reason: `workbench_modules ${action ?? '(unknown action)'} ${id ?? ''} changes plugins on this machine`.trim(),
|
|
100
|
-
generic: {
|
|
101
|
-
en: `The agent wants to ${verb === '装' ? 'install' : verb === '卸载' ? 'remove' : verb === '更新' ? 'update' : 'install/update/remove'} the module ${what()} (it changes plugins on this machine). Allow?`,
|
|
102
|
-
zh: `agent 想${verb}模块 ${what()}(会改这台机器上的插件)。允许吗?`,
|
|
103
|
-
},
|
|
104
|
-
question: {
|
|
105
|
-
en: 'it changes plugins on this machine. Allow?',
|
|
106
|
-
zh: '会改这台机器上的插件。允许吗?',
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
if (name !== 'workbench_panels')
|
|
111
|
-
return UNKNOWN;
|
|
112
|
-
if (readAction)
|
|
113
|
-
return READ;
|
|
114
|
-
if (action === 'hide') {
|
|
115
|
-
return ask({
|
|
116
|
-
reason: 'workbench_panels hide removes a module from the panel',
|
|
117
|
-
generic: {
|
|
118
|
-
en: `The agent wants to remove ${what()} from this project's panel (its agent tools go offline here too). Allow?`,
|
|
119
|
-
zh: `agent 想把 ${what()} 从这个项目的面板上收起来(这个项目里它的 agent 工具也一起下线)。允许吗?`,
|
|
120
|
-
},
|
|
121
|
-
question: {
|
|
122
|
-
en: 'it takes that module off this panel (its agent tools go offline here too). Allow?',
|
|
123
|
-
zh: `${what()}会从这个项目的面板上收起来(这个项目里它的 agent 工具也一起下线)。允许吗?`,
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
if (action === 'move') {
|
|
128
|
-
return ask({
|
|
129
|
-
reason: 'workbench_panels move reorders the panel',
|
|
130
|
-
generic: {
|
|
131
|
-
en: `The agent wants to reorder this project's panel (put ${what()} first). Allow?`,
|
|
132
|
-
zh: `agent 想调整这个项目面板的顺序(把 ${what()} 放前面)。允许吗?`,
|
|
133
|
-
},
|
|
134
|
-
question: {
|
|
135
|
-
en: `it reorders this panel (${what()} goes first). Allow?`,
|
|
136
|
-
zh: `会调整这个项目面板的顺序(${what()}放前面)。允许吗?`,
|
|
137
|
-
},
|
|
138
|
-
});
|
|
16
|
+
let verdict;
|
|
17
|
+
try {
|
|
18
|
+
verdict = context.policy?.(call);
|
|
139
19
|
}
|
|
140
|
-
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
zh: 'agent 想清空这个项目的面板(所有模块在这个项目里都下线)。允许吗?',
|
|
146
|
-
},
|
|
147
|
-
question: {
|
|
148
|
-
en: 'it clears this panel (every module goes offline here). Allow?',
|
|
149
|
-
zh: '会清空这个项目的面板(所有模块在这个项目里都下线)。允许吗?',
|
|
150
|
-
},
|
|
151
|
-
});
|
|
20
|
+
catch (error) {
|
|
21
|
+
return {
|
|
22
|
+
kind: 'deny',
|
|
23
|
+
reason: `问"要不要审批"的时候套件的策略抛了错,已经停下:${messageOf(error)}`,
|
|
24
|
+
};
|
|
152
25
|
}
|
|
153
|
-
|
|
154
|
-
reason: `workbench_panels ${action ?? '(unknown action)'} changes the user's panel`,
|
|
155
|
-
generic: {
|
|
156
|
-
en: `The agent wants to add ${what()} to this project's panel. Allow?`,
|
|
157
|
-
zh: `agent 想把 ${what()} 加到**这个项目**的面板上。允许吗?`,
|
|
158
|
-
},
|
|
159
|
-
question: {
|
|
160
|
-
en: `it adds ${what()} to this project's panel. Allow?`,
|
|
161
|
-
zh: `会把 ${what()} 加到**这个项目**的面板上。允许吗?`,
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
export async function approvalFor(call, context = {}) {
|
|
166
|
-
const effect = effectOf(call, context);
|
|
167
|
-
if (effect.kind === 'read')
|
|
26
|
+
if (verdict?.kind === 'read')
|
|
168
27
|
return undefined;
|
|
169
|
-
if (
|
|
170
|
-
if (context.writes !== true)
|
|
171
|
-
return undefined;
|
|
28
|
+
if (verdict?.kind !== 'ask') {
|
|
172
29
|
return {
|
|
173
30
|
kind: 'deny',
|
|
174
|
-
reason: `${call.name ?? '这把工具'}
|
|
175
|
-
'
|
|
31
|
+
reason: `${call.name ?? '这把工具'} 在清单里声明会写东西,但它的套件没认领这次调用 —— 停下:` +
|
|
32
|
+
'在套件的 needsApproval 里补一条审批(含"这次会发生什么"),别让写静默过去',
|
|
176
33
|
};
|
|
177
34
|
}
|
|
178
|
-
const { spec } = effect;
|
|
179
35
|
let facts;
|
|
180
36
|
try {
|
|
181
37
|
facts = await context.detailOf?.(call);
|
|
182
38
|
}
|
|
183
39
|
catch (error) {
|
|
184
|
-
|
|
185
|
-
return { kind: 'deny', reason: `拿不到"这次会发生什么",已经停下:${why}` };
|
|
40
|
+
return { kind: 'deny', reason: `拿不到"这次会发生什么",已经停下:${messageOf(error)}` };
|
|
186
41
|
}
|
|
187
42
|
if (facts !== undefined && 'blocked' in facts)
|
|
188
43
|
return { kind: 'deny', reason: facts.blocked };
|
|
189
|
-
return { kind: 'ask', reason:
|
|
44
|
+
return { kind: 'ask', reason: verdict.reason, displayReason: compose(facts, verdict) };
|
|
190
45
|
}
|
package/dist/host/unit-face.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { RouteTable } from '../host/http.js';
|
|
|
4
4
|
import type { LongTaskBase, LongTaskHost, LongTaskRegistry } from '../host/long-task.js';
|
|
5
5
|
import type { ProvisionHandler } from '../host/provision.js';
|
|
6
6
|
import type { UnitCapability } from './unit-libs.js';
|
|
7
|
-
import type { ApprovalCall, ApprovalFacts } from '../host/tool-approval.js';
|
|
7
|
+
import type { ApprovalCall, ApprovalFacts, ApprovalPolicy } from '../host/tool-approval.js';
|
|
8
8
|
import type { ValueHandoff } from '../host/value-handoff.js';
|
|
9
9
|
import type { WorkspaceStore } from '../host/workspace-store.js';
|
|
10
10
|
export interface UnitMountDeps {
|
|
@@ -59,6 +59,7 @@ export interface UnitMountResult {
|
|
|
59
59
|
readonly provision?: ProvisionHandler;
|
|
60
60
|
readonly longTasks?: LongTaskRegistry<LongTaskBase> | undefined;
|
|
61
61
|
readonly approvalDetail?: ((call: ApprovalCall) => ApprovalFacts | undefined | Promise<ApprovalFacts | undefined>) | undefined;
|
|
62
|
+
readonly needsApproval?: ApprovalPolicy | undefined;
|
|
62
63
|
}
|
|
63
64
|
export interface UnitHostFace {
|
|
64
65
|
readonly manifest: UnitManifest;
|
package/dist/i18n/en.js
CHANGED
|
@@ -44,6 +44,7 @@ export const en = {
|
|
|
44
44
|
'panels.enabled': 'enabled in this project',
|
|
45
45
|
'panels.disabled': 'not enabled in this project',
|
|
46
46
|
'panels.choose': 'Choose modules',
|
|
47
|
+
'panels.buildModule': 'Build a module',
|
|
47
48
|
'panels.failed': 'Could not save the change. Try again.',
|
|
48
49
|
'panels.hintEffect': 'Disabling = the agent cannot reach that data',
|
|
49
50
|
'panels.dragHint': 'Drag to reorder',
|
package/dist/i18n/index.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ export declare const dictionaries: {
|
|
|
46
46
|
readonly 'panels.enabled': "这个项目已启用";
|
|
47
47
|
readonly 'panels.disabled': "这个项目未启用";
|
|
48
48
|
readonly 'panels.choose': "选择模块";
|
|
49
|
+
readonly 'panels.buildModule': "造一个模块";
|
|
49
50
|
readonly 'panels.failed': "改配置失败了,再试一次。";
|
|
50
51
|
readonly 'panels.hintEffect': "停用 = agent 拿不到相关数据";
|
|
51
52
|
readonly 'panels.dragHint': "拖动调整顺序";
|
|
@@ -309,7 +310,7 @@ export declare const dictionaries: {
|
|
|
309
310
|
readonly 'panel.conversation': "对话";
|
|
310
311
|
readonly 'panels.label': "全局面板";
|
|
311
312
|
};
|
|
312
|
-
en: Record<"time.now" | "time.minutes" | "time.hours" | "time.days" | "blocker.retry" | "blocker.copy" | "blocker.unknown.title" | "blocker.login.go" | "blocker.login.titleGeneric" | "blocker.login.title" | "blocker.login.manual" | "blocker.login.hint" | "blocker.forbidden.open" | "blocker.forbidden.titleGeneric" | "blocker.forbidden.title" | "blocker.forbidden.hint" | "blocker.timeout.hint" | "blocker.exec.hint" | "blocker.fix.install" | "blocker.fix.update" | "blocker.market.open" | "env.unit.failed" | "env.unit.failedHint" | "env.unit.missingPlugin" | "env.unit.hint" | "env.unit.missingCommands" | "env.unit.missingCommandsHint" | "env.unit.unchecked" | "env.unit.uncheckedHint" | "brand.name" | "toggle.open" | "toggle.collapse" | "project.label" | "workspace.section" | "project.none" | "project.empty" | "project.add" | "project.search.label" | "project.search.placeholder" | "project.search.empty" | "project.rename.row" | "project.rename.current" | "project.rename.title" | "project.rename.description" | "project.rename.field" | "project.rename.placeholder" | "project.rename.hint" | "project.rename.save" | "project.rename.saving" | "project.rename.cancel" | "project.rename.failed" | "session.new" | "session.new.label" | "session.empty" | "session.noProject" | "session.pinned" | "session.actions" | "status.idle" | "status.running" | "status.waitingApproval" | "status.planReview" | "status.waitingAnswer" | "status.completed" | "status.subagentsRunning.one" | "status.subagentsRunning.other" | "panels.title" | "panels.reset" | "panels.enabled" | "panels.disabled" | "panels.choose" | "panels.failed" | "panels.hintEffect" | "panels.dragHint" | "panels.unknownStep" | "panels.labelWithHint" | "panels.dep.failed" | "panels.dep.missingPlugin" | "panels.dep.missingCommands" | "panels.dep.unchecked" | "panels.tab.modules" | "panels.tab.market" | "panels.browseMarket" | "panels.noneInstalled" | "panels.noneInstalledHint" | "panels.market.installed" | "panels.market.outdated" | "panels.market.hasNew" | "panels.market.missing" | "panels.market.unchecked" | "panels.market.checking" | "panels.market.sectionInstalled" | "panels.market.sectionPending" | "panels.market.sectionShared" | "panels.market.sharedUsedBy" | "panels.market.listSeparator" | "panels.market.sharedMissing" | "panels.market.sharedOutdated" | "panels.market.sharedUnsatisfied" | "panels.market.sharedOk" | "panels.market.versionArrow" | "panels.market.lead" | "panels.market.install" | "panels.market.update" | "panels.market.uninstall" | "panels.market.uninstallLabel" | "panels.market.installing" | "panels.market.updating" | "panels.market.uninstalling" | "panels.market.confirmUninstallTitle" | "panels.market.confirmUninstallDetail" | "panels.market.confirmUninstallAck" | "panels.market.confirmYes" | "panels.market.confirmNo" | "panels.market.failed" | "panels.market.needEnv" | "panels.market.noSource" | "panels.market.badSource" | "panels.market.empty" | "panels.market.installedSection" | "panels.market.moduleOutdated" | "panels.market.moduleRestartHint" | "panels.market.restartNeeded" | "panels.market.foot" | "panels.market.recheck" | "panels.market.checkUpdates" | "panels.market.checkedAt" | "panels.market.justNow" | "panels.market.minutesAgo" | "panels.market.hoursAgo" | "panels.market.add.description" | "panels.market.add.specPlaceholder" | "panels.market.add.guideToggle" | "panels.market.add.guideHide" | "panels.market.add.guideIdTitle" | "panels.market.add.guideIdExample" | "panels.market.add.guideIdHint" | "panels.market.add.guideGitTitle" | "panels.market.add.guideGitExample" | "panels.market.add.guideGitHint" | "panels.market.add.guidePathTitle" | "panels.market.add.guidePathExample" | "panels.market.add.guidePathHint" | "panels.market.add.guideExampleLabel" | "panels.market.add.guideFill" | "panels.market.add.guideSafety" | "panels.market.add.registryLegend" | "panels.market.add.registryOfficial" | "panels.market.add.registryMirror" | "panels.market.add.registryWithHost" | "panels.market.add.registryToggle" | "panels.market.add.installVersion" | "panels.market.add.registryDefault" | "panels.market.add.registryCustom" | "panels.market.add.registryCustomPlaceholder" | "panels.market.add.registryCustomHint" | "panels.market.add.registryCustomInvalid" | "panels.market.add.run" | "panels.market.add.edit" | "panels.market.add.changeRegistry" | "panels.market.add.installingTitle" | "panels.market.add.installedTitle" | "panels.market.add.failedTitle" | "panels.market.add.cancelledTitle" | "panels.market.add.failureNetworkHost" | "panels.market.add.problemInvalid" | "panels.market.add.problemInstalled" | "panels.market.add.problemNotFound" | "panels.market.add.problemNotPackage" | "panels.market.add.problemNotBundle" | "panels.market.add.problemNetwork" | "panels.market.add.problemUnknown" | "panels.market.add.failureNetwork" | "panels.market.add.failureNotFound" | "panels.market.add.failureNoMatchingVersion" | "panels.market.add.failureDiskFull" | "panels.market.add.failurePermission" | "panels.market.add.failureBuildBlocked" | "panels.market.add.failureIntegrity" | "panels.market.add.failureTimeout" | "panels.market.add.failurePnpmMissing" | "panels.market.add.failureGeneric" | "panels.market.add.approveTitle" | "panels.market.add.approveCaution" | "panels.market.add.approveRun" | "panels.market.add.restart" | "panels.market.add.tookEffect" | "panels.market.add.details" | "panels.market.add.open" | "panels.market.add.title" | "panels.market.add.intro" | "panels.market.add.specLabel" | "panels.market.add.specHint" | "panels.market.add.exampleTitle" | "panels.market.add.exampleFill" | "panels.market.add.registryLabel" | "panels.market.add.registryProfile" | "panels.market.add.registryPnpm" | "panels.market.add.check" | "panels.market.add.checking" | "panels.market.add.change" | "panels.market.add.previewTitle" | "panels.market.add.fieldName" | "panels.market.add.fieldVersion" | "panels.market.add.fieldDescription" | "panels.market.add.fieldSource" | "panels.market.add.install" | "panels.market.add.installing" | "panels.market.add.cancel" | "panels.market.add.close" | "panels.market.add.retry" | "panels.market.add.changeSource" | "panels.market.add.approveBuilds" | "panels.market.add.approveHint" | "panels.market.add.doneApplied" | "panels.market.add.doneRestart" | "panels.market.add.cancelled" | "panels.market.add.failed" | "panels.market.add.removed" | "panels.market.add.supplyPending" | "panels.market.add.supplyFailed" | "panels.market.add.notModule" | "panels.market.add.removedRestart" | "panels.market.add.remove" | "panels.market.add.problem.invalidSpec" | "panels.market.add.problem.alreadyInstalled" | "panels.market.add.problem.notFound" | "panels.market.add.problem.notAPackage" | "panels.market.add.problem.notABundle" | "panels.market.add.problem.network" | "panels.market.add.problem.unknown" | "panels.market.add.reason.invalidSpec" | "panels.market.add.reason.incompatible" | "panels.market.add.reason.notBundle" | "panels.market.add.reason.notRemovable" | "panels.market.add.reason.bundleInUse" | "panels.market.add.reason.stopProfile" | "panels.market.add.reason.staleApproval" | "panels.market.add.reason.operationError" | "panels.market.add.reason.buildBlocked" | "panels.market.add.reason.pnpmMissing" | "panels.market.add.reason.noMatchingVersion" | "panels.market.add.reason.diskFull" | "panels.market.add.reason.permission" | "panels.market.add.reason.integrity" | "panels.market.add.reason.timeout" | "panels.market.add.reason.network" | "panels.market.add.reason.notFound" | "panels.market.add.reason.unknown" | "blocker.fix.running" | "blocker.fix.failed" | "blocker.retry.running" | "panels.reading" | "panels.cardBroken" | "panels.cardRetry" | "panels.empty" | "panels.emptyHint" | "env.title" | "env.checking" | "env.installing" | "env.failed" | "env.elapsed" | "env.stepFailed.install" | "env.stepFailed.path" | "env.stepFailed.shell" | "env.bridge.ok" | "env.bridge.short" | "env.bridge.why" | "env.bridge.daemon" | "env.bridge.extension" | "env.bridge.extensionLink" | "env.bridge.profile" | "env.bridge.stale" | "env.bridge.unknown" | "env.fix.title" | "env.fix.why" | "env.fix.whyPath" | "env.fix.whyShell" | "env.fix.step" | "env.fix.stepPath" | "env.fix.stepShell" | "env.fix.permission" | "env.fix.network" | "env.fix.details" | "env.retry" | "env.copyCommand" | "env.copied" | "env.step.opencli" | "env.step.codingPlugin" | "env.step.bridge" | "env.step.warm" | "env.warm.done" | "env.warm.short" | "rightbar.label" | "rightbar.expand" | "rightbar.collapse" | "rightbar.column.label" | "rightbar.column.workbench" | "rightbar.column.dsh" | "rightbar.refresh" | "rightbar.refreshing" | "rightbar.updatedAt" | "rightbar.noProject" | "coding.notARepo.title" | "error.unknown" | "board.title" | "panel.conversation" | "panels.label", string>;
|
|
313
|
+
en: Record<"time.now" | "time.minutes" | "time.hours" | "time.days" | "blocker.retry" | "blocker.copy" | "blocker.unknown.title" | "blocker.login.go" | "blocker.login.titleGeneric" | "blocker.login.title" | "blocker.login.manual" | "blocker.login.hint" | "blocker.forbidden.open" | "blocker.forbidden.titleGeneric" | "blocker.forbidden.title" | "blocker.forbidden.hint" | "blocker.timeout.hint" | "blocker.exec.hint" | "blocker.fix.install" | "blocker.fix.update" | "blocker.market.open" | "env.unit.failed" | "env.unit.failedHint" | "env.unit.missingPlugin" | "env.unit.hint" | "env.unit.missingCommands" | "env.unit.missingCommandsHint" | "env.unit.unchecked" | "env.unit.uncheckedHint" | "brand.name" | "toggle.open" | "toggle.collapse" | "project.label" | "workspace.section" | "project.none" | "project.empty" | "project.add" | "project.search.label" | "project.search.placeholder" | "project.search.empty" | "project.rename.row" | "project.rename.current" | "project.rename.title" | "project.rename.description" | "project.rename.field" | "project.rename.placeholder" | "project.rename.hint" | "project.rename.save" | "project.rename.saving" | "project.rename.cancel" | "project.rename.failed" | "session.new" | "session.new.label" | "session.empty" | "session.noProject" | "session.pinned" | "session.actions" | "status.idle" | "status.running" | "status.waitingApproval" | "status.planReview" | "status.waitingAnswer" | "status.completed" | "status.subagentsRunning.one" | "status.subagentsRunning.other" | "panels.title" | "panels.reset" | "panels.enabled" | "panels.disabled" | "panels.choose" | "panels.buildModule" | "panels.failed" | "panels.hintEffect" | "panels.dragHint" | "panels.unknownStep" | "panels.labelWithHint" | "panels.dep.failed" | "panels.dep.missingPlugin" | "panels.dep.missingCommands" | "panels.dep.unchecked" | "panels.tab.modules" | "panels.tab.market" | "panels.browseMarket" | "panels.noneInstalled" | "panels.noneInstalledHint" | "panels.market.installed" | "panels.market.outdated" | "panels.market.hasNew" | "panels.market.missing" | "panels.market.unchecked" | "panels.market.checking" | "panels.market.sectionInstalled" | "panels.market.sectionPending" | "panels.market.sectionShared" | "panels.market.sharedUsedBy" | "panels.market.listSeparator" | "panels.market.sharedMissing" | "panels.market.sharedOutdated" | "panels.market.sharedUnsatisfied" | "panels.market.sharedOk" | "panels.market.versionArrow" | "panels.market.lead" | "panels.market.install" | "panels.market.update" | "panels.market.uninstall" | "panels.market.uninstallLabel" | "panels.market.installing" | "panels.market.updating" | "panels.market.uninstalling" | "panels.market.confirmUninstallTitle" | "panels.market.confirmUninstallDetail" | "panels.market.confirmUninstallAck" | "panels.market.confirmYes" | "panels.market.confirmNo" | "panels.market.failed" | "panels.market.needEnv" | "panels.market.noSource" | "panels.market.badSource" | "panels.market.empty" | "panels.market.installedSection" | "panels.market.moduleOutdated" | "panels.market.moduleRestartHint" | "panels.market.restartNeeded" | "panels.market.foot" | "panels.market.recheck" | "panels.market.checkUpdates" | "panels.market.checkedAt" | "panels.market.justNow" | "panels.market.minutesAgo" | "panels.market.hoursAgo" | "panels.market.add.description" | "panels.market.add.specPlaceholder" | "panels.market.add.guideToggle" | "panels.market.add.guideHide" | "panels.market.add.guideIdTitle" | "panels.market.add.guideIdExample" | "panels.market.add.guideIdHint" | "panels.market.add.guideGitTitle" | "panels.market.add.guideGitExample" | "panels.market.add.guideGitHint" | "panels.market.add.guidePathTitle" | "panels.market.add.guidePathExample" | "panels.market.add.guidePathHint" | "panels.market.add.guideExampleLabel" | "panels.market.add.guideFill" | "panels.market.add.guideSafety" | "panels.market.add.registryLegend" | "panels.market.add.registryOfficial" | "panels.market.add.registryMirror" | "panels.market.add.registryWithHost" | "panels.market.add.registryToggle" | "panels.market.add.installVersion" | "panels.market.add.registryDefault" | "panels.market.add.registryCustom" | "panels.market.add.registryCustomPlaceholder" | "panels.market.add.registryCustomHint" | "panels.market.add.registryCustomInvalid" | "panels.market.add.run" | "panels.market.add.edit" | "panels.market.add.changeRegistry" | "panels.market.add.installingTitle" | "panels.market.add.installedTitle" | "panels.market.add.failedTitle" | "panels.market.add.cancelledTitle" | "panels.market.add.failureNetworkHost" | "panels.market.add.problemInvalid" | "panels.market.add.problemInstalled" | "panels.market.add.problemNotFound" | "panels.market.add.problemNotPackage" | "panels.market.add.problemNotBundle" | "panels.market.add.problemNetwork" | "panels.market.add.problemUnknown" | "panels.market.add.failureNetwork" | "panels.market.add.failureNotFound" | "panels.market.add.failureNoMatchingVersion" | "panels.market.add.failureDiskFull" | "panels.market.add.failurePermission" | "panels.market.add.failureBuildBlocked" | "panels.market.add.failureIntegrity" | "panels.market.add.failureTimeout" | "panels.market.add.failurePnpmMissing" | "panels.market.add.failureGeneric" | "panels.market.add.approveTitle" | "panels.market.add.approveCaution" | "panels.market.add.approveRun" | "panels.market.add.restart" | "panels.market.add.tookEffect" | "panels.market.add.details" | "panels.market.add.open" | "panels.market.add.title" | "panels.market.add.intro" | "panels.market.add.specLabel" | "panels.market.add.specHint" | "panels.market.add.exampleTitle" | "panels.market.add.exampleFill" | "panels.market.add.registryLabel" | "panels.market.add.registryProfile" | "panels.market.add.registryPnpm" | "panels.market.add.check" | "panels.market.add.checking" | "panels.market.add.change" | "panels.market.add.previewTitle" | "panels.market.add.fieldName" | "panels.market.add.fieldVersion" | "panels.market.add.fieldDescription" | "panels.market.add.fieldSource" | "panels.market.add.install" | "panels.market.add.installing" | "panels.market.add.cancel" | "panels.market.add.close" | "panels.market.add.retry" | "panels.market.add.changeSource" | "panels.market.add.approveBuilds" | "panels.market.add.approveHint" | "panels.market.add.doneApplied" | "panels.market.add.doneRestart" | "panels.market.add.cancelled" | "panels.market.add.failed" | "panels.market.add.removed" | "panels.market.add.supplyPending" | "panels.market.add.supplyFailed" | "panels.market.add.notModule" | "panels.market.add.removedRestart" | "panels.market.add.remove" | "panels.market.add.problem.invalidSpec" | "panels.market.add.problem.alreadyInstalled" | "panels.market.add.problem.notFound" | "panels.market.add.problem.notAPackage" | "panels.market.add.problem.notABundle" | "panels.market.add.problem.network" | "panels.market.add.problem.unknown" | "panels.market.add.reason.invalidSpec" | "panels.market.add.reason.incompatible" | "panels.market.add.reason.notBundle" | "panels.market.add.reason.notRemovable" | "panels.market.add.reason.bundleInUse" | "panels.market.add.reason.stopProfile" | "panels.market.add.reason.staleApproval" | "panels.market.add.reason.operationError" | "panels.market.add.reason.buildBlocked" | "panels.market.add.reason.pnpmMissing" | "panels.market.add.reason.noMatchingVersion" | "panels.market.add.reason.diskFull" | "panels.market.add.reason.permission" | "panels.market.add.reason.integrity" | "panels.market.add.reason.timeout" | "panels.market.add.reason.network" | "panels.market.add.reason.notFound" | "panels.market.add.reason.unknown" | "blocker.fix.running" | "blocker.fix.failed" | "blocker.retry.running" | "panels.reading" | "panels.cardBroken" | "panels.cardRetry" | "panels.empty" | "panels.emptyHint" | "env.title" | "env.checking" | "env.installing" | "env.failed" | "env.elapsed" | "env.stepFailed.install" | "env.stepFailed.path" | "env.stepFailed.shell" | "env.bridge.ok" | "env.bridge.short" | "env.bridge.why" | "env.bridge.daemon" | "env.bridge.extension" | "env.bridge.extensionLink" | "env.bridge.profile" | "env.bridge.stale" | "env.bridge.unknown" | "env.fix.title" | "env.fix.why" | "env.fix.whyPath" | "env.fix.whyShell" | "env.fix.step" | "env.fix.stepPath" | "env.fix.stepShell" | "env.fix.permission" | "env.fix.network" | "env.fix.details" | "env.retry" | "env.copyCommand" | "env.copied" | "env.step.opencli" | "env.step.codingPlugin" | "env.step.bridge" | "env.step.warm" | "env.warm.done" | "env.warm.short" | "rightbar.label" | "rightbar.expand" | "rightbar.collapse" | "rightbar.column.label" | "rightbar.column.workbench" | "rightbar.column.dsh" | "rightbar.refresh" | "rightbar.refreshing" | "rightbar.updatedAt" | "rightbar.noProject" | "coding.notARepo.title" | "error.unknown" | "board.title" | "panel.conversation" | "panels.label", string>;
|
|
313
314
|
};
|
|
314
315
|
export type { WorkbenchMessages };
|
|
315
316
|
export interface Dictionaries {
|
package/dist/i18n/zh.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export declare const zh: {
|
|
|
44
44
|
readonly 'panels.enabled': "这个项目已启用";
|
|
45
45
|
readonly 'panels.disabled': "这个项目未启用";
|
|
46
46
|
readonly 'panels.choose': "选择模块";
|
|
47
|
+
readonly 'panels.buildModule': "造一个模块";
|
|
47
48
|
readonly 'panels.failed': "改配置失败了,再试一次。";
|
|
48
49
|
readonly 'panels.hintEffect': "停用 = agent 拿不到相关数据";
|
|
49
50
|
readonly 'panels.dragHint': "拖动调整顺序";
|
package/dist/i18n/zh.js
CHANGED
package/package.json
CHANGED
package/src/api/dsh-client.ts
CHANGED
|
@@ -119,6 +119,7 @@ export interface DshWorkspaceWritten {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
export interface DshWorkspaces {
|
|
122
|
+
readonly list: DshObservable<WorkspaceSnapshot>;
|
|
122
123
|
create(request: { path: string }): Promise<DshWorkspaceWritten | undefined>;
|
|
123
124
|
rename(workspaceId: string, title: string): Promise<DshWorkspaceWritten>;
|
|
124
125
|
}
|
|
@@ -167,6 +168,12 @@ export interface DshConversationService {
|
|
|
167
168
|
export interface DshSessionsService {
|
|
168
169
|
readonly list: { getSnapshot(): SessionsSnapshot };
|
|
169
170
|
scope(sessionId: string): unknown | undefined;
|
|
171
|
+
create?(request: {
|
|
172
|
+
readonly workspaceId?: string;
|
|
173
|
+
readonly sessionId?: string;
|
|
174
|
+
readonly cwd?: string;
|
|
175
|
+
readonly agentPreset?: string;
|
|
176
|
+
}): Promise<unknown>;
|
|
170
177
|
}
|
|
171
178
|
|
|
172
179
|
export interface DshObservable<T> {
|
|
@@ -182,6 +189,18 @@ export interface DshSidebarRight {
|
|
|
182
189
|
openTab(kind: string, options?: unknown): void;
|
|
183
190
|
}
|
|
184
191
|
|
|
192
|
+
export interface DshSessionsRemote {
|
|
193
|
+
create(request: {
|
|
194
|
+
readonly workspaceId?: string;
|
|
195
|
+
readonly cwd?: string;
|
|
196
|
+
readonly agentPreset?: string;
|
|
197
|
+
}): Promise<{ readonly sessionId: string }>;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface DshClientRemote {
|
|
201
|
+
readonly sessions: DshSessionsRemote;
|
|
202
|
+
}
|
|
203
|
+
|
|
185
204
|
export interface DshClientContext {
|
|
186
205
|
get<T = unknown>(name: string): T | undefined;
|
|
187
206
|
provide(name: string, value: unknown): void;
|
|
@@ -192,4 +211,5 @@ export interface DshClientContext {
|
|
|
192
211
|
readonly uiWorkspace: DshUiWorkspace;
|
|
193
212
|
readonly workspaces: DshWorkspaces;
|
|
194
213
|
readonly shortcuts: DshShortcutCatalog;
|
|
214
|
+
readonly remote: DshClientRemote;
|
|
195
215
|
}
|
package/src/api/dsh.ts
CHANGED
|
@@ -102,6 +102,7 @@ export interface DshHostContext {
|
|
|
102
102
|
get<T = unknown>(name: string): T | undefined;
|
|
103
103
|
provide(name: string, value: unknown): () => void;
|
|
104
104
|
effect(effect: () => void | (() => void), label?: string): () => void;
|
|
105
|
+
inject(names: readonly string[], effect: (scoped: DshHostContext) => void): () => void;
|
|
105
106
|
readonly logger?: DshLogger;
|
|
106
107
|
readonly webServer: { register(route: DshWebRoute): () => void };
|
|
107
108
|
}
|
|
@@ -130,8 +131,12 @@ export interface DshShell {
|
|
|
130
131
|
|
|
131
132
|
export interface DshWorkspaceRegistry {
|
|
132
133
|
get(id: string): { id: string; path?: string; title?: string } | undefined;
|
|
134
|
+
resolveByPath(path: string): Promise<{ readonly id: unknown } | undefined>;
|
|
135
|
+
create(path: string, title?: string): Promise<{ id?: string; path?: string; title?: string } | undefined>;
|
|
133
136
|
}
|
|
134
137
|
|
|
138
|
+
export type DshHomePath = (relative: string) => string;
|
|
139
|
+
|
|
135
140
|
export interface DshToolDefinition {
|
|
136
141
|
name: string;
|
|
137
142
|
description: string;
|
|
@@ -22,222 +22,76 @@ export interface ApprovalCall {
|
|
|
22
22
|
readonly workspace?: string | undefined;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export type ApprovalVerdict =
|
|
26
|
+
| { readonly kind: 'read' }
|
|
27
|
+
| {
|
|
28
|
+
readonly kind: 'ask';
|
|
29
|
+
readonly reason: string;
|
|
30
|
+
readonly question: ApprovalDetail;
|
|
31
|
+
readonly fallback: ApprovalDetail;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ApprovalPolicy = (call: ApprovalCall) => ApprovalVerdict | undefined;
|
|
35
|
+
|
|
25
36
|
export interface ApprovalContext {
|
|
26
|
-
readonly titleOf?: ((id: string) => string | undefined) | undefined;
|
|
27
37
|
readonly writes?: boolean | undefined;
|
|
38
|
+
readonly policy?: ApprovalPolicy | undefined;
|
|
28
39
|
readonly detailOf?:
|
|
29
40
|
| ((call: ApprovalCall) => ApprovalFacts | undefined | Promise<ApprovalFacts | undefined>)
|
|
30
41
|
| undefined;
|
|
31
42
|
}
|
|
32
43
|
|
|
44
|
+
function messageOf(error: unknown): string {
|
|
45
|
+
return error instanceof Error ? error.message : String(error);
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
function compose(
|
|
34
49
|
detail: ApprovalDetail | undefined,
|
|
35
|
-
|
|
50
|
+
verdict: { readonly question: ApprovalDetail; readonly fallback: ApprovalDetail },
|
|
36
51
|
): ApprovalDetail {
|
|
37
|
-
if (detail === undefined || typeof detail.zh !== 'string' || detail.zh.trim() === '')
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function actionOf(call: ApprovalCall): { action?: string; id?: string } {
|
|
42
|
-
const raw = call.arguments;
|
|
43
|
-
if (typeof raw !== 'object' || raw === null) return {};
|
|
44
|
-
const value = raw as { action?: unknown; id?: unknown; unit?: unknown };
|
|
52
|
+
if (detail === undefined || typeof detail.zh !== 'string' || detail.zh.trim() === '') {
|
|
53
|
+
return verdict.fallback;
|
|
54
|
+
}
|
|
45
55
|
return {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const READ_ONLY_ACTIONS = new Set(['list', '']);
|
|
52
|
-
|
|
53
|
-
interface AskSpec {
|
|
54
|
-
readonly reason: string;
|
|
55
|
-
readonly generic: ApprovalDetail;
|
|
56
|
-
readonly question: ApprovalDetail;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
type CallEffect =
|
|
60
|
-
| { readonly kind: 'ask'; readonly spec: AskSpec }
|
|
61
|
-
| { readonly kind: 'read' }
|
|
62
|
-
| { readonly kind: 'unknown' };
|
|
63
|
-
|
|
64
|
-
const READ: CallEffect = { kind: 'read' };
|
|
65
|
-
const UNKNOWN: CallEffect = { kind: 'unknown' };
|
|
66
|
-
|
|
67
|
-
function ask(spec: AskSpec): CallEffect {
|
|
68
|
-
return { kind: 'ask', spec };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function confirmed(call: ApprovalCall): boolean | undefined {
|
|
72
|
-
const raw = call.arguments;
|
|
73
|
-
if (typeof raw !== 'object' || raw === null) return undefined;
|
|
74
|
-
const value = (raw as { confirm?: unknown }).confirm;
|
|
75
|
-
return typeof value === 'boolean' ? value : undefined;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function effectOf(call: ApprovalCall, context: ApprovalContext): CallEffect {
|
|
79
|
-
const name = call.name ?? '';
|
|
80
|
-
const { action, id } = actionOf(call);
|
|
81
|
-
const readAction = action !== undefined && action !== '' && READ_ONLY_ACTIONS.has(action);
|
|
82
|
-
|
|
83
|
-
const what = (): string => {
|
|
84
|
-
if (id === undefined || id === '') return '一个模块';
|
|
85
|
-
return `「${context.titleOf?.(id) ?? id}」`;
|
|
56
|
+
en: `${detail.en} — ${verdict.question.en}`,
|
|
57
|
+
zh: `${detail.zh} —— ${verdict.question.zh}`,
|
|
86
58
|
};
|
|
87
|
-
|
|
88
|
-
if (name === 'workbench_cmdb_release') {
|
|
89
|
-
if (confirmed(call) !== true) return READ;
|
|
90
|
-
return ask({
|
|
91
|
-
reason: 'confirm=true releases a version to a real environment',
|
|
92
|
-
generic: {
|
|
93
|
-
en: 'The agent wants to deploy a version for real (confirm: true) — it deploys on the platform (non-production). Allow?',
|
|
94
|
-
zh: 'agent 想在非生产环境上**真的部署一个版本**(confirm: true)—— 会在平台上部署。允许吗?',
|
|
95
|
-
},
|
|
96
|
-
question: {
|
|
97
|
-
en: 'it deploys on the platform for real (non-production). Allow?',
|
|
98
|
-
zh: '会在平台上真的部署一次(非生产环境)。允许吗?',
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (name === 'workbench_coding_issue_comment' || name === 'workbench_coding_issue_transfer') {
|
|
104
|
-
if (confirmed(call) !== true) return READ;
|
|
105
|
-
const comment = name === 'workbench_coding_issue_comment';
|
|
106
|
-
return ask({
|
|
107
|
-
reason: comment
|
|
108
|
-
? 'confirm=true posts a comment on the platform'
|
|
109
|
-
: 'confirm=true moves an issue to another status on the platform',
|
|
110
|
-
generic: comment
|
|
111
|
-
? {
|
|
112
|
-
en: 'The agent wants to post a comment on the platform for real (confirm: true) — it writes on the platform. Allow?',
|
|
113
|
-
zh: 'agent 想**真的给一条缺陷加评论**(confirm: true)—— 会写平台数据。允许吗?',
|
|
114
|
-
}
|
|
115
|
-
: {
|
|
116
|
-
en: 'The agent wants to move an issue to another status for real (confirm: true) — it writes on the platform. Allow?',
|
|
117
|
-
zh: 'agent 想**真的把一条缺陷流转到别的状态**(confirm: true)—— 会写平台数据。允许吗?',
|
|
118
|
-
},
|
|
119
|
-
question: comment
|
|
120
|
-
? {
|
|
121
|
-
en: 'it posts that comment on the platform for real. Allow?',
|
|
122
|
-
zh: '会在平台上真的发表这条评论。允许吗?',
|
|
123
|
-
}
|
|
124
|
-
: {
|
|
125
|
-
en: 'it changes that issue status on the platform for real. Allow?',
|
|
126
|
-
zh: '会在平台上真的改这条缺陷的状态。允许吗?',
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (name === 'workbench_modules') {
|
|
132
|
-
if (readAction) return READ;
|
|
133
|
-
if (action === 'update-dependency') {
|
|
134
|
-
return ask({
|
|
135
|
-
reason: `workbench_modules update-dependency ${id ?? ''} upgrades a shared dependency on this machine`,
|
|
136
|
-
generic: {
|
|
137
|
-
en: `The agent wants to upgrade the shared dependency ${what()} (it changes what is installed on this machine). Allow?`,
|
|
138
|
-
zh: `agent 想升级共用依赖 ${what()}(会改这台机器上装的东西)。允许吗?`,
|
|
139
|
-
},
|
|
140
|
-
question: {
|
|
141
|
-
en: 'it upgrades that shared dependency on this machine. Allow?',
|
|
142
|
-
zh: `会升级共用依赖 ${what()}。允许吗?`,
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
const verb = action === 'install' ? '装' : action === 'uninstall' ? '卸载' : action === 'update' ? '更新' : '装 / 更新 / 卸载';
|
|
147
|
-
return ask({
|
|
148
|
-
reason: `workbench_modules ${action ?? '(unknown action)'} ${id ?? ''} changes plugins on this machine`.trim(),
|
|
149
|
-
generic: {
|
|
150
|
-
en: `The agent wants to ${verb === '装' ? 'install' : verb === '卸载' ? 'remove' : verb === '更新' ? 'update' : 'install/update/remove'} the module ${what()} (it changes plugins on this machine). Allow?`,
|
|
151
|
-
zh: `agent 想${verb}模块 ${what()}(会改这台机器上的插件)。允许吗?`,
|
|
152
|
-
},
|
|
153
|
-
question: {
|
|
154
|
-
en: 'it changes plugins on this machine. Allow?',
|
|
155
|
-
zh: '会改这台机器上的插件。允许吗?',
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (name !== 'workbench_panels') return UNKNOWN;
|
|
161
|
-
|
|
162
|
-
if (readAction) return READ;
|
|
163
|
-
|
|
164
|
-
if (action === 'hide') {
|
|
165
|
-
return ask({
|
|
166
|
-
reason: 'workbench_panels hide removes a module from the panel',
|
|
167
|
-
generic: {
|
|
168
|
-
en: `The agent wants to remove ${what()} from this project's panel (its agent tools go offline here too). Allow?`,
|
|
169
|
-
zh: `agent 想把 ${what()} 从这个项目的面板上收起来(这个项目里它的 agent 工具也一起下线)。允许吗?`,
|
|
170
|
-
},
|
|
171
|
-
question: {
|
|
172
|
-
en: 'it takes that module off this panel (its agent tools go offline here too). Allow?',
|
|
173
|
-
zh: `${what()}会从这个项目的面板上收起来(这个项目里它的 agent 工具也一起下线)。允许吗?`,
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
if (action === 'move') {
|
|
178
|
-
return ask({
|
|
179
|
-
reason: 'workbench_panels move reorders the panel',
|
|
180
|
-
generic: {
|
|
181
|
-
en: `The agent wants to reorder this project's panel (put ${what()} first). Allow?`,
|
|
182
|
-
zh: `agent 想调整这个项目面板的顺序(把 ${what()} 放前面)。允许吗?`,
|
|
183
|
-
},
|
|
184
|
-
question: {
|
|
185
|
-
en: `it reorders this panel (${what()} goes first). Allow?`,
|
|
186
|
-
zh: `会调整这个项目面板的顺序(${what()}放前面)。允许吗?`,
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
if (action === 'reset') {
|
|
191
|
-
return ask({
|
|
192
|
-
reason: 'workbench_panels reset clears the panel',
|
|
193
|
-
generic: {
|
|
194
|
-
en: "The agent wants to clear this project's panel (every module goes offline here). Allow?",
|
|
195
|
-
zh: 'agent 想清空这个项目的面板(所有模块在这个项目里都下线)。允许吗?',
|
|
196
|
-
},
|
|
197
|
-
question: {
|
|
198
|
-
en: 'it clears this panel (every module goes offline here). Allow?',
|
|
199
|
-
zh: '会清空这个项目的面板(所有模块在这个项目里都下线)。允许吗?',
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
return ask({
|
|
204
|
-
reason: `workbench_panels ${action ?? '(unknown action)'} changes the user's panel`,
|
|
205
|
-
generic: {
|
|
206
|
-
en: `The agent wants to add ${what()} to this project's panel. Allow?`,
|
|
207
|
-
zh: `agent 想把 ${what()} 加到**这个项目**的面板上。允许吗?`,
|
|
208
|
-
},
|
|
209
|
-
question: {
|
|
210
|
-
en: `it adds ${what()} to this project's panel. Allow?`,
|
|
211
|
-
zh: `会把 ${what()} 加到**这个项目**的面板上。允许吗?`,
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
59
|
}
|
|
215
60
|
|
|
216
61
|
export async function approvalFor(
|
|
217
62
|
call: ApprovalCall,
|
|
218
63
|
context: ApprovalContext = {},
|
|
219
64
|
): Promise<AskDecision | undefined> {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
65
|
+
if (context.writes !== true) return undefined;
|
|
66
|
+
|
|
67
|
+
let verdict: ApprovalVerdict | undefined;
|
|
68
|
+
try {
|
|
69
|
+
verdict = context.policy?.(call);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return {
|
|
72
|
+
kind: 'deny',
|
|
73
|
+
reason: `问"要不要审批"的时候套件的策略抛了错,已经停下:${messageOf(error)}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (verdict?.kind === 'read') return undefined;
|
|
78
|
+
|
|
79
|
+
if (verdict?.kind !== 'ask') {
|
|
224
80
|
return {
|
|
225
81
|
kind: 'deny',
|
|
226
82
|
reason:
|
|
227
|
-
`${call.name ?? '这把工具'}
|
|
228
|
-
'
|
|
83
|
+
`${call.name ?? '这把工具'} 在清单里声明会写东西,但它的套件没认领这次调用 —— 停下:` +
|
|
84
|
+
'在套件的 needsApproval 里补一条审批(含"这次会发生什么"),别让写静默过去',
|
|
229
85
|
};
|
|
230
86
|
}
|
|
231
|
-
const { spec } = effect;
|
|
232
87
|
|
|
233
88
|
let facts: ApprovalFacts | undefined;
|
|
234
89
|
try {
|
|
235
90
|
facts = await context.detailOf?.(call);
|
|
236
91
|
} catch (error) {
|
|
237
|
-
|
|
238
|
-
return { kind: 'deny', reason: `拿不到"这次会发生什么",已经停下:${why}` };
|
|
92
|
+
return { kind: 'deny', reason: `拿不到"这次会发生什么",已经停下:${messageOf(error)}` };
|
|
239
93
|
}
|
|
240
94
|
if (facts !== undefined && 'blocked' in facts) return { kind: 'deny', reason: facts.blocked };
|
|
241
95
|
|
|
242
|
-
return { kind: 'ask', reason:
|
|
96
|
+
return { kind: 'ask', reason: verdict.reason, displayReason: compose(facts, verdict) };
|
|
243
97
|
}
|
package/src/host/unit-face.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { RouteTable } from '../host/http.js';
|
|
|
4
4
|
import type { LongTaskBase, LongTaskHost, LongTaskRegistry } from '../host/long-task.js';
|
|
5
5
|
import type { ProvisionHandler } from '../host/provision.js';
|
|
6
6
|
import type { UnitCapability } from './unit-libs.js';
|
|
7
|
-
import type { ApprovalCall, ApprovalFacts } from '../host/tool-approval.js';
|
|
7
|
+
import type { ApprovalCall, ApprovalFacts, ApprovalPolicy } from '../host/tool-approval.js';
|
|
8
8
|
import type { ValueHandoff } from '../host/value-handoff.js';
|
|
9
9
|
import type { WorkspaceStore } from '../host/workspace-store.js';
|
|
10
10
|
|
|
@@ -57,6 +57,7 @@ export interface UnitMountResult {
|
|
|
57
57
|
readonly approvalDetail?:
|
|
58
58
|
| ((call: ApprovalCall) => ApprovalFacts | undefined | Promise<ApprovalFacts | undefined>)
|
|
59
59
|
| undefined;
|
|
60
|
+
readonly needsApproval?: ApprovalPolicy | undefined;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export interface UnitHostFace {
|
package/src/i18n/en.ts
CHANGED
|
@@ -49,6 +49,7 @@ export const en: Record<keyof WorkbenchMessages, string> = {
|
|
|
49
49
|
'panels.enabled': 'enabled in this project',
|
|
50
50
|
'panels.disabled': 'not enabled in this project',
|
|
51
51
|
'panels.choose': 'Choose modules',
|
|
52
|
+
'panels.buildModule': 'Build a module',
|
|
52
53
|
'panels.failed': 'Could not save the change. Try again.',
|
|
53
54
|
'panels.hintEffect': 'Disabling = the agent cannot reach that data',
|
|
54
55
|
'panels.dragHint': 'Drag to reorder',
|
package/src/i18n/zh.ts
CHANGED