@openscout/scout 0.2.19 → 0.2.21

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.
@@ -0,0 +1 @@
1
+ :root{--bg: #F9F9F8;--surface: #FFFFFF;--ink: #1C1C1A;--muted: #8A8A86;--dim: #C4C4C0;--border: #E4E4E2;--accent: #0066FF;--accent-soft: rgba(0, 102, 255, .08);--green: #22c55e;--red: #dc2626;--radius: 8px;--shadow-soft: rgba(24, 24, 22, .06);--sidebar-w: 280px;--font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--font-serif: "Spectral", Georgia, serif;font-family:var(--font-sans);font-size:13px;line-height:1.5;color:var(--ink);background:var(--bg);-webkit-font-smoothing:antialiased}*{box-sizing:border-box;margin:0}body{margin:0}.s-app{display:flex;height:100vh;overflow:hidden}.s-sidebar{width:var(--sidebar-w);flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid var(--border);background:var(--surface);height:100vh;overflow:hidden}.s-content{flex:1;min-width:0;height:100vh;overflow-y:auto;background:var(--bg)}.s-sidebar-header{padding:16px 16px 12px;flex-shrink:0}.s-logo{font-family:var(--font-serif);font-size:18px;font-weight:600;letter-spacing:-.02em;cursor:pointer;-webkit-user-select:none;user-select:none}.s-sidebar-footer{flex-shrink:0;padding:8px;border-top:1px solid var(--border)}.s-nav-item{display:flex;align-items:center;gap:8px;width:100%;border:none;background:none;padding:6px 8px;border-radius:6px;font-family:var(--font-sans);font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;transition:background .12s ease,color .12s ease}.s-nav-item:hover{background:var(--bg);color:var(--ink)}.s-nav-item-active{background:var(--accent-soft);color:var(--accent)}.s-sidebar-label{padding:4px 16px 6px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--dim);flex-shrink:0}.s-sidebar-list{flex:1;overflow-y:auto;padding-bottom:12px}.s-sidebar-empty{padding:24px 16px;text-align:center;font-size:11px;color:var(--dim)}.s-sidebar-row{display:flex;align-items:center;gap:8px;padding:8px 12px;margin:0 6px;border-radius:6px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .1s ease}.s-sidebar-row:hover{background:var(--bg)}.s-sidebar-row-active,.s-sidebar-row-active:hover{background:var(--accent-soft)}.s-sidebar-row-body{flex:1;min-width:0}.s-sidebar-row-header{display:flex;align-items:center;gap:5px}.s-sidebar-row-name{font-size:12px;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s-sidebar-row-preview{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px;line-height:1.3}.s-sidebar-row-preview-empty{font-style:italic;color:var(--dim)}.s-sidebar-row-time{font-size:10px;color:var(--dim);font-family:var(--font-mono);flex-shrink:0;align-self:flex-start;margin-top:2px}.s-welcome{display:flex;align-items:center;justify-content:center;height:100%;color:var(--dim)}.s-welcome-inner{text-align:center}.s-welcome h2{font-family:var(--font-serif);font-size:24px;font-weight:600;color:var(--dim);margin-bottom:4px}.s-welcome p{font-size:12px}.s-home{padding:32px;height:100vh;overflow-y:auto}.s-home-header{margin-bottom:32px}.s-home-header h2{font-family:var(--font-serif);font-size:22px;font-weight:600;color:var(--ink);margin-bottom:4px}.s-home-header p{font-size:12px;color:var(--muted)}.s-home-section{margin-bottom:28px}.s-home-section-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--dim);margin-bottom:10px}.s-home-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;max-width:480px}.s-home-card-row{display:flex;align-items:center;gap:10px;padding:10px 14px;font-size:12px}.s-home-card-row+.s-home-card-row{border-top:1px solid var(--border)}.s-home-card-row-label{color:var(--muted);font-size:11px;min-width:80px;flex-shrink:0}.s-home-card-row-value{color:var(--ink);font-family:var(--font-mono);font-size:11px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-home-steps{list-style:none;padding:0;max-width:480px}.s-home-steps li{display:flex;align-items:baseline;gap:10px;padding:6px 0;font-size:12px;color:var(--ink);line-height:1.5}.s-home-steps li:before{content:attr(data-step);font-size:10px;font-weight:700;font-family:var(--font-mono);color:var(--muted);flex-shrink:0;width:18px;height:18px;display:flex;align-items:center;justify-content:center;border-radius:50%;background:var(--bg);border:1px solid var(--border)}.s-home-steps code{font-family:var(--font-mono);font-size:11px;background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:1px 5px}.s-home-card-row-clickable{cursor:pointer;transition:background .1s ease}.s-home-card-row-clickable:hover{background:var(--bg)}.s-activity-stream{max-width:560px}.s-activity-row{display:flex;align-items:baseline;gap:8px;padding:4px 0;font-size:12px;line-height:1.5}.s-activity-row-clickable{cursor:pointer;border-radius:4px;padding:4px 6px;margin:0 -6px;transition:background .1s ease}.s-activity-row-clickable:hover{background:var(--surface)}.s-activity-time{font-size:10px;color:var(--dim);font-family:var(--font-mono);flex-shrink:0;min-width:36px}.s-activity-actor{font-weight:600;color:var(--ink);flex-shrink:0}.s-activity-kind{color:var(--muted)}.s-activity-title{color:var(--muted);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-activity-list{max-width:560px}.s-activity-list-row{display:flex;align-items:flex-start;gap:10px;padding:8px 0}.s-activity-list-item+.s-activity-list-item{border-top:1px solid var(--border)}.s-activity-list-row-clickable{cursor:pointer;border-radius:6px;padding:8px;margin:0 -8px;transition:background .1s ease}.s-activity-list-row-clickable:hover{background:var(--surface)}.s-activity-list-body{flex:1;min-width:0}.s-activity-list-header{display:flex;align-items:baseline;gap:6px}.s-activity-list-actor{font-size:12px;font-weight:600;color:var(--ink)}.s-activity-list-kind{font-size:11px;color:var(--muted)}.s-activity-list-title{font-size:12px;color:var(--ink);margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.s-activity-list-summary{font-size:11px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s-activity-expanded{padding:4px 8px 12px 38px}.s-activity-expanded-summary{font-size:12px;color:var(--ink);margin-bottom:8px;line-height:1.4}.s-activity-expanded-meta{display:flex;gap:8px;font-size:11px;padding:2px 0}.s-activity-meta-label{color:var(--muted);min-width:56px}.s-activity-meta-value{color:var(--ink);word-break:break-all}.s-activity-expanded-actions{margin-top:8px}.s-btn-sm{font-size:11px;padding:4px 10px}.s-error{color:var(--red);font-size:12px;padding:8px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:var(--radius);margin:12px}.s-empty{padding:32px 0;color:var(--muted);font-size:12px}.s-empty p:first-child{font-size:13px;font-weight:500;color:var(--ink);margin-bottom:4px}.s-meta{font-size:11px;color:var(--muted);font-family:var(--font-mono)}.s-spacer{flex:1}.s-dot{display:inline-block;width:6px;height:6px;border-radius:50%;flex-shrink:0}.s-dot-sm{width:5px;height:5px}.s-badge{font-size:10px;font-family:var(--font-mono);color:var(--muted);background:var(--bg);border-radius:4px;padding:1px 6px;flex-shrink:0;text-transform:uppercase;letter-spacing:.04em}.s-time{font-size:10px;color:var(--dim);font-family:var(--font-mono);flex-shrink:0;letter-spacing:.01em}.s-back{border:none;background:none;padding:0;font-family:var(--font-sans);font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;transition:color .12s ease}.s-back:hover{color:var(--ink)}.s-chevron{width:12px;height:12px;flex-shrink:0;position:relative;opacity:.3;transition:opacity .14s ease}.s-chevron:before{content:"";position:absolute;top:3px;left:2px;width:6px;height:6px;border-right:1.5px solid var(--ink);border-bottom:1.5px solid var(--ink);transform:rotate(-45deg)}.s-avatar{width:32px;height:32px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#fff}.s-avatar-sm{width:28px;height:28px;font-size:11px}.s-avatar-lg{width:56px;height:56px;font-size:20px}.s-conversation{display:flex;flex-direction:column;height:100vh}.s-conv-header{display:flex;align-items:center;gap:10px;padding:10px 20px;border-bottom:1px solid var(--border);background:var(--bg);flex-shrink:0;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .12s ease}.s-conv-header:hover{background:#f6f6f5}.s-conv-header .s-back{margin-right:2px;font-size:16px;line-height:1}.s-conv-header-info{flex:1;min-width:0}.s-conv-header-name{font-size:13px;font-weight:600;color:var(--ink)}.s-conv-header-state{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--muted);margin-top:1px}.s-messages{flex:1;overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:6px}.s-msg{max-width:85%;align-self:flex-start}.s-msg-you{align-self:flex-end}.s-msg-header{display:flex;align-items:baseline;gap:6px;margin-bottom:2px;padding:0 2px}.s-msg-actor{font-size:11px;font-weight:600;color:var(--ink)}.s-msg-time{font-size:10px;color:var(--dim);font-family:var(--font-mono)}.s-msg-body{font-size:12px;line-height:1.5;color:var(--ink);white-space:pre-wrap;word-break:break-word;padding:8px 12px;border-radius:14px 14px 14px 4px;background:var(--surface);border:1px solid var(--border)}.s-msg-you .s-msg-body{border-radius:14px 14px 4px;background:var(--accent-soft);border-color:#0066ff1f}.s-msg-you .s-msg-header{justify-content:flex-end}.s-compose{display:flex;align-items:center;gap:8px;padding:10px 20px;border-top:1px solid var(--border);background:var(--bg);flex-shrink:0}.s-compose-input{flex:1;border:1px solid var(--border);background:var(--surface);border-radius:20px;padding:8px 14px;font-family:var(--font-sans);font-size:12px;color:var(--ink);outline:none;transition:border-color .12s ease}.s-compose-input:focus{border-color:var(--accent)}.s-compose-input:disabled{opacity:.5;cursor:not-allowed}.s-compose-send{width:32px;height:32px;border-radius:50%;border:none;background:var(--ink);color:#fff;font-size:14px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:opacity .12s ease}.s-compose-send:disabled{opacity:.3;cursor:not-allowed}.s-agent-profile{display:flex;flex-direction:column;align-items:flex-start;padding:8px 0 16px;gap:4px}.s-agent-profile-name{font-size:18px;font-weight:600;color:var(--ink);margin-top:8px}.s-agent-profile-handle{font-size:12px;font-family:var(--font-mono);color:var(--muted)}.s-agent-profile-state{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);margin-top:4px}.s-agent-details{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-top:16px;max-width:480px}.s-detail-row{display:flex;align-items:baseline;justify-content:space-between;padding:7px 14px;font-size:12px;gap:12px}.s-detail-row+.s-detail-row{border-top:1px solid var(--border)}.s-detail-label{color:var(--muted);flex-shrink:0;font-size:11px}.s-detail-value{color:var(--ink);font-family:var(--font-mono);font-size:11px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-section-title{font-size:15px;font-weight:600;color:var(--ink);margin-bottom:16px}.s-actions{display:flex;gap:8px;margin-bottom:16px}.s-btn{border:1px solid var(--border);background:var(--surface);padding:6px 14px;border-radius:var(--radius);font-family:var(--font-sans);font-size:12px;font-weight:500;cursor:pointer;transition:all .14s cubic-bezier(.16,1,.3,1)}.s-btn:hover{border-color:var(--dim);box-shadow:0 1px 2px var(--shadow-soft)}.s-btn:disabled{opacity:.4;cursor:not-allowed}.s-btn-primary{background:var(--ink);color:var(--bg);border-color:var(--ink)}.s-btn-primary:hover{background:#2a2a28;box-shadow:0 2px 6px var(--shadow-soft)}.s-qr{max-width:240px;margin:20px 0;padding:16px;background:var(--surface);border:1px solid var(--border);border-radius:12px;box-shadow:0 2px 6px var(--shadow-soft)}.s-qr svg{display:block;width:100%;height:auto}.s-pair-meta{max-width:360px;margin:0;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}.s-pair-row{display:flex;align-items:center;justify-content:space-between;padding:7px 14px;font-size:12px}.s-pair-row+.s-pair-row{border-top:1px solid var(--border)}.s-pair-label{color:var(--muted);flex-shrink:0}.s-pair-value{display:flex;align-items:center;color:var(--ink);text-align:right;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-pair-mono{font-family:var(--font-mono);font-size:11px}.s-sidebar-nav{padding:0 8px 4px;flex-shrink:0}.s-nav-badge{font-size:10px;font-weight:600;font-family:var(--font-mono);color:#fff;background:var(--green);border-radius:10px;padding:0 6px;min-width:18px;text-align:center;line-height:16px;margin-left:auto}.s-sidebar-avatar-wrap{position:relative;flex-shrink:0}.s-task-indicator{position:absolute;bottom:-1px;right:-1px;width:8px;height:8px;border-radius:50%;background:var(--green);border:2px solid var(--surface)}.s-sidebar-row-active .s-task-indicator{border-color:#0066ff14}.s-flight-banner{display:flex;align-items:center;gap:6px;padding:6px 20px;background:#22c55e0f;border-bottom:1px solid rgba(34,197,94,.12);font-size:11px;flex-shrink:0}.s-flight-banner-dot{width:6px;height:6px;border-radius:50%;background:var(--green);flex-shrink:0;animation:s-pulse 2s ease-in-out infinite}@keyframes s-pulse{0%,to{opacity:1}50%{opacity:.4}}.s-flight-banner-label{font-weight:600;color:var(--ink)}.s-flight-banner-summary{color:var(--muted);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-flights{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}.s-flight-row{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface);cursor:pointer;transition:background .12s ease}.s-flight-row:hover{background:#f6f6f5}.s-flight-row+.s-flight-row{border-top:1px solid var(--border)}.s-flight-body{flex:1;min-width:0}.s-flight-header{display:flex;align-items:center;gap:6px}.s-flight-name{font-size:12px;font-weight:600;color:var(--ink)}.s-flight-state{font-size:11px;font-family:var(--font-mono);font-weight:500}.s-flight-summary{font-size:11px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s-content>div:not(.s-conversation):not(.s-welcome):not(.s-home):not(.s-agents-layout){padding:24px 32px;max-width:640px}.s-agents-layout{display:flex;height:100vh;overflow:hidden}.s-agents-list-panel{width:100%;height:100vh;overflow-y:auto;padding:24px 16px;transition:width .15s ease}.s-agents-layout-split .s-agents-list-panel{width:280px;flex-shrink:0;border-right:1px solid var(--border);padding:16px 8px}.s-agents-detail-panel{flex:1;min-width:0;height:100vh;overflow-y:auto;padding:24px 32px}.s-agent-list-row{display:flex;align-items:center;gap:10px;padding:8px;border-radius:6px;cursor:pointer;transition:background .1s ease}.s-agent-list-row:hover{background:var(--bg)}.s-agent-list-row-active,.s-agent-list-row-active:hover{background:var(--accent-soft)}.s-agent-list-body{flex:1;min-width:0}.s-agent-list-header{display:flex;align-items:center;gap:5px}.s-agent-list-name{font-size:12px;font-weight:600;color:var(--ink)}.s-agent-list-qualifier{font-size:10px;font-family:var(--font-mono);color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-agent-list-meta{display:flex;align-items:center;gap:8px;margin-top:1px;font-size:11px;color:var(--muted)}.s-agent-list-meta span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s-agent-detail-header{display:flex;align-items:center;gap:16px;margin-bottom:20px}.s-agent-detail-name{font-size:18px;font-weight:600;color:var(--ink);display:flex;align-items:baseline;gap:8px}.s-agent-detail-qualifier{font-size:12px;font-family:var(--font-mono);color:var(--dim);font-weight:400}.s-agent-detail-state{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);margin-top:2px}.s-agent-detail-meta{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;max-width:480px}.s-agent-detail-section{margin-top:20px}.s-agent-detail-messages{max-width:480px}.s-agent-detail-msg{display:flex;align-items:baseline;gap:6px;padding:4px 0;font-size:12px}.s-agent-detail-msg+.s-agent-detail-msg{border-top:1px solid var(--border)}.s-agent-detail-msg-actor{font-weight:600;color:var(--ink);flex-shrink:0}.s-agent-detail-msg-body{color:var(--muted);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:767px){.s-sidebar{width:100%;position:absolute;inset:0;z-index:20;transition:transform .2s cubic-bezier(.16,1,.3,1)}.s-content{position:absolute;inset:0;z-index:10;transform:translate(100%);transition:transform .2s cubic-bezier(.16,1,.3,1)}.s-app{position:relative}.s-app-content-open .s-sidebar{transform:translate(-100%)}.s-app-content-open .s-content{transform:translate(0)}.s-conv-header .s-back{display:block}}@media(min-width:768px){.s-conv-header .s-back{display:none}}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--muted)}
@@ -7,8 +7,8 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&family=Spectral:wght@500;600&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-Cm-Qrks0.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-DcR6uR4b.css">
10
+ <script type="module" crossorigin src="/assets/index-D70CFXZt.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-DAAaRFvV.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
package/dist/main.mjs CHANGED
@@ -2498,7 +2498,7 @@ __export(exports_setup, {
2498
2498
  DEFAULT_OPERATOR_NAME: () => DEFAULT_OPERATOR_NAME
2499
2499
  });
2500
2500
  import { execFileSync as execFileSync2 } from "node:child_process";
2501
- import { existsSync as existsSync3 } from "node:fs";
2501
+ import { existsSync as existsSync3, readFileSync } from "node:fs";
2502
2502
  import { access, mkdir as mkdir2, readdir as readdir2, readFile as readFile3, realpath, rm, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
2503
2503
  import { homedir as homedir3, hostname, userInfo } from "node:os";
2504
2504
  import { basename, dirname as dirname3, isAbsolute, join as join4, relative, resolve as resolve4 } from "node:path";
@@ -2683,7 +2683,25 @@ function primaryDirectConversationIdForAgent(agentId) {
2683
2683
  return `dm.operator.${normalizedAgentId}`;
2684
2684
  }
2685
2685
  function resolveNodeQualifier() {
2686
- return normalizeAgentSelectorSegment(process.env.OPENSCOUT_NODE_QUALIFIER?.trim() || hostname() || "local") || "local";
2686
+ const fromEnv = process.env.OPENSCOUT_NODE_QUALIFIER?.trim();
2687
+ if (fromEnv) {
2688
+ return normalizeAgentSelectorSegment(fromEnv) || "local";
2689
+ }
2690
+ const alias = readNodeAliasSync();
2691
+ if (alias) {
2692
+ return normalizeAgentSelectorSegment(alias) || "local";
2693
+ }
2694
+ return normalizeAgentSelectorSegment(hostname() || "local") || "local";
2695
+ }
2696
+ function readNodeAliasSync() {
2697
+ try {
2698
+ const settingsPath = resolveOpenScoutSupportPaths().settingsPath;
2699
+ const raw = readFileSync(settingsPath, "utf8");
2700
+ const settings = JSON.parse(raw);
2701
+ return settings.node?.alias?.trim() || null;
2702
+ } catch {
2703
+ return null;
2704
+ }
2687
2705
  }
2688
2706
  function detectGitBranchUncached(projectRoot) {
2689
2707
  try {
@@ -2891,6 +2909,9 @@ function defaultSettings() {
2891
2909
  completedAt: null,
2892
2910
  skippedAt: null
2893
2911
  },
2912
+ node: {
2913
+ alias: ""
2914
+ },
2894
2915
  discovery: {
2895
2916
  contextRoot: null,
2896
2917
  workspaceRoots: [],
@@ -3290,6 +3311,7 @@ async function normalizeSettingsRecord(value, options = {}) {
3290
3311
  const candidate = typeof value === "object" && value ? value : {};
3291
3312
  const profile = typeof candidate.profile === "object" && candidate.profile ? candidate.profile : {};
3292
3313
  const onboarding = typeof candidate.onboarding === "object" && candidate.onboarding ? candidate.onboarding : {};
3314
+ const node = typeof candidate.node === "object" && candidate.node ? candidate.node : {};
3293
3315
  const discovery = typeof candidate.discovery === "object" && candidate.discovery ? candidate.discovery : {};
3294
3316
  const agents = typeof candidate.agents === "object" && candidate.agents ? candidate.agents : {};
3295
3317
  const bridges = typeof candidate.bridges === "object" && candidate.bridges ? candidate.bridges : {};
@@ -3329,6 +3351,9 @@ async function normalizeSettingsRecord(value, options = {}) {
3329
3351
  completedAt: normalizeOptionalTimestamp(onboarding.completedAt),
3330
3352
  skippedAt: normalizeOptionalTimestamp(onboarding.skippedAt)
3331
3353
  },
3354
+ node: {
3355
+ alias: normalizeOptionalString(node.alias)
3356
+ },
3332
3357
  discovery: {
3333
3358
  contextRoot,
3334
3359
  workspaceRoots,
@@ -3406,6 +3431,10 @@ async function writeOpenScoutSettings(settings, options = {}) {
3406
3431
  ...current.onboarding,
3407
3432
  ...settings.onboarding ?? {}
3408
3433
  },
3434
+ node: {
3435
+ ...current.node,
3436
+ ...settings.node ?? {}
3437
+ },
3409
3438
  discovery: {
3410
3439
  ...current.discovery,
3411
3440
  ...settings.discovery ?? {},
@@ -4383,6 +4412,9 @@ function resolveClaudeStreamJsonOutput(result, fallbackParts) {
4383
4412
  function sessionKey(options) {
4384
4413
  return `${options.agentName}:${options.sessionId}`;
4385
4414
  }
4415
+ function errorMessage(error) {
4416
+ return error instanceof Error ? error.message : String(error);
4417
+ }
4386
4418
  async function readOptionalFile(filePath) {
4387
4419
  try {
4388
4420
  const raw = await readFile4(filePath, "utf8");
@@ -4535,17 +4567,35 @@ class ClaudeStreamJsonSession {
4535
4567
  if (this.claudeSessionId) {
4536
4568
  args.push("--resume", this.claudeSessionId);
4537
4569
  }
4538
- this.process = spawn("claude", args, {
4539
- cwd: this.options.cwd,
4540
- env: buildManagedAgentEnvironment({
4541
- agentName: this.options.agentName,
4542
- currentDirectory: this.options.cwd,
4543
- baseEnv: process.env
4544
- })
4570
+ let child;
4571
+ try {
4572
+ child = spawn("claude", args, {
4573
+ cwd: this.options.cwd,
4574
+ env: buildManagedAgentEnvironment({
4575
+ agentName: this.options.agentName,
4576
+ currentDirectory: this.options.cwd,
4577
+ baseEnv: process.env
4578
+ })
4579
+ });
4580
+ } catch (error) {
4581
+ throw new Error(`Failed to spawn claude: ${errorMessage(error)}`);
4582
+ }
4583
+ this.process = child;
4584
+ child.on("error", (error) => {
4585
+ console.error(`[openscout-runtime] claude process error for ${this.options.agentName}: ${error.message}`);
4586
+ this.process = null;
4587
+ if (this.activeTurn) {
4588
+ const turn = this.activeTurn;
4589
+ this.activeTurn = null;
4590
+ if (turn.timer) {
4591
+ clearTimeout(turn.timer);
4592
+ }
4593
+ turn.reject(new Error(`Claude process error: ${error.message}`));
4594
+ }
4545
4595
  });
4546
- this.process.stdout.setEncoding("utf8");
4547
- this.process.stderr.setEncoding("utf8");
4548
- this.process.stdout.on("data", (chunk) => {
4596
+ child.stdout.setEncoding("utf8");
4597
+ child.stderr.setEncoding("utf8");
4598
+ child.stdout.on("data", (chunk) => {
4549
4599
  appendFile(this.stdoutLogPath, chunk).catch(() => {
4550
4600
  return;
4551
4601
  });
@@ -4561,12 +4611,12 @@ class ClaudeStreamJsonSession {
4561
4611
  this.handleEvent(JSON.parse(trimmed));
4562
4612
  }
4563
4613
  });
4564
- this.process.stderr.on("data", (chunk) => {
4614
+ child.stderr.on("data", (chunk) => {
4565
4615
  appendFile(this.stderrLogPath, chunk).catch(() => {
4566
4616
  return;
4567
4617
  });
4568
4618
  });
4569
- this.process.on("exit", (code) => {
4619
+ child.on("exit", (code) => {
4570
4620
  if (code !== 0 && this.activeTurn) {
4571
4621
  const turn = this.activeTurn;
4572
4622
  this.activeTurn = null;
@@ -4716,7 +4766,7 @@ function parseJsonLine(line) {
4716
4766
  return null;
4717
4767
  }
4718
4768
  }
4719
- function errorMessage(error) {
4769
+ function errorMessage2(error) {
4720
4770
  if (error instanceof Error) {
4721
4771
  return error.message;
4722
4772
  }
@@ -4741,7 +4791,7 @@ async function readOptionalFile2(filePath) {
4741
4791
  }
4742
4792
  }
4743
4793
  function isMissingCodexRolloutError(error) {
4744
- const message = errorMessage(error).toLowerCase();
4794
+ const message = errorMessage2(error).toLowerCase();
4745
4795
  return message.includes("no rollout found for thread id");
4746
4796
  }
4747
4797
 
@@ -4814,7 +4864,7 @@ class CodexAppServerSession {
4814
4864
  await this.persistState();
4815
4865
  } catch (error) {
4816
4866
  this.clearActiveTurn(turn);
4817
- reject(error instanceof Error ? error : new Error(errorMessage(error)));
4867
+ reject(error instanceof Error ? error : new Error(errorMessage2(error)));
4818
4868
  }
4819
4869
  });
4820
4870
  return {
@@ -4942,7 +4992,7 @@ class CodexAppServerSession {
4942
4992
  });
4943
4993
  });
4944
4994
  child.once("error", (error) => {
4945
- this.failSession(new Error(`Codex app-server failed for ${this.options.agentName}: ${errorMessage(error)}`));
4995
+ this.failSession(new Error(`Codex app-server failed for ${this.options.agentName}: ${errorMessage2(error)}`));
4946
4996
  });
4947
4997
  child.once("exit", (code, signal) => {
4948
4998
  this.failSession(new Error(`Codex app-server exited for ${this.options.agentName}` + (code !== null ? ` with code ${code}` : "") + (signal ? ` (${signal})` : "")));
@@ -4978,7 +5028,7 @@ class CodexAppServerSession {
4978
5028
  await this.persistThreadId();
4979
5029
  return;
4980
5030
  } catch (error) {
4981
- await appendFile2(this.stderrLogPath, `[openscout] failed to resume stored Codex thread ${storedThreadId}: ${errorMessage(error)}
5031
+ await appendFile2(this.stderrLogPath, `[openscout] failed to resume stored Codex thread ${storedThreadId}: ${errorMessage2(error)}
4982
5032
  `).catch(() => {
4983
5033
  return;
4984
5034
  });
@@ -5248,7 +5298,7 @@ class CodexAppServerSession {
5248
5298
  });
5249
5299
  } catch (error) {
5250
5300
  this.pendingRequests.delete(id);
5251
- reject(error instanceof Error ? error : new Error(errorMessage(error)));
5301
+ reject(error instanceof Error ? error : new Error(errorMessage2(error)));
5252
5302
  }
5253
5303
  });
5254
5304
  }
@@ -5414,7 +5464,7 @@ function buildInvocationCollaborationContextPrompt(invocation) {
5414
5464
 
5415
5465
  // ../runtime/dist/broker-service.js
5416
5466
  import { spawnSync } from "node:child_process";
5417
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
5467
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
5418
5468
  import { homedir as homedir4 } from "node:os";
5419
5469
  import { basename as basename2, dirname as dirname4, join as join7, resolve as resolve5 } from "node:path";
5420
5470
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -5667,7 +5717,7 @@ function readLogLines(path) {
5667
5717
  if (!existsSync4(path)) {
5668
5718
  return [];
5669
5719
  }
5670
- return readFileSync(path, "utf8").split(`
5720
+ return readFileSync2(path, "utf8").split(`
5671
5721
  `).map((line) => line.trim()).filter(Boolean);
5672
5722
  }
5673
5723
  function isPackageScriptBanner(line) {
@@ -5933,7 +5983,7 @@ __export(exports_local_agents, {
5933
5983
  });
5934
5984
  import { randomUUID as randomUUID2 } from "node:crypto";
5935
5985
  import { execFileSync as execFileSync3, execSync } from "node:child_process";
5936
- import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
5986
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
5937
5987
  import { mkdir as mkdir5, rm as rm4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
5938
5988
  import { basename as basename3, dirname as dirname5, join as join8, resolve as resolve6 } from "node:path";
5939
5989
  import { fileURLToPath as fileURLToPath4 } from "node:url";
@@ -5949,7 +5999,7 @@ function resolveProjectsRoot(projectPath) {
5949
5999
  if (!existsSync5(supportPaths.settingsPath)) {
5950
6000
  return dirname5(projectPath);
5951
6001
  }
5952
- const raw = JSON.parse(readFileSync2(supportPaths.settingsPath, "utf8"));
6002
+ const raw = JSON.parse(readFileSync3(supportPaths.settingsPath, "utf8"));
5953
6003
  const workspaceRoot = raw.discovery?.workspaceRoots?.find((entry) => typeof entry === "string" && entry.trim().length > 0);
5954
6004
  return workspaceRoot ? resolve6(workspaceRoot) : dirname5(projectPath);
5955
6005
  } catch {
@@ -6730,11 +6780,33 @@ function killAgentSession(sessionName) {
6730
6780
  execSync(`tmux kill-session -t ${JSON.stringify(sessionName)}`, { stdio: "pipe" });
6731
6781
  } catch {}
6732
6782
  }
6783
+ function isHarnessBinaryAvailable(transport) {
6784
+ const binaryMap = {
6785
+ claude_stream_json: "claude",
6786
+ codex_app_server: "codex"
6787
+ };
6788
+ const binary = binaryMap[transport];
6789
+ if (!binary)
6790
+ return true;
6791
+ try {
6792
+ execFileSync3("sh", ["-lc", `command -v ${binary}`], {
6793
+ stdio: ["ignore", "pipe", "ignore"],
6794
+ encoding: "utf8"
6795
+ });
6796
+ return true;
6797
+ } catch {
6798
+ return false;
6799
+ }
6800
+ }
6733
6801
  async function ensureLocalAgentOnline(agentName, record) {
6734
6802
  const normalizedRecord = normalizeLocalAgentRecord(agentName, record);
6735
6803
  if (isLocalAgentRecordOnline(agentName, normalizedRecord)) {
6736
6804
  return normalizedRecord;
6737
6805
  }
6806
+ if (!isHarnessBinaryAvailable(normalizedRecord.transport)) {
6807
+ console.warn(`[openscout-runtime] skipping warmup for ${agentName}: harness binary for ${normalizedRecord.transport} not found in PATH`);
6808
+ return normalizedRecord;
6809
+ }
6738
6810
  const projectPath = normalizedRecord.cwd;
6739
6811
  const projectName = normalizedRecord.project || basename3(projectPath);
6740
6812
  const systemPromptTemplate = normalizedRecord.systemPrompt || buildLocalAgentSystemPromptTemplate();
@@ -6844,7 +6916,7 @@ async function ensureLocalAgentOnline(agentName, record) {
6844
6916
  await new Promise((resolve7) => setTimeout(resolve7, 100));
6845
6917
  }
6846
6918
  if (!isLocalAgentSessionAlive(normalizedRecord.tmuxSession)) {
6847
- const stderrTail = existsSync5(stderrLogFile) ? readFileSync2(stderrLogFile, "utf8").trim().split(/\r?\n/).slice(-10).join(`
6919
+ const stderrTail = existsSync5(stderrLogFile) ? readFileSync3(stderrLogFile, "utf8").trim().split(/\r?\n/).slice(-10).join(`
6848
6920
  `).trim() : "";
6849
6921
  throw new Error(stderrTail ? `Relay agent ${agentName} failed to stay online:
6850
6922
  ${stderrTail}` : `Relay agent ${agentName} failed to stay online.`);
@@ -7206,15 +7278,26 @@ async function loadRegisteredLocalAgentBindings(nodeId, options = {}) {
7206
7278
  ]);
7207
7279
  const requestedAgentIds = new Set((options.agentIds ?? []).filter(Boolean));
7208
7280
  const selectedEntries = Object.entries(registry).filter(([agentId]) => requestedAgentIds.size === 0 || requestedAgentIds.has(agentId));
7209
- return Promise.all(selectedEntries.map(async ([agentId, record]) => {
7281
+ const results = await Promise.all(selectedEntries.map(async ([agentId, record]) => {
7210
7282
  const baseRecord = overrides[agentId]?.projectRoot ? {
7211
7283
  ...record,
7212
7284
  projectRoot: overrides[agentId].projectRoot
7213
7285
  } : record;
7214
7286
  const harnessRecord = options.harness ? recordForHarness(baseRecord, options.harness) : baseRecord;
7215
- const effectiveRecord = options.ensureOnline ? await ensureLocalAgentOnline(agentId, harnessRecord) : normalizeLocalAgentRecord(agentId, harnessRecord);
7287
+ let effectiveRecord;
7288
+ if (options.ensureOnline) {
7289
+ try {
7290
+ effectiveRecord = await ensureLocalAgentOnline(agentId, harnessRecord);
7291
+ } catch (error) {
7292
+ console.error(`[openscout-runtime] failed to warm agent ${agentId}: ${error instanceof Error ? error.message : error}`);
7293
+ effectiveRecord = normalizeLocalAgentRecord(agentId, harnessRecord);
7294
+ }
7295
+ } else {
7296
+ effectiveRecord = normalizeLocalAgentRecord(agentId, harnessRecord);
7297
+ }
7216
7298
  return buildLocalAgentBinding(agentId, effectiveRecord, isLocalAgentRecordOnline(agentId, effectiveRecord), nodeId, "relay-agent-registry");
7217
7299
  }));
7300
+ return results;
7218
7301
  }
7219
7302
  async function inferLocalAgentBinding(agentId, nodeId) {
7220
7303
  if (!agentId || BUILT_IN_LOCAL_AGENT_IDS.has(agentId)) {
@@ -9150,7 +9233,7 @@ var init_service3 = __esm(() => {
9150
9233
  });
9151
9234
 
9152
9235
  // ../../apps/desktop/src/shared/paths.ts
9153
- import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
9236
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
9154
9237
  import { dirname as dirname7, join as join11, resolve as resolve7 } from "node:path";
9155
9238
  import { fileURLToPath as fileURLToPath6 } from "node:url";
9156
9239
  function looksLikeWorkspaceRoot(candidate) {
@@ -9159,7 +9242,7 @@ function looksLikeWorkspaceRoot(candidate) {
9159
9242
  return false;
9160
9243
  }
9161
9244
  try {
9162
- const parsed = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
9245
+ const parsed = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
9163
9246
  return Array.isArray(parsed.workspaces);
9164
9247
  } catch {
9165
9248
  return false;
@@ -9171,7 +9254,7 @@ function looksLikePackagedAppRoot(candidate) {
9171
9254
  return false;
9172
9255
  }
9173
9256
  try {
9174
- const parsed = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
9257
+ const parsed = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
9175
9258
  return parsed.name === "@scout/electron-app";
9176
9259
  } catch {
9177
9260
  return false;
@@ -9183,7 +9266,7 @@ function looksLikeInstalledCliRoot(candidate) {
9183
9266
  return false;
9184
9267
  }
9185
9268
  try {
9186
- const parsed = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
9269
+ const parsed = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
9187
9270
  return parsed.name === "@openscout/scout" || parsed.name === "@openscout/cli";
9188
9271
  } catch {
9189
9272
  return false;
@@ -10061,7 +10144,7 @@ var init_mesh2 = __esm(async () => {
10061
10144
  });
10062
10145
 
10063
10146
  // ../../apps/desktop/src/core/pairing/runtime/config.ts
10064
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
10147
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
10065
10148
  import { homedir as homedir6 } from "node:os";
10066
10149
  import path from "node:path";
10067
10150
  function pairingPaths() {
@@ -10082,7 +10165,7 @@ function loadPairingConfig() {
10082
10165
  return {};
10083
10166
  }
10084
10167
  try {
10085
- const payload = JSON.parse(readFileSync4(configPath, "utf8"));
10168
+ const payload = JSON.parse(readFileSync5(configPath, "utf8"));
10086
10169
  return typeof payload === "object" && payload ? payload : {};
10087
10170
  } catch {
10088
10171
  return {};
@@ -12165,7 +12248,7 @@ var init_noise = __esm(() => {
12165
12248
  });
12166
12249
 
12167
12250
  // ../../apps/desktop/src/core/pairing/runtime/security/identity.ts
12168
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
12251
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
12169
12252
  import { join as join13 } from "path";
12170
12253
  import { homedir as homedir7 } from "os";
12171
12254
  function loadOrCreateIdentity() {
@@ -12175,7 +12258,7 @@ function loadOrCreateIdentity() {
12175
12258
  return createAndSaveIdentity();
12176
12259
  }
12177
12260
  function loadIdentity() {
12178
- const data = JSON.parse(readFileSync5(IDENTITY_FILE, "utf8"));
12261
+ const data = JSON.parse(readFileSync6(IDENTITY_FILE, "utf8"));
12179
12262
  return {
12180
12263
  publicKey: hexToBytes2(data.publicKey),
12181
12264
  privateKey: hexToBytes2(data.privateKey)
@@ -12195,7 +12278,7 @@ function createAndSaveIdentity() {
12195
12278
  function loadTrustedPeers() {
12196
12279
  if (!existsSync10(TRUSTED_PEERS_FILE))
12197
12280
  return new Map;
12198
- const data = JSON.parse(readFileSync5(TRUSTED_PEERS_FILE, "utf8"));
12281
+ const data = JSON.parse(readFileSync6(TRUSTED_PEERS_FILE, "utf8"));
12199
12282
  return new Map(data.map((p) => [p.publicKey, p]));
12200
12283
  }
12201
12284
  function saveTrustedPeer(peer) {
@@ -12376,14 +12459,14 @@ var init_security2 = __esm(() => {
12376
12459
  });
12377
12460
 
12378
12461
  // ../../apps/desktop/src/core/pairing/runtime/runtime-state.ts
12379
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
12462
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync7, renameSync, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
12380
12463
  function readPairingRuntimeSnapshot() {
12381
12464
  const { runtimeStatePath } = pairingPaths();
12382
12465
  if (!existsSync11(runtimeStatePath)) {
12383
12466
  return null;
12384
12467
  }
12385
12468
  try {
12386
- const parsed = JSON.parse(readFileSync6(runtimeStatePath, "utf8"));
12469
+ const parsed = JSON.parse(readFileSync7(runtimeStatePath, "utf8"));
12387
12470
  return parsed?.version === 1 ? parsed : null;
12388
12471
  } catch {
12389
12472
  return null;
@@ -14601,7 +14684,7 @@ var init_bridge = __esm(() => {
14601
14684
  });
14602
14685
 
14603
14686
  // ../../apps/desktop/src/core/pairing/runtime/bridge/config.ts
14604
- import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
14687
+ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
14605
14688
  import { join as join16 } from "path";
14606
14689
  import { homedir as homedir10 } from "os";
14607
14690
  function loadConfigFile() {
@@ -14609,7 +14692,7 @@ function loadConfigFile() {
14609
14692
  return {};
14610
14693
  }
14611
14694
  try {
14612
- const raw = readFileSync7(CONFIG_FILE, "utf8");
14695
+ const raw = readFileSync8(CONFIG_FILE, "utf8");
14613
14696
  const parsed = JSON.parse(raw);
14614
14697
  return parsed;
14615
14698
  } catch (err) {
@@ -15262,7 +15345,7 @@ var init_service5 = __esm(async () => {
15262
15345
  });
15263
15346
 
15264
15347
  // ../../apps/desktop/src/core/pairing/runtime/bridge/server.ts
15265
- import { readdirSync, readFileSync as readFileSync8, realpathSync, statSync as statSync2 } from "fs";
15348
+ import { readdirSync, readFileSync as readFileSync9, realpathSync, statSync as statSync2 } from "fs";
15266
15349
  import { execSync as execSync2 } from "child_process";
15267
15350
  import { basename as basename7, isAbsolute as isAbsolute3, join as join17, relative as relative2 } from "path";
15268
15351
  import { homedir as homedir12 } from "os";
@@ -15558,7 +15641,7 @@ async function handleRPCInner(bridge, req, deviceId) {
15558
15641
  return { id: req.id, error: { code: -32000, message: "Only .jsonl files can be read" } };
15559
15642
  }
15560
15643
  try {
15561
- const content = readFileSync8(p.path, "utf-8");
15644
+ const content = readFileSync9(p.path, "utf-8");
15562
15645
  const lines = content.split(`
15563
15646
  `).filter((l) => l.trim().length > 0);
15564
15647
  const trimmed = lines.length > 500 ? lines.slice(-500) : lines;
@@ -30795,7 +30878,7 @@ var init_db_queries = __esm(() => {
30795
30878
  });
30796
30879
 
30797
30880
  // ../../apps/desktop/src/core/pairing/runtime/bridge/router.ts
30798
- import { readFileSync as readFileSync9, readdirSync as readdirSync2, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
30881
+ import { readFileSync as readFileSync10, readdirSync as readdirSync2, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
30799
30882
  import { execSync as execSync3 } from "child_process";
30800
30883
  import { basename as basename8, isAbsolute as isAbsolute4, join as join19, relative as relative3 } from "path";
30801
30884
  import { homedir as homedir14 } from "os";
@@ -31327,7 +31410,7 @@ var init_router = __esm(async () => {
31327
31410
  });
31328
31411
  }
31329
31412
  try {
31330
- const content = readFileSync9(input.path, "utf-8");
31413
+ const content = readFileSync10(input.path, "utf-8");
31331
31414
  const lines = content.split(`
31332
31415
  `).filter((l) => l.trim().length > 0);
31333
31416
  const trimmed = lines.length > 500 ? lines.slice(-500) : lines;
@@ -37840,7 +37923,7 @@ function renderScoutHelp(version2 = "0.2.18") {
37840
37923
  init_options();
37841
37924
 
37842
37925
  // ../../apps/desktop/src/shared/product.ts
37843
- var SCOUT_APP_VERSION = "0.2.19";
37926
+ var SCOUT_APP_VERSION = "0.2.21";
37844
37927
 
37845
37928
  // ../../apps/desktop/src/cli/main.ts
37846
37929
  async function main2() {