@minasoft/mina-ai-router 0.1.5 → 0.2.0

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 (30) hide show
  1. package/README.md +67 -16
  2. package/dist/apps/cli/src/index.js +1247 -43
  3. package/dist/apps/http-server/src/index.js +558 -42
  4. package/dist/apps/http-server/src/public/assets/index-Bl059Jd0.js +9 -0
  5. package/dist/apps/http-server/src/public/assets/index-CaPxN_Ez.css +1 -0
  6. package/dist/apps/http-server/src/public/index.html +2 -2
  7. package/dist/apps/mcp-server/src/index.js +54 -7
  8. package/dist/packages/core/src/capability-profile.js +145 -0
  9. package/dist/packages/core/src/index.js +3 -0
  10. package/dist/packages/core/src/mcp-preflight.js +80 -0
  11. package/dist/packages/core/src/registry.js +128 -3
  12. package/dist/packages/core/src/request-store.js +158 -0
  13. package/dist/packages/core/src/response-parser.js +76 -8
  14. package/dist/packages/core/src/router.js +408 -13
  15. package/dist/packages/core/src/version.js +57 -0
  16. package/dist/packages/mcp/src/provider.js +57 -6
  17. package/dist/packages/transports/src/headless/headless-transport.js +13 -8
  18. package/dist/packages/transports/src/tmux/tmux-client.js +334 -0
  19. package/dist/packages/transports/src/tmux/tmux-transport.js +10 -0
  20. package/docs/DEVELOPER-START-GUIDE.md +9 -1
  21. package/docs/GETTING-STARTED.md +10 -5
  22. package/docs/HTTP-UI-MCP.md +39 -13
  23. package/docs/MCP-CLIENT-SETUP.md +56 -3
  24. package/docs/SKILL-INSTALL-GUIDE.md +21 -3
  25. package/docs/TROUBLESHOOTING.md +47 -0
  26. package/docs/USER-START-GUIDE.md +155 -26
  27. package/docs/assets/mina-ai-router-overview.svg +109 -0
  28. package/package.json +8 -2
  29. package/dist/apps/http-server/src/public/assets/index-Be0tne90.js +0 -9
  30. package/dist/apps/http-server/src/public/assets/index-CEhd8YGG.css +0 -1
@@ -0,0 +1 @@
1
+ :root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--bg:#fcf9f8;--surface:#fff;--surface-low:#f6f3f2;--surface-container:#f0eded;--surface-high:#eae7e7;--ink:#1c1b1b;--muted:#404846;--muted-2:#717976;--outline:#c0c8c5;--outline-soft:#40484624;--primary:#18443b;--primary-container:#315c52;--primary-soft:#beecdf;--secondary:#006a6a;--success:#16815f;--warning:#a65f14;--danger:#ba1a1a;--terminal:#151716;--terminal-ink:#dff5ea;--radius-sm:.25rem;--radius:.375rem;--radius-lg:.5rem;--shadow-menu:0 1rem 2rem #1e2d2724;--font-ui:"Work Sans", ui-sans-serif, system-ui, sans-serif;--font-head:"Hanken Grotesk", ui-sans-serif, system-ui, sans-serif;--font-mono:"JetBrains Mono", "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace}*{box-sizing:border-box}body{background:radial-gradient(circle at 14% 10%, #315c5214, transparent 26rem), radial-gradient(circle at 92% 18%, #006a6a0f, transparent 24rem), linear-gradient(180deg, #ffffff80, transparent 18rem), var(--bg);min-width:0;min-height:100vh;color:var(--ink);font-family:var(--font-ui);letter-spacing:0;margin:0;font-size:1rem;overflow:hidden}button,input,select,textarea{font:inherit}h1,h2,h3,p{margin:0}h1,h2,h3{font-family:var(--font-head)}code,pre,.mono{font-family:var(--font-mono)}.app-shell{grid-template-rows:auto minmax(0,1fr);height:100vh;min-height:100vh;display:grid}.workspace{grid-template-columns:minmax(0,1fr);min-width:0;min-height:0;display:grid;overflow:hidden}.top-command,.brand-block,.status-cluster,.command-actions,.toolbar,.actions,.meta-row,.agent-title,.request-top,.flow-head,.inspector-head,.activity-head,.modal-head,.terminal-head{align-items:center;gap:.6rem;min-width:0;display:flex}.top-command,.flow-head,.inspector-head,.activity-head,.modal-head{justify-content:space-between}.top-command{border-bottom:1px solid var(--outline-soft);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);background:#fcf9f8eb;flex-wrap:wrap;min-height:3.5rem;padding:.5rem 1rem}.status-cluster,.command-actions,.actions,.toolbar{flex-wrap:wrap}.side-rail{border-right:1px solid var(--outline-soft);background:#ffffff8f;flex-direction:column;align-items:center;gap:.65rem;padding:1rem .7rem;display:flex}.canvas-panel{grid-template-rows:minmax(0,1fr);min-width:0;min-height:0;display:grid}.metrics{grid-template-columns:repeat(4,minmax(0,1fr));gap:.6rem;padding:.75rem 1rem;display:grid}.flow-card{border:1px solid var(--outline-soft);border-radius:var(--radius-lg);background:#ffffffb8;grid-template-rows:auto minmax(0,1fr);min-width:0;min-height:0;margin:1rem;display:grid;overflow:hidden}.flow-head,.inspector-head,.activity-head,.modal-head{border-bottom:1px solid var(--outline-soft);min-height:3.1rem;padding:.65rem 1rem}.map{cursor:grab;touch-action:none;min-height:21rem;padding:1rem;position:relative;overflow:hidden}.map.is-panning{cursor:grabbing}.flow-stage{transform-origin:50%;position:absolute;inset:1rem}.inspector{-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);background:#ffffffb8;grid-template-rows:auto minmax(0,1fr);min-width:0;min-height:0;display:grid}.floating-inspector{z-index:18;border:1px solid var(--outline-soft);border-radius:var(--radius-lg);width:min(24rem,100vw - 2rem);max-height:calc(100vh - 5.5rem);box-shadow:var(--shadow-menu);will-change:transform;transition:transform .18s,box-shadow .18s;position:fixed;top:clamp(4.5rem,8vh,6rem);bottom:1rem;right:1rem;overflow:hidden}.floating-inspector.is-collapsed{transform:translate(calc(100% - 3rem));box-shadow:0 .8rem 1.8rem #312d2b1f}.floating-inspector.is-collapsed .inspector-head,.floating-inspector.is-collapsed .inspector-body{pointer-events:none;opacity:0}.inspector-tab{z-index:2;border-radius:var(--radius-lg) 0 0 var(--radius-lg);border:1px solid var(--outline-soft);background:var(--primary-container);color:#fff;border-right:0;align-content:center;justify-items:center;gap:.35rem;width:3rem;min-height:7rem;padding:.55rem .2rem;display:grid;position:absolute;top:1rem;left:0;box-shadow:-.6rem .8rem 1.6rem #312d2b29}.inspector-tab span:not(.material-symbols-outlined){writing-mode:vertical-rl;text-orientation:mixed;font-family:var(--font-mono);font-size:.72rem;font-weight:700}.inspector-body,.modal-body{align-content:start;gap:.85rem;min-width:0;padding:.85rem 1rem;display:grid;overflow:auto}.activity{border-top:1px solid var(--outline-soft);background:#fffc;grid-template-rows:auto minmax(0,1fr);min-height:0;display:grid}.floating-activity{z-index:12;position:fixed;bottom:0;left:0;right:0;overflow:hidden;box-shadow:0 -.8rem 2rem #312d2b14}.activity-resize-handle{cursor:row-resize;touch-action:none;height:.7rem;position:absolute;top:-.35rem;left:0;right:0}.activity-resize-handle:before{content:"";background:#535c5938;border-radius:999px;width:3.5rem;height:.22rem;position:absolute;top:.22rem;left:50%;transform:translate(-50%)}.activity-body{padding:.35rem .75rem .65rem;overflow:auto}@media (width>=48rem){.detail-grid,.form-grid{grid-template-columns:1fr 1fr}}@media (width>=68.01rem){.top-command{flex-wrap:nowrap}.status-cluster{flex:auto;justify-content:center}.command-actions{flex-wrap:nowrap;flex:none}}@media (width<=68rem){body{overflow:auto}.app-shell{grid-template-rows:auto auto auto;height:auto}.top-command{flex-direction:column;align-items:flex-start}.side-rail{border-right:0;border-bottom:1px solid var(--outline-soft);flex-direction:row;overflow-x:auto}.rail-button:last-child{margin-top:0;margin-left:auto}.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.floating-inspector{width:min(23rem,100vw - 1.5rem);max-height:min(34rem,100vh - 1.5rem);top:auto;bottom:.75rem;right:.75rem}}@media (width<=47.99rem){.command-actions,.status-cluster{width:100%}.command-actions button{flex:10rem}.metrics{grid-template-columns:1fr}.flow-card{margin:.75rem}.map{cursor:default;touch-action:auto;gap:.75rem;min-height:auto;display:grid}.flow-stage{gap:.75rem;display:grid;position:static;inset:auto;transform:none!important}.links{display:none}.router-node,.agent-node{touch-action:auto;width:100%;position:static;transform:none}.router-node{order:-1}.floating-inspector{border-radius:var(--radius-lg) var(--radius-lg) 0 0;width:100vw;max-height:72vh;bottom:0;right:0}}button{border-radius:var(--radius);background:var(--primary-container);color:#fff;cursor:pointer;border:1px solid #0000;justify-content:center;align-items:center;gap:.45rem;min-height:2.75rem;padding:.62rem .85rem;display:inline-flex}.material-symbols-outlined{letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;-webkit-font-feature-settings:"liga";-webkit-font-smoothing:antialiased;direction:ltr;font-style:normal;font-weight:400;line-height:1;display:inline-block;font-family:Material Symbols Outlined!important}button.secondary{background:var(--surface);color:var(--ink);border-color:var(--outline-soft)}button.ghost{color:var(--muted);background:0 0}button.danger{color:var(--danger);background:#fff5f3;border-color:#ba1a1a40}button:disabled{cursor:wait;opacity:.55}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline-offset:2px;outline:2px solid #006a6a52}input,select,textarea{border:1px solid var(--outline-soft);border-radius:var(--radius);background:var(--surface-low);width:100%;min-height:2.75rem;color:var(--ink);padding:.72rem}textarea{resize:vertical;min-height:8rem}label{color:var(--muted);gap:.35rem;font-size:.875rem;display:grid}code,pre{border-radius:var(--radius-sm);background:var(--surface-container)}code{overflow-wrap:anywhere;padding:.12rem .28rem}pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:22rem;margin:0;padding:.75rem;overflow:auto}.brand-mark{border-radius:var(--radius-lg);background:var(--primary-container);color:#fff;place-items:center;width:2rem;height:2rem;display:grid}.brand-title{gap:.08rem;display:grid}.brand-title h1{font-size:1.08rem;line-height:1.15}.subtitle{color:var(--muted);font-size:.82rem;line-height:1.35}.chip{border:1px solid var(--outline-soft);border-radius:var(--radius);max-width:100%;min-height:1.85rem;color:var(--muted);font-family:var(--font-mono);white-space:nowrap;text-overflow:ellipsis;overflow-wrap:anywhere;background:#ffffffb8;flex:auto;align-items:center;gap:.4rem;padding:.35rem .55rem;font-size:.75rem;display:inline-flex;overflow:hidden}.status-cluster .chip{max-width:22rem}.status-cluster .chip:first-child,.flow-head .chip{flex:none}.chip-copy{max-width:min(34rem,100%)}.dot{background:var(--muted-2);border-radius:999px;flex:none;width:.55rem;height:.55rem}.dot.available,.dot.answered,.dot.success{background:var(--success)}.dot.waiting,.dot.busy,.dot.sent,.dot.created,.dot.stale{background:var(--warning)}.dot.failed,.dot.timeout,.dot.missing,.dot.needs-attention,.dot.danger{background:var(--danger)}.rail-button{width:2.75rem;height:2.75rem;color:var(--muted);font-family:var(--font-mono);background:0 0;border-color:#0000;place-items:center;padding:0;display:grid}.rail-button.active{color:var(--secondary);background:#006a6a1a;border-color:#006a6a2e}.rail-button:last-child{margin-top:auto}.metric{border:1px solid var(--outline-soft);border-radius:var(--radius-lg);background:#ffffffad;padding:.65rem .75rem}.metric span{color:var(--muted);font-family:var(--font-mono);align-items:center;gap:.3rem;font-size:.72rem;display:inline-flex}.metric strong{font-family:var(--font-head);margin-top:.3rem;font-size:1.25rem;line-height:1;display:block}.links{pointer-events:none;width:100%;height:100%;position:absolute;inset:0}.link-line{stroke:#4048464d;stroke-width:2px;stroke-dasharray:7 8}.link-line.answered{stroke:#16815fb8;stroke-dasharray:0}.link-line.waiting,.link-line.sent,.link-line.created{stroke:#a65f14d1;stroke-width:3px;animation:1.5s ease-in-out infinite flowPulse}.link-line.failed,.link-line.timeout{stroke:#ba1a1ac7;stroke-dasharray:4 6}.link-line.selected{stroke-width:4px}.router-node,.agent-node{border:1px solid var(--outline-soft);border-radius:var(--radius-lg);background:var(--surface);position:absolute}.router-node{z-index:3;border:2px solid #315c52c7;align-content:center;justify-items:center;gap:.55rem;width:min(14rem,64vw);min-height:9rem;padding:1rem;display:grid;top:50%;left:50%;transform:translate(-50%,-50%)}.router-icon{width:2.6rem;height:2.6rem;color:var(--primary-container);font-family:var(--font-mono);background:#315c521a;border-radius:999px;place-items:center;font-weight:800;display:grid}.legend{color:var(--muted);font-family:var(--font-mono);flex-wrap:wrap;justify-content:center;align-items:center;gap:.45rem;font-size:.72rem;display:flex}.agent-node{width:min(14rem,42vw);min-height:7.7rem;color:var(--ink);cursor:pointer;touch-action:none;-webkit-user-select:none;user-select:none;text-align:left;z-index:4;gap:.45rem;padding:.78rem;display:grid;overflow:hidden}.agent-node:hover,.agent-node.selected{border-color:#006a6a8c;box-shadow:0 .9rem 1.8rem #006a6a1a}.agent-node:active{cursor:grabbing}.agent-node.active-request{border-color:#a65f149e}.agent-node.failed-request{border-color:#ba1a1a80}.agent-title,.request-top{justify-content:space-between}.agent-id{overflow-wrap:anywhere;min-width:0;font-family:var(--font-mono);align-items:center;gap:.35rem;font-size:.82rem;font-weight:700;display:inline-flex}.node-meta,.muted{color:var(--muted)}.node-meta{overflow-wrap:anywhere;font-size:.75rem;line-height:1.35}.request-summary{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.status{border:1px solid var(--outline-soft);border-radius:var(--radius);min-height:1.5rem;color:var(--muted);font-family:var(--font-mono);white-space:nowrap;background:#fff9;align-items:center;gap:.35rem;padding:.12rem .48rem;font-size:.7rem;display:inline-flex}.status.available,.status.answered{color:var(--success);background:#16815f14;border-color:#16815f4d}.status.waiting,.status.sent,.status.created,.status.busy{color:var(--warning);background:#a65f1414;border-color:#a65f1452}.status.missing,.status.needs-attention,.status.timeout,.status.failed,.status.parse_failure,.status.transport_failure,.status.unknown_failure{color:var(--danger);background:#ba1a1a14;border-color:#ba1a1a47}.status.cancelled,.status.archived,.status.unknown{color:var(--muted-2);background:#71797614}.status.fresh{color:var(--success);background:#16815f14;border-color:#16815f4d}.status.manual{color:var(--secondary);background:#006a6a14;border-color:#006a6a47}.status.stale{color:var(--warning);background:#a65f1414;border-color:#a65f1452}.status.missing{color:var(--danger);background:#ba1a1a14;border-color:#ba1a1a47}.section,.request-list,.detail-grid,.health-grid,.form-grid,.guide-steps{gap:.75rem;display:grid}.health-grid{grid-template-columns:repeat(auto-fit,minmax(8.5rem,1fr))}.section-title{color:var(--muted);font-family:var(--font-mono);letter-spacing:.04em;text-transform:uppercase;align-items:center;gap:.35rem;font-size:.68rem;font-weight:700;display:inline-flex}.kv{gap:.3rem;min-width:0;display:grid}.kv span{color:var(--muted);font-size:.78rem}.kv strong,.kv code{overflow-wrap:anywhere;min-width:0}.capability-card{border:1px solid var(--outline-soft);border-radius:var(--radius-lg);background:#ffffff85;gap:.55rem;min-width:0;padding:.72rem;display:grid}.capability-card p{overflow-wrap:anywhere;min-width:0;line-height:1.45}.capability-card-head,.capability-meta,.capability-actions{flex-wrap:wrap;align-items:center;gap:.45rem;min-width:0;display:flex}.capability-card-head{justify-content:space-between}.capability-meta{color:var(--muted);font-family:var(--font-mono);font-size:.72rem;line-height:1.4}.capability-meta span{overflow-wrap:anywhere;min-width:0}.capability-actions button{flex:10rem}.capability-status{text-transform:uppercase}.status.quality-strong,.dot.quality-strong{color:var(--success);background:#16815f1f;border-color:#16815f3d}.status.quality-thin,.dot.quality-thin{color:var(--warning);background:#a65f141f;border-color:#a65f143d}.status.quality-missing,.dot.quality-missing{color:var(--danger);background:#ba1a1a1a;border-color:#ba1a1a38}.capability-profile{border-top:1px solid var(--outline-soft);gap:.55rem;min-width:0;padding-top:.55rem;display:grid}.profile-row{gap:.28rem;min-width:0;display:grid}.profile-row>span:not(.subtitle){overflow-wrap:anywhere;min-width:0;line-height:1.4}.profile-tags{flex-wrap:wrap;gap:.35rem;min-width:0;display:flex}.profile-tag{border:1px solid var(--outline-soft);border-radius:var(--radius-sm);min-width:0;max-width:100%;color:var(--ink);font-family:var(--font-mono);overflow-wrap:anywhere;background:#ffffffad;padding:.25rem .42rem;font-size:.72rem;line-height:1.35}.capability-fresh{background:#16815f0d;border-color:#16815f42}.capability-manual{background:#006a6a0d;border-color:#006a6a3d}.capability-stale{background:#a65f140f;border-color:#a65f1447}.capability-missing{background:#ba1a1a0d;border-color:#ba1a1a3d}.request-detail{min-width:0}.request-route{flex-wrap:wrap;align-items:center;gap:.35rem;min-width:0;line-height:1.45;display:flex}.request-route code{overflow-wrap:anywhere}.diagnostic-section{border:1px solid var(--outline-soft);border-radius:var(--radius);background:#ffffff7a;gap:.55rem;min-width:0;padding:.7rem;display:grid}.diagnostic-grid{grid-template-columns:1fr;gap:.6rem;min-width:0;display:grid}.diagnostic-section pre{white-space:pre-wrap;overflow-wrap:anywhere;border:1px solid var(--outline-soft);border-radius:var(--radius);background:var(--terminal);min-width:0;max-height:15rem;color:var(--terminal-ink);font-family:var(--font-mono);margin:0;padding:.65rem;font-size:.78rem;line-height:1.45;overflow:auto}.recovery-banner{border-radius:var(--radius);color:var(--muted);background:#a65f1412;border:1px solid #a65f1447;padding:.65rem;line-height:1.45}.recovery-events{gap:.5rem;display:grid}.recovery-event{border:1px solid var(--outline-soft);border-radius:var(--radius);background:#ffffff85;gap:.3rem;min-width:0;padding:.6rem;display:grid}.recovery-event p,.recovery-event code{overflow-wrap:anywhere}@media (width>=768px){.diagnostic-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.command-box,.notice{border:1px solid var(--outline-soft);border-radius:var(--radius-lg);padding:.65rem}.command-box{background:var(--terminal);color:var(--terminal-ink);gap:.55rem;display:grid}.command-box code{color:#9af1f1;background:#0000003d;padding:.6rem;display:block}.notice{background:var(--surface-low);color:var(--muted);font-size:.86rem;line-height:1.45}.notice-recovery{gap:.55rem;display:grid}.activity-table{border-collapse:collapse;width:100%;min-width:48rem;font-family:var(--font-mono);font-size:.75rem}.activity-table th,.activity-table td{border-bottom:1px solid var(--outline-soft);text-align:left;vertical-align:top;padding:.42rem .6rem}.activity-table th{color:var(--muted);text-transform:uppercase;font-size:.68rem}.request-item{border:1px solid var(--outline-soft);border-radius:var(--radius-lg);background:var(--surface);width:100%;min-height:4.8rem;color:var(--ink);text-align:left;gap:.42rem;padding:.75rem;display:grid}.empty{text-align:center;min-height:10rem;color:var(--muted);border:1px dashed var(--outline);border-radius:var(--radius-lg);place-items:center;padding:1rem;display:grid}.context-menu{z-index:20;border:1px solid var(--outline-soft);border-radius:var(--radius-lg);width:min(18rem,100vw - 2rem);box-shadow:var(--shadow-menu);background:#fffffffa;padding:.45rem;position:fixed}.context-menu button{width:100%;color:var(--ink);text-align:left;background:0 0;border-color:#0000;justify-content:flex-start}.context-menu .danger{color:var(--danger)}.modal-backdrop{z-index:30;background:#1c1b1b57;place-items:center;padding:1rem;display:grid;position:fixed;inset:0}.modal{border:1px solid var(--outline-soft);border-radius:var(--radius-lg);background:var(--surface);grid-template-rows:auto minmax(0,1fr);width:min(48rem,100%);max-height:min(46rem,100vh - 2rem);display:grid;overflow:hidden;box-shadow:0 1.5rem 4rem #1c1b1b3d}.directory-picker,.terminal-panel{border:1px solid var(--outline-soft);border-radius:var(--radius-lg);grid-column:1/-1;gap:.6rem;padding:.75rem;display:grid}.directory-picker{background:var(--surface)}.directory-list{gap:.35rem;max-height:15rem;display:grid;overflow:auto}.directory-row{background:var(--surface-low);width:100%;min-height:2.5rem;color:var(--ink);border-color:var(--outline-soft);text-align:left;justify-content:space-between;align-items:center;gap:.75rem;display:flex}.terminal-panel{background:var(--terminal);color:var(--terminal-ink)}.terminal-actions{flex-wrap:wrap;gap:.5rem;display:flex}.terminal-panel pre{min-height:16rem;max-height:24rem;color:var(--terminal-ink);background:#080d0a;border:1px solid #d8f5df29}.terminal-panel input{color:var(--terminal-ink);background:#080d0a;border-color:#d8f5df3d}.hidden{display:none!important}@keyframes flowPulse{0%,to{opacity:.42}50%{opacity:1}}
@@ -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=Hanken+Grotesk:wght@600;700&family=JetBrains+Mono:wght@400;500;700&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,400,0,0&family=Work+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-Be0tne90.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-CEhd8YGG.css">
10
+ <script type="module" crossorigin src="/assets/index-Bl059Jd0.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-CaPxN_Ez.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
@@ -7,7 +7,7 @@ const node_fs_1 = require("node:fs");
7
7
  const src_1 = require("../../../packages/core/src");
8
8
  const src_2 = require("../../../packages/transports/src");
9
9
  const importEsm = new Function("specifier", "return import(specifier)");
10
- const runtimeModuleUrl = (0, node_url_1.pathToFileURL)((0, node_path_1.join)(__dirname, "../../../../node_modules/@minasoft/mcp-runtime/dist/index.js")).href;
10
+ const runtimeModuleUrl = (0, node_url_1.pathToFileURL)(require.resolve("@minasoft/mcp-runtime")).href;
11
11
  const statePath = process.env.MINA_ROUTER_STATE ?? (0, node_path_1.join)(process.cwd(), "data", "router-state.json");
12
12
  const logPath = process.env.MINA_MCP_LOG;
13
13
  const context = createContext();
@@ -102,7 +102,11 @@ async function createProvider() {
102
102
  description: "List registered Mina helper agents.",
103
103
  inputSchema: {
104
104
  type: "object",
105
- properties: {},
105
+ properties: {
106
+ callerAgentId: { type: "string" },
107
+ sourceAgent: { type: "string" },
108
+ callerSessionFingerprint: { type: "string" },
109
+ },
106
110
  additionalProperties: false,
107
111
  },
108
112
  },
@@ -117,11 +121,15 @@ async function createProvider() {
117
121
  agentType: { type: "string" },
118
122
  transport: { type: "string" },
119
123
  sessionId: { type: "string" },
124
+ sessionFingerprint: { type: "string" },
120
125
  projectRoot: { type: "string" },
121
126
  tmuxTarget: { type: "string" },
122
127
  startupCommand: { type: "string" },
123
128
  capabilitySummary: { type: "string" },
124
129
  capabilitySources: { type: "string" },
130
+ capabilitySource: { type: "string" },
131
+ capabilityUpdatedAt: { type: "string" },
132
+ lastCapabilityRefreshAt: { type: "string" },
125
133
  },
126
134
  required: ["id", "agentType", "transport", "sessionId", "projectRoot"],
127
135
  additionalProperties: false,
@@ -135,6 +143,10 @@ async function createProvider() {
135
143
  properties: {
136
144
  target: { type: "string" },
137
145
  task: { type: "string" },
146
+ sourceAgent: { type: "string" },
147
+ callerAgentId: { type: "string" },
148
+ callerSessionFingerprint: { type: "string" },
149
+ allowSelfCall: { type: "boolean" },
138
150
  timeoutMs: { type: "number" },
139
151
  },
140
152
  required: ["target", "task"],
@@ -157,7 +169,7 @@ async function createProvider() {
157
169
  providerPromise = Promise.resolve({
158
170
  serverInfo: {
159
171
  name: "mina-ai-router",
160
- version: "0.1.0",
172
+ version: (0, src_1.packageVersion)(),
161
173
  },
162
174
  tools: {
163
175
  async list() {
@@ -173,16 +185,28 @@ async function createProvider() {
173
185
  async function callTool(name, args) {
174
186
  switch (name) {
175
187
  case "list_agents": {
176
- const agents = await context.router.listAgentStatuses();
188
+ const caller = resolveCallerAgent(args);
189
+ const agents = await context.router.listAgentStatuses({ callerAgentId: caller?.id });
177
190
  return jsonToolResult({ agents });
178
191
  }
179
192
  case "register_agent": {
180
193
  try {
181
194
  const agent = agentFromArgs(args);
182
- context.registry.register(agent);
195
+ const now = new Date().toISOString();
196
+ const registered = context.registry.register({
197
+ ...agent,
198
+ bootstrapStatus: "ready",
199
+ registrationSource: "mcp",
200
+ registrationStatus: "confirmed",
201
+ lastRegistrationAttemptAt: now,
202
+ confirmedByAgentAt: now,
203
+ sessionFingerprint: agent.sessionFingerprint ?? agent.sessionId,
204
+ }, {
205
+ capabilitySource: agent.capabilitySummary || agent.capabilitySources ? "generated" : undefined,
206
+ });
183
207
  context.save();
184
208
  const agents = await context.router.listAgentStatuses();
185
- return jsonToolResult({ agent, agents });
209
+ return jsonToolResult({ agent: registered, agents });
186
210
  }
187
211
  catch (error) {
188
212
  return {
@@ -195,11 +219,19 @@ async function callTool(name, args) {
195
219
  const target = typeof args.target === "string" ? args.target : "";
196
220
  const task = typeof args.task === "string" ? args.task : "";
197
221
  const timeoutMs = typeof args.timeoutMs === "number" ? args.timeoutMs : undefined;
222
+ const caller = resolveCallerAgent(args);
223
+ const allowSelfCall = args.allowSelfCall === true;
198
224
  if (!target || !task) {
199
225
  return { kind: "invalid_params", message: "call_agent requires string target and task." };
200
226
  }
201
227
  try {
202
- const response = await context.router.callAgent({ target, task, timeoutMs });
228
+ const response = await context.router.callAgent({
229
+ sourceAgent: caller?.id,
230
+ target,
231
+ task,
232
+ timeoutMs,
233
+ allowSelfCall,
234
+ });
203
235
  return jsonToolResult(response);
204
236
  }
205
237
  catch (error) {
@@ -235,6 +267,14 @@ async function callTool(name, args) {
235
267
  return { kind: "not_found", message: `Unknown tool "${name}".` };
236
268
  }
237
269
  }
270
+ function resolveCallerAgent(args) {
271
+ const callerAgentId = stringValue(args.callerAgentId) ?? stringValue(args.sourceAgent);
272
+ if (callerAgentId) {
273
+ return context.registry.get(callerAgentId);
274
+ }
275
+ const fingerprint = stringValue(args.callerSessionFingerprint);
276
+ return fingerprint ? context.registry.findBySessionFingerprint(fingerprint) : undefined;
277
+ }
238
278
  function agentFromArgs(args) {
239
279
  const id = requiredString(args.id, "id");
240
280
  return {
@@ -243,11 +283,15 @@ function agentFromArgs(args) {
243
283
  agentType: requiredString(args.agentType, "agentType"),
244
284
  transport: requiredString(args.transport, "transport"),
245
285
  sessionId: requiredString(args.sessionId, "sessionId"),
286
+ sessionFingerprint: stringValue(args.sessionFingerprint) ?? stringValue(args.sessionId),
246
287
  projectRoot: requiredString(args.projectRoot, "projectRoot"),
247
288
  tmuxTarget: stringValue(args.tmuxTarget),
248
289
  startupCommand: stringValue(args.startupCommand),
249
290
  capabilitySummary: stringValue(args.capabilitySummary),
250
291
  capabilitySources: stringValue(args.capabilitySources),
292
+ capabilitySource: capabilitySourceValue(args.capabilitySource),
293
+ capabilityUpdatedAt: stringValue(args.capabilityUpdatedAt),
294
+ lastCapabilityRefreshAt: stringValue(args.lastCapabilityRefreshAt),
251
295
  };
252
296
  }
253
297
  function requiredString(value, field) {
@@ -259,6 +303,9 @@ function requiredString(value, field) {
259
303
  function stringValue(value) {
260
304
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
261
305
  }
306
+ function capabilitySourceValue(value) {
307
+ return value === "manual" || value === "generated" ? value : undefined;
308
+ }
262
309
  function jsonToolResult(value) {
263
310
  const text = JSON.stringify(value, null, 2);
264
311
  return {
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildCapabilityProfile = buildCapabilityProfile;
4
+ exports.scoreCapabilityProfile = scoreCapabilityProfile;
5
+ function buildCapabilityProfile(input) {
6
+ const summary = input.capabilitySummary?.trim();
7
+ const profile = normalizeProfile(input.capabilityProfile);
8
+ const quality = scoreCapabilityProfile({ ...input, capabilityProfile: profile });
9
+ if (!summary && !profile) {
10
+ return undefined;
11
+ }
12
+ return {
13
+ ...profile,
14
+ quality: quality.quality,
15
+ qualityReasons: quality.reasons,
16
+ };
17
+ }
18
+ function scoreCapabilityProfile(input) {
19
+ const summary = input.capabilitySummary?.trim() ?? "";
20
+ const sources = splitList(input.capabilitySources);
21
+ const profile = normalizeProfile(input.capabilityProfile);
22
+ const canAnswer = profile?.canAnswer ?? [];
23
+ const evidence = profile?.evidence?.length ? profile.evidence : sources;
24
+ const keyAreas = profile?.keyAreas ?? [];
25
+ const languageCount = profile?.primaryLanguages?.length ?? 0;
26
+ const purpose = profile?.projectPurpose?.trim() ?? "";
27
+ const text = [summary, purpose, canAnswer.join(" "), keyAreas.join(" ")].join(" ").toLowerCase();
28
+ if (!summary && !purpose && canAnswer.length === 0 && keyAreas.length === 0 && evidence.length === 0) {
29
+ return { quality: "missing", reasons: ["No capability summary or structured profile is recorded."] };
30
+ }
31
+ const genericHits = genericCapabilitySignals.filter((signal) => text.includes(signal));
32
+ const domainHits = domainCapabilitySignals.filter((signal) => text.includes(signal));
33
+ const hasStructuredStrongShape = Boolean(purpose)
34
+ && canAnswer.length >= 2
35
+ && keyAreas.length >= 1
36
+ && evidence.length >= 2;
37
+ const hasSummaryStrongShape = domainHits.length >= 3
38
+ && evidence.length >= 2
39
+ && !mostlyGenericFileMention(summary);
40
+ if (hasStructuredStrongShape || hasSummaryStrongShape) {
41
+ return {
42
+ quality: "strong",
43
+ reasons: [
44
+ hasStructuredStrongShape
45
+ ? "Structured profile includes purpose, answerable areas, key areas, and evidence."
46
+ : "Capability summary includes multiple answerable domains backed by evidence.",
47
+ ],
48
+ };
49
+ }
50
+ if (genericHits.length > 0 || mostlyGenericFileMention(summary) || domainHits.length < 2 || evidence.length === 0) {
51
+ return {
52
+ quality: "thin",
53
+ reasons: [
54
+ genericHits.length > 0
55
+ ? `Summary leans on generic file references: ${genericHits.slice(0, 3).join(", ")}.`
56
+ : "Profile does not yet describe enough answerable domains with evidence.",
57
+ ],
58
+ };
59
+ }
60
+ if (languageCount > 0 && (canAnswer.length > 0 || keyAreas.length > 0)) {
61
+ return {
62
+ quality: "thin",
63
+ reasons: ["Profile has some structure but needs more answerable domains and evidence."],
64
+ };
65
+ }
66
+ return { quality: "thin", reasons: ["Capability profile is present but not yet strong."] };
67
+ }
68
+ function normalizeProfile(profile) {
69
+ if (!profile) {
70
+ return undefined;
71
+ }
72
+ const normalized = {
73
+ projectPurpose: stringValue(profile.projectPurpose),
74
+ primaryLanguages: cleanList(profile.primaryLanguages),
75
+ keyAreas: cleanList(profile.keyAreas),
76
+ canAnswer: cleanList(profile.canAnswer),
77
+ cannotAnswerYet: cleanList(profile.cannotAnswerYet),
78
+ evidence: cleanList(profile.evidence),
79
+ };
80
+ if (!normalized.projectPurpose
81
+ && normalized.primaryLanguages.length === 0
82
+ && normalized.keyAreas.length === 0
83
+ && normalized.canAnswer.length === 0
84
+ && normalized.cannotAnswerYet.length === 0
85
+ && normalized.evidence.length === 0) {
86
+ return undefined;
87
+ }
88
+ return normalized;
89
+ }
90
+ function cleanList(value) {
91
+ return Array.isArray(value)
92
+ ? value.map((item) => item.trim()).filter(Boolean)
93
+ : [];
94
+ }
95
+ function splitList(value) {
96
+ return value
97
+ ? value.split(/[,\n]/).map((item) => item.trim()).filter(Boolean)
98
+ : [];
99
+ }
100
+ function stringValue(value) {
101
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
102
+ }
103
+ function mostlyGenericFileMention(summary) {
104
+ if (!summary) {
105
+ return false;
106
+ }
107
+ const lower = summary.toLowerCase();
108
+ const fileMentionCount = (lower.match(/\b(readme|claude\.md|agents\.md|src|package\.json|docs?|directories|files?)\b/g) ?? []).length;
109
+ return fileMentionCount >= 2 && (lower.includes("provides guidance") || lower.includes("contains") || lower.includes("there is"));
110
+ }
111
+ const genericCapabilitySignals = [
112
+ "claude.md",
113
+ "agents.md",
114
+ "readme",
115
+ "src directory",
116
+ "provides guidance",
117
+ "project files",
118
+ "files exist",
119
+ "directory",
120
+ "package.json",
121
+ ];
122
+ const domainCapabilitySignals = [
123
+ "api",
124
+ "endpoint",
125
+ "router",
126
+ "mcp",
127
+ "transport",
128
+ "tmux",
129
+ "request",
130
+ "response",
131
+ "schema",
132
+ "database",
133
+ "auth",
134
+ "payment",
135
+ "frontend",
136
+ "backend",
137
+ "cli",
138
+ "web ui",
139
+ "http",
140
+ "react",
141
+ "typescript",
142
+ "node",
143
+ "test",
144
+ "workflow",
145
+ ];
@@ -15,10 +15,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./ids"), exports);
18
+ __exportStar(require("./capability-profile"), exports);
18
19
  __exportStar(require("./file-state"), exports);
20
+ __exportStar(require("./mcp-preflight"), exports);
19
21
  __exportStar(require("./prompt-envelope"), exports);
20
22
  __exportStar(require("./registry"), exports);
21
23
  __exportStar(require("./request-store"), exports);
22
24
  __exportStar(require("./response-parser"), exports);
23
25
  __exportStar(require("./router"), exports);
24
26
  __exportStar(require("./types"), exports);
27
+ __exportStar(require("./version"), exports);
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildMcpPreflight = buildMcpPreflight;
4
+ function buildMcpPreflight(input) {
5
+ const mcpName = input.mcpName?.trim() || "mina-ai-router";
6
+ const mcpUrl = input.mcpUrl;
7
+ const commands = commandsFor(input.agentType, mcpName, mcpUrl);
8
+ if (!commands) {
9
+ return result({
10
+ status: "unsupported",
11
+ mcpUrl,
12
+ detail: `MCP preflight does not yet support agent type "${input.agentType}".`,
13
+ nextAction: "Configure this client manually before asking it to self-register with Mina AI Router.",
14
+ canSendSelfRegistrationPrompt: false,
15
+ });
16
+ }
17
+ if (input.configured === true || input.configuredUrl === mcpUrl) {
18
+ return result({
19
+ status: "configured",
20
+ mcpUrl,
21
+ detail: `${input.agentType} MCP appears configured for ${mcpUrl}.`,
22
+ nextAction: "MCP preflight is configured; Mina may send the self-registration prompt.",
23
+ canSendSelfRegistrationPrompt: true,
24
+ ...commands,
25
+ });
26
+ }
27
+ if (input.configuredUrl && input.configuredUrl !== mcpUrl) {
28
+ return result({
29
+ status: "stale",
30
+ mcpUrl,
31
+ detail: `${input.agentType} MCP points at ${input.configuredUrl}, but Mina is serving ${mcpUrl}.`,
32
+ nextAction: `Run: ${commands.setupCommand}`,
33
+ canSendSelfRegistrationPrompt: false,
34
+ ...commands,
35
+ });
36
+ }
37
+ return result({
38
+ status: "missing",
39
+ mcpUrl,
40
+ detail: `${input.agentType} MCP setup is not confirmed for ${mcpUrl}.`,
41
+ nextAction: `Run: ${commands.setupCommand}`,
42
+ canSendSelfRegistrationPrompt: false,
43
+ ...commands,
44
+ });
45
+ }
46
+ function commandsFor(agentType, mcpName, mcpUrl) {
47
+ if (agentType === "codex") {
48
+ return {
49
+ setupCommand: `codex mcp add ${shellQuote(mcpName)} --url ${shellQuote(mcpUrl)}`,
50
+ verifyCommand: `codex mcp get ${shellQuote(mcpName)}`,
51
+ removeCommand: `codex mcp remove ${shellQuote(mcpName)}`,
52
+ };
53
+ }
54
+ if (agentType === "claude") {
55
+ return {
56
+ setupCommand: `claude mcp add --transport http ${shellQuote(mcpName)} ${shellQuote(mcpUrl)}`,
57
+ verifyCommand: `claude mcp get ${shellQuote(mcpName)}`,
58
+ removeCommand: `claude mcp remove ${shellQuote(mcpName)}`,
59
+ };
60
+ }
61
+ return null;
62
+ }
63
+ function result(input) {
64
+ return {
65
+ status: input.status,
66
+ mcpPreflightStatus: input.status,
67
+ mcpPreflightDetail: input.detail,
68
+ mcpSetupCommand: input.setupCommand,
69
+ mcpVerifyCommand: input.verifyCommand,
70
+ mcpRemoveCommand: input.removeCommand,
71
+ mcpUrl: input.mcpUrl,
72
+ nextAction: input.nextAction,
73
+ canSendSelfRegistrationPrompt: input.canSendSelfRegistrationPrompt,
74
+ };
75
+ }
76
+ function shellQuote(value) {
77
+ if (/^[A-Za-z0-9_./:=@-]+$/.test(value))
78
+ return value;
79
+ return `'${value.replace(/'/g, "'\\''")}'`;
80
+ }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AgentRegistry = void 0;
4
+ const capability_profile_1 = require("./capability-profile");
4
5
  class AgentRegistry {
5
6
  constructor(agents = []) {
6
7
  this.agents = new Map();
@@ -8,9 +9,67 @@ class AgentRegistry {
8
9
  this.register(agent);
9
10
  }
10
11
  }
11
- register(agent) {
12
- this.agents.set(agent.id, agent);
13
- return agent;
12
+ register(agent, options = {}) {
13
+ const fingerprint = agent.sessionFingerprint;
14
+ const currentById = this.agents.get(agent.id);
15
+ const currentByFingerprint = fingerprint ? this.findBySessionFingerprint(fingerprint) : undefined;
16
+ const current = currentById ?? currentByFingerprint;
17
+ const canonicalId = current?.id ?? agent.id;
18
+ const registrationWarnings = mergeRegistrationWarnings(current?.registrationWarnings, currentByFingerprint && currentByFingerprint.id !== agent.id
19
+ ? `Registration id "${agent.id}" matched existing session fingerprint for "${currentByFingerprint.id}"; preserved existing agent id.`
20
+ : undefined);
21
+ const next = {
22
+ ...agent,
23
+ id: canonicalId,
24
+ name: currentByFingerprint && currentByFingerprint.id !== agent.id
25
+ ? currentByFingerprint.name || currentByFingerprint.id
26
+ : agent.name || current?.name || canonicalId,
27
+ capabilitySummary: agent.capabilitySummary ?? current?.capabilitySummary,
28
+ capabilitySources: agent.capabilitySources ?? current?.capabilitySources,
29
+ capabilitySource: agent.capabilitySource ?? current?.capabilitySource,
30
+ capabilityUpdatedAt: agent.capabilityUpdatedAt ?? current?.capabilityUpdatedAt,
31
+ lastCapabilityRefreshAt: agent.lastCapabilityRefreshAt ?? current?.lastCapabilityRefreshAt,
32
+ capabilityProfile: (0, capability_profile_1.buildCapabilityProfile)({
33
+ capabilitySummary: agent.capabilitySummary ?? current?.capabilitySummary,
34
+ capabilitySources: agent.capabilitySources ?? current?.capabilitySources,
35
+ capabilityProfile: agent.capabilityProfile ?? current?.capabilityProfile,
36
+ }),
37
+ bootstrapStatus: agent.bootstrapStatus ?? current?.bootstrapStatus ?? "ready",
38
+ registrationSource: agent.registrationSource ?? current?.registrationSource ?? "unknown",
39
+ registrationStatus: agent.registrationStatus ?? current?.registrationStatus ?? "confirmed",
40
+ lastRegistrationAttemptAt: agent.lastRegistrationAttemptAt ?? current?.lastRegistrationAttemptAt,
41
+ confirmedByAgentAt: agent.confirmedByAgentAt ?? current?.confirmedByAgentAt,
42
+ sessionFingerprint: agent.sessionFingerprint ?? current?.sessionFingerprint ?? agent.sessionId,
43
+ registrationHistory: appendRegistrationHistory(current?.registrationHistory, agent, canonicalId),
44
+ registrationWarnings,
45
+ permissionProfile: agent.permissionProfile ?? current?.permissionProfile ?? "default",
46
+ permissionProfileStatus: agent.permissionProfileStatus ?? current?.permissionProfileStatus ?? "not-requested",
47
+ permissionProfileDetail: agent.permissionProfileDetail ?? current?.permissionProfileDetail,
48
+ mcpPreflightStatus: agent.mcpPreflightStatus ?? current?.mcpPreflightStatus,
49
+ mcpPreflightDetail: agent.mcpPreflightDetail ?? current?.mcpPreflightDetail,
50
+ mcpSetupCommand: agent.mcpSetupCommand ?? current?.mcpSetupCommand,
51
+ mcpVerifyCommand: agent.mcpVerifyCommand ?? current?.mcpVerifyCommand,
52
+ mcpRemoveCommand: agent.mcpRemoveCommand ?? current?.mcpRemoveCommand,
53
+ mcpUrl: agent.mcpUrl ?? current?.mcpUrl,
54
+ lastSeenAt: agent.lastSeenAt ?? current?.lastSeenAt,
55
+ lastActivityAt: agent.lastActivityAt ?? current?.lastActivityAt,
56
+ activeRequestId: valueOrCurrent(agent, current, "activeRequestId"),
57
+ leaseStatus: valueOrCurrent(agent, current, "leaseStatus"),
58
+ leaseStartedAt: valueOrCurrent(agent, current, "leaseStartedAt"),
59
+ leaseExpiresAt: valueOrCurrent(agent, current, "leaseExpiresAt"),
60
+ leaseReleasedAt: valueOrCurrent(agent, current, "leaseReleasedAt"),
61
+ };
62
+ if ((agent.capabilitySummary !== undefined || agent.capabilitySources !== undefined)
63
+ && (options.capabilitySource !== undefined || options.refreshedAt !== undefined)) {
64
+ const timestamp = options.refreshedAt ?? new Date().toISOString();
65
+ next.capabilitySource = options.capabilitySource ?? agent.capabilitySource ?? current?.capabilitySource;
66
+ next.capabilityUpdatedAt = timestamp;
67
+ if ((options.capabilitySource ?? agent.capabilitySource) === "generated") {
68
+ next.lastCapabilityRefreshAt = timestamp;
69
+ }
70
+ }
71
+ this.agents.set(canonicalId, next);
72
+ return next;
14
73
  }
15
74
  list() {
16
75
  return Array.from(this.agents.values()).sort((a, b) => a.id.localeCompare(b.id));
@@ -18,6 +77,9 @@ class AgentRegistry {
18
77
  get(id) {
19
78
  return this.agents.get(id);
20
79
  }
80
+ findBySessionFingerprint(sessionFingerprint) {
81
+ return this.list().find((agent) => agent.sessionFingerprint === sessionFingerprint);
82
+ }
21
83
  unregister(id) {
22
84
  const agent = this.require(id);
23
85
  this.agents.delete(id);
@@ -30,5 +92,68 @@ class AgentRegistry {
30
92
  }
31
93
  return agent;
32
94
  }
95
+ updateCapabilities(id, update) {
96
+ const agent = this.require(id);
97
+ const timestamp = update.refreshedAt ?? new Date().toISOString();
98
+ const next = {
99
+ ...agent,
100
+ capabilitySummary: update.summary ?? agent.capabilitySummary,
101
+ capabilitySources: update.sources ?? agent.capabilitySources,
102
+ capabilitySource: update.source,
103
+ capabilityUpdatedAt: timestamp,
104
+ lastCapabilityRefreshAt: update.source === "generated" ? timestamp : agent.lastCapabilityRefreshAt,
105
+ capabilityProfile: (0, capability_profile_1.buildCapabilityProfile)({
106
+ capabilitySummary: update.summary ?? agent.capabilitySummary,
107
+ capabilitySources: update.sources ?? agent.capabilitySources,
108
+ capabilityProfile: update.profile ?? agent.capabilityProfile,
109
+ }),
110
+ };
111
+ this.agents.set(id, next);
112
+ return next;
113
+ }
114
+ updateHealth(id, update) {
115
+ const agent = this.require(id);
116
+ const next = {
117
+ ...agent,
118
+ lastSeenAt: update.lastSeenAt ?? agent.lastSeenAt,
119
+ lastActivityAt: update.lastActivityAt ?? agent.lastActivityAt,
120
+ };
121
+ this.agents.set(id, next);
122
+ return next;
123
+ }
33
124
  }
34
125
  exports.AgentRegistry = AgentRegistry;
126
+ function appendRegistrationHistory(current = [], agent, canonicalId) {
127
+ const at = agent.confirmedByAgentAt ?? agent.lastRegistrationAttemptAt;
128
+ const source = agent.registrationSource;
129
+ const status = agent.registrationStatus;
130
+ if (!at || !source || !status) {
131
+ return current;
132
+ }
133
+ const event = {
134
+ at,
135
+ source,
136
+ status,
137
+ agentId: canonicalId,
138
+ sessionFingerprint: agent.sessionFingerprint,
139
+ };
140
+ const last = current[current.length - 1];
141
+ if (last
142
+ && last.at === event.at
143
+ && last.source === event.source
144
+ && last.status === event.status
145
+ && last.agentId === event.agentId
146
+ && last.sessionFingerprint === event.sessionFingerprint) {
147
+ return current;
148
+ }
149
+ return [...current, event];
150
+ }
151
+ function mergeRegistrationWarnings(current = [], next) {
152
+ if (!next) {
153
+ return current.length ? current : undefined;
154
+ }
155
+ return current.includes(next) ? current : [...current, next];
156
+ }
157
+ function valueOrCurrent(agent, current, key) {
158
+ return Object.prototype.hasOwnProperty.call(agent, key) ? agent[key] : current?.[key];
159
+ }