@p-dsh-market/graph-job-orchestrator 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -3
- package/lib/capabilities.js +12 -4
- package/lib/client.js +128 -38
- package/lib/executors.js +74 -21
- package/lib/graph-schema.js +13 -8
- package/lib/index.js +271 -11
- package/lib/orchestrator.js +56 -4
- package/lib/validator.js +8 -6
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- `%LOCALAPPDATA%/dsh-desktop/plugin-data/graph-job-orchestrator/` 下的原子 JSON 与 append-only JSONL 存储;
|
|
7
7
|
- DSH in-process Subagent executor、merge barrier、read 并行/write 串行、失败暂停、有限 transport/rate-limit retry、取消和终止;
|
|
8
8
|
- `artifactRefs` workspace-relative 校验、插件 Skill/Graph Job 递归保护、`/graphjob` 命令、Web API、SSE 和任务图编辑面板;
|
|
9
|
-
- Codex provider 的能力发现、model/reasoningEffort 快照与 capability mismatch
|
|
9
|
+
- Codex provider 的能力发现、model/reasoningEffort 快照与 capability mismatch 错误;官方 `subagent-codex` 按 one-shot 运行,不会误走 DSH continuable 路径。
|
|
10
10
|
|
|
11
11
|
运行时必须先安装并注册 Codex Subagent provider,阶段 4 才会显示 Codex executor 可用;没有 provider 时不会伪装成可运行,而是在能力快照和运行错误中明确说明。当前仓库的本地 runtime 只有 DSH `spawn/fork` provider,因此不会在安装插件时自动修改 profile。
|
|
12
12
|
|
|
@@ -18,6 +18,26 @@ dsh plugin --profile <name> add @deepseek-ai/dsh-subagent-codex
|
|
|
18
18
|
|
|
19
19
|
插件只负责发现和校验 provider,不会代替用户安装 Bundle、登录 Codex 或修改 Codex 原生权限配置。
|
|
20
20
|
|
|
21
|
+
官方 Codex Bundle 的 `model` 是 provider 实例级静态配置,不是一次调用时的动态参数。若希望在 Graph Job 设置中选择多个 Codex 模型,可在目标 Profile 的 `cordis.patch.yml` 挂载多个命名实例,例如:
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
- insert:
|
|
25
|
+
- id: graph-job-codex-luna
|
|
26
|
+
name: '@deepseek-ai/dsh-subagent-codex'
|
|
27
|
+
config:
|
|
28
|
+
providerName: codex-luna
|
|
29
|
+
model: gpt-5.6-luna
|
|
30
|
+
permissionMode: never
|
|
31
|
+
- id: graph-job-codex-sol
|
|
32
|
+
name: '@deepseek-ai/dsh-subagent-codex'
|
|
33
|
+
config:
|
|
34
|
+
providerName: codex-sol
|
|
35
|
+
model: gpt-5.6-sol
|
|
36
|
+
permissionMode: never
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Graph Job 会从 runtime capability snapshot 读取这些 provider/model;`reasoningEffort` 只有 Codex provider 明确公布可选值时才显示和发送。
|
|
40
|
+
|
|
21
41
|
主要端点:
|
|
22
42
|
|
|
23
43
|
- `GET /graph-job-orchestrator/capabilities`
|
|
@@ -29,12 +49,13 @@ dsh plugin --profile <name> add @deepseek-ai/dsh-subagent-codex
|
|
|
29
49
|
- `GET /graph-job-orchestrator/templates/previews/:previewId`
|
|
30
50
|
- `POST /graph-job-orchestrator/templates/preview|confirm|bind`
|
|
31
51
|
- `GET/POST /graph-job-orchestrator/subagents`
|
|
52
|
+
- `POST /graph-job-orchestrator/planner/decision`
|
|
32
53
|
- `GET /graph-job-orchestrator/runs/:runId/events`
|
|
33
54
|
- `POST /graph-job-orchestrator/runs/:runId/retry|cancel|terminate`
|
|
34
55
|
|
|
35
|
-
带目标的 `/graphjob <目标>` 会向当前主对话排入一次 Planner follow-up;空 `/graphjob`
|
|
56
|
+
带目标的 `/graphjob <目标>` 会向当前主对话排入一次 Planner follow-up;空 `/graphjob` 只打开或创建空图。首次打开时生成的空手工占位图不会阻挡第一份模型草案;真正编辑过的手工锁定图会在任务图面板要求选择 `saveAs` 或 `overwrite`,选择通过 `/planner/decision` 提交,不会隐式改写当前模板。Planner 只接受当前会话的 roster、Graph JSON Schema 和受限 patch;候选图必须先 preview,再由用户确认。运行时输出严格投影为 `text` 和 `artifactRefs`,子会话 ID 只保存在运行状态和事件中。
|
|
36
57
|
|
|
37
|
-
`POST /graph-job-orchestrator/subagents`
|
|
58
|
+
`POST /graph-job-orchestrator/subagents` 按所选 Agent Profile 创建 Subagent。DSH provider 创建可继续对话的子会话;官方 Codex provider 使用 one-shot `start`,等待单轮结果后直接显示最终正文和脱敏诊断,不创建可继续的 Codex 会话。官方 provider 的 model、权限、MCP 和 Skills 由 Profile Bundle/原生 Codex 配置决定;只有 provider 明确公布动态能力时才允许按 Profile 传入对应字段。该入口默认过滤 `skill` 和 Graph Job 管理工具。Host 和客户端会输出 `[graph-job-orchestrator]` 前缀的脱敏诊断日志,只记录会话短标识、长度、状态、耗时和错误代码,不记录用户任务正文或 token。
|
|
38
59
|
|
|
39
60
|
模板 manifest 支持 `scope: "workspace" | "global"`,默认是当前 workspace;workspace 模板不会出现在其他工作区的模板列表中。切换模板会创建新的 Graph Instance,不会把可变模板文件直接绑定为活动 Graph。
|
|
40
61
|
|
package/lib/capabilities.js
CHANGED
|
@@ -105,16 +105,23 @@ function providerCapabilities(item) {
|
|
|
105
105
|
persona: Boolean(value.persona),
|
|
106
106
|
permissionMode: Boolean(value.permissionMode || value.permissionModes),
|
|
107
107
|
permissionModes: stringArray(value.permissionModes),
|
|
108
|
-
reasoningEfforts: stringArray(value.reasoningEfforts || value.reasoningEffort)
|
|
108
|
+
reasoningEfforts: stringArray(value.reasoningEfforts || value.reasoningEffort),
|
|
109
|
+
dynamicModel: Boolean(value.dynamicModel || value.modelOverride),
|
|
110
|
+
dynamicReasoning: Boolean(value.dynamicReasoning || value.reasoningEffortOverride)
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
function normalizeProvider(item) {
|
|
113
115
|
if (typeof item === 'string') return { name: item, capabilities: {} }
|
|
116
|
+
const config = item?.config && typeof item.config === 'object' ? item.config : {}
|
|
117
|
+
const staticModel = String(item?.staticModel || item?.model || config.model || '').trim()
|
|
118
|
+
const staticPermissionMode = String(item?.staticPermissionMode || config.permissionMode || '').trim()
|
|
114
119
|
return {
|
|
115
120
|
name: String(item?.name || item?.id || item?.providerName || '').trim(),
|
|
116
121
|
inheritsParentContext: item?.inheritsParentContext === true,
|
|
117
|
-
models: asArray(item?.models || item?.capabilities?.models).map((model) => typeof model === 'string' ? model : String(model?.id || model?.name || '').trim()).filter(Boolean),
|
|
122
|
+
models: asArray(item?.models || item?.capabilities?.models || (staticModel ? [staticModel] : [])).map((model) => typeof model === 'string' ? model : String(model?.id || model?.name || '').trim()).filter(Boolean),
|
|
123
|
+
...(staticModel ? { staticModel } : {}),
|
|
124
|
+
...(staticPermissionMode ? { staticPermissionMode } : {}),
|
|
118
125
|
capabilities: providerCapabilities(item)
|
|
119
126
|
}
|
|
120
127
|
}
|
|
@@ -171,8 +178,9 @@ export async function buildCapabilityCatalog(options = {}) {
|
|
|
171
178
|
const models = await discoverModels(options.llm)
|
|
172
179
|
const codexProviders = subagentProviders.filter((item) => /codex/i.test(item.name))
|
|
173
180
|
const codexReasoning = [...new Set(codexProviders.flatMap((item) => item.capabilities.reasoningEfforts))]
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
// Reasoning is an executor/provider capability. LLM model metadata from an
|
|
182
|
+
// unrelated DSH adapter must never make the Codex selector appear supported.
|
|
183
|
+
const reasoningEfforts = [...new Set(codexReasoning)]
|
|
176
184
|
const catalog = {
|
|
177
185
|
schemaVersion: GRAPH_SCHEMA_VERSION,
|
|
178
186
|
capturedAt: Date.now(),
|
package/lib/client.js
CHANGED
|
@@ -12,6 +12,7 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
var PLUGIN_ID = '@p-dsh-market/graph-job-orchestrator'
|
|
13
13
|
var BASE = '/graph-job-orchestrator'
|
|
14
14
|
var DESKTOP_RPC_METHOD = 'graphJobOrchestrator.open'
|
|
15
|
+
var DESKTOP_SUBAGENT_RPC_METHOD = 'graphJobOrchestrator.openSubagent'
|
|
15
16
|
var sessionsService = null
|
|
16
17
|
var plannerWaitUntil = {}
|
|
17
18
|
var subagentOverlayStore = { open: false, version: 0, initialPrompt: '' }
|
|
@@ -86,7 +87,10 @@ window.__ModuleLoader__.load({
|
|
|
86
87
|
if (!response.ok || body.ok === false) {
|
|
87
88
|
reportedFailure = true
|
|
88
89
|
clientDiagnostic('request.failed', { method: method, path: path, status: response.status, durationMs: Date.now() - startedAt, code: body.code || 'HTTP_ERROR' })
|
|
89
|
-
|
|
90
|
+
var requestError = new Error(body.error || 'Graph Job 请求失败。')
|
|
91
|
+
requestError.code = body.code || 'HTTP_ERROR'
|
|
92
|
+
requestError.status = response.status
|
|
93
|
+
throw requestError
|
|
90
94
|
}
|
|
91
95
|
clientDiagnostic('request.done', { method: method, path: path, status: response.status, durationMs: Date.now() - startedAt })
|
|
92
96
|
return body
|
|
@@ -246,7 +250,7 @@ window.__ModuleLoader__.load({
|
|
|
246
250
|
|
|
247
251
|
function GraphJobView(props) {
|
|
248
252
|
var sessionId = useSessionId(props)
|
|
249
|
-
var state = React.useState({ graph: null, profiles: [], capabilities: null, templates: [], templateId: '', templateScope: 'workspace', run: null, preview: null, templatePreview: null, loading: true, planning: false, saving: false, error: '', message: '', selectedId: '', connectFrom: '' })
|
|
253
|
+
var state = React.useState({ graph: null, profiles: [], capabilities: null, templates: [], templateId: '', templateScope: 'workspace', templateDecisionMode: 'saveAs', templateName: '', planner: null, run: null, preview: null, templatePreview: null, loading: true, planning: false, saving: false, error: '', message: '', selectedId: '', connectFrom: '' })
|
|
250
254
|
var store = state[0]
|
|
251
255
|
var setStore = state[1]
|
|
252
256
|
function patch(next) { setStore(function (current) { return Object.assign({}, current, next) }) }
|
|
@@ -257,12 +261,13 @@ window.__ModuleLoader__.load({
|
|
|
257
261
|
Promise.all([request('/graphs?sessionId=' + encodeURIComponent(sessionId)), request('/profiles'), request('/capabilities'), request('/templates?sessionId=' + encodeURIComponent(sessionId))]).then(function (values) {
|
|
258
262
|
var body = values[0]
|
|
259
263
|
var planner = body.planner || null
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
if (planner && ['queued', 'running'].indexOf(planner.state) >= 0 && !plannerWaitUntil[sessionId]) plannerWaitUntil[sessionId] = Date.now() + 90000
|
|
265
|
+
var waiting = Boolean((plannerWaitUntil[sessionId] || 0) > Date.now())
|
|
266
|
+
var plannerNeedsDecision = planner && planner.state === 'needs-decision'
|
|
267
|
+
var freshPreview = plannerNeedsDecision ? null : (planner && planner.state === 'ready' && body.preview ? body.preview : (waiting ? store.preview : body.preview))
|
|
268
|
+
var plannerFailed = planner && planner.state === 'failed'
|
|
269
|
+
patch({ graph: freshPreview && freshPreview.graph ? layoutGraph(freshPreview.graph) : (body.graph ? layoutGraph(body.graph) : null), preview: freshPreview || null, templatePreview: body.templatePreview || null, planner: planner, run: body.binding && body.binding.activeRunId ? null : null, profiles: values[1].profiles || [], capabilities: values[2].capabilities || null, templates: values[3].templates || [], planning: waiting && !plannerFailed && !plannerNeedsDecision, loading: false, error: plannerFailed || plannerNeedsDecision ? planner.error || '模型修订需要先选择 saveAs 或 overwrite 模板。' : '', message: planner && planner.state === 'ready' && body.preview ? 'Planner 草案已生成;请检查 DAG 后确认 revision。' : plannerNeedsDecision ? '手工 Graph 已锁定;请选择模型候选的模板保存方式。' : '' })
|
|
270
|
+
if ((planner && (planner.state === 'ready' || planner.state === 'failed' || planner.state === 'needs-decision')) || !waiting) delete plannerWaitUntil[sessionId]
|
|
266
271
|
else setTimeout(load, 1000)
|
|
267
272
|
if (body.binding && body.binding.activeRunId) request('/runs/' + encodeURIComponent(body.binding.activeRunId)).then(function (runBody) { patch({ run: runBody.run }) }).catch(function () {})
|
|
268
273
|
}).catch(function (error) { patch({ loading: false, error: error.message || String(error) }) })
|
|
@@ -273,14 +278,27 @@ window.__ModuleLoader__.load({
|
|
|
273
278
|
var detail = event && event.detail || {}
|
|
274
279
|
if (detail.sessionId && detail.sessionId !== sessionId) return
|
|
275
280
|
plannerWaitUntil[sessionId] = Date.now() + 90000
|
|
276
|
-
patch({ preview: null, planning: true, error: '', message: '已收到 /graphjob 目标,正在生成 Graph Job 草案…' })
|
|
281
|
+
patch({ preview: null, templatePreview: null, planner: { state: 'queued' }, planning: true, error: '', message: '已收到 /graphjob 目标,正在生成 Graph Job 草案…' })
|
|
277
282
|
load()
|
|
278
283
|
}
|
|
279
284
|
window.addEventListener('gjo:planner-start', onPlannerStart)
|
|
280
285
|
return function () { window.removeEventListener('gjo:planner-start', onPlannerStart) }
|
|
281
286
|
}, [sessionId])
|
|
282
287
|
|
|
283
|
-
|
|
288
|
+
function submitPlannerDecision() {
|
|
289
|
+
var mode = store.templateDecisionMode || 'saveAs'
|
|
290
|
+
if (mode === 'overwrite' && !store.templateId) { patch({ error: '覆盖模板前必须选择一个模板。' }); return }
|
|
291
|
+
plannerWaitUntil[sessionId] = Date.now() + 90000
|
|
292
|
+
patch({ planning: true, error: '', message: '正在按所选模板方式生成候选…' })
|
|
293
|
+
request('/planner/decision', { method: 'POST', body: { sessionId: sessionId, mode: mode, templateId: mode === 'overwrite' ? store.templateId : '', templateScope: store.templateScope || 'workspace', templateName: store.templateName || (store.graph && store.graph.goal) || 'Graph Job 模板' } }).then(function (body) {
|
|
294
|
+
var next = { planning: body.state === 'queued', error: '', message: body.state === 'queued' ? '已提交模板选择,正在生成候选…' : '候选模板已生成;请确认后保存。' }
|
|
295
|
+
if (body.templatePreview) next.templatePreview = body.templatePreview
|
|
296
|
+
if (body.state === 'ready') { next.planner = Object.assign({}, store.planner || {}, { state: 'ready', previewId: body.preview && body.preview.previewId || '' }); next.planning = false }
|
|
297
|
+
patch(next)
|
|
298
|
+
if (body.state === 'queued') setTimeout(load, 600)
|
|
299
|
+
}).catch(function (error) { delete plannerWaitUntil[sessionId]; patch({ planning: false, error: error.message || String(error) }) })
|
|
300
|
+
}
|
|
301
|
+
function save() {
|
|
284
302
|
if (!store.graph || store.saving) return
|
|
285
303
|
patch({ saving: true, error: '', message: '' })
|
|
286
304
|
request('/graphs/' + encodeURIComponent(store.graph.graphId), { method: 'PATCH', body: { sessionId: sessionId, graph: store.graph } }).then(function (body) {
|
|
@@ -340,13 +358,23 @@ window.__ModuleLoader__.load({
|
|
|
340
358
|
}
|
|
341
359
|
function removeEdge(edge) { patch({ graph: Object.assign({}, clone(store.graph), { edges: (store.graph.edges || []).filter(function (item) { return item.from !== edge.from || item.to !== edge.to }) }), preview: null }) }
|
|
342
360
|
function updateGoal(event) { patch({ graph: Object.assign({}, store.graph, { goal: event.target.value }), preview: null }) }
|
|
343
|
-
|
|
361
|
+
var selected = nodeById(store.graph, store.selectedId)
|
|
362
|
+
var plannerNeedsDecision = store.planner && store.planner.state === 'needs-decision'
|
|
344
363
|
if (store.loading) return React.createElement('div', { className: 'gjo-shell gjo-empty' }, '正在加载 Graph Job…')
|
|
345
364
|
if (!store.graph) return React.createElement('div', { className: 'gjo-shell gjo-empty' }, [React.createElement('div', { key: 'icon', className: 'gjo-icon' }, '◇'), React.createElement('h2', { key: 'h' }, '当前会话还没有 Graph Job'), React.createElement('p', { key: 'p' }, '从空白任务图开始,或让当前对话中的 Planner 先提出一个受限草案。'), React.createElement('button', { key: 'create', type: 'button', className: 'gjo-primary', onClick: newGraph }, '创建任务图')])
|
|
346
365
|
return React.createElement('div', { className: 'gjo-shell' }, [
|
|
347
366
|
React.createElement('header', { key: 'header', className: 'gjo-header' }, [React.createElement('div', { key: 'copy' }, [React.createElement('div', { key: 'eyebrow', className: 'gjo-eyebrow' }, 'GRAPH JOB / SESSION ' + sessionId.slice(0, 12)), React.createElement('h2', { key: 'title' }, '多 Subagent 任务图'), React.createElement('p', { key: 'sub' }, 'Revision ' + store.graph.revision + ' · ' + (store.graph.manualLock ? '手工锁定' : 'Planner 草案')), React.createElement('div', { key: 'actions', className: 'gjo-header-actions' }, [React.createElement('button', { key: 'new', type: 'button', className: 'gjo-secondary', onClick: newGraph }, '新建'), React.createElement('button', { key: 'refresh', type: 'button', className: 'gjo-secondary', onClick: load }, '刷新'), React.createElement('button', { key: 'save', type: 'button', className: 'gjo-secondary', disabled: store.saving, onClick: save }, store.saving ? '保存中…' : '保存 revision'), React.createElement('button', { key: 'template', type: 'button', className: 'gjo-secondary', onClick: function () { previewTemplate('saveAs') } }, '另存模板'), React.createElement('select', { key: 'scope', className: 'gjo-template-select', value: store.templateScope || 'workspace', onChange: function (event) { patch({ templateScope: event.target.value }) } }, [React.createElement('option', { key: 'workspace', value: 'workspace' }, 'workspace 模板'), React.createElement('option', { key: 'global', value: 'global' }, 'global 模板')]), store.templates.length ? React.createElement('select', { key: 'template-select', className: 'gjo-template-select', value: store.templateId || '', onChange: function (event) { var id = event.target.value; var selected = (store.templates || []).filter(function (template) { return template.id === id })[0]; patch({ templateId: id, templateScope: selected && selected.scope || 'workspace' }) } }, [React.createElement('option', { key: 'empty', value: '' }, '选择覆盖模板'), store.templates.map(function (template) { return React.createElement('option', { key: template.id, value: template.id }, (template.name || template.id) + ' · ' + (template.scope || 'workspace')) })]) : null, React.createElement('button', { key: 'bind', type: 'button', className: 'gjo-secondary', disabled: !store.templateId, onClick: bindTemplate }, '切换到模板'), React.createElement('button', { key: 'overwrite', type: 'button', className: 'gjo-secondary', disabled: !store.templateId, onClick: function () { previewTemplate('overwrite') } }, '覆盖模板'), React.createElement('button', { key: 'preview', type: 'button', className: 'gjo-primary', onClick: preview }, '预览 DAG')])])]),
|
|
348
367
|
React.createElement('div', { key: 'goal', className: 'gjo-goal-bar' }, [React.createElement('label', { key: 'label' }, '目标'), React.createElement('textarea', { key: 'input', rows: 2, value: store.graph.goal || '', onChange: updateGoal, placeholder: '这个 Graph 要完成什么?' }), React.createElement('div', { key: 'limit', className: 'gjo-limit-copy' }, 'maxParallel ' + (store.graph.limits && store.graph.limits.maxParallel || 4) + ' · write 串行 · 失败暂停')]),
|
|
349
|
-
|
|
368
|
+
store.error ? React.createElement('div', { key: 'error', className: 'gjo-error' }, store.error) : null,
|
|
369
|
+
plannerNeedsDecision ? React.createElement('section', { key: 'planner-decision', className: 'gjo-decision-panel' }, [
|
|
370
|
+
React.createElement('strong', { key: 'title' }, '选择模型候选的保存方式'),
|
|
371
|
+
React.createElement('p', { key: 'copy' }, '当前会话的 Graph 已被手工编辑锁定。模型只能生成候选,必须另存为新模板或覆盖已有模板。'),
|
|
372
|
+
React.createElement('label', { key: 'mode' }, ['保存方式', React.createElement('select', { key: 'input', value: store.templateDecisionMode || 'saveAs', onChange: function (event) { patch({ templateDecisionMode: event.target.value }) } }, [React.createElement('option', { key: 'saveAs', value: 'saveAs' }, 'saveAs:另存为新模板'), React.createElement('option', { key: 'overwrite', value: 'overwrite' }, 'overwrite:覆盖已有模板')])]),
|
|
373
|
+
store.templateDecisionMode === 'overwrite' ? React.createElement('label', { key: 'template' }, ['目标模板', React.createElement('select', { key: 'input', value: store.templateId || '', onChange: function (event) { patch({ templateId: event.target.value }) } }, [React.createElement('option', { key: 'empty', value: '' }, '请选择模板'), (store.templates || []).map(function (template) { return React.createElement('option', { key: template.id, value: template.id }, (template.name || template.id) + ' · ' + (template.scope || 'workspace')) })])]) : null,
|
|
374
|
+
React.createElement('label', { key: 'name' }, ['模板名称', React.createElement('input', { key: 'input', value: store.templateName || (store.graph && store.graph.goal) || '', onChange: function (event) { patch({ templateName: event.target.value }) } })]),
|
|
375
|
+
React.createElement('label', { key: 'scope' }, ['模板作用域', React.createElement('select', { key: 'input', value: store.templateScope || 'workspace', onChange: function (event) { patch({ templateScope: event.target.value }) } }, [React.createElement('option', { key: 'workspace', value: 'workspace' }, 'workspace'), React.createElement('option', { key: 'global', value: 'global' }, 'global')])]),
|
|
376
|
+
React.createElement('button', { key: 'submit', type: 'button', className: 'gjo-primary', disabled: store.planning, onClick: submitPlannerDecision }, store.planning ? '生成中…' : '按选择生成候选')
|
|
377
|
+
]) : null,
|
|
350
378
|
store.message ? React.createElement('div', { key: 'message', className: 'gjo-message' }, store.message) : null,
|
|
351
379
|
store.planning ? React.createElement('div', { key: 'planning', className: 'gjo-message' }, 'Planner 正在生成草案;完成后会显示预览。') : null,
|
|
352
380
|
store.preview ? React.createElement(ValidationPanel, { key: 'validation', validation: store.preview.validation }) : null,
|
|
@@ -384,21 +412,28 @@ window.__ModuleLoader__.load({
|
|
|
384
412
|
return Object.assign({}, value, { draft: draft })
|
|
385
413
|
})
|
|
386
414
|
}
|
|
415
|
+
function providerEntry(name) {
|
|
416
|
+
return ((store.capabilities && store.capabilities.subagentProviders) || []).filter(function (item) { return item.name === name })[0] || null
|
|
417
|
+
}
|
|
387
418
|
function listValues(value) {
|
|
388
419
|
return Array.from(new Set((value || []).map(function (item) { return String(item || '').trim() }).filter(Boolean)))
|
|
389
420
|
}
|
|
390
421
|
function providersFor(profile) {
|
|
391
422
|
var capabilities = store.capabilities || {}
|
|
392
|
-
var providers =
|
|
423
|
+
var providers = profile.executor === 'codex'
|
|
424
|
+
? (capabilities.subagentProviders || []).filter(function (item) { return /codex/i.test(item.name || '') }).map(function (item) { return item.name }).filter(Boolean)
|
|
425
|
+
: (capabilities.models || []).map(function (item) { return typeof item === 'string' ? item : item.provider }).filter(Boolean)
|
|
393
426
|
if (profile.executor === 'codex') providers = listValues((capabilities.executors && capabilities.executors.codex && capabilities.executors.codex.providers) || providers)
|
|
394
|
-
else if (capabilities.
|
|
427
|
+
else if (capabilities.defaultModel && capabilities.defaultModel.provider) providers.unshift(capabilities.defaultModel.provider)
|
|
395
428
|
if (profile.provider) providers.unshift(profile.provider)
|
|
396
429
|
return listValues(providers)
|
|
397
430
|
}
|
|
398
431
|
function modelsFor(profile) {
|
|
399
|
-
var values = (store.capabilities && store.capabilities.models || []).filter(function (item) { return typeof item === 'string' || !profile.provider || item.provider === profile.provider }).map(function (item) { return typeof item === 'string' ? item : item.model })
|
|
400
432
|
var provider = (store.capabilities && store.capabilities.subagentProviders || []).filter(function (item) { return item.name === profile.provider })[0]
|
|
401
|
-
values =
|
|
433
|
+
var values = profile.executor === 'codex'
|
|
434
|
+
? (provider && provider.models || [])
|
|
435
|
+
: (store.capabilities && store.capabilities.models || []).filter(function (item) { return typeof item === 'string' || !profile.provider || item.provider === profile.provider }).map(function (item) { return typeof item === 'string' ? item : item.model })
|
|
436
|
+
values = values.concat(provider && provider.staticModel || [])
|
|
402
437
|
if (profile.model) values.unshift(profile.model)
|
|
403
438
|
return listValues(values)
|
|
404
439
|
}
|
|
@@ -408,22 +443,58 @@ window.__ModuleLoader__.load({
|
|
|
408
443
|
var values = provider && provider.capabilities && provider.capabilities.reasoningEfforts || (store.capabilities && store.capabilities.executors && store.capabilities.executors.codex && store.capabilities.executors.codex.reasoningEfforts) || []
|
|
409
444
|
return listValues(values)
|
|
410
445
|
}
|
|
446
|
+
function changeProvider(index, providerName) {
|
|
447
|
+
var profile = (store.draft || [])[index] || {}
|
|
448
|
+
var next = { provider: providerName }
|
|
449
|
+
if (profile.executor === 'codex') {
|
|
450
|
+
var provider = providerEntry(providerName)
|
|
451
|
+
var models = listValues(provider && (provider.models || []).concat(provider.staticModel || []))
|
|
452
|
+
next.model = models.length ? models[0] : ''
|
|
453
|
+
next.reasoningEffort = ''
|
|
454
|
+
}
|
|
455
|
+
update(index, next)
|
|
456
|
+
}
|
|
411
457
|
function changeExecutor(index, executor) {
|
|
412
458
|
var profile = (store.draft || [])[index] || {}
|
|
413
|
-
var next = Object.assign({}, profile, { executor: executor,
|
|
459
|
+
var next = Object.assign({}, profile, { executor: executor, provider: profile.provider || '', model: '', reasoningEffort: '' })
|
|
414
460
|
var providers = providersFor(next)
|
|
461
|
+
if (executor === 'codex') {
|
|
462
|
+
var providerEntries = (store.capabilities && store.capabilities.subagentProviders || []).filter(function (item) { return /codex/i.test(item.name || '') })
|
|
463
|
+
var configured = providerEntries.filter(function (item) { return (item.models && item.models.length) || item.staticModel })[0]
|
|
464
|
+
if (configured) next.provider = configured.name
|
|
465
|
+
} else {
|
|
466
|
+
var dshProviders = providers
|
|
467
|
+
if (dshProviders.length && dshProviders.indexOf(next.provider) < 0) next.provider = dshProviders[0]
|
|
468
|
+
}
|
|
415
469
|
if (providers.length && providers.indexOf(next.provider) < 0) next.provider = providers[0]
|
|
416
470
|
var models = modelsFor(next)
|
|
417
471
|
if (models.length && models.indexOf(next.model) < 0) next.model = models[0]
|
|
418
472
|
update(index, next)
|
|
419
473
|
}
|
|
474
|
+
function newProfile() {
|
|
475
|
+
setStore(function (value) {
|
|
476
|
+
var draft = clone(value.draft || [])
|
|
477
|
+
var ids = {}
|
|
478
|
+
draft.forEach(function (profile) { ids[profile.id] = true })
|
|
479
|
+
var index = draft.length + 1
|
|
480
|
+
var id = 'agent-' + index
|
|
481
|
+
while (ids[id]) { index += 1; id = 'agent-' + index }
|
|
482
|
+
var capabilities = value.capabilities || {}
|
|
483
|
+
var codexEntries = (capabilities.subagentProviders || []).filter(function (item) { return /codex/i.test(item.name || '') })
|
|
484
|
+
var codex = codexEntries.filter(function (item) { return (item.models && item.models.length) || item.staticModel })[0] || codexEntries[0]
|
|
485
|
+
var useCodex = Boolean(capabilities.executors && capabilities.executors.codex && capabilities.executors.codex.available && codex)
|
|
486
|
+
var model = useCodex ? listValues((codex.models || []).concat(codex.staticModel || []))[0] || '' : ''
|
|
487
|
+
draft.push({ id: id, name: useCodex ? 'Codex Agent ' + index : 'DSH Agent ' + index, executor: useCodex ? 'codex' : 'dsh', provider: useCodex ? codex.name : '', model: model, reasoningEffort: '', persona: '', permissionMode: 'default', maxOutputTokens: 4096, capabilities: { tools: [], skills: [] }, enabled: true })
|
|
488
|
+
return Object.assign({}, value, { draft: draft, message: useCodex ? '已新增 Codex Profile,请检查 Model 后保存。' : '已新增 DSH Profile,请检查配置后保存。' })
|
|
489
|
+
})
|
|
490
|
+
}
|
|
420
491
|
function save() {
|
|
421
492
|
setStore(function (value) { return Object.assign({}, value, { saving: true, error: '' }) })
|
|
422
493
|
request('/profiles', { method: 'PUT', body: { profiles: store.draft } }).then(function (body) { setStore(function (value) { return Object.assign({}, value, { profiles: body.profiles, draft: clone(body.profiles), saving: false, message: 'Agent Profile 已保存。' }) }) }).catch(function (error) { setStore(function (value) { return Object.assign({}, value, { saving: false, error: error.message || String(error) }) }) })
|
|
423
494
|
}
|
|
424
495
|
if (store.loading) return React.createElement('div', { className: 'gjo-settings gjo-empty' }, '正在读取 Agent Profile…')
|
|
425
496
|
return React.createElement('div', { className: 'gjo-settings' }, [
|
|
426
|
-
React.createElement('div', { key: 'head', className: 'gjo-settings-head' }, [React.createElement('div', { key: 'copy' }, [React.createElement('h2', { key: 'h' }, 'Graph Job Agent Profiles'), React.createElement('p', { key: 'p' }, 'revision 创建时会快照 Provider/Model;普通 DSH 不显示 reasoningEffort,Codex 只显示能力快照公布的值。')]), React.createElement('button', { key: 'save', type: 'button', className: 'gjo-primary', disabled: store.saving, onClick: save }, store.saving ? '保存中…' : '保存')]),
|
|
497
|
+
React.createElement('div', { key: 'head', className: 'gjo-settings-head' }, [React.createElement('div', { key: 'copy' }, [React.createElement('h2', { key: 'h' }, 'Graph Job Agent Profiles'), React.createElement('p', { key: 'p' }, 'revision 创建时会快照 Provider/Model;普通 DSH 不显示 reasoningEffort,Codex 只显示能力快照公布的值。')]), React.createElement('div', { key: 'actions', className: 'gjo-header-actions' }, [React.createElement('button', { key: 'new', type: 'button', className: 'gjo-secondary', onClick: newProfile }, '新增 Profile'), React.createElement('button', { key: 'save', type: 'button', className: 'gjo-primary', disabled: store.saving, onClick: save }, store.saving ? '保存中…' : '保存')])]),
|
|
427
498
|
store.error ? React.createElement('div', { key: 'error', className: 'gjo-error' }, store.error) : null,
|
|
428
499
|
store.message ? React.createElement('div', { key: 'message', className: 'gjo-message' }, store.message) : null,
|
|
429
500
|
React.createElement('div', { key: 'list', className: 'gjo-profile-list' }, (store.draft || []).map(function (profile, index) {
|
|
@@ -433,10 +504,10 @@ window.__ModuleLoader__.load({
|
|
|
433
504
|
return React.createElement('article', { key: profile.id, className: 'gjo-profile-card' }, [
|
|
434
505
|
React.createElement('div', { key: 'top', className: 'gjo-profile-top' }, [React.createElement('strong', { key: 'name' }, profile.name || profile.id), React.createElement('span', { key: 'executor', className: 'gjo-pill' }, profile.executor)]),
|
|
435
506
|
React.createElement('label', { key: 'executor' }, ['Executor', React.createElement('select', { key: 'input', value: profile.executor || 'dsh', onChange: function (event) { changeExecutor(index, event.target.value) } }, [React.createElement('option', { key: 'dsh', value: 'dsh' }, 'DSH in-process'), React.createElement('option', { key: 'codex', value: 'codex', disabled: !(store.capabilities && store.capabilities.executors && store.capabilities.executors.codex && store.capabilities.executors.codex.available) }, 'Codex')])]),
|
|
436
|
-
React.createElement('label', { key: 'provider' }, ['Provider', providers.length ? React.createElement('select', { key: 'input', value: profile.provider || '', onChange: function (event) {
|
|
507
|
+
React.createElement('label', { key: 'provider' }, ['Provider', providers.length ? React.createElement('select', { key: 'input', value: profile.provider || '', onChange: function (event) { changeProvider(index, event.target.value) } }, [React.createElement('option', { key: 'empty', value: '' }, '选择 Provider'), providers.map(function (item) { return React.createElement('option', { key: item, value: item }, item) })]) : React.createElement('input', { key: 'input', value: profile.provider || '', placeholder: 'revision 时继承默认', onChange: function (event) { update(index, { provider: event.target.value }) } })]),
|
|
437
508
|
React.createElement('label', { key: 'model' }, ['Model', models.length ? React.createElement('select', { key: 'input', value: profile.model || '', onChange: function (event) { update(index, { model: event.target.value }) } }, [React.createElement('option', { key: 'empty', value: '' }, '选择 Model'), models.map(function (item) { return React.createElement('option', { key: item, value: item }, item) })]) : React.createElement('input', { key: 'input', value: profile.model || '', placeholder: 'revision 时继承默认', onChange: function (event) { update(index, { model: event.target.value }) } })]),
|
|
438
509
|
profile.executor === 'codex' ? React.createElement('label', { key: 'reasoning' }, ['Reasoning effort', reasoning.length ? React.createElement('select', { key: 'input', value: profile.reasoningEffort || '', onChange: function (event) { update(index, { reasoningEffort: event.target.value }) } }, [React.createElement('option', { key: 'empty', value: '' }, '不设置'), reasoning.map(function (item) { return React.createElement('option', { key: item, value: item }, item) })]) : React.createElement('span', { key: 'empty', className: 'gjo-muted' }, '当前 Codex capability 未公布可选值')]) : null,
|
|
439
|
-
|
|
510
|
+
React.createElement('label', { key: 'permission' }, ['Permission mode', React.createElement('select', { key: 'input', value: profile.permissionMode || 'default', onChange: function (event) { update(index, { permissionMode: event.target.value }) } }, [React.createElement('option', { key: 'default', value: 'default' }, 'default'), React.createElement('option', { key: 'never', value: 'never' }, 'never'), React.createElement('option', { key: 'approve', value: 'approve-for-me' }, 'approve-for-me'), React.createElement('option', { key: 'dangerous', value: 'dangerously-bypass-approvals-and-sandbox' }, 'dangerously-bypass-approvals-and-sandbox')])]),
|
|
440
511
|
React.createElement('label', { key: 'persona' }, ['Persona', React.createElement('textarea', { key: 'input', rows: 3, value: profile.persona || '', onChange: function (event) { update(index, { persona: event.target.value }) } } )]),
|
|
441
512
|
React.createElement('p', { key: 'caps', className: 'gjo-muted' }, '工具 ' + ((profile.capabilities && profile.capabilities.tools || []).join(', ') || '无') + ' · Skills ' + ((profile.capabilities && profile.capabilities.skills || []).join(', ') || '无') + (profile.executor === 'codex' ? ' · reasoningEffort 由 capability 校验' : ''))
|
|
442
513
|
])
|
|
@@ -465,19 +536,22 @@ window.__ModuleLoader__.load({
|
|
|
465
536
|
var sessions = props.sessions || sessionsService
|
|
466
537
|
var sessionId = useSessionId({ sessions: sessions })
|
|
467
538
|
var overlay = useSubagentOverlay()
|
|
468
|
-
|
|
539
|
+
var state = React.useState({ label: '新建 Subagent', prompt: overlay.initialPrompt || '', profileId: '', profiles: [], capabilities: null, loading: false, busy: false, error: '', message: '', oneShotResult: null })
|
|
469
540
|
var store = state[0]
|
|
470
541
|
var setStore = state[1]
|
|
471
542
|
React.useEffect(function () {
|
|
472
543
|
if (!overlay.open) return undefined
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
544
|
+
setStore({ label: '新建 Subagent', prompt: overlay.initialPrompt || '', profileId: '', profiles: [], capabilities: null, loading: true, busy: false, error: '', message: '', oneShotResult: null })
|
|
545
|
+
Promise.all([request('/profiles'), request('/capabilities')]).then(function (values) {
|
|
546
|
+
var body = values[0]
|
|
547
|
+
var profiles = (body.profiles || []).filter(function (profile) { return profile.enabled !== false })
|
|
548
|
+
setStore(function (value) { return Object.assign({}, value, { profiles: profiles, capabilities: values[1].capabilities || null, profileId: profiles[0] && profiles[0].id || '', loading: false }) })
|
|
549
|
+
}).catch(function (error) { setStore(function (value) { return Object.assign({}, value, { loading: false, error: error.message || String(error) }) }) })
|
|
478
550
|
return undefined
|
|
479
551
|
}, [overlay.open, overlay.version, sessionId])
|
|
480
|
-
|
|
552
|
+
function update(patch) { setStore(function (value) { return Object.assign({}, value, patch) }) }
|
|
553
|
+
var selectedProfile = (store.profiles || []).filter(function (profile) { return profile.id === store.profileId })[0] || null
|
|
554
|
+
var selectedCodex = selectedProfile && selectedProfile.executor === 'codex'
|
|
481
555
|
function openChild(child) {
|
|
482
556
|
var childId = child && (child.childId || child.id)
|
|
483
557
|
if (!childId) throw new Error('Runtime 没有返回子会话 ID。')
|
|
@@ -499,24 +573,34 @@ window.__ModuleLoader__.load({
|
|
|
499
573
|
event.preventDefault()
|
|
500
574
|
if (store.busy || !sessionId || !store.prompt.trim()) return
|
|
501
575
|
update({ busy: true, error: '', message: '' })
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
576
|
+
request('/subagents', { method: 'POST', body: { sessionId: sessionId, label: store.label, prompt: store.prompt, profileId: store.profileId } }).then(function (body) {
|
|
577
|
+
var child = body.child || {}
|
|
578
|
+
if (child.mode === 'one-shot') {
|
|
579
|
+
update({ busy: false, message: 'Codex one-shot 已完成;结果保留在这里,未创建可继续会话。', oneShotResult: child.result || null })
|
|
580
|
+
return null
|
|
581
|
+
}
|
|
582
|
+
update({ message: 'Subagent 已创建,正在打开独立会话。' })
|
|
583
|
+
return openChild(child).then(function () { setSubagentOverlayOpen(false) })
|
|
584
|
+
}).catch(function (error) { update({ busy: false, error: error.message || String(error) }) })
|
|
508
585
|
}
|
|
509
586
|
if (!overlay.open) return null
|
|
510
587
|
return React.createElement('div', { className: 'gjo-subagent-overlay', onMouseDown: function (event) { if (event.target === event.currentTarget) setSubagentOverlayOpen(false) } }, React.createElement('aside', { className: 'gjo-subagent-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': '新建 Subagent' }, [
|
|
511
588
|
React.createElement('div', { key: 'head', className: 'gjo-subagent-head' }, [React.createElement('div', { key: 'copy' }, [React.createElement('span', { key: 'eyebrow', className: 'gjo-eyebrow' }, 'SUBAGENT'), React.createElement('h2', { key: 'title' }, '新建 Subagent')]), React.createElement('button', { key: 'close', type: 'button', className: 'gjo-subagent-close', onClick: function () { setSubagentOverlayOpen(false) } }, '×')]),
|
|
512
|
-
|
|
513
|
-
|
|
589
|
+
React.createElement('p', { key: 'intro', className: 'gjo-subagent-intro' }, selectedCodex ? 'Codex 通过官方 one-shot provider 运行一次任务;它会使用 Profile Bundle/原生配置中的模型和权限,完成后在此显示结果。' : '创建一个可继续对话的独立 Agent。创建后会自动切换到它的会话,当前主会话仍保留。'),
|
|
590
|
+
store.error ? React.createElement('div', { key: 'error', className: 'gjo-error' }, store.error) : null,
|
|
591
|
+
selectedProfile ? React.createElement('div', { key: 'profile-summary', className: 'gjo-subagent-profile-summary' }, [
|
|
592
|
+
React.createElement('strong', { key: 'title' }, '当前 Profile 能力'),
|
|
593
|
+
React.createElement('span', { key: 'provider' }, 'Executor:' + (selectedProfile.executor || 'dsh') + ' · Provider:' + (selectedProfile.provider || '默认')),
|
|
594
|
+
React.createElement('span', { key: 'model' }, 'Model:' + (selectedProfile.model || (selectedCodex ? '由 Codex 原生配置决定' : '由 DSH 默认配置决定'))),
|
|
595
|
+
selectedCodex ? React.createElement('span', { key: 'reasoning' }, 'Reasoning:' + (selectedProfile.reasoningEffort || '未设置;官方 provider 未公布动态推理档次')) : null
|
|
596
|
+
]) : null,
|
|
597
|
+
store.oneShotResult ? React.createElement('div', { key: 'one-shot-result', className: 'gjo-subagent-result' }, [React.createElement('strong', { key: 'title' }, 'Codex 结果'), React.createElement('pre', { key: 'text' }, store.oneShotResult.text || '(无正文输出)'), store.oneShotResult.diagnostic ? React.createElement('small', { key: 'diagnostic' }, '诊断:' + store.oneShotResult.diagnostic) : null]) : null,
|
|
514
598
|
React.createElement('form', { key: 'form', onSubmit: create }, [
|
|
515
599
|
React.createElement('label', { key: 'label' }, ['名称', React.createElement('input', { key: 'input', value: store.label, maxLength: 120, onChange: function (event) { update({ label: event.target.value }) }, placeholder: '例如:资料检索员' })]),
|
|
516
600
|
React.createElement('label', { key: 'profile' }, ['Agent Profile', store.loading ? React.createElement('span', { key: 'loading', className: 'gjo-muted' }, '读取中…') : React.createElement('select', { key: 'select', value: store.profileId, onChange: function (event) { update({ profileId: event.target.value }) } }, [React.createElement('option', { key: 'empty', value: '' }, '使用默认 Profile'), store.profiles.map(function (profile) { return React.createElement('option', { key: profile.id, value: profile.id }, (profile.name || profile.id) + ' · ' + profile.executor) })])]),
|
|
517
601
|
React.createElement('label', { key: 'prompt' }, ['初始任务', React.createElement('textarea', { key: 'textarea', rows: 10, maxLength: 20000, value: store.prompt, onChange: function (event) { update({ prompt: event.target.value }) }, placeholder: '告诉 Subagent 要完成什么,以及需要关注的约束。' })]),
|
|
518
|
-
|
|
519
|
-
|
|
602
|
+
React.createElement('p', { key: 'note', className: 'gjo-note' }, selectedCodex ? '官方 Codex provider 不接受 per-call agentOptions、Tools、Skills、persona 或动态 reasoning;需要先在 Profile Bundle/原生 Codex 配置中设置。' : 'Subagent 默认不能使用 Graph Job 管理工具或 Skill 工具;模型、Provider 和权限沿用所选 Profile 的受控配置。'),
|
|
603
|
+
React.createElement('div', { key: 'actions', className: 'gjo-subagent-actions' }, [React.createElement('button', { key: 'cancel', type: 'button', className: 'gjo-secondary', onClick: function () { setSubagentOverlayOpen(false) } }, '取消'), React.createElement('button', { key: 'submit', type: 'submit', className: 'gjo-primary', disabled: store.busy || !sessionId || !store.prompt.trim() }, store.busy ? '创建中…' : selectedCodex ? '运行 Codex one-shot' : '创建并打开')])
|
|
520
604
|
])
|
|
521
605
|
]))
|
|
522
606
|
}
|
|
@@ -530,6 +614,10 @@ window.__ModuleLoader__.load({
|
|
|
530
614
|
CSS += `
|
|
531
615
|
.gjo-sidebar-action{display:flex;align-items:center;justify-content:center;width:calc(100% - 20px);margin:6px 10px 0;padding:7px 10px;border:1px solid var(--dsw-alias-border-l2,#30343e);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#aab1bd);cursor:pointer;font:inherit;font-size:12px}.gjo-sidebar-action:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d);color:var(--dsw-alias-label-primary,#e7e9ee);border-color:var(--dsw-alias-state-business-primary,#4f8cff)}
|
|
532
616
|
.gjo-subagent-overlay{position:fixed;z-index:1000;inset:0;display:flex;justify-content:flex-end;background:#0007}.gjo-subagent-panel{box-sizing:border-box;width:min(440px,100vw);height:100%;overflow:auto;padding:20px;background:var(--dsw-alias-bg-layer-1,#1d2028);border-left:1px solid var(--dsw-alias-border-l2,#30343e);box-shadow:-8px 0 30px #0006;color:var(--dsw-alias-label-primary,#e7e9ee)}.gjo-subagent-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.gjo-subagent-head h2{margin:3px 0 0;font-size:19px}.gjo-subagent-close{border:0;background:transparent;color:var(--dsw-alias-label-secondary,#aab1bd);font-size:24px;line-height:1;cursor:pointer}.gjo-subagent-intro{margin:12px 0 18px;color:var(--dsw-alias-label-secondary,#aab1bd);font-size:12px}.gjo-subagent-panel .gjo-error{margin:0 0 12px}.gjo-subagent-panel label{display:flex;flex-direction:column;gap:6px;margin:13px 0;color:var(--dsw-alias-label-secondary,#aab1bd);font-size:11px}.gjo-subagent-panel input,.gjo-subagent-panel select,.gjo-subagent-panel textarea{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2,#30343e);border-radius:7px;background:var(--dsw-specific-input-major,#101217);color:inherit;padding:8px 9px;font:inherit;font-size:12px}.gjo-subagent-panel textarea{resize:vertical}.gjo-subagent-panel .gjo-note{margin-top:16px}.gjo-subagent-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}
|
|
617
|
+
`
|
|
618
|
+
|
|
619
|
+
CSS += `
|
|
620
|
+
.gjo-decision-panel{margin:10px 22px 0;padding:12px;border:1px solid var(--dsw-alias-state-warn-primary,#c77b26);border-radius:8px;background:var(--dsw-alias-state-warn-tertiary,#c77b2614)}.gjo-decision-panel p{margin:5px 0 10px;color:var(--dsw-alias-label-secondary,#aab1bd);font-size:11px}.gjo-decision-panel label{display:flex;flex-direction:column;gap:5px;margin:9px 0;color:var(--dsw-alias-label-secondary,#aab1bd);font-size:11px}.gjo-decision-panel input,.gjo-decision-panel select{box-sizing:border-box;width:100%;border:1px solid var(--dsw-alias-border-l2,#30343e);border-radius:7px;background:var(--dsw-specific-input-major,#101217);color:inherit;padding:7px 9px;font:inherit;font-size:12px}.gjo-decision-panel button{margin-top:4px}.gjo-subagent-profile-summary,.gjo-subagent-result{display:flex;flex-direction:column;gap:4px;margin:10px 0;padding:9px;border:1px solid var(--dsw-alias-border-l2,#30343e);border-radius:7px;background:var(--dsw-alias-bg-layer-2,#ffffff08);font-size:11px}.gjo-subagent-profile-summary span,.gjo-subagent-result small{color:var(--dsw-alias-label-secondary,#aab1bd)}.gjo-subagent-result pre{max-height:260px;overflow:auto;margin:4px 0;white-space:pre-wrap;word-break:break-word;font:11px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
533
621
|
`
|
|
534
622
|
|
|
535
623
|
function activateConversationView() {
|
|
@@ -550,7 +638,9 @@ window.__ModuleLoader__.load({
|
|
|
550
638
|
function onDesktopMessage(event) {
|
|
551
639
|
if (event.source !== window.parent) return
|
|
552
640
|
var data = event.data
|
|
553
|
-
if (data
|
|
641
|
+
if (!data || data.source !== 'dsh-desktop' || data.type !== 'plugin-rpc' || data.pluginId !== PLUGIN_ID) return
|
|
642
|
+
if (data.method === DESKTOP_RPC_METHOD) activateConversationView()
|
|
643
|
+
if (data.method === DESKTOP_SUBAGENT_RPC_METHOD) openSubagentOverlay()
|
|
554
644
|
}
|
|
555
645
|
window.addEventListener('message', onDesktopMessage)
|
|
556
646
|
return function () { window.removeEventListener('message', onDesktopMessage) }
|
package/lib/executors.js
CHANGED
|
@@ -123,6 +123,25 @@ export function resolveCodexPermission(profile = {}, providerCapabilities = {})
|
|
|
123
123
|
return { mode: requested, passThrough: true }
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
function isNativeCodexProvider(providerName, provider, providerCapabilities) {
|
|
127
|
+
return /codex/i.test(String(providerName || '')) &&
|
|
128
|
+
provider?.inheritsParentContext === false &&
|
|
129
|
+
providerCapabilities.toolFilter !== true &&
|
|
130
|
+
providerCapabilities.depthLimit !== true &&
|
|
131
|
+
providerCapabilities.persona !== true &&
|
|
132
|
+
providerCapabilities.outputSchema !== true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function staticProviderValue(provider, descriptor, key) {
|
|
136
|
+
return String(
|
|
137
|
+
provider?.[key] ||
|
|
138
|
+
provider?.config?.[key] ||
|
|
139
|
+
descriptor?.[key] ||
|
|
140
|
+
descriptor?.config?.[key] ||
|
|
141
|
+
''
|
|
142
|
+
).trim()
|
|
143
|
+
}
|
|
144
|
+
|
|
126
145
|
function contentText(content) {
|
|
127
146
|
if (typeof content === 'string') return content.trim()
|
|
128
147
|
if (content && typeof content === 'object' && !Array.isArray(content)) {
|
|
@@ -434,29 +453,63 @@ export class CodexExecutor {
|
|
|
434
453
|
}
|
|
435
454
|
const descriptor = this.capabilityCatalog.subagentProviders?.find((item) => item.name === providerName)
|
|
436
455
|
const providerCapabilities = provider?.capabilities || descriptor?.capabilities || {}
|
|
437
|
-
if (providerCapabilities.toolFilter !== true) throw new CapabilityMismatchError(`Codex provider ${providerName} 未明确支持安全 Tool allowlist。`)
|
|
438
|
-
if (providerCapabilities.depthLimit !== true) throw new CapabilityMismatchError(`Codex provider ${providerName} 未明确支持 delegation depth 限制。`)
|
|
439
|
-
if (profile.persona && providerCapabilities.persona !== true) throw new CapabilityMismatchError(`Codex provider ${providerName} 未明确支持 per-profile persona。`)
|
|
440
|
-
if (profile.reasoningEffort && (!Array.isArray(providerCapabilities.reasoningEfforts) || !providerCapabilities.reasoningEfforts.includes(profile.reasoningEffort))) {
|
|
441
|
-
throw new CapabilityMismatchError(`Codex provider ${providerName} 未明确支持 reasoningEffort=${profile.reasoningEffort}。`)
|
|
442
|
-
}
|
|
443
|
-
const permission = resolveCodexPermission(profile, providerCapabilities)
|
|
444
456
|
const prompt = buildNodePrompt({ graph, node, profile, predecessorResults })
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
if (
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
457
|
+
const nativeCodex = isNativeCodexProvider(providerName, provider, providerCapabilities)
|
|
458
|
+
let startRequest
|
|
459
|
+
if (nativeCodex) {
|
|
460
|
+
// The official subagent-codex bundle is intentionally one-shot and
|
|
461
|
+
// exposes NO_START_CAPABILITIES. It fixes model/permission on the
|
|
462
|
+
// provider instance and rejects shared agentOptions, tool filtering,
|
|
463
|
+
// persona, output schema and depth controls. Do not send generic DSH
|
|
464
|
+
// fields here: doing so makes the provider reject the whole run.
|
|
465
|
+
if (profile.capabilities?.tools?.length || profile.capabilities?.skills?.length) {
|
|
466
|
+
throw new CapabilityMismatchError(`Codex provider ${providerName} 不支持通过 Graph Profile 传入 Tools/Skills;请在 Codex 原生配置中管理它们。`)
|
|
467
|
+
}
|
|
468
|
+
if (profile.persona) throw new CapabilityMismatchError(`Codex provider ${providerName} 不支持 per-profile persona;请在 Codex 原生配置中管理身份。`)
|
|
469
|
+
if (profile.reasoningEffort && (!Array.isArray(providerCapabilities.reasoningEfforts) || !providerCapabilities.reasoningEfforts.includes(profile.reasoningEffort))) {
|
|
470
|
+
throw new CapabilityMismatchError(`Codex provider ${providerName} 未公布 reasoningEffort=${profile.reasoningEffort};当前包不支持按调用指定推理档次。`)
|
|
471
|
+
}
|
|
472
|
+
const staticModel = staticProviderValue(provider, descriptor, 'staticModel') || staticProviderValue(provider, descriptor, 'model')
|
|
473
|
+
if (profile.model && providerCapabilities.dynamicModel !== true && staticModel !== profile.model) {
|
|
474
|
+
throw new CapabilityMismatchError(`Codex provider ${providerName} 的 model 由 Profile Bundle 静态配置;当前运行不能按节点动态覆盖 ${profile.model}。`)
|
|
475
|
+
}
|
|
476
|
+
const staticPermission = staticProviderValue(provider, descriptor, 'staticPermissionMode') || staticProviderValue(provider, descriptor, 'permissionMode')
|
|
477
|
+
if (profile.permissionMode && profile.permissionMode !== 'default' && staticPermission && staticPermission !== profile.permissionMode) {
|
|
478
|
+
throw new CapabilityMismatchError(`Codex provider ${providerName} 的 permissionMode 固定为 ${staticPermission},不能在节点运行时改成 ${profile.permissionMode}。`)
|
|
479
|
+
}
|
|
480
|
+
if (profile.permissionMode && profile.permissionMode !== 'default' && !staticPermission) {
|
|
481
|
+
throw new CapabilityMismatchError(`Codex provider ${providerName} 未公布可动态设置 permissionMode;请在 Profile Bundle 配置后使用 default。`)
|
|
482
|
+
}
|
|
483
|
+
startRequest = {
|
|
484
|
+
label: `graph-job:codex:${graph.graphId}:${node.id}:attempt-${attempt}`,
|
|
485
|
+
prompt: [{ type: 'text', text: prompt }],
|
|
486
|
+
parent: parentAgent,
|
|
487
|
+
signal
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
if (providerCapabilities.toolFilter !== true) throw new CapabilityMismatchError(`Codex provider ${providerName} 未明确支持安全 Tool allowlist。`)
|
|
491
|
+
if (providerCapabilities.depthLimit !== true) throw new CapabilityMismatchError(`Codex provider ${providerName} 未明确支持 delegation depth 限制。`)
|
|
492
|
+
if (profile.persona && providerCapabilities.persona !== true) throw new CapabilityMismatchError(`Codex provider ${providerName} 未明确支持 per-profile persona。`)
|
|
493
|
+
if (profile.reasoningEffort && (!Array.isArray(providerCapabilities.reasoningEfforts) || !providerCapabilities.reasoningEfforts.includes(profile.reasoningEffort))) {
|
|
494
|
+
throw new CapabilityMismatchError(`Codex provider ${providerName} 未明确支持 reasoningEffort=${profile.reasoningEffort}。`)
|
|
495
|
+
}
|
|
496
|
+
const permission = resolveCodexPermission(profile, providerCapabilities)
|
|
497
|
+
const toolFilter = buildToolFilter(profile, this.capabilityCatalog.tools || [])
|
|
498
|
+
const agentOptions = { model: profile.model, maxTokens: profile.maxOutputTokens }
|
|
499
|
+
if (profile.reasoningEffort) agentOptions.reasoningEffort = profile.reasoningEffort
|
|
500
|
+
startRequest = {
|
|
501
|
+
label: `graph-job:codex:${graph.graphId}:${node.id}:attempt-${attempt}`,
|
|
502
|
+
prompt: [{ type: 'text', text: prompt }],
|
|
503
|
+
parent: parentAgent,
|
|
504
|
+
signal,
|
|
505
|
+
maxDepth: graph.limits.maxDepth,
|
|
506
|
+
persona: profile.persona || undefined,
|
|
507
|
+
agentOptions
|
|
508
|
+
}
|
|
509
|
+
if (permission.passThrough) startRequest.permissionMode = permission.mode
|
|
510
|
+
if (toolFilter) startRequest.toolFilter = toolFilter
|
|
511
|
+
if (providerCapabilities.outputSchema) startRequest.outputSchema = NODE_OUTPUT_SCHEMA
|
|
456
512
|
}
|
|
457
|
-
if (permission.passThrough) startRequest.permissionMode = permission.mode
|
|
458
|
-
if (toolFilter) startRequest.toolFilter = toolFilter
|
|
459
|
-
if (providerCapabilities.outputSchema) startRequest.outputSchema = NODE_OUTPUT_SCHEMA
|
|
460
513
|
const run = await start.call(this.services.subagents, providerName, startRequest)
|
|
461
514
|
const childSessionId = childSessionIdFromRun(run)
|
|
462
515
|
try {
|
package/lib/graph-schema.js
CHANGED
|
@@ -13,13 +13,13 @@ const AGENT_PROFILE_SCHEMA = {
|
|
|
13
13
|
properties: {
|
|
14
14
|
id: { type: 'string', minLength: 1, maxLength: 120 },
|
|
15
15
|
name: { type: 'string', maxLength: 200 },
|
|
16
|
-
executor: { enum: [...EXECUTORS] },
|
|
16
|
+
executor: { type: 'string', enum: [...EXECUTORS] },
|
|
17
17
|
provider: { type: 'string', maxLength: 200 },
|
|
18
18
|
subagentProvider: { type: 'string', maxLength: 200 },
|
|
19
19
|
model: { type: 'string', maxLength: 300 },
|
|
20
20
|
reasoningEffort: { type: 'string', maxLength: 40 },
|
|
21
21
|
persona: { type: 'string', maxLength: 12000 },
|
|
22
|
-
permissionMode: { enum: ['default', 'never', 'approve-for-me'] },
|
|
22
|
+
permissionMode: { type: 'string', enum: ['default', 'never', 'approve-for-me', 'dangerously-bypass-approvals-and-sandbox'] },
|
|
23
23
|
maxOutputTokens: { type: 'integer', minimum: 256, maximum: 128000 },
|
|
24
24
|
capabilities: {
|
|
25
25
|
type: 'object',
|
|
@@ -37,7 +37,7 @@ const OUTPUT_CONTRACT_SCHEMA = {
|
|
|
37
37
|
type: 'object',
|
|
38
38
|
additionalProperties: false,
|
|
39
39
|
properties: {
|
|
40
|
-
fields: { type: 'array', items: { enum: ['text', 'artifactRefs'] }, maxItems: 2 },
|
|
40
|
+
fields: { type: 'array', items: { type: 'string', enum: ['text', 'artifactRefs'] }, maxItems: 2 },
|
|
41
41
|
text: { type: 'boolean' },
|
|
42
42
|
artifactRefs: { type: 'boolean' },
|
|
43
43
|
requireText: { type: 'boolean' },
|
|
@@ -52,13 +52,13 @@ const NODE_SCHEMA = {
|
|
|
52
52
|
required: ['id', 'kind', 'title'],
|
|
53
53
|
properties: {
|
|
54
54
|
id: { type: 'string', minLength: 1, maxLength: 120 },
|
|
55
|
-
kind: { enum: [...NODE_KINDS] },
|
|
55
|
+
kind: { type: 'string', enum: [...NODE_KINDS] },
|
|
56
56
|
title: { type: 'string', minLength: 1, maxLength: 300 },
|
|
57
57
|
instruction: { type: 'string', maxLength: 20000 },
|
|
58
58
|
agentProfileId: { type: 'string', maxLength: 120 },
|
|
59
|
-
access: { enum: [...ACCESS_MODES] },
|
|
59
|
+
access: { type: 'string', enum: [...ACCESS_MODES] },
|
|
60
60
|
outputContract: OUTPUT_CONTRACT_SCHEMA,
|
|
61
|
-
failurePolicy: { enum: [...FAILURE_POLICIES] },
|
|
61
|
+
failurePolicy: { type: 'string', enum: [...FAILURE_POLICIES] },
|
|
62
62
|
ui: {
|
|
63
63
|
type: 'object',
|
|
64
64
|
additionalProperties: false,
|
|
@@ -117,7 +117,7 @@ export const GRAPH_PATCH_SCHEMA = Object.freeze({
|
|
|
117
117
|
type: 'object',
|
|
118
118
|
required: ['op'],
|
|
119
119
|
properties: {
|
|
120
|
-
op: { enum: ['addNode', 'removeNode', 'updateNode', 'addEdge', 'removeEdge', 'updateLimits', 'setGoal'] },
|
|
120
|
+
op: { type: 'string', enum: ['addNode', 'removeNode', 'updateNode', 'addEdge', 'removeEdge', 'updateLimits', 'setGoal'] },
|
|
121
121
|
id: { type: 'string' },
|
|
122
122
|
nodeId: { type: 'string' },
|
|
123
123
|
node: NODE_SCHEMA,
|
|
@@ -180,9 +180,14 @@ export function normalizeProfile(input = {}, options = {}) {
|
|
|
180
180
|
provider = executor === 'codex' ? 'codex' : defaultProvider
|
|
181
181
|
modelSource = 'default'
|
|
182
182
|
}
|
|
183
|
-
if (!model && options.resolveDefaults) {
|
|
183
|
+
if (!model && options.resolveDefaults && executor !== 'codex') {
|
|
184
184
|
model = defaultModelName
|
|
185
185
|
modelSource = 'default'
|
|
186
|
+
} else if (!model && options.resolveDefaults && executor === 'codex') {
|
|
187
|
+
// The official Codex provider may inherit its model from the static
|
|
188
|
+
// provider bundle/native Codex settings. Never inject the DSH chat model
|
|
189
|
+
// into a Codex one-shot request.
|
|
190
|
+
modelSource = 'provider-default'
|
|
186
191
|
}
|
|
187
192
|
const reasoningEffort = executor === 'codex' ? text(input.reasoningEffort) : ''
|
|
188
193
|
return {
|
package/lib/index.js
CHANGED
|
@@ -55,7 +55,16 @@ function diagnosticLog(logger, level, event, details = {}) {
|
|
|
55
55
|
} catch { /* diagnostics must never break the user path */ }
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function makePlannerFollowup(goal) {
|
|
58
|
+
function makePlannerFollowup(goal, decision = {}) {
|
|
59
|
+
const decisionText = decision.templateMode
|
|
60
|
+
? [
|
|
61
|
+
'用户已经在 Graph Job 面板选择了手工图修订方式。',
|
|
62
|
+
`templateMode:${decision.templateMode}`,
|
|
63
|
+
decision.templateId ? `templateId:${decision.templateId}` : 'templateId:无(请创建新模板)',
|
|
64
|
+
`templateScope:${decision.templateScope || 'workspace'}`,
|
|
65
|
+
decision.templateName ? `templateName:${decision.templateName}` : ''
|
|
66
|
+
].filter(Boolean).join('\n')
|
|
67
|
+
: ''
|
|
59
68
|
return {
|
|
60
69
|
id: `graphjob-command-${randomUUID()}`,
|
|
61
70
|
role: 'user',
|
|
@@ -65,8 +74,9 @@ function makePlannerFollowup(goal) {
|
|
|
65
74
|
'用户刚刚通过 /graphjob 提交了一个任务图目标。',
|
|
66
75
|
'请只调用 graphjob_plan,为这个目标生成受限的 Graph Job 草案;不要运行 Graph,不要执行目标中的业务动作,也不要发送外部消息。',
|
|
67
76
|
`用户目标:${goal}`,
|
|
77
|
+
decisionText,
|
|
68
78
|
'生成预览后等待用户在 Graph Job 面板中确认。'
|
|
69
|
-
].join('\n\n')
|
|
79
|
+
].filter(Boolean).join('\n\n')
|
|
70
80
|
}],
|
|
71
81
|
source: { kind: 'plugin', plugin: PLUGIN_ID, form: 'instructions' }
|
|
72
82
|
}
|
|
@@ -108,8 +118,20 @@ function commandDefinition(orchestrator, logger) {
|
|
|
108
118
|
}
|
|
109
119
|
return { kind: 'success', text: 'Graph Job 已打开。请在任务图面板中补充节点,完成预览和确认后再运行。' }
|
|
110
120
|
}
|
|
121
|
+
orchestrator.setPlannerRequest?.(sessionId, { goal: rawGoal, cwd: agent.session.header.cwd })
|
|
111
122
|
try {
|
|
112
|
-
|
|
123
|
+
// Agent.followup is deliberately a synchronous queue operation. Awaiting
|
|
124
|
+
// a promise here would make the slash-command lifecycle depend on the
|
|
125
|
+
// whole planner/model turn and leaves the composer in "停止生成" state.
|
|
126
|
+
const queuedFollowup = agent.followup(makePlannerFollowup(rawGoal))
|
|
127
|
+
if (queuedFollowup && typeof queuedFollowup.catch === 'function') {
|
|
128
|
+
queuedFollowup.catch((error) => diagnosticLog(logger, 'error', 'command.planner_turn_failed', {
|
|
129
|
+
sessionId: sessionMarker(sessionId),
|
|
130
|
+
code: error?.code || 'UNKNOWN',
|
|
131
|
+
message: diagnosticMessage(error)
|
|
132
|
+
}))
|
|
133
|
+
}
|
|
134
|
+
orchestrator.setPlannerStatus?.(sessionId, { state: 'queued' })
|
|
113
135
|
diagnosticLog(logger, 'info', 'command.planner_queued', { sessionId: sessionMarker(sessionId), goalLength: rawGoal.length })
|
|
114
136
|
return { kind: 'success', text: 'Graph Job 已打开,正在生成草案;完成后请在任务图面板预览并确认。' }
|
|
115
137
|
} catch (error) {
|
|
@@ -130,14 +152,16 @@ const planOutputSchema = {
|
|
|
130
152
|
confirmationToken: { type: 'string' },
|
|
131
153
|
summary: { type: 'object' },
|
|
132
154
|
validation: { type: 'object' },
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
required: [
|
|
155
|
+
templateDecision: {
|
|
156
|
+
type: 'object',
|
|
157
|
+
additionalProperties: false,
|
|
158
|
+
required: [],
|
|
137
159
|
properties: {
|
|
138
160
|
mode: { type: 'string', enum: ['saveAs', 'overwrite'] },
|
|
139
161
|
templateId: { type: 'string' },
|
|
140
|
-
scope: { type: 'string', enum: ['global', 'workspace'] }
|
|
162
|
+
scope: { type: 'string', enum: ['global', 'workspace'] },
|
|
163
|
+
required: { type: 'boolean' },
|
|
164
|
+
message: { type: 'string' }
|
|
141
165
|
}
|
|
142
166
|
}
|
|
143
167
|
}
|
|
@@ -165,7 +189,9 @@ function planTool(orchestrator, logger) {
|
|
|
165
189
|
},
|
|
166
190
|
output: {
|
|
167
191
|
schema: planOutputSchema,
|
|
168
|
-
render: (_args, value) => [{ type: 'text', text:
|
|
192
|
+
render: (_args, value) => [{ type: 'text', text: value?.templateDecision?.required
|
|
193
|
+
? `Graph Job 草案等待用户选择模板保存方式:${value.templateDecision.message || 'saveAs 或 overwrite'}。`
|
|
194
|
+
: `Graph Job 草案已生成,等待用户预览确认。\n${JSON.stringify(value.summary)}` }]
|
|
169
195
|
},
|
|
170
196
|
presentCall(args) {
|
|
171
197
|
return { card: 'generic', kind: 'graph-job-plan', title: '生成 Graph Job 任务图', rawInput: JSON.stringify({ goal: args?.goal || '' }) }
|
|
@@ -180,6 +206,13 @@ function planTool(orchestrator, logger) {
|
|
|
180
206
|
if (!parentAgent || !header?.id) throw new Error(`${PLAN_TOOL_NAME} 只能从正在运行的 DSH 主对话调用。`)
|
|
181
207
|
if (header.origin === 'subagent') throw new Error('Subagent 不能递归调用 Graph Job Planner。')
|
|
182
208
|
const startedAt = Date.now()
|
|
209
|
+
orchestrator.setPlannerRequest?.(header.id, {
|
|
210
|
+
goal: args?.goal,
|
|
211
|
+
cwd: header.cwd,
|
|
212
|
+
draft: args?.draft,
|
|
213
|
+
patch: args?.patch,
|
|
214
|
+
baseGraph: args?.baseGraph
|
|
215
|
+
})
|
|
183
216
|
orchestrator.setPlannerStatus?.(header.id, { state: 'running' })
|
|
184
217
|
diagnosticLog(logger, 'info', 'planner.request', {
|
|
185
218
|
sessionId: sessionMarker(header.id),
|
|
@@ -218,7 +251,18 @@ function planTool(orchestrator, logger) {
|
|
|
218
251
|
message: diagnosticMessage(error),
|
|
219
252
|
durationMs: Date.now() - startedAt
|
|
220
253
|
})
|
|
221
|
-
|
|
254
|
+
if (error?.code === 'TEMPLATE_DECISION_REQUIRED') {
|
|
255
|
+
const message = diagnosticMessage(error)
|
|
256
|
+
orchestrator.setPlannerStatus?.(header.id, { state: 'needs-decision', code: error.code, error: message })
|
|
257
|
+
return {
|
|
258
|
+
previewId: '',
|
|
259
|
+
confirmationRequired: false,
|
|
260
|
+
summary: { requiresTemplateDecision: true },
|
|
261
|
+
validation: { valid: false, errors: [{ code: error.code, message }], warnings: [] },
|
|
262
|
+
templateDecision: { required: true, message }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
orchestrator.setPlannerStatus?.(header.id, { state: 'failed', code: error?.code || 'UNKNOWN', error: diagnosticMessage(error) })
|
|
222
266
|
throw error
|
|
223
267
|
}
|
|
224
268
|
}
|
|
@@ -246,6 +290,147 @@ function graphJobToolFilter(toolNames) {
|
|
|
246
290
|
return denied.length ? { deny: denied } : undefined
|
|
247
291
|
}
|
|
248
292
|
|
|
293
|
+
function oneShotText(value) {
|
|
294
|
+
if (typeof value === 'string') return value.trim()
|
|
295
|
+
if (Array.isArray(value)) return value.map(oneShotText).filter(Boolean).join('\n\n').trim()
|
|
296
|
+
if (!value || typeof value !== 'object') return ''
|
|
297
|
+
if (typeof value.text === 'string') return value.text.trim()
|
|
298
|
+
return oneShotText(value.content || value.output || value.message)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function oneShotDiagnostic(result) {
|
|
302
|
+
const value = result?.diagnostic || result?.error?.message || result?.error?.code || result?.code || ''
|
|
303
|
+
if (!value) return ''
|
|
304
|
+
if (typeof value === 'string') return diagnosticMessage({ message: value })
|
|
305
|
+
try { return diagnosticMessage({ message: JSON.stringify(value) }) } catch { return diagnosticMessage(value) }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function oneShotResult(result) {
|
|
309
|
+
if (!result || typeof result !== 'object') throw codedError('Codex Subagent 没有返回结果 payload。', 'MISSING_SUBAGENT_PAYLOAD')
|
|
310
|
+
const stopReason = String(result.stopReason || 'completed').trim()
|
|
311
|
+
const diagnostic = oneShotDiagnostic(result)
|
|
312
|
+
if (result.isError === true || stopReason !== 'completed') {
|
|
313
|
+
const lower = `${stopReason} ${diagnostic}`.toLowerCase()
|
|
314
|
+
if (/abort|cancel/.test(lower)) throw codedError('Codex Subagent 已取消。', 'ABORTED')
|
|
315
|
+
if (/auth|unauthori[sz]|credential|login|token/.test(lower)) throw codedError(diagnostic || 'Codex Subagent 需要认证。', 'AUTH_REQUIRED')
|
|
316
|
+
if (/rate.?limit|too many requests|\b429\b/.test(lower)) throw codedError(diagnostic || 'Codex Subagent 请求受到限流。', 'RATE_LIMIT')
|
|
317
|
+
if (/timeout|timed.?out|network|transport|connection|econn/.test(lower)) throw codedError(diagnostic || 'Codex Subagent transport 失败。', 'TRANSPORT_ERROR')
|
|
318
|
+
throw codedError(diagnostic || `Codex Subagent 以 ${stopReason || 'error'} 结束。`, 'SUBAGENT_ERROR')
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
stopReason,
|
|
322
|
+
text: oneShotText(result.output || result.text || result.content || result.structured?.text).slice(0, 40000),
|
|
323
|
+
diagnostic: diagnostic.slice(0, 320)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function providerStaticValue(provider, descriptor, key) {
|
|
328
|
+
return String(
|
|
329
|
+
provider?.[key] || provider?.config?.[key] ||
|
|
330
|
+
descriptor?.[key] || descriptor?.config?.[key] || ''
|
|
331
|
+
).trim()
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function providerModels(provider, descriptor) {
|
|
335
|
+
return [...new Set([
|
|
336
|
+
...(Array.isArray(provider?.models) ? provider.models : []),
|
|
337
|
+
...(Array.isArray(descriptor?.models) ? descriptor.models : []),
|
|
338
|
+
providerStaticValue(provider, descriptor, 'staticModel'),
|
|
339
|
+
providerStaticValue(provider, descriptor, 'model')
|
|
340
|
+
].map((item) => typeof item === 'string' ? item.trim() : String(item?.id || item?.name || '').trim()).filter(Boolean))]
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function createCodexSubagent({ services, parentAgent, profile, providerName, provider, catalog, label, prompt, sessionId, logger }) {
|
|
344
|
+
if (!services.subagents || typeof services.subagents.start !== 'function') {
|
|
345
|
+
throw codedError('当前 runtime 未提供 Codex one-shot Subagent 能力。', 'CODEX_PROVIDER_UNAVAILABLE')
|
|
346
|
+
}
|
|
347
|
+
if (!provider) throw codedError(`Codex provider 不可用:${providerName}`, 'NO_PROVIDER')
|
|
348
|
+
|
|
349
|
+
const descriptor = catalog.subagentProviders?.find((item) => item.name === providerName) || {}
|
|
350
|
+
const capabilities = { ...(descriptor.capabilities || {}), ...(provider.capabilities || {}) }
|
|
351
|
+
const staticModel = providerStaticValue(provider, descriptor, 'staticModel') || providerStaticValue(provider, descriptor, 'model')
|
|
352
|
+
const models = providerModels(provider, descriptor)
|
|
353
|
+
const requestedModel = String(profile.model || '').trim()
|
|
354
|
+
const dynamicModel = capabilities.dynamicModel === true || capabilities.modelOverride === true
|
|
355
|
+
if (requestedModel && !dynamicModel && (!models.length || !models.includes(requestedModel))) {
|
|
356
|
+
throw codedError(`Codex provider ${providerName} 的 model 由 Profile Bundle/原生配置静态决定,不能动态指定 ${requestedModel}。请把 Profile 的 Model 留空,或选择与 Bundle 相同的 model。`, 'UNSUPPORTED_CAPABILITY')
|
|
357
|
+
}
|
|
358
|
+
if (requestedModel && staticModel && requestedModel !== staticModel && !dynamicModel) {
|
|
359
|
+
throw codedError(`Codex provider ${providerName} 的 model 固定为 ${staticModel},不能改成 ${requestedModel}。`, 'UNSUPPORTED_CAPABILITY')
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const requestedReasoning = String(profile.reasoningEffort || '').trim()
|
|
363
|
+
const reasoningEfforts = Array.isArray(capabilities.reasoningEfforts) ? capabilities.reasoningEfforts : []
|
|
364
|
+
if (requestedReasoning && (!reasoningEfforts.includes(requestedReasoning) || capabilities.dynamicReasoning !== true && capabilities.reasoningEffortOverride !== true)) {
|
|
365
|
+
throw codedError(`Codex provider ${providerName} 未公布可按调用指定的 reasoningEffort=${requestedReasoning};当前官方包只支持静态配置。`, 'UNSUPPORTED_CAPABILITY')
|
|
366
|
+
}
|
|
367
|
+
if (profile.capabilities?.tools?.length || profile.capabilities?.skills?.length) {
|
|
368
|
+
throw codedError(`Codex provider ${providerName} 不支持通过 Profile 传入 Tools/Skills;请在 Codex 原生配置中管理它们。`, 'UNSUPPORTED_CAPABILITY')
|
|
369
|
+
}
|
|
370
|
+
if (profile.persona) throw codedError(`Codex provider ${providerName} 不支持 per-profile persona;请在 Codex 原生配置中管理身份。`, 'UNSUPPORTED_CAPABILITY')
|
|
371
|
+
|
|
372
|
+
const requestedPermission = String(profile.permissionMode || 'default').trim()
|
|
373
|
+
const staticPermission = providerStaticValue(provider, descriptor, 'staticPermissionMode') || providerStaticValue(provider, descriptor, 'permissionMode')
|
|
374
|
+
const permissionModes = Array.isArray(capabilities.permissionModes) ? capabilities.permissionModes : []
|
|
375
|
+
if (requestedPermission !== 'default' && staticPermission && staticPermission !== requestedPermission) {
|
|
376
|
+
throw codedError(`Codex provider ${providerName} 的 permissionMode 固定为 ${staticPermission},不能改成 ${requestedPermission}。`, 'UNSUPPORTED_CAPABILITY')
|
|
377
|
+
}
|
|
378
|
+
if (requestedPermission !== 'default' && !staticPermission && !(capabilities.permissionMode === true || permissionModes.includes(requestedPermission))) {
|
|
379
|
+
throw codedError(`Codex provider ${providerName} 未公布可动态设置 permissionMode;请在 Profile Bundle 配置后使用 default。`, 'UNSUPPORTED_CAPABILITY')
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const request = {
|
|
383
|
+
label,
|
|
384
|
+
prompt: [{ type: 'text', text: prompt }],
|
|
385
|
+
parent: parentAgent,
|
|
386
|
+
signal: new AbortController().signal
|
|
387
|
+
}
|
|
388
|
+
diagnosticLog(logger, 'info', 'subagent.codex_one_shot_requested', {
|
|
389
|
+
parentSessionId: sessionMarker(sessionId),
|
|
390
|
+
labelLength: label.length,
|
|
391
|
+
promptLength: prompt.length,
|
|
392
|
+
profileId: profile.id,
|
|
393
|
+
provider: providerName,
|
|
394
|
+
model: requestedModel ? '[profile]' : '[provider-default]',
|
|
395
|
+
reasoningEffort: requestedReasoning ? '[profile]' : '[provider-default]'
|
|
396
|
+
})
|
|
397
|
+
let run
|
|
398
|
+
try {
|
|
399
|
+
run = await services.subagents.start.call(services.subagents, providerName, request)
|
|
400
|
+
if (!run || run.result === undefined) throw codedError('Codex provider 返回了缺少 result 的 payload。', 'MISSING_SUBAGENT_PAYLOAD')
|
|
401
|
+
const result = oneShotResult(await run.result)
|
|
402
|
+
diagnosticLog(logger, 'info', 'subagent.codex_one_shot_completed', {
|
|
403
|
+
parentSessionId: sessionMarker(sessionId),
|
|
404
|
+
profileId: profile.id,
|
|
405
|
+
provider: providerName,
|
|
406
|
+
stopReason: result.stopReason,
|
|
407
|
+
outputLength: result.text.length
|
|
408
|
+
})
|
|
409
|
+
return {
|
|
410
|
+
mode: 'one-shot',
|
|
411
|
+
runId: String(run.id || '').trim(),
|
|
412
|
+
label,
|
|
413
|
+
parentSessionId: sessionId,
|
|
414
|
+
profileId: profile.id,
|
|
415
|
+
provider: providerName,
|
|
416
|
+
result
|
|
417
|
+
}
|
|
418
|
+
} catch (error) {
|
|
419
|
+
diagnosticLog(logger, 'error', 'subagent.codex_one_shot_failed', {
|
|
420
|
+
parentSessionId: sessionMarker(sessionId),
|
|
421
|
+
profileId: profile.id,
|
|
422
|
+
provider: providerName,
|
|
423
|
+
code: error?.code || 'UNKNOWN',
|
|
424
|
+
message: diagnosticMessage(error)
|
|
425
|
+
})
|
|
426
|
+
throw error
|
|
427
|
+
} finally {
|
|
428
|
+
try { await run?.dispose?.() } catch (error) {
|
|
429
|
+
diagnosticLog(logger, 'warn', 'subagent.codex_one_shot_dispose_failed', { provider: providerName, message: diagnosticMessage(error) })
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
249
434
|
export async function createSubagent(orchestrator, services, input = {}, logger) {
|
|
250
435
|
const sessionId = String(input.sessionId || '').trim()
|
|
251
436
|
if (!sessionId) throw codedError('创建 Subagent 需要当前会话。', 'MISSING_SESSION')
|
|
@@ -264,6 +449,9 @@ export async function createSubagent(orchestrator, services, input = {}, logger)
|
|
|
264
449
|
const catalog = await orchestrator.capabilities({ cwd })
|
|
265
450
|
const providerName = String(profile.subagentProvider || (profile.executor === 'codex' ? profile.provider : catalog.executors?.dsh?.provider) || 'spawn').trim()
|
|
266
451
|
const provider = services.subagents?.getProvider?.(providerName)
|
|
452
|
+
if (profile.executor === 'codex') {
|
|
453
|
+
return createCodexSubagent({ services, parentAgent, profile, providerName, provider, catalog, label, prompt, sessionId, logger })
|
|
454
|
+
}
|
|
267
455
|
if (!services.subagents || typeof services.subagents.startContinuable !== 'function') {
|
|
268
456
|
throw codedError('当前 DSH Runtime 未提供 continuable Subagent 能力。', 'CONTINUATION_UNAVAILABLE')
|
|
269
457
|
}
|
|
@@ -431,6 +619,66 @@ async function handleGraphRoutes(req, res, orchestrator) {
|
|
|
431
619
|
return jsonResponse(res, 404, { ok: false, error: 'Graph action 不存在。' })
|
|
432
620
|
}
|
|
433
621
|
|
|
622
|
+
async function handlePlannerDecisionRoute(req, res, orchestrator, services, logger) {
|
|
623
|
+
if (methodOf(req) !== 'POST') return jsonResponse(res, 405, { ok: false, error: '仅支持 POST。' })
|
|
624
|
+
const body = await readJson(req)
|
|
625
|
+
const sessionId = sessionIdFrom(req, body)
|
|
626
|
+
if (!sessionId) return jsonResponse(res, 400, { ok: false, error: '缺少 sessionId。' })
|
|
627
|
+
const mode = String(body.mode || '').trim()
|
|
628
|
+
if (!['saveAs', 'overwrite'].includes(mode)) throw codedError('必须选择 saveAs 或 overwrite。', 'TEMPLATE_DECISION_REQUIRED')
|
|
629
|
+
const templateId = String(body.templateId || '').trim()
|
|
630
|
+
if (mode === 'overwrite' && !templateId) throw codedError('覆盖模板必须选择 templateId。', 'TEMPLATE_DECISION_REQUIRED')
|
|
631
|
+
const parentAgent = services.agents?.get?.(sessionId)
|
|
632
|
+
if (!parentAgent?.session?.header?.id) throw codedError('当前会话没有可用的主 Agent;请先让主对话完成启动。', 'PARENT_AGENT_NOT_LIVE')
|
|
633
|
+
const pending = orchestrator.plannerRequestForSession?.(sessionId)
|
|
634
|
+
if (!pending?.goal) throw codedError('当前会话没有等待模板选择的 Planner 草案,请重新发送 /graphjob。', 'PLANNER_REQUEST_NOT_FOUND')
|
|
635
|
+
const decision = {
|
|
636
|
+
templateMode: mode,
|
|
637
|
+
templateId,
|
|
638
|
+
templateScope: String(body.templateScope || 'workspace').trim() || 'workspace',
|
|
639
|
+
templateName: String(body.templateName || '').trim().slice(0, 200)
|
|
640
|
+
}
|
|
641
|
+
diagnosticLog(logger, 'info', 'planner.decision', {
|
|
642
|
+
sessionId: sessionMarker(sessionId),
|
|
643
|
+
mode,
|
|
644
|
+
hasTemplateId: Boolean(templateId)
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
const plannerInput = {
|
|
648
|
+
sessionId,
|
|
649
|
+
cwd: parentAgent.session.header.cwd || pending.cwd,
|
|
650
|
+
goal: pending.goal,
|
|
651
|
+
templateMode: mode,
|
|
652
|
+
...(templateId ? { templateId } : {}),
|
|
653
|
+
...(decision.templateScope ? { templateScope: decision.templateScope } : {}),
|
|
654
|
+
...(decision.templateName ? { templateName: decision.templateName } : {}),
|
|
655
|
+
...(pending.draft ? { draft: pending.draft } : {}),
|
|
656
|
+
...(pending.patch ? { patch: pending.patch } : {}),
|
|
657
|
+
...(pending.baseGraph ? { baseGraph: pending.baseGraph } : {})
|
|
658
|
+
}
|
|
659
|
+
if (pending.draft || pending.patch) {
|
|
660
|
+
orchestrator.setPlannerStatus?.(sessionId, { state: 'running' })
|
|
661
|
+
try {
|
|
662
|
+
const preview = await orchestrator.previewPlanner(plannerInput)
|
|
663
|
+
orchestrator.setPlannerStatus?.(sessionId, { state: 'ready', previewId: preview.previewId })
|
|
664
|
+
return jsonResponse(res, 200, { ok: true, state: 'ready', preview, templatePreview: preview })
|
|
665
|
+
} catch (error) {
|
|
666
|
+
orchestrator.setPlannerStatus?.(sessionId, { state: 'failed', code: error?.code || 'UNKNOWN', error: diagnosticMessage(error) })
|
|
667
|
+
throw error
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
orchestrator.setPlannerStatus?.(sessionId, { state: 'queued' })
|
|
672
|
+
orchestrator.setPlannerRequest?.(sessionId, { goal: pending.goal, cwd: plannerInput.cwd })
|
|
673
|
+
try {
|
|
674
|
+
parentAgent.followup(makePlannerFollowup(pending.goal, decision))
|
|
675
|
+
} catch (error) {
|
|
676
|
+
orchestrator.setPlannerStatus?.(sessionId, { state: 'failed', code: error?.code || 'UNKNOWN', error: diagnosticMessage(error) })
|
|
677
|
+
throw error
|
|
678
|
+
}
|
|
679
|
+
return jsonResponse(res, 202, { ok: true, state: 'queued' })
|
|
680
|
+
}
|
|
681
|
+
|
|
434
682
|
async function handleSubagentRoutes(req, res, orchestrator, services, logger) {
|
|
435
683
|
const segments = pathSegments(req, `${BASE_PATH}/subagents`)
|
|
436
684
|
if (!segments) return false
|
|
@@ -575,7 +823,19 @@ export function createHost(options = {}) {
|
|
|
575
823
|
const result = await handleSubagentRoutes(req, res, orchestrator, services, logger)
|
|
576
824
|
if (result === false) return jsonResponse(res, 404, { ok: false, error: 'Not found' })
|
|
577
825
|
} catch (error) {
|
|
578
|
-
const status = ['PARENT_AGENT_NOT_LIVE', 'CONTINUATION_UNAVAILABLE', 'UNSUPPORTED_CAPABILITY', 'NO_PROVIDER'].includes(error?.code) ? 409 : 400
|
|
826
|
+
const status = ['PARENT_AGENT_NOT_LIVE', 'CONTINUATION_UNAVAILABLE', 'CODEX_PROVIDER_UNAVAILABLE', 'UNSUPPORTED_CAPABILITY', 'NO_PROVIDER'].includes(error?.code) ? 409 : 400
|
|
827
|
+
return writeError(res, status, error)
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
})
|
|
831
|
+
registerEffect(ctx, services.webServer, {
|
|
832
|
+
kind: 'exact',
|
|
833
|
+
path: `${BASE_PATH}/planner/decision`,
|
|
834
|
+
handler: async (req, res) => {
|
|
835
|
+
try {
|
|
836
|
+
return await handlePlannerDecisionRoute(req, res, orchestrator, services, logger)
|
|
837
|
+
} catch (error) {
|
|
838
|
+
const status = ['PARENT_AGENT_NOT_LIVE', 'PLANNER_REQUEST_NOT_FOUND'].includes(error?.code) ? 409 : error?.code === 'TEMPLATE_DECISION_REQUIRED' ? 412 : 400
|
|
579
839
|
return writeError(res, status, error)
|
|
580
840
|
}
|
|
581
841
|
}
|
package/lib/orchestrator.js
CHANGED
|
@@ -55,6 +55,17 @@ function templateVisible(template, workspaceId) {
|
|
|
55
55
|
return Boolean(workspaceId) && normalizeWorkspaceId(template?.workspaceId) === normalizeWorkspaceId(workspaceId)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function isUntouchedManualPlaceholder(graph) {
|
|
59
|
+
if (!graph || graph.source !== 'manual' || graph.manualLock !== true) return false
|
|
60
|
+
if ((graph.nodes || []).length || (graph.edges || []).length) return false
|
|
61
|
+
const goal = String(graph.goal || '').trim()
|
|
62
|
+
return !goal || [
|
|
63
|
+
'请填写任务图目标。',
|
|
64
|
+
'请在任务图编辑器中补充目标。',
|
|
65
|
+
'Graph Job 已打开。请在任务图面板中补充节点,完成预览和确认后再运行。'
|
|
66
|
+
].includes(goal)
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
function publicProfile(profile) {
|
|
59
70
|
const value = clone(profile)
|
|
60
71
|
if (!value) return value
|
|
@@ -77,6 +88,7 @@ export class GraphJobOrchestrator {
|
|
|
77
88
|
this.bindings = {}
|
|
78
89
|
this.previews = new Map()
|
|
79
90
|
this.plannerStatuses = new Map()
|
|
91
|
+
this.plannerRequests = new Map()
|
|
80
92
|
this.templatePreviews = new Map()
|
|
81
93
|
this.schedulers = new Map()
|
|
82
94
|
this.startLocks = new Map()
|
|
@@ -340,20 +352,20 @@ export class GraphJobOrchestrator {
|
|
|
340
352
|
this.initialize()
|
|
341
353
|
const sessionId = String(input.sessionId || '').trim()
|
|
342
354
|
const binding = this.getBinding(sessionId)
|
|
343
|
-
const
|
|
355
|
+
const activeGraph = this.graphForSession(sessionId)
|
|
356
|
+
const manualTemplateEdit = binding?.manualLock === true && !isUntouchedManualPlaceholder(activeGraph)
|
|
344
357
|
if (manualTemplateEdit && !['saveAs', 'overwrite'].includes(input.templateMode)) {
|
|
345
358
|
throw errorWithCode('当前会话的 Graph 已被手工编辑锁定;模型修订只能作为候选,并必须选择 saveAs 或 overwrite 模板。', 'TEMPLATE_DECISION_REQUIRED')
|
|
346
359
|
}
|
|
347
360
|
const cwd = String(input.cwd || '').trim() || process.cwd()
|
|
348
361
|
const catalog = await this.capabilities({ cwd })
|
|
349
|
-
const activeGraph = this.graphForSession(sessionId)
|
|
350
362
|
if (input.patch && input.baseGraph && activeGraph && (
|
|
351
363
|
String(input.baseGraph.graphId || '') !== String(activeGraph.graphId || '') ||
|
|
352
364
|
Number(input.baseGraph.revision || 0) !== Number(activeGraph.revision || 0)
|
|
353
365
|
)) {
|
|
354
366
|
throw errorWithCode('Planner patch 的 baseGraph 不是当前会话活动 revision。', 'GRAPH_REVISION_CONFLICT')
|
|
355
367
|
}
|
|
356
|
-
const baseGraph = input.patch ? activeGraph : (input.baseGraph || null)
|
|
368
|
+
const baseGraph = input.patch ? activeGraph : (input.baseGraph || (isUntouchedManualPlaceholder(activeGraph) ? activeGraph : null))
|
|
357
369
|
let draft
|
|
358
370
|
if (input.patch) {
|
|
359
371
|
if (!baseGraph) throw errorWithCode('Planner patch 找不到当前 Graph。', 'GRAPH_NOT_FOUND')
|
|
@@ -474,6 +486,7 @@ export class GraphJobOrchestrator {
|
|
|
474
486
|
state: String(status.state || 'idle'),
|
|
475
487
|
updatedAt: nowValue(this.now),
|
|
476
488
|
...(status.previewId ? { previewId: String(status.previewId) } : {}),
|
|
489
|
+
...(status.code ? { code: String(status.code).slice(0, 120) } : {}),
|
|
477
490
|
...(status.error ? { error: String(status.error).slice(0, 320) } : {})
|
|
478
491
|
}
|
|
479
492
|
this.plannerStatuses.set(id, value)
|
|
@@ -485,6 +498,25 @@ export class GraphJobOrchestrator {
|
|
|
485
498
|
return id ? clone(this.plannerStatuses.get(id) || null) : null
|
|
486
499
|
}
|
|
487
500
|
|
|
501
|
+
setPlannerRequest(sessionId, request = {}) {
|
|
502
|
+
const id = String(sessionId || '').trim()
|
|
503
|
+
if (!id) return null
|
|
504
|
+
const value = {
|
|
505
|
+
goal: String(request.goal || '').trim().slice(0, 20000),
|
|
506
|
+
cwd: String(request.cwd || '').trim()
|
|
507
|
+
}
|
|
508
|
+
if (request.draft && typeof request.draft === 'object') value.draft = clone(request.draft)
|
|
509
|
+
if (request.patch && typeof request.patch === 'object') value.patch = clone(request.patch)
|
|
510
|
+
if (request.baseGraph && typeof request.baseGraph === 'object') value.baseGraph = clone(request.baseGraph)
|
|
511
|
+
this.plannerRequests.set(id, value)
|
|
512
|
+
return clone(value)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
plannerRequestForSession(sessionId) {
|
|
516
|
+
const id = String(sessionId || '').trim()
|
|
517
|
+
return id ? clone(this.plannerRequests.get(id) || null) : null
|
|
518
|
+
}
|
|
519
|
+
|
|
488
520
|
latestPreviewForSession(sessionId) {
|
|
489
521
|
this.initialize()
|
|
490
522
|
const id = String(sessionId || '').trim()
|
|
@@ -497,6 +529,18 @@ export class GraphJobOrchestrator {
|
|
|
497
529
|
return latest ? this.publicPreview(latest) : null
|
|
498
530
|
}
|
|
499
531
|
|
|
532
|
+
latestTemplatePreviewForSession(sessionId) {
|
|
533
|
+
this.initialize()
|
|
534
|
+
const id = String(sessionId || '').trim()
|
|
535
|
+
if (!id) return null
|
|
536
|
+
let latest = null
|
|
537
|
+
for (const record of this.templatePreviews.values()) {
|
|
538
|
+
if (record.sessionId !== id || record.expiresAt < nowValue(this.now)) continue
|
|
539
|
+
latest = record
|
|
540
|
+
}
|
|
541
|
+
return latest ? this.getTemplatePreview(latest.previewId) : null
|
|
542
|
+
}
|
|
543
|
+
|
|
500
544
|
saveManualGraph(input = {}) {
|
|
501
545
|
this.initialize()
|
|
502
546
|
const graphInput = input.graph || input
|
|
@@ -544,7 +588,14 @@ export class GraphJobOrchestrator {
|
|
|
544
588
|
this.initialize()
|
|
545
589
|
const binding = this.getBinding(sessionId)
|
|
546
590
|
const graph = binding?.activeGraphId ? this.getGraph(binding.activeGraphId, binding.activeRevision) : null
|
|
547
|
-
return {
|
|
591
|
+
return {
|
|
592
|
+
binding,
|
|
593
|
+
graph,
|
|
594
|
+
preview: this.latestPreviewForSession(sessionId),
|
|
595
|
+
templatePreview: this.latestTemplatePreviewForSession(sessionId),
|
|
596
|
+
planner: this.plannerStatusForSession(sessionId),
|
|
597
|
+
graphs: [...this.graphs.values()].map(clone).filter(Boolean)
|
|
598
|
+
}
|
|
548
599
|
}
|
|
549
600
|
|
|
550
601
|
listTemplates(options = {}) {
|
|
@@ -847,6 +898,7 @@ export class GraphJobOrchestrator {
|
|
|
847
898
|
this.listeners.clear()
|
|
848
899
|
this.previews.clear()
|
|
849
900
|
this.plannerStatuses.clear()
|
|
901
|
+
this.plannerRequests.clear()
|
|
850
902
|
this.templatePreviews.clear()
|
|
851
903
|
this.startLocks.clear()
|
|
852
904
|
}
|
package/lib/validator.js
CHANGED
|
@@ -51,9 +51,11 @@ function reasoningEfforts(capabilities) {
|
|
|
51
51
|
function availableModels(capabilities, profile) {
|
|
52
52
|
if (!capabilities) return null
|
|
53
53
|
const providerModels = capabilities.subagentProviders?.find?.((item) => item?.name === profile.provider)?.models
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
if (profile.executor === 'codex') {
|
|
55
|
+
if (!Array.isArray(providerModels) || !providerModels.length) return null
|
|
56
|
+
return new Set(providerModels.map((model) => `${profile.provider}\u0000${String(model)}`))
|
|
57
|
+
}
|
|
58
|
+
const values = Array.isArray(capabilities.models) ? capabilities.models : []
|
|
57
59
|
if (!values.length) return null
|
|
58
60
|
return new Set(values.map((item) => {
|
|
59
61
|
if (typeof item === 'string') return `${profile.provider}\u0000${item}`
|
|
@@ -95,7 +97,7 @@ export function validateGraph(input, options = {}) {
|
|
|
95
97
|
profileMap.set(profile.id, profile)
|
|
96
98
|
if (!EXECUTORS.has(profile.executor)) push(errors, `agentProfiles[${index}].executor`, `不支持的 executor:${profile.executor}。`, 'EXECUTOR')
|
|
97
99
|
if (!profile.provider) push(errors, `agentProfiles[${index}].provider`, 'provider 必须在 revision 创建时解析完成。', 'MODEL_UNRESOLVED')
|
|
98
|
-
if (!profile.model) push(errors, `agentProfiles[${index}].model`, 'model 必须在 revision 创建时解析完成。', 'MODEL_UNRESOLVED')
|
|
100
|
+
if (!profile.model && profile.executor !== 'codex') push(errors, `agentProfiles[${index}].model`, 'model 必须在 revision 创建时解析完成。', 'MODEL_UNRESOLVED')
|
|
99
101
|
if (options.enforceExecutorAvailability && capabilities?.executors?.[profile.executor]) {
|
|
100
102
|
const executor = capabilities.executors[profile.executor]
|
|
101
103
|
if (executor.available === false) push(errors, `agentProfiles[${index}].executor`, `${profile.executor} executor 当前不可用。`, 'CAPABILITY_MISMATCH')
|
|
@@ -107,7 +109,7 @@ export function validateGraph(input, options = {}) {
|
|
|
107
109
|
if (modelSet && !modelSet.has(`${profile.provider}\u0000${profile.model}`)) {
|
|
108
110
|
push(errors, `agentProfiles[${index}].model`, `当前 capability 不提供 ${profile.provider}/${profile.model}。`, 'CAPABILITY_MISMATCH')
|
|
109
111
|
}
|
|
110
|
-
if (!['default', 'never', 'approve-for-me'].includes(profile.permissionMode)) {
|
|
112
|
+
if (!['default', 'never', 'approve-for-me', 'dangerously-bypass-approvals-and-sandbox'].includes(profile.permissionMode)) {
|
|
111
113
|
push(errors, `agentProfiles[${index}].permissionMode`, `不支持的 permissionMode:${profile.permissionMode}。`, 'PERMISSION_MODE')
|
|
112
114
|
}
|
|
113
115
|
for (const tool of profile.capabilities.tools) {
|
|
@@ -126,7 +128,7 @@ export function validateGraph(input, options = {}) {
|
|
|
126
128
|
}
|
|
127
129
|
if (profile.executor === 'codex' && profile.reasoningEffort) {
|
|
128
130
|
const effortSet = reasoningEfforts(capabilities)
|
|
129
|
-
if (
|
|
131
|
+
if (capabilities && (!effortSet || !effortSet.has(profile.reasoningEffort))) {
|
|
130
132
|
push(errors, `agentProfiles[${index}].reasoningEffort`, `当前 Codex capability 不支持 reasoningEffort=${profile.reasoningEffort}。`, 'CAPABILITY_MISMATCH')
|
|
131
133
|
}
|
|
132
134
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@p-dsh-market/graph-job-orchestrator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "DSH Graph Job 多 Subagent 任务图编排器",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -72,10 +72,20 @@
|
|
|
72
72
|
"type": "pluginRpc",
|
|
73
73
|
"method": "graphJobOrchestrator.open"
|
|
74
74
|
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "open-graph-job-subagent",
|
|
78
|
+
"slot": "desktop.titlebar.workspaceActions",
|
|
79
|
+
"label": "新建 Subagent",
|
|
80
|
+
"order": 145,
|
|
81
|
+
"when": ["dshRunning", "pluginActive", "restartNotRequired"],
|
|
82
|
+
"action": {
|
|
83
|
+
"type": "pluginRpc",
|
|
84
|
+
"method": "graphJobOrchestrator.openSubagent"
|
|
85
|
+
}
|
|
75
86
|
}
|
|
76
87
|
]
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
}
|
|
80
91
|
}
|
|
81
|
-
|