@njeybe/nativ 2.2.0 → 2.3.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/CHANGELOG.md +24 -1
- package/README.md +15 -0
- package/dist/runner/agent-supervisor.d.ts +5 -3
- package/dist/runner/agent-supervisor.js +17 -12
- package/dist/server/studio-server.js +1 -130
- package/dist/server/studio-ui/document.d.ts +3 -0
- package/dist/server/studio-ui/document.js +22 -0
- package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
- package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
- package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/browser.js +326 -0
- package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/connection.js +158 -0
- package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
- package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/drift.js +301 -0
- package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/events.js +231 -0
- package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/explorer.js +183 -0
- package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/database/index.js +28 -0
- package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/markup.js +32 -0
- package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
- package/dist/server/studio-ui/pages/database/state.js +57 -0
- package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/styles.js +118 -0
- package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/flow/index.js +11 -0
- package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/layout.js +105 -0
- package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
- package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/script.js +61 -0
- package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/styles.js +59 -0
- package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
- package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
- package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/home/index.js +11 -0
- package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/home/script.js +160 -0
- package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/home/styles.js +48 -0
- package/dist/server/studio-ui/pages/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/index.js +17 -0
- package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/tasks/index.js +11 -0
- package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/tasks/script.js +96 -0
- package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
- package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/team/index.js +11 -0
- package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/team/script.js +112 -0
- package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/team/styles.js +22 -0
- package/dist/server/studio-ui/pages/types.d.ts +13 -0
- package/dist/server/studio-ui/pages/types.js +1 -0
- package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
- package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
- package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
- package/dist/server/studio-ui/shell/actions.d.ts +1 -0
- package/dist/server/studio-ui/shell/actions.js +49 -0
- package/dist/server/studio-ui/shell/boot.d.ts +2 -0
- package/dist/server/studio-ui/shell/boot.js +13 -0
- package/dist/server/studio-ui/shell/console.d.ts +1 -0
- package/dist/server/studio-ui/shell/console.js +194 -0
- package/dist/server/studio-ui/shell/core.d.ts +1 -0
- package/dist/server/studio-ui/shell/core.js +50 -0
- package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/detail-view.js +80 -0
- package/dist/server/studio-ui/shell/detail.d.ts +1 -0
- package/dist/server/studio-ui/shell/detail.js +83 -0
- package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
- package/dist/server/studio-ui/shell/dispatch.js +150 -0
- package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
- package/dist/server/studio-ui/shell/events-tools.js +94 -0
- package/dist/server/studio-ui/shell/format.d.ts +1 -0
- package/dist/server/studio-ui/shell/format.js +151 -0
- package/dist/server/studio-ui/shell/icons.d.ts +2 -0
- package/dist/server/studio-ui/shell/icons.js +20 -0
- package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
- package/dist/server/studio-ui/shell/markup-head.js +43 -0
- package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
- package/dist/server/studio-ui/shell/markup-panels.js +7 -0
- package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
- package/dist/server/studio-ui/shell/markup-tools.js +222 -0
- package/dist/server/studio-ui/shell/model.d.ts +1 -0
- package/dist/server/studio-ui/shell/model.js +192 -0
- package/dist/server/studio-ui/shell/paint.d.ts +1 -0
- package/dist/server/studio-ui/shell/paint.js +154 -0
- package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/palette-view.js +60 -0
- package/dist/server/studio-ui/shell/palette.d.ts +1 -0
- package/dist/server/studio-ui/shell/palette.js +80 -0
- package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
- package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
- package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
- package/dist/server/studio-ui/shell/proposals.js +197 -0
- package/dist/server/studio-ui/shell/pure.d.ts +3 -0
- package/dist/server/studio-ui/shell/pure.js +9 -0
- package/dist/server/studio-ui/shell/router.d.ts +1 -0
- package/dist/server/studio-ui/shell/router.js +95 -0
- package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
- package/dist/server/studio-ui/shell/sync.d.ts +1 -0
- package/dist/server/studio-ui/shell/sync.js +120 -0
- package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
- package/dist/server/studio-ui/shell/toasts-format.js +89 -0
- package/dist/server/studio-ui/shell/triage.d.ts +1 -0
- package/dist/server/studio-ui/shell/triage.js +210 -0
- package/dist/server/studio-ui/styles/console.d.ts +1 -0
- package/dist/server/studio-ui/styles/console.js +83 -0
- package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
- package/dist/server/studio-ui/styles/dialogs.js +118 -0
- package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
- package/dist/server/studio-ui/styles/responsive.js +28 -0
- package/dist/server/studio-ui/styles/shell.d.ts +1 -0
- package/dist/server/studio-ui/styles/shell.js +117 -0
- package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
- package/dist/server/studio-ui/styles/strategist.js +77 -0
- package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
- package/dist/server/studio-ui/styles/tokens-base.js +131 -0
- package/dist/server/studio-ui.d.ts +1 -10
- package/dist/server/studio-ui.js +63 -5314
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Studio Dispatch on Windows: task text with quotes or symbols like > could cut off the agent's instructions and send its output to a stray file.
|
|
8
|
+
|
|
9
|
+
### Removed
|
|
10
|
+
|
|
11
|
+
- The simulation endpoints (`/api/pipeline/simulation/start` and `/stop`) that fed the old Workflow Canvas.
|
|
12
|
+
|
|
13
|
+
## 2.3.0
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **Studio redesign.** New Home, Tasks, Flow and Team pages focused on what needs you, what
|
|
18
|
+
finished, how the work connects, and how each agent is doing. Ctrl+K command palette to find
|
|
19
|
+
any task, a detail panel to read and act on one task, and light and dark themes.
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- Agent Cockpits and Workflow Canvas pages and the simulation controls in the UI. The
|
|
24
|
+
simulation server endpoints remain for now and will be removed in a later cleanup.
|
|
25
|
+
|
|
3
26
|
## 2.2.0
|
|
4
27
|
|
|
5
28
|
### Added
|
|
@@ -34,7 +57,7 @@
|
|
|
34
57
|
|
|
35
58
|
Documentation and package metadata only; no behaviour changes.
|
|
36
59
|
|
|
37
|
-
- **npm is the only install path.** The source repository is private, so the instructions for `npm install -g github
|
|
60
|
+
- **npm is the only install path.** The source repository is private, so the instructions for `npm install -g github:...` and for adding the plugin marketplace were removed from the README and the installation guide. `nativ setup` is the complete route: it writes the agents, the enforcement hook, the MCP entry and the permission rules, which a plugin cannot ship.
|
|
38
61
|
- The plugin files still ship inside the package (`plugin/`) and can be tried for one session with `claude --plugin-dir`.
|
|
39
62
|
- `package.json` no longer declares `repository`, `homepage` or `bugs`, so the npm page has no dead links.
|
|
40
63
|
- The 2.0.0 entry below still mentions the plugin marketplace; that route is not available outside the private repository.
|
package/README.md
CHANGED
|
@@ -234,6 +234,21 @@ nativ bench --scenario concurrency --concurrency 8
|
|
|
234
234
|
nativ bench --json
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
+
### Studio
|
|
238
|
+
|
|
239
|
+
`nativ studio` runs a local dashboard at http://localhost:4983 with live updates. It loads
|
|
240
|
+
nothing from the internet and runs under a strict content security policy. The sidebar
|
|
241
|
+
groups pages by role.
|
|
242
|
+
|
|
243
|
+
**Home** shows what needs you, what's running now, and what just finished.
|
|
244
|
+
**Tasks** displays one milestone at a time, sorted newest completed first.
|
|
245
|
+
**Flow** shows how the work connects and visualizes how the run really happened.
|
|
246
|
+
**Team** lists each agent's activity in plain terms: tasks done, time spent, cost.
|
|
247
|
+
**Worktrees, Benchmarks, Database** are system tools for branches, performance and schemas.
|
|
248
|
+
|
|
249
|
+
Press Ctrl+K (or Cmd+K or /) to find any task by id, title, agent or file name. The theme
|
|
250
|
+
toggle switches between light and dark; your choice is saved locally.
|
|
251
|
+
|
|
237
252
|
### Database studio and schema telemetry (masked, structure only)
|
|
238
253
|
|
|
239
254
|
```bash
|
|
@@ -209,14 +209,16 @@ export declare function buildRunnerPermissions(task: Pick<MasterPlanTask, 'verif
|
|
|
209
209
|
/** Where a task's runner settings live: a subdirectory, because run history parses every top-level .json in runs/. */
|
|
210
210
|
export declare function runnerSettingsPath(runsDir: string, taskId: string): string;
|
|
211
211
|
export declare function writeRunnerSettings(runsDir: string, taskId: string, settings: RunnerPermissions): string;
|
|
212
|
+
/** The instructions text handed to the agent on its standard input. */
|
|
213
|
+
export declare function buildRunnerPrompt(task: MasterPlanTask): string;
|
|
212
214
|
/**
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
+
* Arguments for Claude Code CLI to run headless. The prompt goes on stdin, never in this string:
|
|
216
|
+
* quotes or symbols in task text would break the shell.
|
|
215
217
|
*
|
|
216
218
|
* It runs in `acceptEdits` mode with the rules in `settingsFile` (see buildRunnerPermissions); it does not bypass
|
|
217
219
|
* permissions. Without a settings file only the file-edit tools and read-only commands are pre-approved.
|
|
218
220
|
*/
|
|
219
|
-
export declare function buildDefaultClaudeCommand(
|
|
221
|
+
export declare function buildDefaultClaudeCommand(_task: MasterPlanTask, model?: string | null, settingsFile?: string): string;
|
|
220
222
|
/** True when a runner command asks Claude Code for its line-delimited JSON event stream. */
|
|
221
223
|
export declare function usesClaudeStreamJson(command: string): boolean;
|
|
222
224
|
/** Final `result` event of a Claude Code stream: grounded usage for cli runs. */
|
|
@@ -213,15 +213,9 @@ export function writeRunnerSettings(runsDir, taskId, settings) {
|
|
|
213
213
|
fs.writeFileSync(file, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
214
214
|
return file;
|
|
215
215
|
}
|
|
216
|
-
/**
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
*
|
|
220
|
-
* It runs in `acceptEdits` mode with the rules in `settingsFile` (see buildRunnerPermissions); it does not bypass
|
|
221
|
-
* permissions. Without a settings file only the file-edit tools and read-only commands are pre-approved.
|
|
222
|
-
*/
|
|
223
|
-
export function buildDefaultClaudeCommand(task, model, settingsFile) {
|
|
224
|
-
const parts = [
|
|
216
|
+
/** The instructions text handed to the agent on its standard input. */
|
|
217
|
+
export function buildRunnerPrompt(task) {
|
|
218
|
+
return [
|
|
225
219
|
`Execute task ${task.id} (${task.title}).`,
|
|
226
220
|
task.description ? `Description: ${task.description}.` : '',
|
|
227
221
|
task.verificationCommand ? `Verify your work using: ${task.verificationCommand}.` : '',
|
|
@@ -230,11 +224,19 @@ export function buildDefaultClaudeCommand(task, model, settingsFile) {
|
|
|
230
224
|
]
|
|
231
225
|
.filter(Boolean)
|
|
232
226
|
.join(' ');
|
|
233
|
-
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Arguments for Claude Code CLI to run headless. The prompt goes on stdin, never in this string:
|
|
230
|
+
* quotes or symbols in task text would break the shell.
|
|
231
|
+
*
|
|
232
|
+
* It runs in `acceptEdits` mode with the rules in `settingsFile` (see buildRunnerPermissions); it does not bypass
|
|
233
|
+
* permissions. Without a settings file only the file-edit tools and read-only commands are pre-approved.
|
|
234
|
+
*/
|
|
235
|
+
export function buildDefaultClaudeCommand(_task, model, settingsFile) {
|
|
234
236
|
const modelFlag = model ? ` --model ${model}` : '';
|
|
235
237
|
// Plain `claude -p` prints nothing until it finishes; stream-json emits every message and tool call as it happens.
|
|
236
238
|
const settingsFlag = settingsFile ? ` --settings "${settingsFile}"` : '';
|
|
237
|
-
return `claude -p
|
|
239
|
+
return `claude -p${modelFlag} --output-format stream-json --verbose --permission-mode acceptEdits${settingsFlag}`;
|
|
238
240
|
}
|
|
239
241
|
/** True when a runner command asks Claude Code for its line-delimited JSON event stream. */
|
|
240
242
|
export function usesClaudeStreamJson(command) {
|
|
@@ -1092,7 +1094,7 @@ export class AgentSupervisor extends EventEmitter {
|
|
|
1092
1094
|
// stdio pipes, so we stay attached and let `taskkill /T` walk the tree.
|
|
1093
1095
|
detached: process.platform !== 'win32',
|
|
1094
1096
|
windowsHide: true,
|
|
1095
|
-
stdio: ['
|
|
1097
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1096
1098
|
env: {
|
|
1097
1099
|
...buildRunnerEnv(process.env, options.env),
|
|
1098
1100
|
...(record.thinking?.budgetTokens ? { MAX_THINKING_TOKENS: String(record.thinking.budgetTokens) } : {}),
|
|
@@ -1103,6 +1105,9 @@ export class AgentSupervisor extends EventEmitter {
|
|
|
1103
1105
|
});
|
|
1104
1106
|
run.child = child;
|
|
1105
1107
|
record.pid = child.pid ?? null;
|
|
1108
|
+
// A runner that exits without reading its input must not crash us or fail the run.
|
|
1109
|
+
child.stdin?.on('error', () => { });
|
|
1110
|
+
child.stdin?.end(buildRunnerPrompt(task), 'utf8');
|
|
1106
1111
|
child.stdout?.on('data', (chunk) => {
|
|
1107
1112
|
if (run.streamJson)
|
|
1108
1113
|
this.consumeStreamJson(run, chunk.toString());
|
|
@@ -1580,128 +1580,6 @@ class PipelineEventHub {
|
|
|
1580
1580
|
res.write(frame);
|
|
1581
1581
|
}
|
|
1582
1582
|
}
|
|
1583
|
-
/**
|
|
1584
|
-
* Interactive simulation engine for Mission Control Studio.
|
|
1585
|
-
* Broadcasts realistic multi-agent execution steps over the SSE stream
|
|
1586
|
-
* without modifying workspace files or invoking external model APIs.
|
|
1587
|
-
*/
|
|
1588
|
-
class SimulationController {
|
|
1589
|
-
events;
|
|
1590
|
-
activeTimer = null;
|
|
1591
|
-
running = false;
|
|
1592
|
-
currentId = null;
|
|
1593
|
-
constructor(events) {
|
|
1594
|
-
this.events = events;
|
|
1595
|
-
}
|
|
1596
|
-
start(body = {}) {
|
|
1597
|
-
this.stop();
|
|
1598
|
-
const id = `sim-${Date.now()}`;
|
|
1599
|
-
this.currentId = id;
|
|
1600
|
-
this.running = true;
|
|
1601
|
-
const speed = Math.max(1, Math.min(5, Number(body.speedMultiplier) || 1));
|
|
1602
|
-
const scenario = String(body.scenario || 'default');
|
|
1603
|
-
const steps = [
|
|
1604
|
-
// Step 1: Foundation task (qa-tester)
|
|
1605
|
-
{ taskId: 'task-01', phase: 'spawning_worktree', log: '[qa-tester] Provisioning isolated worktree branch for task-01...\n', delay: 400 },
|
|
1606
|
-
{ taskId: 'task-01', phase: 'running', log: '[qa-tester] Validating dependencies, project manifest and baseline checks...\n', delay: 600 },
|
|
1607
|
-
{ taskId: 'task-01', phase: 'verifying', log: '[gatekeeper] Running verification command: npm test\n✔ Baseline contracts validated\n', delay: 600 },
|
|
1608
|
-
{ taskId: 'task-01', phase: 'completed', log: '[qa-tester] Task task-01 passed verification and merged.\n', delay: 400 },
|
|
1609
|
-
// Step 2: Parallel execution (backend & frontend)
|
|
1610
|
-
{ taskId: 'task-studio-sse-api', phase: 'spawning_worktree', log: '[backend] Initializing git worktree .nativ/worktrees/task-studio-sse-api...\n', delay: 300 },
|
|
1611
|
-
{ taskId: 'task-studio-light-ui', phase: 'spawning_worktree', log: '[frontend] Initializing git worktree .nativ/worktrees/task-studio-light-ui...\n', delay: 300 },
|
|
1612
|
-
{ taskId: 'task-studio-sse-api', phase: 'running', log: '[backend] Implementing real-time event pipeline fanout & simulation endpoints in studio-server.ts...\n', delay: 700 },
|
|
1613
|
-
{ taskId: 'task-studio-light-ui', phase: 'running', log: '[frontend] Constructing SVG Workflow Canvas with Bezier cables and floating playback bar...\n', delay: 700 },
|
|
1614
|
-
{ taskId: 'task-studio-sse-api', phase: 'verifying', log: '[gatekeeper] Executing test-studio-pipeline-api.mjs...\n✔ All API endpoints passed\n', delay: 600 },
|
|
1615
|
-
{ taskId: 'task-studio-sse-api', phase: 'merging', log: '[backend] Merging worktree branch to main...\n', delay: 300 },
|
|
1616
|
-
{ taskId: 'task-studio-sse-api', phase: 'completed', log: '[backend] SSE pipeline stream online.\n', delay: 400 },
|
|
1617
|
-
{ taskId: 'task-studio-light-ui', phase: 'verifying', log: '[gatekeeper] Executing test-studio-light-ui.mjs...\n✔ Zero-emoji compliance and Canvas DOM validated\n', delay: 600 },
|
|
1618
|
-
{ taskId: 'task-studio-light-ui', phase: 'merging', log: '[frontend] Merging worktree branch to main...\n', delay: 300 },
|
|
1619
|
-
{ taskId: 'task-studio-light-ui', phase: 'completed', log: '[frontend] Workflow Canvas ready.\n', delay: 400 },
|
|
1620
|
-
// Step 3: End-to-end integration & verification
|
|
1621
|
-
{ taskId: 'task-studio-e2e-verify', phase: 'spawning_worktree', log: '[qa-tester] Spawning E2E integration test runner...\n', delay: 300 },
|
|
1622
|
-
{ taskId: 'task-studio-e2e-verify', phase: 'running', log: '[qa-tester] Running comprehensive integration test matrix...\n', delay: 800 },
|
|
1623
|
-
...(scenario === 'circuit_breaker_heal'
|
|
1624
|
-
? [
|
|
1625
|
-
{ taskId: 'task-studio-e2e-verify', phase: 'failed', stream: 'stderr', log: '[qa-tester] Invariant violation detected: test mock failure. Circuit breaker tripped.\n', delay: 600 },
|
|
1626
|
-
]
|
|
1627
|
-
: [
|
|
1628
|
-
{ taskId: 'task-studio-e2e-verify', phase: 'verifying', log: '[gatekeeper] Full E2E suite passed.\n', delay: 600 },
|
|
1629
|
-
{ taskId: 'task-studio-e2e-verify', phase: 'completed', log: '[qa-tester] All milestones verified.\n', delay: 400 },
|
|
1630
|
-
]),
|
|
1631
|
-
];
|
|
1632
|
-
let stepIndex = 0;
|
|
1633
|
-
const runNext = () => {
|
|
1634
|
-
if (!this.running || stepIndex >= steps.length) {
|
|
1635
|
-
this.running = false;
|
|
1636
|
-
return;
|
|
1637
|
-
}
|
|
1638
|
-
const step = steps[stepIndex++];
|
|
1639
|
-
const record = {
|
|
1640
|
-
runId: `${id}-${step.taskId}`,
|
|
1641
|
-
taskId: step.taskId,
|
|
1642
|
-
status: step.phase,
|
|
1643
|
-
engine: 'native',
|
|
1644
|
-
model: 'claude-opus-5-5 (simulation)',
|
|
1645
|
-
thinking: { budget: 2048, effort: 'medium', budgetTokens: 2048 },
|
|
1646
|
-
usage: {
|
|
1647
|
-
model: 'claude-opus-5-5 (simulation)',
|
|
1648
|
-
turns: 3,
|
|
1649
|
-
inputTokens: 1400,
|
|
1650
|
-
outputTokens: 380,
|
|
1651
|
-
cacheCreationTokens: 500,
|
|
1652
|
-
cacheReadTokens: 4000,
|
|
1653
|
-
thinkingTokens: 120,
|
|
1654
|
-
costUsd: 0.011,
|
|
1655
|
-
},
|
|
1656
|
-
pid: 99000 + stepIndex,
|
|
1657
|
-
branch: `nativ/${step.taskId}`,
|
|
1658
|
-
worktreeDir: `.nativ/worktrees/${step.taskId}`,
|
|
1659
|
-
command: 'simulation',
|
|
1660
|
-
startedAt: new Date(Date.now() - 2000).toISOString(),
|
|
1661
|
-
endedAt: step.phase === 'completed' || step.phase === 'failed' ? new Date().toISOString() : null,
|
|
1662
|
-
durationMs: 2500,
|
|
1663
|
-
exitCode: step.phase === 'completed' ? 0 : step.phase === 'failed' ? 1 : null,
|
|
1664
|
-
signal: null,
|
|
1665
|
-
timeoutSeconds: 300,
|
|
1666
|
-
timedOut: false,
|
|
1667
|
-
logBytes: (step.log || '').length,
|
|
1668
|
-
logFile: `.nativ/logs/${step.taskId}.log`,
|
|
1669
|
-
error: step.phase === 'failed' ? (step.log || 'Task failed') : null,
|
|
1670
|
-
abortReason: null,
|
|
1671
|
-
verification: step.phase === 'completed' ? { command: 'npm test', success: true, exitCode: 0, durationMs: 400, skipped: false } : null,
|
|
1672
|
-
};
|
|
1673
|
-
this.events.publish('runner_status', runView(record));
|
|
1674
|
-
if (step.log) {
|
|
1675
|
-
this.events.publish('runner_log', {
|
|
1676
|
-
runId: `${id}-${step.taskId}`,
|
|
1677
|
-
taskId: step.taskId,
|
|
1678
|
-
stream: step.stream ?? 'stdout',
|
|
1679
|
-
chunk: step.log,
|
|
1680
|
-
at: new Date().toISOString(),
|
|
1681
|
-
});
|
|
1682
|
-
}
|
|
1683
|
-
this.activeTimer = setTimeout(runNext, Math.max(80, Math.round(step.delay / speed)));
|
|
1684
|
-
};
|
|
1685
|
-
this.activeTimer = setTimeout(runNext, Math.round(150 / speed));
|
|
1686
|
-
return {
|
|
1687
|
-
ok: true,
|
|
1688
|
-
simulationId: id,
|
|
1689
|
-
scenario,
|
|
1690
|
-
speed,
|
|
1691
|
-
status: 'running',
|
|
1692
|
-
};
|
|
1693
|
-
}
|
|
1694
|
-
stop() {
|
|
1695
|
-
if (this.activeTimer) {
|
|
1696
|
-
clearTimeout(this.activeTimer);
|
|
1697
|
-
this.activeTimer = null;
|
|
1698
|
-
}
|
|
1699
|
-
const wasRunning = this.running;
|
|
1700
|
-
this.running = false;
|
|
1701
|
-
this.currentId = null;
|
|
1702
|
-
return { ok: true, status: 'stopped', wasRunning };
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
1583
|
// ─── Server ────────────────────────────────────────────────────────────────────
|
|
1706
1584
|
export function createStudioServer(options = {}) {
|
|
1707
1585
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -1742,7 +1620,6 @@ export function createStudioServer(options = {}) {
|
|
|
1742
1620
|
supervisor.on('runner_status', (record) => events.publish('runner_status', runView(record)));
|
|
1743
1621
|
supervisor.on('runner_log', (entry) => events.publish('runner_log', entry));
|
|
1744
1622
|
supervisor.on('runner_token_usage', (usage) => events.publish('runner_token_usage', usage));
|
|
1745
|
-
const simulation = new SimulationController(events);
|
|
1746
1623
|
// Tier 1 strategist: settings and counters live for this server session only.
|
|
1747
1624
|
const liaison = new Tier1Liaison(root, options.triage);
|
|
1748
1625
|
const triageInFlight = new Map();
|
|
@@ -1842,10 +1719,6 @@ export function createStudioServer(options = {}) {
|
|
|
1842
1719
|
return sendJson(res, 200, handleEscalations(root, url.searchParams));
|
|
1843
1720
|
case 'POST /api/pipeline/escalations/resolve':
|
|
1844
1721
|
return sendJson(res, 200, await handleEscalationResolve(root, await readJsonBody(req)));
|
|
1845
|
-
case 'POST /api/pipeline/simulation/start':
|
|
1846
|
-
return sendJson(res, 200, simulation.start(await readJsonBody(req)));
|
|
1847
|
-
case 'POST /api/pipeline/simulation/stop':
|
|
1848
|
-
return sendJson(res, 200, simulation.stop());
|
|
1849
1722
|
case 'POST /api/pipeline/triage/evaluate':
|
|
1850
1723
|
return sendJson(res, 200, await handleTriageEvaluate(root, liaison, triageInFlight, await readJsonBody(req)));
|
|
1851
1724
|
case 'GET /api/pipeline/triage/status':
|
|
@@ -1863,8 +1736,7 @@ export function createStudioServer(options = {}) {
|
|
|
1863
1736
|
'/api/pipeline/tasks/abort', '/api/pipeline/tasks/runs', '/api/pipeline/tasks/logs', '/api/pipeline/worktrees',
|
|
1864
1737
|
'/api/pipeline/worktrees/action', '/api/pipeline/worktrees/diff', '/api/pipeline/benchmarks', '/api/pipeline/benchmarks/run',
|
|
1865
1738
|
'/api/pipeline/telemetry/detailed', '/api/pipeline/escalations', '/api/pipeline/escalations/resolve',
|
|
1866
|
-
'/api/pipeline/
|
|
1867
|
-
'/api/pipeline/triage/status', '/api/pipeline/triage/config',
|
|
1739
|
+
'/api/pipeline/triage/evaluate', '/api/pipeline/triage/status', '/api/pipeline/triage/config',
|
|
1868
1740
|
];
|
|
1869
1741
|
if (known.includes(url.pathname.replace(/\/+$/, '')))
|
|
1870
1742
|
throw new HttpError(405, 'METHOD_NOT_ALLOWED', `${req.method} not allowed on ${url.pathname}`);
|
|
@@ -1884,7 +1756,6 @@ export function createStudioServer(options = {}) {
|
|
|
1884
1756
|
// Open SSE streams and in-flight agent runners would otherwise keep close() waiting forever.
|
|
1885
1757
|
const closeServer = server.close.bind(server);
|
|
1886
1758
|
server.close = ((callback) => {
|
|
1887
|
-
simulation.stop();
|
|
1888
1759
|
supervisor.shutdown('Studio server shutting down');
|
|
1889
1760
|
events.close();
|
|
1890
1761
|
return closeServer(callback);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function escapeHtml(value) {
|
|
2
|
+
return value.replace(/[&<>"']/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[ch]);
|
|
3
|
+
}
|
|
4
|
+
// Runs before first paint so a saved theme never flashes the wrong colours.
|
|
5
|
+
const themeScript = String.raw `(function () {
|
|
6
|
+
try {
|
|
7
|
+
var t = localStorage.getItem('nativ-studio:theme');
|
|
8
|
+
if (t === 'dark' || t === 'light') document.documentElement.setAttribute('data-theme', t);
|
|
9
|
+
} catch (e) { /* storage blocked */ }
|
|
10
|
+
})();`;
|
|
11
|
+
// Opening of the document up to the style block; the CSS chunks follow it.
|
|
12
|
+
export const documentStart = String.raw `<!doctype html>
|
|
13
|
+
<html lang="en">
|
|
14
|
+
<head>
|
|
15
|
+
<meta charset="utf-8">
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
17
|
+
<meta name="color-scheme" content="light dark">
|
|
18
|
+
<title>Nativ Studio</title>
|
|
19
|
+
<script>${themeScript}</script>
|
|
20
|
+
<style>
|
|
21
|
+
`;
|
|
22
|
+
export const styleEnd = '</style>\n';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ICON_PATHS } from '../../shell/icons.js';
|
|
2
|
+
import { benchmarksCss } from './styles.js';
|
|
3
|
+
import { benchmarksScript } from './script.js';
|
|
4
|
+
export const benchmarksPage = {
|
|
5
|
+
id: 'benchmarks',
|
|
6
|
+
group: 'system',
|
|
7
|
+
label: 'Benchmarks',
|
|
8
|
+
icon: ICON_PATHS.benchmarks,
|
|
9
|
+
css: benchmarksCss,
|
|
10
|
+
script: benchmarksScript,
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const benchmarksScript: string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Benchmarks view script.
|
|
2
|
+
export const benchmarksScript = String.raw ` // ─── View 4: Synthetic benchmarks ──────────────────────────────────────────
|
|
3
|
+
var SCENARIOS = [
|
|
4
|
+
{ id: 'concurrency', label: 'Multi-Agent Concurrency' },
|
|
5
|
+
{ id: 'telemetry', label: 'Telemetry Engine' },
|
|
6
|
+
{ id: 'governor', label: 'Governor Invariants' },
|
|
7
|
+
{ id: 'verification', label: 'Gatekeeper' },
|
|
8
|
+
{ id: 'e2e_pipeline', label: 'E2E Lifecycle' }
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
function renderBenchmarks() {
|
|
12
|
+
var running = !!pipe.busy.bench;
|
|
13
|
+
var runBtn = '<button class="btn primary" type="button" data-act="bench-run"' + (running ? ' disabled' : '') + '>' +
|
|
14
|
+
(running ? '<span class="spinner" aria-hidden="true"></span>Running…' : ICON.play + 'Run Benchmark') + '</button>';
|
|
15
|
+
var head = panelHead('Synthetic Benchmarks', 'Sandboxed stress runs of plan locking, telemetry, the contract governor, the verification gatekeeper and the full agent lifecycle.', runBtn);
|
|
16
|
+
if (!pipe.loaded.benchmarks) return head + '<div class="card section" aria-hidden="true"><div class="skeleton tall"></div></div><div class="scenario-grid" aria-hidden="true">' + skeletonCards(5, 'scenario') + '</div>';
|
|
17
|
+
if (pipe.errors.benchmarks && !pipe.report) return head + errorCard('benchmark report', pipe.errors.benchmarks, 'benchmarks');
|
|
18
|
+
var r = pipe.report;
|
|
19
|
+
if (!r) {
|
|
20
|
+
return head + '<div class="card state" aria-busy="' + running + '"><div class="state-icon">' + (running ? '<span class="spinner" aria-hidden="true"></span>' : ICON.gauge) + '</div>' +
|
|
21
|
+
'<h2>' + (running ? 'Running benchmark matrix…' : 'No benchmark report yet') + '</h2>' +
|
|
22
|
+
'<p>' + (running ? 'Scenarios run in throwaway sandboxes; this usually takes a few seconds.' : 'Run the synthetic matrix to measure throughput and score all five pipeline scenarios. Results are cached in .ai/benchmark_report.json.') + '</p></div>';
|
|
23
|
+
}
|
|
24
|
+
return head + staleNote('benchmarks') + benchHero(r, running) + '<div class="scenario-grid">' + scenarioCards(r) + '</div>';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function benchHero(r, running) {
|
|
28
|
+
var s = r.summary || {};
|
|
29
|
+
var env = r.environment || {};
|
|
30
|
+
var meta = [];
|
|
31
|
+
if (r.timestamp) meta.push('Ran ' + esc(fmtDate(r.timestamp)));
|
|
32
|
+
if (env.platform) meta.push(esc(env.platform + (env.arch ? ' ' + env.arch : '')));
|
|
33
|
+
if (env.nodeVersion) meta.push('Node ' + esc(env.nodeVersion));
|
|
34
|
+
if (env.cpuCount) meta.push(env.cpuCount + ' CPUs');
|
|
35
|
+
var score = clampPct(s.score);
|
|
36
|
+
return '<section class="card hero' + (running ? ' is-running' : '') + '" aria-labelledby="hero-h" aria-busy="' + running + '">' +
|
|
37
|
+
'<div><h2 class="eyebrow" id="hero-h">Average throughput</h2>' +
|
|
38
|
+
'<div class="hero-value">' + num(Math.round(s.averageThroughputOpsPerSec || 0)) + '<span class="badge badge-accent">ops/sec</span></div>' +
|
|
39
|
+
'<p class="hero-meta">' + meta.join(' · ') + '</p></div>' +
|
|
40
|
+
'<dl class="hero-stats">' +
|
|
41
|
+
'<div><dt>Score</dt><dd class="' + (score < 100 ? 'text-danger' : '') + '">' + score + '%</dd></div>' +
|
|
42
|
+
'<div><dt>Scenarios passed</dt><dd>' + num(s.passedScenarios) + '/' + num(s.totalScenarios) + '</dd></div>' +
|
|
43
|
+
'<div><dt>Total duration</dt><dd>' + fmtMs(s.totalDurationMs) + '</dd></div>' +
|
|
44
|
+
'<div><dt>Operations</dt><dd>' + num(s.totalOperations) + '</dd></div>' +
|
|
45
|
+
'</dl></section>';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function scenarioCards(r) {
|
|
49
|
+
var byId = {}, known = {};
|
|
50
|
+
(r.scenarios || []).forEach(function (s) { byId[s.id] = s; });
|
|
51
|
+
var cards = SCENARIOS.map(function (meta) { known[meta.id] = true; return scenarioCard(meta.label, byId[meta.id]); });
|
|
52
|
+
(r.scenarios || []).forEach(function (s) { if (!known[s.id]) cards.push(scenarioCard(s.name || s.id, s)); });
|
|
53
|
+
return cards.join('');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function scenarioCard(label, s) {
|
|
57
|
+
if (!s) {
|
|
58
|
+
return '<article class="card scenario is-idle"><header class="scenario-head"><h3>' + esc(label) + '</h3><span class="badge badge-neutral">Not run</span></header>' +
|
|
59
|
+
'<p class="muted">Not part of the last run. Run the full matrix to score it.</p></article>';
|
|
60
|
+
}
|
|
61
|
+
var total = (s.assertionsPassed || 0) + (s.assertionsFailed || 0);
|
|
62
|
+
var passed = s.status === 'passed';
|
|
63
|
+
var score = total ? Math.round((s.assertionsPassed || 0) / total * 100) : (passed ? 100 : 0);
|
|
64
|
+
var details = Object.keys(s.details || {}).slice(0, 4).map(function (k) {
|
|
65
|
+
var v = s.details[k];
|
|
66
|
+
// Lists (e.g. invariant names) get a full-width row of chips instead of a cramped value column.
|
|
67
|
+
if (Array.isArray(v)) {
|
|
68
|
+
return '<dt class="wide">' + esc(humanize(k)) + '</dt><dd class="wide">' + v.map(function (item) { return '<span class="chip">' + esc(detailValue(item)) + '</span>'; }).join('') + '</dd>';
|
|
69
|
+
}
|
|
70
|
+
return '<dt>' + esc(humanize(k)) + '</dt><dd>' + esc(detailValue(v)) + '</dd>';
|
|
71
|
+
}).join('');
|
|
72
|
+
var errors = (s.errors || []).length ? '<ul class="scenario-errors">' + s.errors.map(function (e) { return '<li>' + esc(e) + '</li>'; }).join('') + '</ul>' : '';
|
|
73
|
+
return '<article class="card scenario"><header class="scenario-head"><h3>' + esc(label) + '</h3>' +
|
|
74
|
+
'<span class="badge badge-' + (passed ? 'success' : 'danger') + '">' + (passed ? 'Passed' : 'Failed') + '</span></header>' +
|
|
75
|
+
(s.name && s.name !== label ? '<p class="scenario-sub">' + esc(s.name) + '</p>' : '') +
|
|
76
|
+
'<div class="metrics"><div><span class="metric-v">' + score + '%</span><span class="metric-l">Score</span></div>' +
|
|
77
|
+
'<div><span class="metric-v">' + fmtMs(s.durationMs) + '</span><span class="metric-l">Latency</span></div>' +
|
|
78
|
+
'<div><span class="metric-v">' + num(Math.round(s.throughputOpsPerSec || 0)) + '</span><span class="metric-l">ops/sec</span></div></div>' +
|
|
79
|
+
progressBar(score, 'sc-' + s.id, label + ' score', passed ? 'success' : 'danger') +
|
|
80
|
+
(details ? '<dl class="details">' + details + '</dl>' : '') + errors + '</article>';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function runBenchmark() {
|
|
84
|
+
if (pipe.busy.bench) return;
|
|
85
|
+
pipe.busy.bench = true;
|
|
86
|
+
repaint('benchmarks');
|
|
87
|
+
postJson('/api/pipeline/benchmarks/run', {}).then(function (r) {
|
|
88
|
+
if (r.report) { pipe.report = r.report; pipe.errors.benchmarks = null; pipe.loaded.benchmarks = true; }
|
|
89
|
+
var s = r.report && r.report.summary;
|
|
90
|
+
toast('Benchmark finished' + (s ? ': score ' + clampPct(s.score) + '%, ' + s.passedScenarios + '/' + s.totalScenarios + ' scenarios passed' : ''), 'ok');
|
|
91
|
+
}, function (e) {
|
|
92
|
+
toast('Benchmark run failed: ' + e.message);
|
|
93
|
+
}).then(function () {
|
|
94
|
+
pipe.busy.bench = false;
|
|
95
|
+
repaint('benchmarks');
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
Studio.registerPage('benchmarks', {
|
|
100
|
+
deps: ['benchmarks'],
|
|
101
|
+
render: function () { return renderBenchmarks(); },
|
|
102
|
+
actions: { 'bench-run': function () { runBenchmark(); } }
|
|
103
|
+
});
|
|
104
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const benchmarksCss: string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Benchmarks view styles.
|
|
2
|
+
export const benchmarksCss = String.raw `/* Benchmarks */
|
|
3
|
+
.hero { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 20px 32px; padding: 22px; margin-bottom: 16px; transition: opacity .2s; }
|
|
4
|
+
.hero.is-running { opacity: .6; }
|
|
5
|
+
.eyebrow { margin: 0; font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
|
|
6
|
+
.hero-value { display: flex; align-items: center; gap: 10px; margin: 6px 0; font-size: 40px; font-weight: 650; letter-spacing: -0.03em; line-height: 1.1; font-variant-numeric: tabular-nums; }
|
|
7
|
+
.hero-value .badge { font-size: 12px; letter-spacing: 0; }
|
|
8
|
+
.hero-meta { margin: 0; font-size: 13.5px; color: var(--muted); }
|
|
9
|
+
.hero-stats { display: grid; grid-template-columns: repeat(4, minmax(96px, auto)); gap: 4px 28px; margin: 0; }
|
|
10
|
+
.hero-stats dt { font-size: 12.5px; color: var(--muted); }
|
|
11
|
+
.hero-stats dd { margin: 0; font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
12
|
+
.scenario-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
|
|
13
|
+
.scenario { display: flex; flex-direction: column; gap: 10px; min-width: 0; padding: 16px; }
|
|
14
|
+
.scenario.is-idle { box-shadow: none; border-style: dashed; }
|
|
15
|
+
.scenario-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
|
|
16
|
+
.scenario-head h3 { margin: 0; font-size: 14.5px; font-weight: 600; }
|
|
17
|
+
.scenario-sub { margin: -6px 0 0; font-size: 13px; color: var(--muted); }
|
|
18
|
+
.scenario p { margin: 0; font-size: 13.5px; line-height: 1.5; }
|
|
19
|
+
.metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
|
|
20
|
+
.metric-v { display: block; font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
21
|
+
.metric-l { display: block; font-size: 12px; color: var(--muted); }
|
|
22
|
+
.details { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 12px; margin: 0; padding-top: 10px; font-size: 12.5px; border-top: 1px solid var(--border); }
|
|
23
|
+
.details dt { color: var(--muted); }
|
|
24
|
+
.details dd { margin: 0; text-align: right; font-family: var(--mono); font-size: 12px; overflow-wrap: anywhere; }
|
|
25
|
+
.details .wide { grid-column: 1 / -1; }
|
|
26
|
+
.details dd.wide { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; text-align: left; }
|
|
27
|
+
.details dd.wide .chip { font-size: 11px; }
|
|
28
|
+
.scenario-errors { margin: 0; padding: 8px 10px 8px 26px; font-size: 12.5px; color: var(--danger); background: var(--danger-bg); border: 1px solid var(--danger-line); border-radius: var(--r-control); }
|
|
29
|
+
|
|
30
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const databaseBrowserScript: string;
|