@minionry/minion 0.7.36 → 0.7.39

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 (120) hide show
  1. package/bin/commands/minions.js +19 -0
  2. package/bin/minion.js +33 -4
  3. package/dist/server/cli/headless/claude-invoker-process.js +23 -7
  4. package/dist/server/cli/headless/claude-invoker-stream.js +8 -3
  5. package/dist/server/cli/headless/haiku-assessments.js +2 -1
  6. package/dist/server/cli/headless/mcp-config.js +2 -1
  7. package/dist/server/engines/claude/claude-command.js +1 -0
  8. package/dist/server/engines/factory.js +2 -0
  9. package/dist/server/engines/hermes/HermesEngine.js +112 -219
  10. package/dist/server/engines/hermes/hermes-gateway-registry.js +467 -0
  11. package/dist/server/index.js +10 -4
  12. package/dist/server/mcp/bouncer-integration.js +3 -2
  13. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js +35 -8
  14. package/dist/server/mcp/classifier/shadow-eval-scheduler.js +42 -0
  15. package/dist/server/mcp/classifier/telemetry.js +0 -0
  16. package/dist/server/mcp/permission-channels.js +176 -0
  17. package/dist/server/mcp/security-analysis.js +9 -1
  18. package/dist/server/mcp/security-audit.js +22 -2
  19. package/dist/server/mcp/security-patterns.js +73 -1
  20. package/dist/server/mcp/server.js +80 -50
  21. package/dist/server/routes/internal.js +6 -1
  22. package/dist/server/routes/notifications.js +6 -18
  23. package/dist/server/server-setup.js +24 -48
  24. package/dist/server/services/analytics.js +4 -2
  25. package/dist/server/services/browser/host-mock.js +6 -1
  26. package/dist/server/services/browser/host.js +27 -3
  27. package/dist/server/services/browser/reasoning-agent.js +3 -1
  28. package/dist/server/services/chain/chain-engine.js +494 -0
  29. package/dist/server/services/chain/chain-store.js +542 -0
  30. package/dist/server/services/git/haiku.js +2 -2
  31. package/dist/server/services/plan/agents/check-injection.md +43 -2
  32. package/dist/server/services/plan/agents/review-code.md +62 -3
  33. package/dist/server/services/plan/agents/review-quality.md +52 -2
  34. package/dist/server/services/plan/board-export.js +4 -0
  35. package/dist/server/services/plan/composer-prompt.js +27 -8
  36. package/dist/server/services/plan/composer.js +10 -4
  37. package/dist/server/services/plan/config-installer.js +4 -83
  38. package/dist/server/services/plan/executor.js +155 -73
  39. package/dist/server/services/plan/issue-effort.js +9 -21
  40. package/dist/server/services/plan/issue-prompt-builder.js +72 -9
  41. package/dist/server/services/plan/parser-core.js +6 -1
  42. package/dist/server/services/plan/quality-delta.js +6 -24
  43. package/dist/server/services/plan/readiness-planner.js +12 -4
  44. package/dist/server/services/plan/record-file.js +52 -0
  45. package/dist/server/services/plan/review-approval.js +64 -0
  46. package/dist/server/services/plan/review-gate.js +186 -77
  47. package/dist/server/services/plan/review-outcome.js +146 -0
  48. package/dist/server/services/plan/review-report.js +28 -1
  49. package/dist/server/services/plan/review-target.js +57 -0
  50. package/dist/server/services/plan/state-reconciler.js +3 -3
  51. package/dist/server/services/plan/template-diff.js +2 -0
  52. package/dist/server/services/plan/template-instantiator.js +4 -0
  53. package/dist/server/services/plan/watcher.js +11 -1
  54. package/dist/server/services/platform-reconnect.js +7 -0
  55. package/dist/server/services/platform-token-lifecycle.js +2 -1
  56. package/dist/server/services/platform.js +293 -50
  57. package/dist/server/services/relay-http-client.js +107 -0
  58. package/dist/server/services/schedule/agent-schedule-store.js +69 -8
  59. package/dist/server/services/schedule/agent-scheduler.js +48 -7
  60. package/dist/server/services/schedule/daily-timing.js +52 -0
  61. package/dist/server/services/schedule/prompt-file.js +55 -0
  62. package/dist/server/services/schedule/run-bootstrap.js +20 -5
  63. package/dist/server/services/schedule/schedule-store.js +54 -4
  64. package/dist/server/services/schedule/schedule-wire.js +17 -0
  65. package/dist/server/services/schedule/scheduler.js +43 -9
  66. package/dist/server/services/sdk/agent-schedule.js +294 -0
  67. package/dist/server/services/sdk/agents.js +95 -13
  68. package/dist/server/services/sdk/app-tools.js +44 -7
  69. package/dist/server/services/sdk/atomic-write.js +75 -0
  70. package/dist/server/services/sdk/browser.js +5 -1
  71. package/dist/server/services/sdk/composer.js +2 -0
  72. package/dist/server/services/sdk/file-change-notify.js +60 -13
  73. package/dist/server/services/sdk/files-app.js +12 -7
  74. package/dist/server/services/sdk/files-search.js +14 -0
  75. package/dist/server/services/sdk/files-transfer.js +1 -1
  76. package/dist/server/services/sdk/files-watch.js +721 -0
  77. package/dist/server/services/sdk/files-workspace.js +37 -11
  78. package/dist/server/services/sdk/inference-claude.js +4 -2
  79. package/dist/server/services/sdk/inference.js +16 -2
  80. package/dist/server/services/sdk/pm-chain.js +0 -0
  81. package/dist/server/services/sdk/pm-schedule.js +72 -15
  82. package/dist/server/services/sdk/pm.js +22 -0
  83. package/dist/server/services/sdk/rate-limits.js +32 -0
  84. package/dist/server/services/sdk/registry.js +40 -2
  85. package/dist/server/services/sdk/terminal.js +1 -1
  86. package/dist/server/services/sentry.js +5 -1
  87. package/dist/server/services/terminal/pty-manager.js +26 -11
  88. package/dist/server/services/terminal/pty-utils.js +12 -1
  89. package/dist/server/services/terminal/terminal-modes.js +87 -0
  90. package/dist/server/services/timeline/assembler.js +2 -2
  91. package/dist/server/services/timeline/index.js +1 -1
  92. package/dist/server/services/websocket/agent-schedule-handlers.js +62 -4
  93. package/dist/server/services/websocket/ask-user-question-bridge.js +2 -2
  94. package/dist/server/services/websocket/browser-agent-runtime.js +3 -1
  95. package/dist/server/services/websocket/browser-handlers.js +5 -2
  96. package/dist/server/services/websocket/browser-viewer-lifecycle.js +8 -0
  97. package/dist/server/services/websocket/file-explorer-handlers.js +17 -0
  98. package/dist/server/services/websocket/file-transfer-http.js +12 -6
  99. package/dist/server/services/websocket/handler.js +185 -33
  100. package/dist/server/services/websocket/msg-id-tracker.js +94 -19
  101. package/dist/server/services/websocket/plan-execution-handlers.js +33 -5
  102. package/dist/server/services/websocket/plan-handlers.js +10 -5
  103. package/dist/server/services/websocket/plan-helpers.js +23 -0
  104. package/dist/server/services/websocket/plan-issue-handlers.js +161 -4
  105. package/dist/server/services/websocket/plan-sprint-handlers.js +3 -2
  106. package/dist/server/services/websocket/schedule-handlers.js +8 -2
  107. package/dist/server/services/websocket/sdk-agent-host.js +5 -1
  108. package/dist/server/services/websocket/sdk-agent-schedule-host.js +30 -0
  109. package/dist/server/services/websocket/sdk-browser-host.js +11 -4
  110. package/dist/server/services/websocket/sdk-handlers.js +6 -1
  111. package/dist/server/services/websocket/sdk-pm-chain-host.js +20 -0
  112. package/dist/server/services/websocket/sdk-pm-execution-host.js +21 -14
  113. package/dist/server/services/websocket/sdk-pm-schedule-host.js +9 -0
  114. package/dist/server/services/websocket/sdk-terminal-host.js +7 -0
  115. package/dist/server/services/websocket/settings-handlers.js +6 -18
  116. package/dist/server/services/websocket/tab-broadcast.js +5 -1
  117. package/dist/server/services/websocket/terminal-handlers.js +51 -6
  118. package/dist/server/services/websocket/types.js +6 -5
  119. package/dist/server/services/websocket/viewer-interest.js +30 -0
  120. package/package.json +6 -6
@@ -1,7 +1,6 @@
1
1
  // Copyright (c) 2025-present Minionry, Inc.
2
2
  // SPDX-License-Identifier: LicenseRef-Minionry-Software
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
- import { basename, join } from 'node:path';
3
+ import { basename } from 'node:path';
5
4
  import { createAdaptorServer } from '@hono/node-server';
6
5
  import { getClientId } from './services/client-id.js';
7
6
  import { PlatformConnection } from './services/platform.js';
@@ -19,48 +18,6 @@ export function setTerminalTitle(directory) {
19
18
  const title = `minionry: ${dirName}`;
20
19
  process.stdout.write(`\x1b]0;${title}\x07`);
21
20
  }
22
- export function ensureClaudeSettings(workingDir) {
23
- const claudeDir = join(workingDir, '.claude');
24
- const settingsPath = join(claudeDir, 'settings.json');
25
- if (!existsSync(claudeDir)) {
26
- mkdirSync(claudeDir, { recursive: true });
27
- }
28
- const recommendedSettings = {
29
- env: {
30
- CLAUDE_CODE_MAX_OUTPUT_TOKENS: "64000",
31
- DISABLE_NONESSENTIAL_TRAFFIC: "1"
32
- }
33
- };
34
- if (!existsSync(settingsPath)) {
35
- writeFileSync(settingsPath, JSON.stringify(recommendedSettings, null, 2));
36
- console.log(`📝 Created .claude/settings.json with recommended settings`);
37
- }
38
- else {
39
- try {
40
- const existingSettings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
41
- let updated = false;
42
- if (!existingSettings.env) {
43
- existingSettings.env = {};
44
- updated = true;
45
- }
46
- if (!existingSettings.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
47
- existingSettings.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = "64000";
48
- updated = true;
49
- }
50
- if (!existingSettings.env.DISABLE_NONESSENTIAL_TRAFFIC) {
51
- existingSettings.env.DISABLE_NONESSENTIAL_TRAFFIC = "1";
52
- updated = true;
53
- }
54
- if (updated) {
55
- writeFileSync(settingsPath, JSON.stringify(existingSettings, null, 2));
56
- console.log(`📝 Updated .claude/settings.json with recommended env settings`);
57
- }
58
- }
59
- catch (_e) {
60
- console.warn(`⚠️ Could not parse existing .claude/settings.json, skipping update`);
61
- }
62
- }
63
- }
64
21
  export async function listenWithPortFallback(opts) {
65
22
  const { app, preferredPort, maxScan = 20 } = opts;
66
23
  const listenOnce = (port) => new Promise((resolve, reject) => {
@@ -179,6 +136,7 @@ export function formatConnectedBanner(opts) {
179
136
  export function createPlatformRelay(workingDir, wsHandler, hooks = {}) {
180
137
  let platformRelayContext = null;
181
138
  let pendingRelayMessages = [];
139
+ const pendingViewerIds = new Set();
182
140
  const fileTransferAgent = createFileTransferAgent({
183
141
  platformUrl: resolvePlatformUrl(),
184
142
  workingDir,
@@ -223,13 +181,19 @@ export function createPlatformRelay(workingDir, wsHandler, hooks = {}) {
223
181
  platformRelayContext = null;
224
182
  }
225
183
  pendingRelayMessages = [];
184
+ pendingViewerIds.clear();
226
185
  },
227
186
  onWebConnected: () => {
187
+ wsHandler.handleWebConnected();
228
188
  if (platformRelayContext) {
229
- wsHandler.handleTransportDetach(platformRelayContext);
189
+ return;
230
190
  }
231
191
  platformRelayContext = createPlatformRelayContext((message) => platformConnection.send(message), workingDir, () => platformConnection.getOutboundBufferedAmount());
232
192
  wsHandler.handleConnection(platformRelayContext, workingDir);
193
+ for (const viewerId of pendingViewerIds) {
194
+ wsHandler.handleViewerConnected(platformRelayContext, viewerId);
195
+ }
196
+ pendingViewerIds.clear();
233
197
  if (pendingRelayMessages.length > 0) {
234
198
  for (const message of pendingRelayMessages) {
235
199
  wsHandler.handleMessage(platformRelayContext, JSON.stringify(message), workingDir);
@@ -238,13 +202,25 @@ export function createPlatformRelay(workingDir, wsHandler, hooks = {}) {
238
202
  }
239
203
  },
240
204
  onWebDisconnected: () => {
241
- if (platformRelayContext) {
242
- wsHandler.handleClose(platformRelayContext);
243
- platformRelayContext = null;
205
+ const context = platformRelayContext;
206
+ if (context) {
207
+ wsHandler.handleWebDisconnected(context, () => {
208
+ if (platformRelayContext !== context)
209
+ return;
210
+ platformRelayContext = null;
211
+ pendingRelayMessages = [];
212
+ });
244
213
  }
245
214
  pendingRelayMessages = [];
246
215
  },
216
+ onViewerConnected: (viewerId) => {
217
+ if (platformRelayContext)
218
+ wsHandler.handleViewerConnected(platformRelayContext, viewerId);
219
+ else
220
+ pendingViewerIds.add(viewerId);
221
+ },
247
222
  onViewerDisconnected: (viewerId) => {
223
+ pendingViewerIds.delete(viewerId);
248
224
  wsHandler.handleBrowserViewerDisconnected(viewerId);
249
225
  },
250
226
  onSuperseded: (info) => hooks.onSuperseded?.(info),
@@ -6,6 +6,8 @@ import { PostHog } from 'posthog-node';
6
6
  import { ensureMachineDir, machineFile } from '../../bin/machine-layout.js';
7
7
  import { getClientId } from './client-id.js';
8
8
  import { DEFAULT_PLATFORM_URL } from './platform-url.js';
9
+ import { relayHttp } from './relay-http-client.js';
10
+ const CONFIG_FETCH_BUDGET_MS = 5_000;
9
11
  function telemetryFile() {
10
12
  return machineFile('telemetry');
11
13
  }
@@ -51,10 +53,10 @@ function isTelemetryEnabled() {
51
53
  }
52
54
  async function fetchAnalyticsConfig() {
53
55
  try {
54
- const response = await fetch(`${platformUrl()}/api/config/client`, {
56
+ const response = await relayHttp.fetch(`${platformUrl()}/api/config/client`, {
55
57
  method: 'GET',
56
58
  headers: { 'Content-Type': 'application/json' },
57
- });
59
+ }, { budgetMs: CONFIG_FETCH_BUDGET_MS });
58
60
  if (!response.ok) {
59
61
  return null;
60
62
  }
@@ -14,6 +14,7 @@ export class MockBrowserHost extends EventEmitter {
14
14
  agentTabAliases = new Map();
15
15
  leaseRegistry;
16
16
  tabInfoCache = new TabInfoCache();
17
+ tabOpeners = new Map();
17
18
  calls = {
18
19
  navigateCalls: [],
19
20
  setBoundsCalls: [],
@@ -140,9 +141,11 @@ export class MockBrowserHost extends EventEmitter {
140
141
  async closeCaptureFor(sessionId) {
141
142
  this.calls.closeCaptureForCalls.push(sessionId);
142
143
  }
143
- async openTab(url) {
144
+ async openTab(url, opener) {
144
145
  this.calls.openTabCalls.push(url);
145
146
  const id = `mock-tab-${this.nextTabId++}`;
147
+ if (opener !== undefined)
148
+ this.tabOpeners.set(id, opener);
146
149
  this.tabInfoCache.upsert(id, { url, title: '' });
147
150
  this.currentTabId = id;
148
151
  this.emitTabs();
@@ -153,6 +156,7 @@ export class MockBrowserHost extends EventEmitter {
153
156
  if (!this.tabInfoCache.get(id))
154
157
  return;
155
158
  this.tabInfoCache.delete(id);
159
+ this.tabOpeners.delete(id);
156
160
  if (this.currentTabId === id) {
157
161
  const next = this.tabInfoCache.entries()[0]?.id ?? null;
158
162
  this.currentTabId = next;
@@ -180,6 +184,7 @@ export class MockBrowserHost extends EventEmitter {
180
184
  canGoBack: e.canGoBack ?? false,
181
185
  canGoForward: e.canGoForward ?? false,
182
186
  ...(e.deviceProfile !== undefined ? { deviceProfile: e.deviceProfile } : {}),
187
+ ...(this.tabOpeners.has(e.id) ? { opener: this.tabOpeners.get(e.id) } : {}),
183
188
  }));
184
189
  const focused = this.currentTabId && this.tabInfoCache.get(this.currentTabId)
185
190
  ? this.currentTabId
@@ -67,6 +67,7 @@ export class ElectronBrowserHost extends EventEmitter {
67
67
  ipc = null;
68
68
  childFailure = null;
69
69
  crashedTabs = new Map();
70
+ tabOpeners = new Map();
70
71
  childStderrTail = [];
71
72
  stopRequested = false;
72
73
  restartAttempts = 0;
@@ -80,6 +81,7 @@ export class ElectronBrowserHost extends EventEmitter {
80
81
  startTimer = null;
81
82
  tabInfoCache = new TabInfoCache();
82
83
  currentTabId = null;
84
+ pendingOpeners = [];
83
85
  agentTabAliases = new Map();
84
86
  dom;
85
87
  js;
@@ -314,11 +316,25 @@ export class ElectronBrowserHost extends EventEmitter {
314
316
  return;
315
317
  }
316
318
  }
317
- async openTab(url) {
319
+ async openTab(url, opener) {
318
320
  log('openTab.enter', url);
319
321
  this.ensureReady('openTab');
320
- const tab = await this.ipc.call('tab.create', { url });
321
- log.info('openTab.created', `id=${tab.id.slice(0, 8)} url=${url}`);
322
+ const slot = { opener };
323
+ this.pendingOpeners.push(slot);
324
+ let tab;
325
+ try {
326
+ tab = await this.ipc.call('tab.create', { url });
327
+ }
328
+ finally {
329
+ const index = this.pendingOpeners.indexOf(slot);
330
+ if (index !== -1)
331
+ this.pendingOpeners.splice(index, 1);
332
+ }
333
+ log.info('openTab.created', `id=${tab.id.slice(0, 8)} url=${url} opener=${opener?.kind ?? 'none'}`);
334
+ if (opener !== undefined)
335
+ this.tabOpeners.set(tab.id, opener);
336
+ else
337
+ this.tabOpeners.delete(tab.id);
322
338
  this.tabInfoCache.upsert(tab.id, { url });
323
339
  this.currentTabId = tab.id;
324
340
  this.emitTabs();
@@ -329,6 +345,7 @@ export class ElectronBrowserHost extends EventEmitter {
329
345
  this.ensureReady('closeTab');
330
346
  await this.ipc.call('tab.close', { id });
331
347
  this.crashedTabs.delete(id);
348
+ this.tabOpeners.delete(id);
332
349
  if (this.tabInfoCache.has(id)) {
333
350
  this.tabInfoCache.delete(id);
334
351
  if (this.currentTabId === id) {
@@ -733,6 +750,9 @@ export class ElectronBrowserHost extends EventEmitter {
733
750
  const url = typeof obj.url === 'string' ? obj.url : '';
734
751
  const isNew = !this.tabInfoCache.has(id);
735
752
  log('ipc.tabCreated', `id=${id.slice(0, 8)} url=${url} isNew=${isNew}`);
753
+ const opener = isNew ? this.pendingOpeners.shift()?.opener : undefined;
754
+ if (opener !== undefined)
755
+ this.tabOpeners.set(id, opener);
736
756
  this.tabInfoCache.upsert(id, { url, title: '' });
737
757
  if (isNew) {
738
758
  if (this.currentTabId === null)
@@ -801,6 +821,7 @@ export class ElectronBrowserHost extends EventEmitter {
801
821
  return;
802
822
  log('ipc.tabClosed', `id=${id.slice(0, 8)} cached=${this.tabInfoCache.has(id)}`);
803
823
  this.crashedTabs.delete(id);
824
+ this.tabOpeners.delete(id);
804
825
  if (!this.tabInfoCache.has(id))
805
826
  return;
806
827
  this.tabInfoCache.delete(id);
@@ -822,6 +843,7 @@ export class ElectronBrowserHost extends EventEmitter {
822
843
  canGoBack: e.canGoBack ?? false,
823
844
  canGoForward: e.canGoForward ?? false,
824
845
  ...(e.deviceProfile !== undefined ? { deviceProfile: e.deviceProfile } : {}),
846
+ ...(this.tabOpeners.has(e.id) ? { opener: this.tabOpeners.get(e.id) } : {}),
825
847
  ...(crashed !== undefined ? { error: crashed } : {}),
826
848
  };
827
849
  });
@@ -1035,6 +1057,8 @@ export class ElectronBrowserHost extends EventEmitter {
1035
1057
  const hadTabs = this.tabInfoCache.entries().length > 0;
1036
1058
  this.tabInfoCache.clear();
1037
1059
  this.crashedTabs.clear();
1060
+ this.tabOpeners.clear();
1061
+ this.pendingOpeners.length = 0;
1038
1062
  this.currentTabId = null;
1039
1063
  this.agentTabAliases.clear();
1040
1064
  if (hadTabs)
@@ -175,6 +175,7 @@ export class ReasoningAgent {
175
175
  agentId;
176
176
  agentLabel;
177
177
  initialUrl;
178
+ opener;
178
179
  maxSteps;
179
180
  heartbeatIntervalMs;
180
181
  settleDelayMs;
@@ -207,6 +208,7 @@ export class ReasoningAgent {
207
208
  this.agentLabel = opts.agentLabel ?? 'Browser Agent';
208
209
  this.tabId = opts.tabId;
209
210
  this.initialUrl = opts.initialUrl ?? 'about:blank';
211
+ this.opener = opts.opener ?? { kind: 'agent', label: this.agentLabel };
210
212
  this.maxSteps = opts.maxSteps ?? DEFAULT_MAX_STEPS;
211
213
  this.heartbeatIntervalMs = opts.heartbeatIntervalMs ?? AGENT_HEARTBEAT_INTERVAL_MS;
212
214
  this.settleDelayMs = opts.settleDelayMs ?? 200;
@@ -223,7 +225,7 @@ export class ReasoningAgent {
223
225
  async run() {
224
226
  try {
225
227
  if (!this.tabId) {
226
- const tab = await this.host.openTab(this.initialUrl);
228
+ const tab = await this.host.openTab(this.initialUrl, this.opener);
227
229
  this.tabId = tab.id;
228
230
  }
229
231
  await this.host.leaseTab({ tabId: this.tabId, agentId: this.agentId });