@polderlabs/bizar 4.3.0 → 4.4.1
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/bizar-dash/src/server/opencode-sdk.mjs +72 -0
- package/bizar-dash/src/server/routes/background.mjs +92 -0
- package/bizar-dash/src/server/routes/chat.mjs +300 -123
- package/bizar-dash/src/server/routes/opencode-session-detail.mjs +26 -0
- package/bizar-dash/src/server/routes/tasks.mjs +59 -1
- package/bizar-dash/src/server/task-delegator.mjs +154 -8
- package/bizar-dash/src/server/tasks-store.mjs +50 -2
- package/bizar-dash/src/web/components/background/AttachButton.tsx +96 -0
- package/bizar-dash/src/web/components/background/TmuxAttachCard.tsx +122 -0
- package/bizar-dash/src/web/components/chat/AgentNode.tsx +127 -0
- package/bizar-dash/src/web/components/chat/AgentTree.tsx +80 -0
- package/bizar-dash/src/web/components/chat/ChatComposer.tsx +76 -0
- package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +144 -0
- package/bizar-dash/src/web/components/chat/ChatRail.tsx +387 -0
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +28 -7
- package/bizar-dash/src/web/components/chat/JumpToLatest.tsx +58 -0
- package/bizar-dash/src/web/components/chat/MessageBlock.tsx +260 -0
- package/bizar-dash/src/web/components/chat/SessionRowMenu.tsx +206 -0
- package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +33 -5
- package/bizar-dash/src/web/components/chat/_legacy.ts +30 -0
- package/bizar-dash/src/web/components/chat/index.ts +11 -0
- package/bizar-dash/src/web/components/chat/useChat.ts +345 -167
- package/bizar-dash/src/web/components/tasks/BacklogPanel.css +109 -0
- package/bizar-dash/src/web/components/tasks/BacklogPanel.tsx +209 -0
- package/bizar-dash/src/web/styles/chat.css +1536 -133
- package/bizar-dash/src/web/views/BackgroundAgents.tsx +3 -0
- package/bizar-dash/src/web/views/Chat.tsx +147 -57
- package/bizar-dash/src/web/views/Tasks.tsx +23 -1
- package/cli/bg.mjs +94 -71
- package/cli/bin.mjs +36 -8
- package/cli/service-controller.mjs +587 -0
- package/cli/service-controller.test.mjs +92 -0
- package/cli/service.mjs +162 -14
- package/config/agents/baldr.md +2 -0
- package/config/agents/browser-harness.md +2 -0
- package/config/agents/forseti.md +2 -0
- package/config/agents/frigg.md +2 -0
- package/config/agents/heimdall.md +2 -0
- package/config/agents/hermod.md +2 -0
- package/config/agents/mimir.md +2 -0
- package/config/agents/odin.md +2 -0
- package/config/agents/quick.md +2 -0
- package/config/agents/semble-search.md +2 -0
- package/config/agents/thor.md +2 -0
- package/config/agents/tyr.md +2 -0
- package/config/agents/vidarr.md +2 -0
- package/config/agents/vor.md +2 -0
- package/config/opencode.json.template +1 -0
- package/install.sh +448 -787
- package/package.json +2 -2
- package/packages/sdk/package.json +20 -0
- package/packages/sdk/src/client.ts +5 -0
- package/packages/sdk/src/errors.ts +11 -2
- package/packages/sdk/src/index.ts +19 -0
- package/packages/sdk/src/opencode-events.ts +134 -0
- package/packages/sdk/src/opencode-types.ts +66 -0
- package/packages/sdk/src/opencode.ts +335 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/opencode-sdk.mjs
|
|
3
|
+
*
|
|
4
|
+
* Dashboard-side wrapper that reads serve-info.mjs, constructs the
|
|
5
|
+
* opencode SDK instance with auth, and exports a singleton `getOpencodeSdk()`.
|
|
6
|
+
*
|
|
7
|
+
* Also exports `pingOpencodeSdk(info)` which uses the SDK's `health.check()`
|
|
8
|
+
* (replaces the old `pingOpencodeServe` from serve-info.mjs; the old name
|
|
9
|
+
* is kept as a backwards-compatible alias).
|
|
10
|
+
*
|
|
11
|
+
* v0.1.0 — initial implementation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readServeInfo, pingOpencodeServe as _pingOpencodeServe } from "./serve-info.mjs";
|
|
15
|
+
|
|
16
|
+
let _sdk = null;
|
|
17
|
+
let _sdkInfo = null;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Lazily create and cache an opencode SDK instance from the active
|
|
21
|
+
* serve-info. Returns `null` when no serve-info is available.
|
|
22
|
+
*
|
|
23
|
+
* @returns {Promise<import('@polderlabs/bizar-sdk').OpencodeSdk | null>}
|
|
24
|
+
*/
|
|
25
|
+
export async function getOpencodeSdk() {
|
|
26
|
+
const info = readServeInfo();
|
|
27
|
+
if (!info) return null;
|
|
28
|
+
|
|
29
|
+
// Re-use existing instance if serve-info hasn't changed.
|
|
30
|
+
if (_sdk && _sdkInfo && _sdkInfo.password === info.password && _sdkInfo.port === info.port) {
|
|
31
|
+
return _sdk;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { createOpencodeSdk } = await import("@polderlabs/bizar-sdk/opencode");
|
|
35
|
+
const sdk = await createOpencodeSdk({
|
|
36
|
+
baseUrl: info.baseUrl,
|
|
37
|
+
password: info.password,
|
|
38
|
+
throwOnError: false,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
_sdk = sdk;
|
|
42
|
+
_sdkInfo = { password: info.password, port: info.port };
|
|
43
|
+
return sdk;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Ping the opencode serve child using the SDK's health endpoint.
|
|
48
|
+
* Falls back to TCP-connect ping if the SDK health check fails.
|
|
49
|
+
*
|
|
50
|
+
* @param {import('./serve-info.mjs').ServeInfo} info
|
|
51
|
+
* @returns {Promise<boolean>}
|
|
52
|
+
*/
|
|
53
|
+
export async function pingOpencodeSdk(info) {
|
|
54
|
+
const sdk = await getOpencodeSdk();
|
|
55
|
+
if (!sdk) return false;
|
|
56
|
+
try {
|
|
57
|
+
const result = await sdk.health.check();
|
|
58
|
+
if (result && typeof result === "object" && "ok" in result) {
|
|
59
|
+
return result.ok === true;
|
|
60
|
+
}
|
|
61
|
+
// BizarError shape — fall back to TCP ping
|
|
62
|
+
return pingOpencodeServe(info);
|
|
63
|
+
} catch {
|
|
64
|
+
return pingOpencodeServe(info);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Backwards-compatible alias for `pingOpencodeSdk`.
|
|
70
|
+
* @deprecated Use `pingOpencodeSdk` instead.
|
|
71
|
+
*/
|
|
72
|
+
export const pingOpencodeServe = pingOpencodeSdk;
|
|
@@ -161,5 +161,97 @@ export function createBackgroundRouter({ broadcast }) {
|
|
|
161
161
|
res.json(summary);
|
|
162
162
|
}));
|
|
163
163
|
|
|
164
|
+
// v3.22.0 — Open-terminal endpoint. The frontend calls this when the
|
|
165
|
+
// operator clicks "Open in terminal" on a TmuxAttachCard. The server
|
|
166
|
+
// spawns the platform's terminal emulator with the tmux attach command.
|
|
167
|
+
// Whitelisted emulators: 'system' (auto-detect), 'tmux-iterm' (macOS
|
|
168
|
+
// iTerm2), 'tmux-wt' (Windows Terminal).
|
|
169
|
+
//
|
|
170
|
+
// The endpoint ALWAYS returns 200 with `{ ok, command }` or
|
|
171
|
+
// `{ ok: false, error }` — never a raw 5xx, so the frontend can fall
|
|
172
|
+
// back to clipboard copy.
|
|
173
|
+
router.post('/background/:id/open-terminal', wrap(async (req, res) => {
|
|
174
|
+
const { backgroundStore } = await import('../background-store.mjs');
|
|
175
|
+
const info = backgroundStore.tmuxAttachInfo(req.params.id);
|
|
176
|
+
|
|
177
|
+
if (!info || !info.session) {
|
|
178
|
+
res.json({ ok: false, error: 'no_tmux_session' });
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Validate actual tmux session name matches the bgr_<hex> pattern.
|
|
183
|
+
if (!/^bgr_[A-Za-z0-9_-]{1,24}$/.test(info.session)) {
|
|
184
|
+
res.json({ ok: false, error: 'invalid_session_name' });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Whitelist emulator selection.
|
|
189
|
+
const emulator = (req.body?.emulator || 'system').toString();
|
|
190
|
+
const ALLOWED_EMULATORS = ['system', 'tmux-iterm', 'tmux-wt'];
|
|
191
|
+
if (!ALLOWED_EMULATORS.includes(emulator)) {
|
|
192
|
+
res.json({ ok: false, error: 'unknown_emulator', allowed: ALLOWED_EMULATORS });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const { execFileSync, spawn } = await import('node:child_process');
|
|
197
|
+
const platform = process.platform;
|
|
198
|
+
const command = `tmux attach -t ${info.session}`;
|
|
199
|
+
|
|
200
|
+
if (emulator === 'system') {
|
|
201
|
+
// Auto-detect platform terminal.
|
|
202
|
+
if (platform === 'darwin') {
|
|
203
|
+
spawn('osascript', [
|
|
204
|
+
'-e', `tell application "Terminal" to do script "${command}"`,
|
|
205
|
+
'-e', 'activate application "Terminal"',
|
|
206
|
+
], { detached: true, stdio: 'ignore' }).unref();
|
|
207
|
+
} else if (platform === 'linux') {
|
|
208
|
+
const candidates = [
|
|
209
|
+
['gnome-terminal', '--', ...command.split(' ')],
|
|
210
|
+
['konsole', '-e', command],
|
|
211
|
+
['xterm', '-e', command],
|
|
212
|
+
['x-terminal-emulator', '-e', command],
|
|
213
|
+
];
|
|
214
|
+
let launched = false;
|
|
215
|
+
for (const [cmd, ...args] of candidates) {
|
|
216
|
+
try {
|
|
217
|
+
execFileSync('which', [cmd], { stdio: 'pipe' });
|
|
218
|
+
spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();
|
|
219
|
+
launched = true;
|
|
220
|
+
break;
|
|
221
|
+
} catch { /* try next */ }
|
|
222
|
+
}
|
|
223
|
+
if (!launched) {
|
|
224
|
+
res.json({ ok: false, error: 'no_terminal_emulator_found' });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
} else if (platform === 'win32') {
|
|
228
|
+
try {
|
|
229
|
+
execFileSync('where', ['wt.exe'], { stdio: 'pipe' });
|
|
230
|
+
spawn('wt.exe', ['-e', ...command.split(' ')], { detached: true, stdio: 'ignore' }).unref();
|
|
231
|
+
} catch {
|
|
232
|
+
spawn('cmd', ['/c', 'start', '', 'cmd', '/k', ...command.split(' ')], { detached: true, stdio: 'ignore' }).unref();
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
res.json({ ok: false, error: `unsupported_platform: ${platform}` });
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
} else if (emulator === 'tmux-iterm') {
|
|
239
|
+
if (platform !== 'darwin') {
|
|
240
|
+
res.json({ ok: false, error: 'tmux-iterm requires macOS' });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// iTerm2 has a "tmux integration" mode; open -a iTerm sends the command.
|
|
244
|
+
spawn('open', ['-a', 'iTerm2', command], { detached: true, stdio: 'ignore' }).unref();
|
|
245
|
+
} else if (emulator === 'tmux-wt') {
|
|
246
|
+
if (platform !== 'win32') {
|
|
247
|
+
res.json({ ok: false, error: 'tmux-wt requires Windows' });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
spawn('wt.exe', ['-e', ...command.split(' ')], { detached: true, stdio: 'ignore' }).unref();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
res.json({ ok: true, command });
|
|
254
|
+
}));
|
|
255
|
+
|
|
164
256
|
return router;
|
|
165
257
|
}
|