@goodandready/dsh-agent-orchestrator 0.1.6

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/lib/client.js ADDED
@@ -0,0 +1,1374 @@
1
+ /**
2
+ * DeepSeek Harness Multi-Agent Orchestrator Browser Client
3
+ *
4
+ * Scoped ID: @goodandready/dsh-agent-orchestrator
5
+ * Namespace: dsh-agent-orchestrator
6
+ */
7
+
8
+ window.__ModuleLoader__.load({
9
+ id: '@goodandready/dsh-agent-orchestrator',
10
+ factory: (require) => {
11
+ const module = { exports: {} }
12
+ const React = require('react')
13
+ const { useState, useEffect, useCallback } = React
14
+ const h = React.createElement
15
+
16
+ const NS = 'dsh-agent-orchestrator'
17
+
18
+ // ErrorBoundary to guard against any component rendering exceptions
19
+ class ErrorBoundary extends React.Component {
20
+ constructor(props) {
21
+ super(props)
22
+ this.state = { hasError: false, error: null }
23
+ }
24
+ static getDerivedStateFromError(error) {
25
+ return { hasError: true, error }
26
+ }
27
+ componentDidCatch(error, errorInfo) {
28
+ console.error('[dsh-agent-orchestrator] UI render error:', error, errorInfo)
29
+ }
30
+ render() {
31
+ if (this.state.hasError) {
32
+ return h('div', { style: { padding: '12px 16px', color: 'var(--dsw-alias-state-error-primary)', fontSize: 13 } },
33
+ h('strong', null, 'Orchestrator UI Error: '),
34
+ String(this.state.error?.message || this.state.error),
35
+ h('button', {
36
+ type: 'button',
37
+ style: { marginLeft: 12, padding: '2px 8px', fontSize: 11, cursor: 'pointer' },
38
+ onClick: () => this.setState({ hasError: false, error: null }),
39
+ }, 'Retry')
40
+ )
41
+ }
42
+ return this.props?.children || null
43
+ }
44
+ }
45
+
46
+ // System Chevron icon helper
47
+ let ChevronIcon = null
48
+ try {
49
+ const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
50
+ ChevronIcon = primitives && primitives.IconChevronDownOutline14
51
+ } catch {
52
+ ChevronIcon = null
53
+ }
54
+
55
+ const FallbackChevron = () =>
56
+ h('svg', {
57
+ width: 14,
58
+ height: 14,
59
+ viewBox: '0 0 14 14',
60
+ fill: 'none',
61
+ stroke: 'currentColor',
62
+ strokeWidth: 1.5,
63
+ }, h('path', { d: 'M3.5 5.25L7 8.75L10.5 5.25', strokeLinecap: 'round', strokeLinejoin: 'round' }))
64
+
65
+ const Chevron = ChevronIcon || FallbackChevron
66
+
67
+ // CSS Theme Styles Injection
68
+ const CSS = `
69
+
70
+ .dso-filter-btn { appearance: none; font: inherit; background: transparent; border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; padding: 2px 8px; font-size: 11px; color: var(--dsw-alias-label-secondary); cursor: pointer; transition: all 0.12s ease; }
71
+ .dso-filter-btn:hover { background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary); }
72
+ .dso-filter-btn.active { background: var(--dsw-alias-bg-layer-3); border-color: var(--dsw-alias-state-info-border); color: var(--dsw-alias-state-info-primary); font-weight: 600; }
73
+ .dso-toggle-label { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary); cursor: pointer; }
74
+ .dso-toggle-checkbox { width: 14px; height: 14px; cursor: pointer; }
75
+ .dso-preset-btn { appearance: none; font: inherit; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; padding: 3px 8px; font-size: 11px; font-weight: 500; color: var(--dsw-alias-label-primary); cursor: pointer; display: inline-flex; align-items: center; gap: 4px; transition: all 0.12s ease; }
76
+ .dso-preset-btn:hover { background: var(--dsw-alias-bg-layer-4); border-color: var(--dsw-alias-state-info-border); }
77
+
78
+ .dso-card { border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-3); border-radius: 12px; list-style: none; margin-bottom: 12px; }
79
+ .dso-head { appearance: none; width: 100%; font: inherit; color: inherit; text-align: left; cursor: pointer; background: 0 0; border: 0; border-radius: 12px; display: flex; align-items: center; gap: 12px; padding: 14px 16px; }
80
+ .dso-head-info { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
81
+ .dso-title { color: var(--dsw-alias-label-primary); font-size: 15px; font-weight: 600; line-height: 1.4; display: flex; align-items: center; gap: 8px; }
82
+ .dso-badge { font-size: 11px; font-weight: 500; padding: 2px 7px; border-radius: 6px; background: var(--dsw-alias-state-info-bg); color: var(--dsw-alias-state-info-primary); border: 1px solid var(--dsw-alias-state-info-border); }
83
+ .dso-sub { color: var(--dsw-alias-label-secondary); font-size: 13px; }
84
+ .dso-chev { margin-left: auto; flex: none; color: var(--dsw-alias-label-tertiary); transition: transform 0.16s ease; }
85
+ .dso-chev-open { transform: rotate(180deg); }
86
+ .dso-body { border-top: 1px solid var(--dsw-alias-border-l2); margin: 0 16px; padding: 12px 0 16px; }
87
+
88
+ .dso-tabs { display: flex; gap: 6px; border-bottom: 1px solid var(--dsw-alias-border-l2); padding-bottom: 10px; margin-bottom: 14px; overflow-x: auto; }
89
+ .dso-tab { appearance: none; background: transparent; border: 1px solid transparent; border-radius: 7px; padding: 5px 12px; font-size: 13px; color: var(--dsw-alias-label-secondary); cursor: pointer; font-weight: 500; transition: all 0.15s; }
90
+ .dso-tab:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-2); }
91
+ .dso-tab-active { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-2); border-color: var(--dsw-alias-border-l2); }
92
+
93
+ .dso-panel { display: flex; flex-direction: column; gap: 14px; }
94
+ .dso-agent-list { display: flex; flex-direction: column; gap: 10px; max-height: 480px; overflow-y: auto; padding-right: 4px; }
95
+ .dso-agent-row { border: 1px solid var(--dsw-alias-border-l2); border-radius: 9px; padding: 12px; background: var(--dsw-alias-bg-layer-2); display: flex; flex-direction: column; gap: 8px; }
96
+ .dso-agent-header { display: flex; align-items: center; justify-content: space-between; }
97
+ .dso-agent-name { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary); }
98
+ .dso-agent-role { font-size: 12px; color: var(--dsw-alias-label-secondary); }
99
+
100
+ .dso-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
101
+ .dso-field { display: flex; flex-direction: column; gap: 4px; }
102
+ .dso-label { font-size: 12px; font-weight: 500; color: var(--dsw-alias-label-secondary); }
103
+ .dso-input, .dso-select { height: 32px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary); border-radius: 7px; padding: 0 10px; font-size: 13px; outline: none; }
104
+ .dso-textarea { border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary); border-radius: 7px; padding: 8px 10px; font-size: 12px; font-family: monospace; outline: none; resize: vertical; min-height: 70px; }
105
+
106
+ .dso-stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 12px; }
107
+ .dso-stat-box { border: 1px solid var(--dsw-alias-border-l2); border-radius: 9px; padding: 12px; text-align: center; background: var(--dsw-alias-bg-layer-2); }
108
+ .dso-stat-val { font-size: 20px; font-weight: 700; color: var(--dsw-alias-state-success-primary); }
109
+ .dso-stat-lbl { font-size: 12px; color: var(--dsw-alias-label-secondary); margin-top: 4px; }
110
+
111
+ .dso-foot { border-top: 1px solid var(--dsw-alias-border-l2); display: flex; justify-content: flex-end; align-items: center; gap: 8px; padding-top: 12px; margin-top: 10px; }
112
+ .dso-btn { appearance: none; font: inherit; cursor: pointer; border: 1px solid transparent; border-radius: 8px; padding: 6px 14px; font-size: 13px; font-weight: 500; }
113
+ .dso-btn-primary { background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-3); }
114
+ .dso-btn-subtle { background: transparent; border-color: var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); }
115
+
116
+ .dso-widget { display: inline-flex; align-items: center; gap: 8px; padding: 4px 10px; border-radius: 8px; background: var(--dsw-alias-state-info-bg); border: 1px solid var(--dsw-alias-state-info-border); font-size: 12px; color: var(--dsw-alias-label-primary); }
117
+ .dso-widget-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-state-success-primary); box-shadow: 0 0 6px var(--dsw-alias-state-success-primary); }
118
+ .dso-widget-badge { font-weight: 600; color: var(--dsw-alias-state-info-primary); }
119
+
120
+ .dso-dock { box-sizing: border-box; width: calc(100% - var(--dsh-composer-side-clearance, 0px) * 2 - var(--dsh-composer-dock-inset, 0px) * 4); max-width: calc(var(--dsh-composer-card-max-width, 768px) - var(--dsh-composer-dock-inset, 0px) * 4); margin: 0 auto 8px auto; display: flex; justify-content: center; }
121
+ .dso-dock.dso-dock-quick { justify-content: flex-start; margin-bottom: 4px; }
122
+ .dso-banner { box-sizing: border-box; width: 100%; display: flex; align-items: center; justify-content: space-between; background: var(--dsw-alias-bg-layer-3); border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; padding: 7px 14px; font-family: inherit; font-size: 13px; color: var(--dsw-alias-label-primary); user-select: none; box-shadow: none; transition: all 0.15s ease; gap: 8px; }
123
+ .dso-banner:hover { border-color: var(--dsw-alias-label-tertiary); }
124
+ .dso-banner.completed { border-color: var(--dsw-alias-state-success-primary); }
125
+ .dso-banner.failed { border-color: var(--dsw-alias-state-error-primary); }
126
+ .dso-banner-left { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; }
127
+ .dso-banner-badge { font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2); display: inline-flex; align-items: center; gap: 4px; font-weight: 600; white-space: nowrap; background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }
128
+ .dso-banner-badge.dso-badge-running { border-color: var(--dsw-alias-state-info-border); color: var(--dsw-alias-state-info-primary); background: var(--dsw-alias-state-info-bg); }
129
+ .dso-banner-badge.dso-badge-ok { border-color: var(--dsw-alias-state-success-border); color: var(--dsw-alias-state-success-primary); background: var(--dsw-alias-state-success-bg); }
130
+ .dso-banner-badge.dso-badge-err { border-color: var(--dsw-alias-state-error-border); color: var(--dsw-alias-state-error-primary); background: var(--dsw-alias-state-error-bg); }
131
+ .dso-banner-title { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; }
132
+ .dso-banner-stage { font-size: 12px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-bg-layer-2); padding: 2px 6px; border-radius: 5px; white-space: nowrap; }
133
+ .dso-banner-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
134
+ .dso-banner-time { font-size: 12px; color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; }
135
+ .dso-dock-btn { appearance: none; font: inherit; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; padding: 3px 8px; font-size: 12px; color: var(--dsw-alias-label-primary); cursor: pointer; display: inline-flex; align-items: center; gap: 4px; transition: all 0.12s; }
136
+ .dso-dock-btn:hover { background: var(--dsw-alias-bg-layer-4); border-color: var(--dsw-alias-label-tertiary); }
137
+ .dso-dock-btn.close { padding: 3px 6px; color: var(--dsw-alias-label-secondary); }
138
+ .dso-dock-btn.close:hover { color: var(--dsw-alias-state-error-primary); }
139
+ .dso-quicklaunch-btn { appearance: none; font: inherit; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px; padding: 4px 12px; font-size: 12px; font-weight: 500; color: var(--dsw-alias-label-secondary); cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: all 0.12s ease; }
140
+ .dso-quicklaunch-btn:hover { background: var(--dsw-alias-bg-layer-3); border-color: var(--dsw-alias-label-tertiary); color: var(--dsw-alias-label-primary); }
141
+ .dso-pulse-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-state-info-primary); animation: dso-pulse 1.6s infinite ease-in-out; }
142
+ @keyframes dso-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.4; transform: scale(0.8); } }
143
+ .dso-modal-backdrop { position: fixed; inset: 0; background: var(--dsw-alias-bg-mask, var(--dsw-alias-bg-layer-4)); z-index: 99999; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(2px); }
144
+ .dso-modal { background: var(--dsw-alias-bg-layer-3); border: 1px solid var(--dsw-alias-border-l2); border-radius: 14px; width: 92%; max-width: 580px; max-height: 85vh; display: flex; flex-direction: column; overflow: hidden; box-shadow: var(--dsw-alias-shadow-modal, 0 8px 24px var(--dsw-alias-border-l2)); font-family: inherit; color: var(--dsw-alias-label-primary); }
145
+ .dso-modal-head { padding: 14px 18px; border-bottom: 1px solid var(--dsw-alias-border-l2); display: flex; align-items: center; justify-content: space-between; font-weight: 600; font-size: 15px; }
146
+ .dso-modal-body { padding: 16px 18px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; }
147
+ .dso-modal-foot { padding: 12px 18px; border-top: 1px solid var(--dsw-alias-border-l2); display: flex; justify-content: flex-end; gap: 8px; }
148
+ .dso-stage-item { border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; padding: 10px 12px; background: var(--dsw-alias-bg-layer-2); display: flex; flex-direction: column; gap: 6px; }
149
+ .dso-stage-head { display: flex; align-items: center; justify-content: space-between; font-size: 13px; font-weight: 500; }
150
+ .dso-stage-log { font-family: monospace; font-size: 11px; background: var(--dsw-alias-bg-layer-1); padding: 8px; border-radius: 6px; max-height: 120px; overflow-y: auto; white-space: pre-wrap; color: var(--dsw-alias-label-secondary); }
151
+ /* Issue #3: Subagent Header Utilities Chip */
152
+ .dso-subagent-header-chip { display: inline-flex; align-items: center; gap: 6px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l2); border-radius: 999px; padding: 3px 10px; font-size: 12px; font-weight: 500; color: var(--dsw-alias-label-primary); }
153
+ .dso-chip-icon { font-size: 13px; }
154
+ .dso-chip-role { font-weight: 600; color: var(--dsw-alias-state-info-primary); }
155
+ .dso-chip-sep { color: var(--dsw-alias-label-tertiary); }
156
+ .dso-chip-model { font-size: 11px; color: var(--dsw-alias-label-secondary); }
157
+ .dso-chip-parent-link { color: var(--dsw-alias-link); text-decoration: none; font-size: 11px; padding: 1px 6px; background: var(--dsw-alias-state-info-bg); border-radius: 4px; transition: all 0.15s ease; cursor: pointer; }
158
+ .dso-chip-parent-link:hover { background: var(--dsw-alias-bg-layer-3); text-decoration: underline; }
159
+
160
+ /* Issue #5: Specialist Toolview Card */
161
+ .dso-toolview-card { background: var(--dsw-alias-bg-layer-3); border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px; padding: 12px 14px; margin: 6px 0; display: flex; flex-direction: column; gap: 10px; font-family: inherit; font-size: 13px; color: var(--dsw-alias-label-primary); }
162
+ .dso-toolview-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
163
+ .dso-toolview-badge { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; font-size: 13px; }
164
+ .dso-toolview-model-tag { font-size: 11px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-bg-layer-2); padding: 2px 7px; border-radius: 6px; border: 1px solid var(--dsw-alias-border-l1); }
165
+ .dso-model-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 500; padding: 2px 7px; border-radius: 6px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary); cursor: help; white-space: nowrap; user-select: none; }
166
+ .dso-model-chip.reasoner { border-color: var(--dsw-alias-state-warning-border, var(--dsw-alias-border-l2)); color: var(--dsw-alias-state-warning-text, var(--dsw-alias-label-primary)); background: var(--dsw-alias-state-warning-bg); }
167
+ .dso-model-chip.coder { border-color: var(--dsw-alias-state-info-border, var(--dsw-alias-border-l2)); color: var(--dsw-alias-state-info-text, var(--dsw-alias-label-primary)); background: var(--dsw-alias-state-info-bg); }
168
+ .dso-model-chip.fast { border-color: var(--dsw-alias-state-success-border, var(--dsw-alias-border-l2)); color: var(--dsw-alias-state-success-text, var(--dsw-alias-label-primary)); background: var(--dsw-alias-state-success-bg); }
169
+ .dso-model-chip.local { border-color: var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-bg-layer-1); }
170
+ .dso-toolview-status-pill { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; }
171
+ .dso-toolview-status-pill.ok { background: var(--dsw-alias-state-success-bg); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-border); }
172
+ .dso-toolview-status-pill.running { background: var(--dsw-alias-state-info-bg); color: var(--dsw-alias-state-info-primary); border: 1px solid var(--dsw-alias-state-info-border); }
173
+ .dso-toolview-status-pill.err { background: var(--dsw-alias-state-error-bg); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-border); }
174
+ .dso-toolview-tooldiff { background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; padding: 8px 10px; display: flex; flex-direction: column; gap: 6px; }
175
+ .dso-tooldiff-summary { cursor: pointer; font-size: 12px; font-weight: 500; color: var(--dsw-alias-label-secondary); display: flex; align-items: center; gap: 6px; user-select: none; }
176
+ .dso-tooldiff-pills { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
177
+ .dso-pill { padding: 1px 6px; border-radius: 4px; font-size: 11px; font-family: monospace; }
178
+ .dso-pill.allow { background: var(--dsw-alias-state-success-bg); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-border); }
179
+ .dso-pill.blocked { background: var(--dsw-alias-state-error-bg); color: var(--dsw-alias-state-error-primary); text-decoration: line-through; border: 1px solid var(--dsw-alias-state-error-border); }
180
+ .dso-pill.denied { background: var(--dsw-alias-state-warning-bg); color: var(--dsw-alias-state-warning-primary); border: 1px solid var(--dsw-alias-state-warning-border); }
181
+ .dso-toolview-deliverable { background: var(--dsw-alias-bg-layer-2); border-radius: 6px; padding: 10px 12px; font-size: 13px; line-height: 1.5; max-height: 280px; overflow-y: auto; white-space: pre-wrap; color: var(--dsw-alias-label-primary); border: 1px solid var(--dsw-alias-border-l1); }
182
+ .dso-toolview-callout { padding: 6px 10px; border-radius: 6px; font-size: 12px; font-weight: 500; background: var(--dsw-alias-state-success-bg); border: 1px solid var(--dsw-alias-state-success-border); color: var(--dsw-alias-state-success-primary); display: flex; align-items: center; gap: 6px; }
183
+
184
+ `
185
+
186
+ function formatModelChip(modelStr, options = {}) {
187
+ const m = String(modelStr || '').toLowerCase()
188
+ let label = 'General'
189
+ let alias = modelStr || 'default'
190
+ let badgeClass = 'general'
191
+
192
+ if (/reasoner|r1/i.test(m)) {
193
+ alias = 'R1'
194
+ label = 'Reasoner'
195
+ badgeClass = 'reasoner'
196
+ } else if (/o1/i.test(m)) {
197
+ alias = 'o1'
198
+ label = 'Reasoner'
199
+ badgeClass = 'reasoner'
200
+ } else if (/o3/i.test(m)) {
201
+ alias = 'o3'
202
+ label = 'Reasoner'
203
+ badgeClass = 'reasoner'
204
+ } else if (/deepseek-chat|v3/i.test(m)) {
205
+ alias = 'V3'
206
+ label = 'Fast Coder'
207
+ badgeClass = 'coder'
208
+ } else if (/coder/i.test(m)) {
209
+ alias = 'Coder'
210
+ label = 'Coder'
211
+ badgeClass = 'coder'
212
+ } else if (/sonnet/i.test(m)) {
213
+ alias = 'Sonnet 3.5'
214
+ label = 'Coder'
215
+ badgeClass = 'coder'
216
+ } else if (/flash|mini|haiku/i.test(m)) {
217
+ alias = m.includes('mini') ? 'Mini' : (m.includes('haiku') ? 'Haiku' : 'Flash')
218
+ label = 'Fast'
219
+ badgeClass = 'fast'
220
+ } else if (/qwen/i.test(m)) {
221
+ alias = 'Qwen'
222
+ label = 'Local'
223
+ badgeClass = 'local'
224
+ }
225
+
226
+ const maxTokens = options.maxTokens || (/reasoner|r1|o1|o3/i.test(m) ? 8192 : (/mini|flash|fast/i.test(m) ? 2048 : 4096))
227
+ const reasoningText = /reasoner|r1|o1|o3/i.test(m) ? 'Supported' : 'No'
228
+ const tooltip = `${modelStr || 'default'} (Context: 64k, Output: ${maxTokens}, Reasoning: ${reasoningText})`
229
+
230
+ return {
231
+ text: `[${label} · ${alias}]`,
232
+ label,
233
+ alias,
234
+ badgeClass,
235
+ tooltip,
236
+ }
237
+ }
238
+
239
+ function ensureStyles() {
240
+ if (document.querySelector('style[data-dsh-plugin="dsh-agent-orchestrator"]')) return
241
+ const style = document.createElement('style')
242
+ style.dataset.dshPlugin = 'dsh-agent-orchestrator'
243
+ style.textContent = CSS
244
+ document.head.appendChild(style)
245
+ }
246
+
247
+ // --- Tab 1: Agent Profiles (Issue #101 Enabled Roster / Toggle Filter) ---
248
+ function AgentProfilesTab({ roles = {}, onRoleChange, onAddRole }) {
249
+ const [filterMode, setFilterMode] = useState('all')
250
+ const allRoles = Object.values(roles)
251
+ const roleList = filterMode === 'active' ? allRoles.filter((r) => r.enabled !== false) : allRoles
252
+
253
+ return h('div', { className: 'dso-panel' },
254
+ h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10 } },
255
+ h('span', { className: 'dso-sub' }, 'Self-contained agent profiles stored purely in plugin settings (no external files).'),
256
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
257
+ h('div', { style: { display: 'inline-flex', gap: 4 } },
258
+ h('button', {
259
+ type: 'button',
260
+ className: `dso-filter-btn ${filterMode === 'all' ? 'active' : ''}`,
261
+ onClick: () => setFilterMode('all'),
262
+ }, `All (${allRoles.length})`),
263
+ h('button', {
264
+ type: 'button',
265
+ className: `dso-filter-btn ${filterMode === 'active' ? 'active' : ''}`,
266
+ onClick: () => setFilterMode('active'),
267
+ }, `Active Only (${allRoles.filter((r) => r.enabled !== false).length})`)
268
+ ),
269
+ h('button', {
270
+ className: 'dso-btn dso-btn-subtle',
271
+ type: 'button',
272
+ onClick: onAddRole,
273
+ }, '+ Add Custom Agent')
274
+ )
275
+ ),
276
+ h('div', { className: 'dso-agent-list' },
277
+ roleList.map((role) => {
278
+ const isEnabled = role.enabled !== false
279
+ const modelChip = formatModelChip(role.defaultModel?.model, { maxTokens: role.maxTokens })
280
+ return h('div', { key: role.id, className: 'dso-agent-row', style: { opacity: isEnabled ? 1 : 0.65 } },
281
+ h('div', { className: 'dso-agent-header' },
282
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
283
+ h('label', { className: 'dso-toggle-label', title: isEnabled ? 'Agent is Active' : 'Agent is Disabled' },
284
+ h('input', {
285
+ type: 'checkbox',
286
+ className: 'dso-toggle-checkbox',
287
+ checked: isEnabled,
288
+ onChange: (e) => onRoleChange(role.id, { enabled: e.target.checked }),
289
+ }),
290
+ h('span', { style: { fontWeight: 500, color: isEnabled ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-tertiary)' } },
291
+ isEnabled ? 'Active' : 'Disabled'
292
+ )
293
+ ),
294
+ h('span', { className: 'dso-agent-name' }, role.name),
295
+ h('span', { className: 'dso-agent-role' }, `(${role.category || role.id})`),
296
+ h('span', { className: `dso-model-chip ${modelChip.badgeClass}`, title: modelChip.tooltip }, modelChip.text)
297
+ ),
298
+ h('span', { className: 'dso-badge' }, `Reasoning: ${role.reasoningEffort || 'medium'}`)
299
+ ),
300
+ h('div', { className: 'dso-grid-2' },
301
+ h('div', { className: 'dso-field' },
302
+ h('label', { className: 'dso-label' }, 'Model Provider & Name'),
303
+ h('input', {
304
+ className: 'dso-input',
305
+ value: role.defaultModel?.model || '',
306
+ onChange: (e) => onRoleChange(role.id, {
307
+ defaultModel: { ...(role.defaultModel || { provider: 'deepseek' }), model: e.target.value },
308
+ }),
309
+ placeholder: 'e.g. deepseek-chat or claude-3-5-sonnet',
310
+ })
311
+ ),
312
+ h('div', { className: 'dso-field' },
313
+ h('label', { className: 'dso-label' }, 'Max Output Tokens (0 = Default)'),
314
+ h('input', {
315
+ className: 'dso-input',
316
+ type: 'number',
317
+ value: role.maxTokens || '',
318
+ onChange: (e) => onRoleChange(role.id, {
319
+ maxTokens: e.target.value ? parseInt(e.target.value, 10) : undefined,
320
+ }),
321
+ placeholder: 'e.g. 2048, 4096, 8192',
322
+ })
323
+ )
324
+ ),
325
+ h('div', { className: 'dso-field' },
326
+ h('label', { className: 'dso-label' }, 'Reasoning Effort (Supported on Reasoning Models)'),
327
+ h('select', {
328
+ className: 'dso-select',
329
+ value: role.reasoningEffort || 'medium',
330
+ onChange: (e) => onRoleChange(role.id, { reasoningEffort: e.target.value }),
331
+ },
332
+ h('option', { value: 'disabled' }, 'Disabled (None)'),
333
+ h('option', { value: 'off' }, 'Off'),
334
+ h('option', { value: 'low' }, 'Low (Fast)'),
335
+ h('option', { value: 'medium' }, 'Medium (Standard)'),
336
+ h('option', { value: 'high' }, 'High (Deep Thinking)'),
337
+ h('option', { value: 'max' }, 'Max (Exhaustive Reasoning)')
338
+ )
339
+ ),
340
+ h('div', { className: 'dso-field' },
341
+ h('label', { className: 'dso-label' }, 'SOUL / Specialized Role Instructions'),
342
+ h('textarea', {
343
+ className: 'dso-textarea',
344
+ value: role.systemPrompt || '',
345
+ onChange: (e) => onRoleChange(role.id, { systemPrompt: e.target.value }),
346
+ placeholder: 'Describe role responsibilities, deliverables, and strict boundaries...',
347
+ })
348
+ ),
349
+ h('div', { className: 'dso-field' },
350
+ h('label', { className: 'dso-label' }, 'Skills (comma-separated)'),
351
+ h('input', {
352
+ className: 'dso-input',
353
+ value: Array.isArray(role.skills) ? role.skills.join(', ') : '',
354
+ onChange: (e) => onRoleChange(role.id, {
355
+ skills: e.target.value.split(',').map((s) => s.trim()).filter(Boolean),
356
+ }),
357
+ placeholder: 'e.g. project-design-contract, dsh-ui-design',
358
+ })
359
+ )
360
+ )
361
+ })
362
+ )
363
+ )
364
+ }
365
+
366
+ // --- Tab 2: Workflow Scenarios ---
367
+ function ScenariosTab({ scenarios = {}, roles = {} }) {
368
+ const scenarioList = Object.values(scenarios)
369
+
370
+ return h('div', { className: 'dso-panel' },
371
+ h('span', { className: 'dso-sub' }, 'Interactive DAG topologies: stages, blockers, and concurrency permissions.'),
372
+ scenarioList.map((sc) =>
373
+ h('div', { key: sc.id, className: 'dso-agent-row' },
374
+ h('div', { className: 'dso-agent-header' },
375
+ h('div', null,
376
+ h('strong', { style: { color: 'var(--dsw-alias-label-primary)' } }, sc.title),
377
+ h('div', { className: 'dso-sub' }, sc.description)
378
+ ),
379
+ h('span', { className: 'dso-badge' }, `${sc.stages?.length || 0} Stages`)
380
+ ),
381
+ h('div', { style: { marginTop: 6, display: 'flex', flexDirection: 'column', gap: 6 } },
382
+ (sc.stages || []).map((st, idx) =>
383
+ h('div', {
384
+ key: st.id,
385
+ style: {
386
+ display: 'flex',
387
+ alignItems: 'center',
388
+ gap: 8,
389
+ padding: '6px 10px',
390
+ background: 'var(--dsw-alias-bg-layer-2)',
391
+ borderRadius: 6,
392
+ fontSize: 12,
393
+ },
394
+ },
395
+ h('span', { style: { fontWeight: 600, width: 20 } }, `${idx + 1}.`),
396
+ h('span', { style: { flex: 1, color: 'var(--dsw-alias-label-primary)' } }, st.name),
397
+ h('span', { className: 'dso-badge', style: { fontSize: 10 } }, `Role: ${roles[st.roleId]?.name || st.roleId}`),
398
+ st.dependsOn?.length > 0
399
+ ? h('span', { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: 11 } }, `Blocks on: ${st.dependsOn.join(', ')}`)
400
+ : h('span', { style: { color: 'var(--dsw-alias-state-success-primary)', fontSize: 11 } }, 'Starts immediately')
401
+ )
402
+ )
403
+ )
404
+ )
405
+ )
406
+ )
407
+ }
408
+
409
+ // --- Tab 3: Prompt Caching Telemetry ---
410
+ function CacheMetricsTab({ metrics = {} }) {
411
+ const hitRatioPct = Math.round((metrics.overallHitRatio || 0) * 100)
412
+
413
+ return h('div', { className: 'dso-panel' },
414
+ h('div', { className: 'dso-stats-grid' },
415
+ h('div', { className: 'dso-stat-box' },
416
+ h('div', { className: 'dso-stat-val' }, `${hitRatioPct}%`),
417
+ h('div', { className: 'dso-stat-lbl' }, 'Overall Cache Hit Rate')
418
+ ),
419
+ h('div', { className: 'dso-stat-box' },
420
+ h('div', { className: 'dso-stat-val', style: { color: 'var(--dsw-alias-state-info-primary)' } }, (metrics.totalCacheHitTokens || 0).toLocaleString()),
421
+ h('div', { className: 'dso-stat-lbl' }, 'Cached Input Tokens Reused')
422
+ ),
423
+ h('div', { className: 'dso-stat-box' },
424
+ h('div', { className: 'dso-stat-val', style: { color: 'var(--dsw-alias-state-warning-primary)' } }, String(metrics.completedPipelines || 0)),
425
+ h('div', { className: 'dso-stat-lbl' }, 'Completed Pipelines')
426
+ )
427
+ ),
428
+ h('div', { style: { padding: 12, background: 'var(--dsw-alias-bg-layer-2)', borderRadius: 8, fontSize: 13, lineHeight: 1.6 } },
429
+ h('strong', { style: { color: 'var(--dsw-alias-label-primary)' } }, 'Prompt Caching Mechanics:'),
430
+ h('p', { style: { margin: '6px 0 0', color: 'var(--dsw-alias-label-secondary)' } },
431
+ 'All agents sharing the same model receive an identical canonical base anchor (>1024 tokens) and shared task context. Subsequent stages sequentially append prior artifacts, yielding near-instantaneous Time to First Token (TTFT) and an estimated 80–90% cost savings on DeepSeek and Claude models.'
432
+ )
433
+ )
434
+ )
435
+ }
436
+
437
+ // --- Tab 4: Kanban Integration ---
438
+ function KanbanTab({ config = {}, onConfigChange }) {
439
+ const kanbanSync = config.kanbanSync || {
440
+ enabled: false,
441
+ targetColumn: 'review',
442
+ createChecklist: true,
443
+ }
444
+
445
+ const updateSync = (patch) => {
446
+ onConfigChange({
447
+ kanbanSync: { ...kanbanSync, ...patch },
448
+ })
449
+ }
450
+
451
+ return h('div', { className: 'dso-panel' },
452
+ h('span', { className: 'dso-sub' },
453
+ 'Bidirectional synchronization with @goodandready/dsh-kanban: update task checklists and advance cards upon pipeline completion.'
454
+ ),
455
+ h('div', { className: 'dso-agent-row' },
456
+ h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
457
+ h('div', null,
458
+ h('strong', { style: { color: 'var(--dsw-alias-label-primary)' } }, 'Enable Kanban Synchronization'),
459
+ h('div', { className: 'dso-sub' }, 'Automatically link orchestrated DAG pipelines to Kanban task cards.')
460
+ ),
461
+ h('input', {
462
+ type: 'checkbox',
463
+ style: { width: 18, height: 18, cursor: 'pointer' },
464
+ checked: !!kanbanSync.enabled,
465
+ onChange: (e) => updateSync({ enabled: e.target.checked }),
466
+ })
467
+ ),
468
+ h('div', { className: 'dso-grid-2', style: { marginTop: 10 } },
469
+ h('div', { className: 'dso-field' },
470
+ h('label', { className: 'dso-label' }, 'Target Column on Completion'),
471
+ h('input', {
472
+ className: 'dso-input',
473
+ value: kanbanSync.targetColumn || 'review',
474
+ onChange: (e) => updateSync({ targetColumn: e.target.value }),
475
+ placeholder: 'e.g. review, deploy, in-progress',
476
+ }),
477
+ h('span', { style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', marginTop: 3 } },
478
+ 'Default: "review". Automated agents finish work in Review; moving to "done" is reserved for human acceptance.'
479
+ )
480
+ ),
481
+ h('div', { className: 'dso-field' },
482
+ h('label', { className: 'dso-label' }, 'Auto-create Checklist Items'),
483
+ h('select', {
484
+ className: 'dso-select',
485
+ value: kanbanSync.createChecklist !== false ? 'true' : 'false',
486
+ onChange: (e) => updateSync({ createChecklist: e.target.value === 'true' }),
487
+ },
488
+ h('option', { value: 'true' }, 'Enabled (Add DAG stages to card checklist)'),
489
+ h('option', { value: 'false' }, 'Disabled (Only move columns)')
490
+ )
491
+ )
492
+ ),
493
+ h('div', { style: { marginTop: 8, padding: '8px 12px', background: 'var(--dsw-alias-bg-layer-2)', borderRadius: 7, fontSize: 12, color: 'var(--dsw-alias-label-secondary)' } },
494
+ 'Connected to Kanban Service on port 3080/3082. Changes are applied upon clicking "Save Changes" below.'
495
+ )
496
+ )
497
+ )
498
+ }
499
+
500
+ // --- Main Settings Card ---
501
+ function OrchestratorSettingsCard() {
502
+ const [isOpen, setIsOpen] = useState(false)
503
+ const [activeTab, setActiveTab] = useState('roles')
504
+ const [config, setConfig] = useState(null)
505
+ const [metrics, setMetrics] = useState({})
506
+ const [saving, setSaving] = useState(false)
507
+ const [statusMsg, setStatusMsg] = useState('')
508
+
509
+ useEffect(() => {
510
+ ensureStyles()
511
+ fetch('/dsh-agent-orchestrator/config')
512
+ .then((r) => r.json())
513
+ .then((d) => setConfig(d.config))
514
+ .catch(() => {})
515
+
516
+ fetch('/dsh-agent-orchestrator/status')
517
+ .then((r) => r.json())
518
+ .then((d) => setMetrics(d.metrics || {}))
519
+ .catch(() => {})
520
+ }, [])
521
+
522
+ const handleRoleChange = useCallback((roleId, patch) => {
523
+ setConfig((prev) => {
524
+ if (!prev) return prev
525
+ const updatedRoles = {
526
+ ...prev.roles,
527
+ [roleId]: { ...prev.roles[roleId], ...patch },
528
+ }
529
+ return { ...prev, roles: updatedRoles }
530
+ })
531
+ }, [])
532
+
533
+ const handleAddRole = useCallback(() => {
534
+ const newId = `custom_role_${Date.now().toString(36)}`
535
+ setConfig((prev) => {
536
+ if (!prev) return prev
537
+ const updatedRoles = {
538
+ ...prev.roles,
539
+ [newId]: {
540
+ id: newId,
541
+ name: 'New Custom Specialist',
542
+ category: 'Custom Specialty',
543
+ defaultModel: { provider: 'deepseek', model: 'deepseek-chat' },
544
+ reasoningEffort: 'medium',
545
+ skills: [],
546
+ tools: ['view_file'],
547
+ systemPrompt: 'You are a specialized agent. Fulfill designated stage deliverables.',
548
+ temperature: 0.2,
549
+ maxTokens: 4096,
550
+ },
551
+ }
552
+ return { ...prev, roles: updatedRoles }
553
+ })
554
+ }, [])
555
+
556
+ const handleSave = async () => {
557
+ setSaving(true)
558
+ setStatusMsg('')
559
+ try {
560
+ const res = await fetch('/dsh-agent-orchestrator/config', {
561
+ method: 'POST',
562
+ headers: { 'Content-Type': 'application/json' },
563
+ body: JSON.stringify(config),
564
+ })
565
+ if (res.ok) {
566
+ setStatusMsg('Settings saved successfully')
567
+ setTimeout(() => setStatusMsg(''), 3000)
568
+ } else {
569
+ setStatusMsg('Error saving settings')
570
+ }
571
+ } catch {
572
+ setStatusMsg('Network error')
573
+ } finally {
574
+ setSaving(false)
575
+ }
576
+ }
577
+
578
+ return h('li', { className: 'dso-card' },
579
+ h('button', {
580
+ type: 'button',
581
+ className: 'dso-head',
582
+ onClick: () => setIsOpen(!isOpen),
583
+ 'aria-expanded': isOpen,
584
+ },
585
+ h('div', { className: 'dso-head-info' },
586
+ h('div', { className: 'dso-title' },
587
+ 'Multi-Agent Orchestrator',
588
+ h('span', { className: 'dso-badge' }, 'Prompt Caching Optimized')
589
+ ),
590
+ h('div', { className: 'dso-sub' },
591
+ 'Decomposes tasks into 12 specialized roles (Architecture, Spec, UI, Code, QA, Docs) with DAG execution.'
592
+ )
593
+ ),
594
+ h('div', { className: `dso-chev ${isOpen ? 'dso-chev-open' : ''}` },
595
+ h(Chevron)
596
+ )
597
+ ),
598
+ isOpen ? h('div', { className: 'dso-body' },
599
+ h('div', { className: 'dso-tabs' },
600
+ h('button', {
601
+ type: 'button',
602
+ className: `dso-tab ${activeTab === 'roles' ? 'dso-tab-active' : ''}`,
603
+ onClick: () => setActiveTab('roles'),
604
+ }, `Agent Profiles (${Object.keys(config?.roles || {}).length})`),
605
+ h('button', {
606
+ type: 'button',
607
+ className: `dso-tab ${activeTab === 'scenarios' ? 'dso-tab-active' : ''}`,
608
+ onClick: () => setActiveTab('scenarios'),
609
+ }, 'Workflow Scenarios (5)'),
610
+ h('button', {
611
+ type: 'button',
612
+ className: `dso-tab ${activeTab === 'metrics' ? 'dso-tab-active' : ''}`,
613
+ onClick: () => setActiveTab('metrics'),
614
+ }, 'Prompt Caching Telemetry'),
615
+ h('button', {
616
+ type: 'button',
617
+ className: `dso-tab ${activeTab === 'kanban' ? 'dso-tab-active' : ''}`,
618
+ onClick: () => setActiveTab('kanban'),
619
+ }, 'Kanban Integration')
620
+ ),
621
+ activeTab === 'roles' ? h(AgentProfilesTab, {
622
+ roles: config?.roles || {},
623
+ onRoleChange: handleRoleChange,
624
+ onAddRole: handleAddRole,
625
+ }) : null,
626
+ activeTab === 'scenarios' ? h(ScenariosTab, {
627
+ scenarios: config?.scenarios || {},
628
+ roles: config?.roles || {},
629
+ }) : null,
630
+ activeTab === 'metrics' ? h(CacheMetricsTab, {
631
+ metrics,
632
+ }) : null,
633
+ activeTab === 'kanban' ? h(KanbanTab, {
634
+ config: config || {},
635
+ onConfigChange: (patch) => setConfig((prev) => ({ ...prev, ...patch })),
636
+ }) : null,
637
+ h('div', { className: 'dso-foot' },
638
+ statusMsg ? h('span', { style: { fontSize: 13, color: 'var(--dsw-alias-state-success-primary)', marginRight: 'auto' } }, statusMsg) : null,
639
+ h('button', {
640
+ className: 'dso-btn dso-btn-primary',
641
+ type: 'button',
642
+ disabled: saving,
643
+ onClick: handleSave,
644
+ }, saving ? 'Saving...' : 'Save Changes')
645
+ )
646
+ ) : null
647
+ )
648
+ }
649
+
650
+ function formatElapsedSec(sec) {
651
+ if (sec < 60) return `${sec}s`
652
+ const m = Math.floor(sec / 60)
653
+ const s = sec % 60
654
+ return `${m}m ${s}s`
655
+ }
656
+
657
+ function QuickDispatchModal({ onClose, onSuccess }) {
658
+ const [tab, setTab] = useState('pipeline')
659
+ const [taskTitle, setTaskTitle] = useState('')
660
+ const [taskDesc, setTaskDesc] = useState('')
661
+ const [scenarioId, setScenarioId] = useState('auto')
662
+ const [roleId, setRoleId] = useState('architecture')
663
+ const [loading, setLoading] = useState(false)
664
+ const [error, setError] = useState(null)
665
+
666
+ const handleDispatch = async () => {
667
+ if (!taskTitle.trim() && tab === 'pipeline') return
668
+ if (!taskDesc.trim() && tab === 'specialist') return
669
+ setLoading(true)
670
+ setError(null)
671
+ try {
672
+ if (tab === 'pipeline') {
673
+ const res = await fetch('/dsh-agent-orchestrator/dispatch', {
674
+ method: 'POST',
675
+ headers: { 'Content-Type': 'application/json' },
676
+ body: JSON.stringify({
677
+ taskTitle,
678
+ taskDescription: taskDesc || taskTitle,
679
+ scenarioId,
680
+ }),
681
+ })
682
+ const data = await res.json()
683
+ if (!res.ok) throw new Error(data.error || 'Dispatch failed')
684
+ onSuccess(data.pipeline)
685
+ } else {
686
+ const res = await fetch('/dsh-agent-orchestrator/delegate', {
687
+ method: 'POST',
688
+ headers: { 'Content-Type': 'application/json' },
689
+ body: JSON.stringify({
690
+ roleId,
691
+ task: taskDesc,
692
+ }),
693
+ })
694
+ const data = await res.json()
695
+ if (!res.ok) throw new Error(data.error || 'Delegation failed')
696
+ onClose()
697
+ }
698
+ } catch (e) {
699
+ setError(e?.message || String(e))
700
+ setLoading(false)
701
+ }
702
+ }
703
+
704
+ return h('div', { className: 'dso-modal-backdrop', onClick: onClose },
705
+ h('div', { className: 'dso-modal', onClick: (e) => e.stopPropagation() },
706
+ h('div', { className: 'dso-modal-head' },
707
+ h('span', null, '🚀 Launch Multi-Agent Orchestrator'),
708
+ h('button', { className: 'dso-dock-btn close', onClick: onClose }, '✕')
709
+ ),
710
+ h('div', { className: 'dso-modal-body' },
711
+ h('div', { className: 'dso-tabs', style: { marginBottom: 8 } },
712
+ h('button', {
713
+ type: 'button',
714
+ className: `dso-tab ${tab === 'pipeline' ? 'dso-tab-active' : ''}`,
715
+ onClick: () => setTab('pipeline'),
716
+ }, 'Full DAG Pipeline'),
717
+ h('button', {
718
+ type: 'button',
719
+ className: `dso-tab ${tab === 'specialist' ? 'dso-tab-active' : ''}`,
720
+ onClick: () => setTab('specialist'),
721
+ }, 'Direct Specialist Subagent')
722
+ ),
723
+ tab === 'pipeline'
724
+ ? h(React.Fragment, null,
725
+ h('div', { className: 'dso-field' },
726
+ h('label', { className: 'dso-label' }, 'Objective Title'),
727
+ h('input', {
728
+ className: 'dso-input',
729
+ placeholder: 'e.g. Implement user profile settings card with theme tokens',
730
+ value: taskTitle,
731
+ onChange: (e) => setTaskTitle(e.target.value),
732
+ })
733
+ ),
734
+ h('div', { className: 'dso-field' },
735
+ h('label', { className: 'dso-label' }, 'Scope & Requirements'),
736
+ h('textarea', {
737
+ className: 'dso-textarea',
738
+ placeholder: 'Detailed functional specs, acceptance criteria, constraints...',
739
+ value: taskDesc,
740
+ onChange: (e) => setTaskDesc(e.target.value),
741
+ rows: 3,
742
+ })
743
+ ),
744
+ h('div', { className: 'dso-field' },
745
+ h('label', { className: 'dso-label' }, 'Topology Scenario'),
746
+ h('select', {
747
+ className: 'dso-select',
748
+ value: scenarioId,
749
+ onChange: (e) => setScenarioId(e.target.value),
750
+ },
751
+ h('option', { value: 'auto' }, 'Auto (Infer based on prompt complexity)'),
752
+ h('option', { value: 'hotfix' }, 'Hotfix (1 Stage: Triage & Minimal Fix)'),
753
+ h('option', { value: 'simple' }, 'Simple (2 Stages: Spec + Exec)'),
754
+ h('option', { value: 'medium' }, 'Medium (4 Stages: Spec -> Design -> Code -> QA)'),
755
+ h('option', { value: 'complex' }, 'Complex (6 Stages: Full Engineering Lifecycle)'),
756
+ h('option', { value: 'enterprise' }, 'Enterprise (7 Stages: R&D Spike -> Fullstack -> Gate)')
757
+ )
758
+ )
759
+ )
760
+ : h(React.Fragment, null,
761
+ h('div', { className: 'dso-field' },
762
+ h('label', { className: 'dso-label' }, 'Target Specialist Role'),
763
+ h('select', {
764
+ className: 'dso-select',
765
+ value: roleId,
766
+ onChange: (e) => setRoleId(e.target.value),
767
+ },
768
+ h('option', { value: 'ui_design' }, 'UI/UX Interface Designer'),
769
+ h('option', { value: 'architecture' }, 'System Architect (DESIGN.md / ADR)'),
770
+ h('option', { value: 'spec' }, 'Technical Spec Analyst'),
771
+ h('option', { value: 'frontend' }, 'Senior Frontend Developer'),
772
+ h('option', { value: 'backend' }, 'Senior Backend Developer'),
773
+ h('option', { value: 'qa_tests' }, 'QA Automation Engineer'),
774
+ h('option', { value: 'refactoring' }, 'Refactoring & Complexity Specialist'),
775
+ h('option', { value: 'bugfix' }, 'Hotfix & Diagnostic Engineer'),
776
+ h('option', { value: 'docs' }, 'Documentation Specialist'),
777
+ h('option', { value: 'research' }, 'Spike & R&D Researcher'),
778
+ h('option', { value: 'devops' }, 'DevOps & Tooling Specialist')
779
+ )
780
+ ),
781
+ h('div', { className: 'dso-field' },
782
+ h('label', { className: 'dso-label' }, 'Instructions for Subagent'),
783
+ h('textarea', {
784
+ className: 'dso-textarea',
785
+ placeholder: 'Specific instructions, questions, or requirements for this specialist...',
786
+ value: taskDesc,
787
+ onChange: (e) => setTaskDesc(e.target.value),
788
+ rows: 4,
789
+ })
790
+ )
791
+ ),
792
+ error ? h('div', { style: { color: 'var(--dsw-alias-state-error-primary)', fontSize: 12 } }, error) : null
793
+ ),
794
+ h('div', { className: 'dso-modal-foot' },
795
+ h('button', { className: 'dso-btn dso-btn-subtle', onClick: onClose }, 'Cancel'),
796
+ h('button', {
797
+ className: 'dso-btn dso-btn-primary',
798
+ disabled: loading,
799
+ onClick: handleDispatch,
800
+ }, loading ? 'Dispatching...' : tab === 'pipeline' ? 'Start Pipeline' : 'Delegate Subagent')
801
+ )
802
+ )
803
+ )
804
+ }
805
+
806
+ function OrchestratorDetailsModal({ pipeline, onClose, onCancel }) {
807
+ const [fullData, setFullData] = useState(pipeline)
808
+
809
+ useEffect(() => {
810
+ if (!pipeline?.pipelineId) return
811
+ fetch(`/dsh-agent-orchestrator/pipelines/${encodeURIComponent(pipeline.pipelineId)}`)
812
+ .then((r) => r.json())
813
+ .then((d) => {
814
+ if (d.pipeline) setFullData(d.pipeline)
815
+ })
816
+ .catch(() => {})
817
+ }, [pipeline])
818
+
819
+ const stages = fullData?.stages || []
820
+ const isRunning = fullData.status === 'running' || fullData.status === 'pending'
821
+
822
+ return h('div', { className: 'dso-modal-backdrop', onClick: onClose },
823
+ h('div', { className: 'dso-modal', style: { maxWidth: 680 }, onClick: (e) => e.stopPropagation() },
824
+ h('div', { className: 'dso-modal-head' },
825
+ h('div', null,
826
+ h('span', null, `DAG Pipeline: ${fullData.taskTitle}`),
827
+ h('span', { className: 'dso-badge', style: { marginLeft: 8 } }, fullData.scenarioTitle || fullData.scenarioId)
828
+ ),
829
+ h('button', { className: 'dso-dock-btn close', onClick: onClose }, '✕')
830
+ ),
831
+ h('div', { className: 'dso-modal-body' },
832
+ h('div', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } },
833
+ fullData.taskDescription
834
+ ),
835
+ h('div', { style: { fontWeight: 600, fontSize: 13, marginTop: 6 } }, 'Execution Stages:'),
836
+ stages.map((stg, idx) => {
837
+ const icon = stg.status === 'completed' ? '✅' : stg.status === 'running' ? '⚡' : stg.status === 'failed' ? '❌' : '⏳'
838
+ const output = fullData.artifacts?.[stg.id] || fullData.stageLogs?.[stg.id]
839
+ return h('div', { key: stg.id || idx, className: 'dso-stage-item' },
840
+ h('div', { className: 'dso-stage-head' },
841
+ h('span', null, `${icon} ${idx + 1}. ${stg.name} `),
842
+ h('span', { className: 'dso-agent-role' }, stg.roleName || stg.roleId)
843
+ ),
844
+ output
845
+ ? h('div', { className: 'dso-stage-log' }, output)
846
+ : null
847
+ )
848
+ })
849
+ ),
850
+ h('div', { className: 'dso-modal-foot' },
851
+ isRunning && onCancel
852
+ ? h('button', {
853
+ className: 'dso-btn dso-btn-subtle',
854
+ style: { color: 'var(--dsw-alias-state-error-primary)', marginRight: 'auto' },
855
+ onClick: () => onCancel(fullData.pipelineId),
856
+ }, 'Cancel Pipeline')
857
+ : null,
858
+ h('button', { className: 'dso-btn dso-btn-subtle', onClick: onClose }, 'Close')
859
+ )
860
+ )
861
+ )
862
+ }
863
+
864
+ // --- Dock Widget above Chat Composer (conversation.input.dock) ---
865
+ function OrchestratorTopBanner(props) {
866
+ const { ctx } = props || {}
867
+ const [activePipeline, setActivePipeline] = useState(null)
868
+ const [recentFinished, setRecentFinished] = useState(null)
869
+ const [dismissedId, setDismissedId] = useState(null)
870
+ const [isDetailsOpen, setIsDetailsOpen] = useState(false)
871
+ const [isQuickOpen, setIsQuickOpen] = useState(false)
872
+ const [now, setNow] = useState(() => Date.now())
873
+
874
+ useEffect(() => {
875
+ ensureStyles()
876
+ const timer = setInterval(() => setNow(Date.now()), 1000)
877
+ return () => clearInterval(timer)
878
+ }, [])
879
+
880
+ const pollStatus = useCallback(async () => {
881
+ try {
882
+ const res = await fetch('/dsh-agent-orchestrator/status')
883
+ if (res.ok) {
884
+ const data = await res.json()
885
+ const active = (data.activePipelines || [])[0] || null
886
+ setActivePipeline(active)
887
+
888
+ // If no active, check recent completed/failed within last 60 seconds
889
+ if (!active && data.recentPipelines && data.recentPipelines.length > 0) {
890
+ const latest = data.recentPipelines[0]
891
+ const completedAgo = latest.completedAt ? Date.now() - latest.completedAt : 999999
892
+ if (completedAgo < 60000 && latest.pipelineId !== dismissedId) {
893
+ setRecentFinished(latest)
894
+ } else {
895
+ setRecentFinished(null)
896
+ }
897
+ } else {
898
+ setRecentFinished(null)
899
+ }
900
+ }
901
+ } catch (_) {
902
+ /* safe ignore */
903
+ }
904
+ }, [dismissedId])
905
+
906
+ useEffect(() => {
907
+ pollStatus()
908
+ const intervalMs = activePipeline ? 2000 : 5000
909
+ const timer = setInterval(pollStatus, intervalMs)
910
+ return () => clearInterval(timer)
911
+ }, [activePipeline, pollStatus])
912
+
913
+ const handleCancel = async (pipelineId) => {
914
+ try {
915
+ await fetch('/dsh-agent-orchestrator/cancel', {
916
+ method: 'POST',
917
+ headers: { 'Content-Type': 'application/json' },
918
+ body: JSON.stringify({ pipelineId }),
919
+ })
920
+ setIsDetailsOpen(false)
921
+ pollStatus()
922
+ } catch (_) {
923
+ /* safe ignore */
924
+ }
925
+ }
926
+
927
+ const currentPipeline = activePipeline || recentFinished
928
+ const isCompleted = currentPipeline?.status === 'completed'
929
+ const isFailed = currentPipeline?.status === 'failed'
930
+ const isRunning = currentPipeline?.status === 'running' || currentPipeline?.status === 'pending'
931
+
932
+ if (!currentPipeline || currentPipeline.pipelineId === dismissedId) {
933
+ // Quick Launch Pill in conversation.input.dock
934
+ return h(
935
+ React.Fragment,
936
+ null,
937
+ h(
938
+ 'div',
939
+ { className: 'dso-dock dso-dock-quick', 'data-orchestrator-dock': 'true' },
940
+ h(
941
+ 'button',
942
+ {
943
+ className: 'dso-quicklaunch-btn',
944
+ title: 'Multi-Agent Orchestrator (DAG Pipeline & Specialist Subagents)',
945
+ onClick: () => setIsQuickOpen(true),
946
+ },
947
+ h('span', null, '🚀'),
948
+ h('span', null, 'Orchestrator')
949
+ )
950
+ ),
951
+ isQuickOpen
952
+ ? h(QuickDispatchModal, {
953
+ onClose: () => setIsQuickOpen(false),
954
+ onSuccess: (p) => {
955
+ setActivePipeline(p)
956
+ setIsQuickOpen(false)
957
+ },
958
+ })
959
+ : null
960
+ )
961
+ }
962
+
963
+ // Format elapsed time
964
+ const startTime = currentPipeline.startedAt || currentPipeline.createdAt || now
965
+ const endTime = currentPipeline.completedAt || now
966
+ const elapsedSec = Math.max(0, Math.floor((endTime - startTime) / 1000))
967
+ const elapsedStr = formatElapsedSec(elapsedSec)
968
+
969
+ // Active stage & progress
970
+ const stages = currentPipeline.stages || []
971
+ const totalStages = stages.length
972
+ const completedCount = stages.filter((s) => s.status === 'completed').length
973
+ const activeStage = stages.find((s) => s.status === 'running') || stages.find((s) => s.status === 'pending')
974
+
975
+ const bannerClass = `dso-banner ${isCompleted ? 'completed' : ''} ${isFailed ? 'failed' : ''}`
976
+
977
+ return h(
978
+ React.Fragment,
979
+ null,
980
+ h(
981
+ 'div',
982
+ { className: 'dso-dock', 'data-orchestrator-dock': 'true' },
983
+ h(
984
+ 'div',
985
+ { className: bannerClass },
986
+ h(
987
+ 'div',
988
+ { className: 'dso-banner-left' },
989
+ isRunning
990
+ ? h('div', { className: 'dso-pulse-dot' })
991
+ : h('span', null, isCompleted ? '✅' : '❌'),
992
+ h(
993
+ 'span',
994
+ {
995
+ className: `dso-banner-badge ${isRunning ? 'dso-badge-running' : isCompleted ? 'dso-badge-ok' : 'dso-badge-err'}`,
996
+ },
997
+ isRunning
998
+ ? `Stage ${completedCount + 1}/${totalStages}`
999
+ : isCompleted
1000
+ ? 'Done'
1001
+ : 'Failed'
1002
+ ),
1003
+ currentPipeline.scenarioTitle
1004
+ ? h(
1005
+ 'span',
1006
+ {
1007
+ className: 'dso-badge',
1008
+ style: { fontSize: 10, padding: '1px 5px' },
1009
+ },
1010
+ currentPipeline.scenarioTitle
1011
+ )
1012
+ : null,
1013
+ h(
1014
+ 'span',
1015
+ {
1016
+ className: 'dso-banner-title',
1017
+ title: currentPipeline.taskTitle,
1018
+ },
1019
+ currentPipeline.taskTitle
1020
+ ),
1021
+ activeStage && isRunning
1022
+ ? h(
1023
+ 'span',
1024
+ { className: 'dso-banner-stage' },
1025
+ `${activeStage.name} (${activeStage.roleName || activeStage.roleId})`
1026
+ )
1027
+ : null
1028
+ ),
1029
+ h(
1030
+ 'div',
1031
+ { className: 'dso-banner-right' },
1032
+ h('span', { className: 'dso-banner-time' }, `⏱ ${elapsedStr}`),
1033
+ h(
1034
+ 'button',
1035
+ {
1036
+ className: 'dso-dock-btn',
1037
+ title: 'View Pipeline DAG & Deliverables',
1038
+ onClick: () => setIsDetailsOpen(true),
1039
+ },
1040
+ 'Details'
1041
+ ),
1042
+ !isRunning
1043
+ ? h(
1044
+ 'button',
1045
+ {
1046
+ className: 'dso-dock-btn close',
1047
+ title: 'Dismiss Banner',
1048
+ onClick: () => setDismissedId(currentPipeline.pipelineId),
1049
+ },
1050
+ '✕'
1051
+ )
1052
+ : null
1053
+ )
1054
+ )
1055
+ ),
1056
+ isDetailsOpen
1057
+ ? h(OrchestratorDetailsModal, {
1058
+ pipeline: currentPipeline,
1059
+ onClose: () => setIsDetailsOpen(false),
1060
+ onCancel: handleCancel,
1061
+ })
1062
+ : null,
1063
+ isQuickOpen
1064
+ ? h(QuickDispatchModal, {
1065
+ onClose: () => setIsQuickOpen(false),
1066
+ onSuccess: (p) => {
1067
+ setActivePipeline(p)
1068
+ setIsQuickOpen(false)
1069
+ },
1070
+ })
1071
+ : null
1072
+ )
1073
+ }
1074
+
1075
+
1076
+ // --- Issue #5: Specialist Toolview Component (tool.call.toolview) ---
1077
+ function SpecialistToolview(props) {
1078
+ const [showDiff, setShowDiff] = useState(false)
1079
+ const call = props.call || props.toolCall || {}
1080
+ const args = call.arguments || call.args || {}
1081
+ const result = props.result || props.output || {}
1082
+ const roleName = result.roleName || args.roleId || args.name || 'Specialist Worker'
1083
+ const modelName = result.model || 'inherited'
1084
+ const chip = formatModelChip(modelName, { maxTokens: result.maxTokens })
1085
+ const isErr = result.isError || result.status === 'failed'
1086
+ const isRunning = !props.result && !props.output
1087
+ const diff = result.toolDiff || {}
1088
+ const outputText = typeof result.output === 'string' ? result.output : (result.output ? JSON.stringify(result.output, null, 2) : '')
1089
+
1090
+ return h('div', { className: 'dso-toolview-card' },
1091
+ h('div', { className: 'dso-toolview-header' },
1092
+ h('div', { className: 'dso-toolview-badge' },
1093
+ h('span', null, '🤖'),
1094
+ h('span', null, roleName),
1095
+ h('span', { className: `dso-model-chip ${chip.badgeClass}`, title: chip.tooltip }, chip.text)
1096
+ ),
1097
+ h('div', { className: `dso-toolview-status-pill ${isRunning ? 'running' : (isErr ? 'err' : 'ok')}` },
1098
+ isRunning ? '⏳ Running...' : (isErr ? '🔴 Failed' : '🟢 Completed')
1099
+ )
1100
+ ),
1101
+ diff && (diff.allowed?.length > 0 || diff.strippedSecurity?.length > 0) ?
1102
+ h('div', { className: 'dso-toolview-tooldiff' },
1103
+ h('div', { className: 'dso-tooldiff-summary', onClick: () => setShowDiff(!showDiff) },
1104
+ h('span', null, '🛡️ Tool Permissions:'),
1105
+ h('span', null, `${diff.allowed?.length || 0} allowed, ${diff.strippedSecurity?.length || 0} stripped`),
1106
+ h(ChevronIcon || FallbackChevron, { style: { transform: showDiff ? 'rotate(180deg)' : 'none', transition: 'transform 0.15s' } })
1107
+ ),
1108
+ showDiff ? h('div', { className: 'dso-tooldiff-pills' },
1109
+ (diff.allowed || []).map(t => h('span', { key: t, className: 'dso-pill allow', title: 'Allowed' }, t)),
1110
+ (diff.strippedSecurity || []).map(t => h('span', { key: t, className: 'dso-pill blocked', title: 'Blocked by Security Policy' }, t)),
1111
+ (diff.denied || []).map(t => h('span', { key: t, className: 'dso-pill denied', title: 'Not in Parent' }, t))
1112
+ ) : null
1113
+ ) : null,
1114
+ outputText ? h('div', { className: 'dso-toolview-deliverable' }, outputText) : null,
1115
+ !isRunning && !isErr ? h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
1116
+ h('div', { className: 'dso-toolview-callout', style: { flex: 1 } },
1117
+ h('span', null, (ctx?.locale ? ctx.locale.bind(NS)('badge.accepted') : '🟢 Subagent result accepted by orchestrator'))
1118
+ ),
1119
+ h('button', {
1120
+ type: 'button',
1121
+ className: 'dso-preset-btn',
1122
+ title: 'Save this deliverable configuration as a reusable workflow preset (Issue 109)',
1123
+ onClick: async () => {
1124
+ try {
1125
+ await fetch('/dsh-agent-orchestrator/snapshots/save-as-preset', {
1126
+ method: 'POST',
1127
+ headers: { 'Content-Type': 'application/json' },
1128
+ body: JSON.stringify({
1129
+ snapshotId: result.executionId || args.executionId || `snap-${roleName}`,
1130
+ name: `Preset: ${roleName}`,
1131
+ description: `Created from specialist deliverable [${roleName}]`,
1132
+ }),
1133
+ })
1134
+ } catch (_) {
1135
+ /* safe ignore */
1136
+ }
1137
+ },
1138
+ }, '💾 Save as Preset')
1139
+ ) : null
1140
+ )
1141
+ }
1142
+
1143
+ // --- Header Utility Progress Chip (Goal-style) ---
1144
+ // --- Header Utility Progress Chip & Subagent Child Session Badge (Issue #3) ---
1145
+ function HeaderOrchestratorWidget(props) {
1146
+ const [activePipeline, setActivePipeline] = useState(null)
1147
+ const session = props.useSession ? props.useSession(s => s) : props.session
1148
+
1149
+ useEffect(() => {
1150
+ ensureStyles()
1151
+ const check = () => {
1152
+ fetch('/dsh-agent-orchestrator/status')
1153
+ .then((r) => r.json())
1154
+ .then((d) => {
1155
+ const p = (d.activePipelines || [])[0]
1156
+ setActivePipeline(p || null)
1157
+ })
1158
+ .catch(() => {})
1159
+ }
1160
+ check()
1161
+ const interval = setInterval(check, 4000)
1162
+ return () => clearInterval(interval)
1163
+ }, [])
1164
+
1165
+ // Issue #3: Subagent child session header utility badge
1166
+ const subagentMeta = session?.subagent || session?.options?.subagent || (session?.meta && session.meta.subagent)
1167
+ const parentId = session?.parentId || session?.options?.parent || (session?.meta && session.meta.parent)
1168
+ if (subagentMeta || parentId) {
1169
+ const role = subagentMeta?.role || subagentMeta?.label || 'Subagent'
1170
+ const model = subagentMeta?.model || session?.options?.model || ''
1171
+ const chip = formatModelChip(model)
1172
+ return h('div', { className: 'dso-subagent-header-chip' },
1173
+ h('span', { className: 'dso-chip-icon' }, '🤖'),
1174
+ h('span', { className: 'dso-chip-role' }, role),
1175
+ model ? h('span', { className: 'dso-chip-sep' }, '·') : null,
1176
+ model ? h('span', { className: `dso-model-chip ${chip.badgeClass}`, title: chip.tooltip }, chip.text) : null,
1177
+ parentId ? h('a', {
1178
+ href: `#session-${parentId}`,
1179
+ className: 'dso-chip-parent-link',
1180
+ onClick: (e) => {
1181
+ e.preventDefault()
1182
+ if (props.ctx?.sessions?.activate) {
1183
+ props.ctx.sessions.activate(parentId)
1184
+ } else if (window.location) {
1185
+ window.location.hash = `session-${parentId}`
1186
+ }
1187
+ },
1188
+ title: `Return to parent session #${parentId}`
1189
+ }, `⮌ Parent #${String(parentId).slice(0, 8)}`) : null
1190
+ )
1191
+ }
1192
+
1193
+ if (!activePipeline) return null
1194
+
1195
+ const total = activePipeline.stages?.length || 0
1196
+ const done = activePipeline.stages?.filter((s) => s.status === 'completed').length || 0
1197
+ const current = activePipeline.stages?.find((s) => s.status === 'running')
1198
+
1199
+ return h('div', { className: 'dso-widget', title: `Active Pipeline: ${activePipeline.taskTitle}` },
1200
+ h('div', { className: 'dso-widget-dot' }),
1201
+ h('span', { className: 'dso-widget-badge' }, `${done}/${total}`),
1202
+ h('span', null, current ? current.name : 'Orchestrating...')
1203
+ )
1204
+ }
1205
+
1206
+ // Polling helper to ensure describe mirror sees this namespace
1207
+ function refreshMirrorUntilVisible(ctx) {
1208
+ const visible = () => {
1209
+ try {
1210
+ const s = (ctx?.get && ctx.get('lanSettings')) || (ctx?.get && ctx.get('settingsScope')) || ctx?.settingsScope
1211
+ const view = s?.describe?.()?.getSnapshot?.()?.view
1212
+ return !!view && Array.isArray(view.namespaces) && view.namespaces.some((row) => row.ns === NS)
1213
+ } catch (_) {
1214
+ return false
1215
+ }
1216
+ }
1217
+ if (visible()) return () => {}
1218
+ let tries = 0
1219
+ const timer = setInterval(() => {
1220
+ if (visible() || tries >= 15) { clearInterval(timer); return }
1221
+ tries += 1
1222
+ try {
1223
+ const s = (ctx?.get && ctx.get('lanSettings')) || (ctx?.get && ctx.get('settingsScope')) || ctx?.settingsScope
1224
+ s?.describe?.()?.load?.()
1225
+ } catch (_) {
1226
+ /* safe ignore */
1227
+ }
1228
+ }, 1000)
1229
+ return () => clearInterval(timer)
1230
+ }
1231
+
1232
+ // Plugin Client Exports & Lifecycle
1233
+ module.exports.inject = ['slots', 'locale']
1234
+ module.exports.apply = function apply(ctx) {
1235
+ // Register localized strings via ctx.effect (Issue #115 & #116)
1236
+ if (ctx.locale && typeof ctx.locale.register === 'function') {
1237
+ const dicts = {
1238
+ en: {
1239
+ title: 'Multi-Agent Orchestrator',
1240
+ subtitle: 'Autonomous multi-agent DAG workflow & prompt caching optimizer',
1241
+ description: 'Decompose and execute tasks across specialized autonomous agent roles with prompt caching.',
1242
+ 'badge.accepted': '🟢 Subagent result accepted by orchestrator',
1243
+ },
1244
+ zh: {
1245
+ title: '多智能体编排器',
1246
+ subtitle: '自主多智能体有向无环图工作流与提示词缓存优化器',
1247
+ description: '基于提示词缓存优化,将任务分解并在专门的智能体角色之间协同执行。',
1248
+ 'badge.accepted': '🟢 子代理结果已被编排器接受',
1249
+ },
1250
+ }
1251
+ if (typeof ctx.effect === 'function') {
1252
+ ctx.effect(() => ctx.locale.register(NS, dicts), 'dsh-agent-orchestrator: locale')
1253
+ } else {
1254
+ ctx.locale.register(NS, dicts)
1255
+ }
1256
+ }
1257
+
1258
+ refreshMirrorUntilVisible(ctx)
1259
+
1260
+ const registerSlotSafe = (name, entry, comp) => {
1261
+ try {
1262
+ if (typeof ctx.slots?.inject === 'function') {
1263
+ ctx.slots.inject(name, () => {
1264
+ try {
1265
+ return ctx.slots.register(entry, comp)
1266
+ } catch (_) {
1267
+ /* safe ignore */
1268
+ }
1269
+ })
1270
+ } else if (typeof ctx.slots?.register === 'function') {
1271
+ ctx.slots.register(entry, comp)
1272
+ }
1273
+ } catch (_) {
1274
+ /* safe ignore */
1275
+ }
1276
+ }
1277
+
1278
+ // Issue #139: settings.plugin.item does not render in DSH core 0.1.6-alpha.2+.
1279
+ // Register into settings.plugins.tab (official tabs under Settings -> Plugins),
1280
+ // plugins.item (Plugin Manager page), and fallback settings.section.
1281
+
1282
+ // 1. Official tab in Settings -> Plugins
1283
+ registerSlotSafe(
1284
+ 'settings.plugins.tab',
1285
+ {
1286
+ name: 'settings.plugins.tab',
1287
+ id: 'dsh-agent-orchestrator',
1288
+ order: 25,
1289
+ label: () => (typeof ctx.t === 'function' ? ctx.t(`${NS}.settings.title`) : 'Agent Orchestrator') || 'Agent Orchestrator',
1290
+ locale: NS,
1291
+ inject: () => ({ ctx }),
1292
+ },
1293
+ (props) => h(ErrorBoundary, null, h(OrchestratorSettingsCard, { ...props, ctx: props.ctx || ctx }))
1294
+ )
1295
+
1296
+ // 2. Plugin item on Plugins page
1297
+ registerSlotSafe(
1298
+ 'plugins.item',
1299
+ {
1300
+ name: 'plugins.item',
1301
+ id: '@goodandready/dsh-agent-orchestrator',
1302
+ order: 25,
1303
+ label: () => (typeof ctx.t === 'function' ? ctx.t(`${NS}.settings.title`) : 'Agent Orchestrator') || 'Agent Orchestrator',
1304
+ locale: NS,
1305
+ inject: () => ({ ctx }),
1306
+ },
1307
+ (props) => h(ErrorBoundary, null, h(OrchestratorSettingsCard, { ...props, ctx: props.ctx || ctx }))
1308
+ )
1309
+
1310
+ // 3. Standalone settings.section fallback
1311
+ registerSlotSafe(
1312
+ 'settings.section',
1313
+ {
1314
+ name: 'settings.section',
1315
+ id: 'dsh-agent-orchestrator',
1316
+ order: 95,
1317
+ label: () => (typeof ctx.t === 'function' ? ctx.t(`${NS}.settings.title`) : 'Agent Orchestrator') || 'Agent Orchestrator',
1318
+ locale: NS,
1319
+ inject: () => ({ ctx }),
1320
+ },
1321
+ (props) => h(ErrorBoundary, null, h(OrchestratorSettingsCard, { ...props, ctx: props.ctx || ctx }))
1322
+ )
1323
+
1324
+ // 3. Dock Widget: conversation.input.dock (above chat input box, like dsh-goal)
1325
+ registerSlotSafe(
1326
+ 'conversation.input.dock',
1327
+ {
1328
+ name: 'conversation.input.dock',
1329
+ id: '@goodandready/dsh-agent-orchestrator',
1330
+ order: 10,
1331
+ locale: NS,
1332
+ inject: (slotProps) => ({ ctx, ...slotProps }),
1333
+ },
1334
+ (props) => h(ErrorBoundary, null, h(OrchestratorTopBanner, { ...props, ctx: props.ctx || ctx }))
1335
+ )
1336
+
1337
+ // 4. Header Widget: conversation.session.header.utilities
1338
+
1339
+ // 5. Tool Call View: tool.call.toolview for specialist delegation & agent_run (Issue #5)
1340
+ registerSlotSafe(
1341
+ 'tool.call.toolview',
1342
+ {
1343
+ name: 'tool.call.toolview',
1344
+ key: 'orchestrator_delegate_specialist',
1345
+ inject: (slotProps) => ({ ctx, ...slotProps }),
1346
+ },
1347
+ (props) => h(ErrorBoundary, null, h(SpecialistToolview, { ...props, ctx: props.ctx || ctx }))
1348
+ )
1349
+
1350
+ registerSlotSafe(
1351
+ 'tool.call.toolview',
1352
+ {
1353
+ name: 'tool.call.toolview',
1354
+ key: 'agent_run',
1355
+ inject: (slotProps) => ({ ctx, ...slotProps }),
1356
+ },
1357
+ (props) => h(ErrorBoundary, null, h(SpecialistToolview, { ...props, ctx: props.ctx || ctx }))
1358
+ )
1359
+
1360
+ registerSlotSafe(
1361
+ 'conversation.session.header.utilities',
1362
+ {
1363
+ name: 'conversation.session.header.utilities',
1364
+ id: 'dsh-agent-orchestrator-widget',
1365
+ order: 15,
1366
+ inject: () => ({ ctx }),
1367
+ },
1368
+ (props) => h(ErrorBoundary, null, h(HeaderOrchestratorWidget, { ...props, ctx: props.ctx || ctx }))
1369
+ )
1370
+ }
1371
+
1372
+ return module.exports
1373
+ },
1374
+ })