@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.
Files changed (141) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +15 -0
  3. package/dist/runner/agent-supervisor.d.ts +5 -3
  4. package/dist/runner/agent-supervisor.js +17 -12
  5. package/dist/server/studio-server.js +1 -130
  6. package/dist/server/studio-ui/document.d.ts +3 -0
  7. package/dist/server/studio-ui/document.js +22 -0
  8. package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
  9. package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
  10. package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
  11. package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
  12. package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
  13. package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
  14. package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
  15. package/dist/server/studio-ui/pages/database/browser.js +326 -0
  16. package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
  17. package/dist/server/studio-ui/pages/database/connection.js +158 -0
  18. package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
  19. package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
  20. package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
  21. package/dist/server/studio-ui/pages/database/drift.js +301 -0
  22. package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
  23. package/dist/server/studio-ui/pages/database/events.js +231 -0
  24. package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
  25. package/dist/server/studio-ui/pages/database/explorer.js +183 -0
  26. package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
  27. package/dist/server/studio-ui/pages/database/index.js +28 -0
  28. package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
  29. package/dist/server/studio-ui/pages/database/markup.js +32 -0
  30. package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
  31. package/dist/server/studio-ui/pages/database/state.js +57 -0
  32. package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
  33. package/dist/server/studio-ui/pages/database/styles.js +118 -0
  34. package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
  35. package/dist/server/studio-ui/pages/flow/index.js +11 -0
  36. package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
  37. package/dist/server/studio-ui/pages/flow/layout.js +105 -0
  38. package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
  39. package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
  40. package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
  41. package/dist/server/studio-ui/pages/flow/script.js +61 -0
  42. package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
  43. package/dist/server/studio-ui/pages/flow/styles.js +59 -0
  44. package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
  45. package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
  46. package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
  47. package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
  48. package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
  49. package/dist/server/studio-ui/pages/home/index.js +11 -0
  50. package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
  51. package/dist/server/studio-ui/pages/home/script.js +160 -0
  52. package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
  53. package/dist/server/studio-ui/pages/home/styles.js +48 -0
  54. package/dist/server/studio-ui/pages/index.d.ts +2 -0
  55. package/dist/server/studio-ui/pages/index.js +17 -0
  56. package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
  57. package/dist/server/studio-ui/pages/tasks/index.js +11 -0
  58. package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
  59. package/dist/server/studio-ui/pages/tasks/script.js +96 -0
  60. package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
  61. package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
  62. package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
  63. package/dist/server/studio-ui/pages/team/index.js +11 -0
  64. package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
  65. package/dist/server/studio-ui/pages/team/script.js +112 -0
  66. package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
  67. package/dist/server/studio-ui/pages/team/styles.js +22 -0
  68. package/dist/server/studio-ui/pages/types.d.ts +13 -0
  69. package/dist/server/studio-ui/pages/types.js +1 -0
  70. package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
  71. package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
  72. package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
  73. package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
  74. package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
  75. package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
  76. package/dist/server/studio-ui/shell/actions.d.ts +1 -0
  77. package/dist/server/studio-ui/shell/actions.js +49 -0
  78. package/dist/server/studio-ui/shell/boot.d.ts +2 -0
  79. package/dist/server/studio-ui/shell/boot.js +13 -0
  80. package/dist/server/studio-ui/shell/console.d.ts +1 -0
  81. package/dist/server/studio-ui/shell/console.js +194 -0
  82. package/dist/server/studio-ui/shell/core.d.ts +1 -0
  83. package/dist/server/studio-ui/shell/core.js +50 -0
  84. package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
  85. package/dist/server/studio-ui/shell/detail-view.js +80 -0
  86. package/dist/server/studio-ui/shell/detail.d.ts +1 -0
  87. package/dist/server/studio-ui/shell/detail.js +83 -0
  88. package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
  89. package/dist/server/studio-ui/shell/dispatch.js +150 -0
  90. package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
  91. package/dist/server/studio-ui/shell/events-tools.js +94 -0
  92. package/dist/server/studio-ui/shell/format.d.ts +1 -0
  93. package/dist/server/studio-ui/shell/format.js +151 -0
  94. package/dist/server/studio-ui/shell/icons.d.ts +2 -0
  95. package/dist/server/studio-ui/shell/icons.js +20 -0
  96. package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
  97. package/dist/server/studio-ui/shell/markup-head.js +43 -0
  98. package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
  99. package/dist/server/studio-ui/shell/markup-panels.js +7 -0
  100. package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
  101. package/dist/server/studio-ui/shell/markup-tools.js +222 -0
  102. package/dist/server/studio-ui/shell/model.d.ts +1 -0
  103. package/dist/server/studio-ui/shell/model.js +192 -0
  104. package/dist/server/studio-ui/shell/paint.d.ts +1 -0
  105. package/dist/server/studio-ui/shell/paint.js +154 -0
  106. package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
  107. package/dist/server/studio-ui/shell/palette-view.js +60 -0
  108. package/dist/server/studio-ui/shell/palette.d.ts +1 -0
  109. package/dist/server/studio-ui/shell/palette.js +80 -0
  110. package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
  111. package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
  112. package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
  113. package/dist/server/studio-ui/shell/proposals.js +197 -0
  114. package/dist/server/studio-ui/shell/pure.d.ts +3 -0
  115. package/dist/server/studio-ui/shell/pure.js +9 -0
  116. package/dist/server/studio-ui/shell/router.d.ts +1 -0
  117. package/dist/server/studio-ui/shell/router.js +95 -0
  118. package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
  119. package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
  120. package/dist/server/studio-ui/shell/sync.d.ts +1 -0
  121. package/dist/server/studio-ui/shell/sync.js +120 -0
  122. package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
  123. package/dist/server/studio-ui/shell/toasts-format.js +89 -0
  124. package/dist/server/studio-ui/shell/triage.d.ts +1 -0
  125. package/dist/server/studio-ui/shell/triage.js +210 -0
  126. package/dist/server/studio-ui/styles/console.d.ts +1 -0
  127. package/dist/server/studio-ui/styles/console.js +83 -0
  128. package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
  129. package/dist/server/studio-ui/styles/dialogs.js +118 -0
  130. package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
  131. package/dist/server/studio-ui/styles/responsive.js +28 -0
  132. package/dist/server/studio-ui/styles/shell.d.ts +1 -0
  133. package/dist/server/studio-ui/styles/shell.js +117 -0
  134. package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
  135. package/dist/server/studio-ui/styles/strategist.js +77 -0
  136. package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
  137. package/dist/server/studio-ui/styles/tokens-base.js +131 -0
  138. package/dist/server/studio-ui.d.ts +1 -10
  139. package/dist/server/studio-ui.js +63 -5314
  140. package/package.json +2 -2
  141. 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:…` 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.
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
- * Builds the autonomous prompt and arguments required by Claude Code CLI
214
- * to execute non-interactively in a headless child process.
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(task: MasterPlanTask, model?: string | null, settingsFile?: string): string;
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
- * Builds the autonomous prompt and arguments required by Claude Code CLI
218
- * to execute non-interactively in a headless child process.
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
- const escapedPrompt = parts.replace(/"/g, '\\"');
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 "${escapedPrompt}"${modelFlag} --output-format stream-json --verbose --permission-mode acceptEdits${settingsFlag}`;
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: ['ignore', 'pipe', 'pipe'],
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/simulation/start', '/api/pipeline/simulation/stop', '/api/pipeline/triage/evaluate',
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,3 @@
1
+ export declare function escapeHtml(value: string): string;
2
+ export declare const documentStart: string;
3
+ export declare const styleEnd = "</style>\n";
@@ -0,0 +1,22 @@
1
+ export function escapeHtml(value) {
2
+ return value.replace(/[&<>"']/g, (ch) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[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,2 @@
1
+ import type { PageModule } from '../types.js';
2
+ export declare const benchmarksPage: PageModule;
@@ -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;