@nac3/forge-cli 0.2.0-alpha.59z.121 → 0.2.0-alpha.59z.158
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/bridge.d.ts +69 -0
- package/dist/agents/bridge.d.ts.map +1 -0
- package/dist/agents/bridge.js +295 -0
- package/dist/agents/bridge.js.map +1 -0
- package/dist/agents/cli_real.d.ts +38 -0
- package/dist/agents/cli_real.d.ts.map +1 -0
- package/dist/agents/cli_real.js +46 -0
- package/dist/agents/cli_real.js.map +1 -0
- package/dist/agents/config_schema.d.ts +1 -1
- package/dist/agents/config_schema.d.ts.map +1 -1
- package/dist/agents/config_schema.js +16 -0
- package/dist/agents/config_schema.js.map +1 -1
- package/dist/agents/defaults.d.ts.map +1 -1
- package/dist/agents/defaults.js.map +1 -1
- package/dist/agents/delegate.d.ts +36 -0
- package/dist/agents/delegate.d.ts.map +1 -0
- package/dist/agents/delegate.js +68 -0
- package/dist/agents/delegate.js.map +1 -0
- package/dist/agents/iterative_step.d.ts +95 -0
- package/dist/agents/iterative_step.d.ts.map +1 -0
- package/dist/agents/iterative_step.js +249 -0
- package/dist/agents/iterative_step.js.map +1 -0
- package/dist/agents/llm_step.d.ts +50 -0
- package/dist/agents/llm_step.d.ts.map +1 -0
- package/dist/agents/llm_step.js +28 -0
- package/dist/agents/llm_step.js.map +1 -0
- package/dist/agents/real_step_factory.d.ts +95 -0
- package/dist/agents/real_step_factory.d.ts.map +1 -0
- package/dist/agents/real_step_factory.js +51 -0
- package/dist/agents/real_step_factory.js.map +1 -0
- package/dist/agents/runner.d.ts +14 -0
- package/dist/agents/runner.d.ts.map +1 -1
- package/dist/agents/runner.js +10 -1
- package/dist/agents/runner.js.map +1 -1
- package/dist/agents/runtime.d.ts +115 -0
- package/dist/agents/runtime.d.ts.map +1 -0
- package/dist/agents/runtime.js +429 -0
- package/dist/agents/runtime.js.map +1 -0
- package/dist/agents/system_prompt.d.ts +36 -0
- package/dist/agents/system_prompt.d.ts.map +1 -0
- package/dist/agents/system_prompt.js +101 -0
- package/dist/agents/system_prompt.js.map +1 -0
- package/dist/agents/types.d.ts +12 -1
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/agents/types.js.map +1 -1
- package/dist/bin/yf.d.ts.map +1 -1
- package/dist/bin/yf.js +9 -0
- package/dist/bin/yf.js.map +1 -1
- package/dist/chat/claude_cli.d.ts +42 -0
- package/dist/chat/claude_cli.d.ts.map +1 -0
- package/dist/chat/claude_cli.js +299 -0
- package/dist/chat/claude_cli.js.map +1 -0
- package/dist/chat/graph_snapshot.d.ts +38 -0
- package/dist/chat/graph_snapshot.d.ts.map +1 -0
- package/dist/chat/graph_snapshot.js +159 -0
- package/dist/chat/graph_snapshot.js.map +1 -0
- package/dist/chat/ingest_session.d.ts +24 -4
- package/dist/chat/ingest_session.d.ts.map +1 -1
- package/dist/chat/ingest_session.js +139 -4
- package/dist/chat/ingest_session.js.map +1 -1
- package/dist/chat/panel.d.ts.map +1 -1
- package/dist/chat/panel.js +1358 -0
- package/dist/chat/panel.js.map +1 -1
- package/dist/chat/server.d.ts +21 -0
- package/dist/chat/server.d.ts.map +1 -1
- package/dist/chat/server.js +904 -4
- package/dist/chat/server.js.map +1 -1
- package/dist/chat/tools/nac3_invoke.d.ts +5 -0
- package/dist/chat/tools/nac3_invoke.d.ts.map +1 -1
- package/dist/chat/tools/nac3_invoke.js +1 -0
- package/dist/chat/tools/nac3_invoke.js.map +1 -1
- package/dist/chat/tools.d.ts.map +1 -1
- package/dist/chat/tools.js +12 -0
- package/dist/chat/tools.js.map +1 -1
- package/dist/commands/agent.d.ts +12 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +92 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/chat.d.ts +10 -0
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +12 -1
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/director.d.ts +37 -0
- package/dist/commands/director.d.ts.map +1 -0
- package/dist/commands/director.js +290 -0
- package/dist/commands/director.js.map +1 -0
- package/dist/commands/keys_setup.d.ts +1 -0
- package/dist/commands/keys_setup.d.ts.map +1 -1
- package/dist/commands/keys_setup.js +61 -11
- package/dist/commands/keys_setup.js.map +1 -1
- package/dist/core/pilot_setup.d.ts +14 -1
- package/dist/core/pilot_setup.d.ts.map +1 -1
- package/dist/core/pilot_setup.js +62 -1
- package/dist/core/pilot_setup.js.map +1 -1
- package/dist/core/provider_keys.d.ts.map +1 -1
- package/dist/core/provider_keys.js +12 -5
- package/dist/core/provider_keys.js.map +1 -1
- package/dist/design/yujin_design_resolver.d.ts +77 -0
- package/dist/design/yujin_design_resolver.d.ts.map +1 -0
- package/dist/design/yujin_design_resolver.js +113 -0
- package/dist/design/yujin_design_resolver.js.map +1 -0
- package/dist/docs/doctrine/default-design-system.md +120 -0
- package/dist/docs/doctrine/language-sovereignty.md +143 -0
- package/dist/docs/doctrine/testing-policy.md +211 -0
- package/dist/nac3/approval_queue.d.ts +8 -0
- package/dist/nac3/approval_queue.d.ts.map +1 -1
- package/dist/nac3/approval_queue.js.map +1 -1
- package/dist/nac3/internal_manifest.d.ts.map +1 -1
- package/dist/nac3/internal_manifest.js +178 -0
- package/dist/nac3/internal_manifest.js.map +1 -1
- package/dist/office/excel.d.ts +22 -0
- package/dist/office/excel.d.ts.map +1 -1
- package/dist/office/excel.js +82 -0
- package/dist/office/excel.js.map +1 -1
- package/dist/qa/brain_matrix/fixtures/scenario_a11y_focus_after_open.json +22 -0
- package/dist/qa/brain_matrix/fixtures/scenario_a11y_label_unlabeled_button.json +26 -0
- package/dist/qa/brain_matrix/fixtures/scenario_agent_director_consults_workflow_before_dispatch.json +42 -0
- package/dist/qa/brain_matrix/fixtures/scenario_analyst_extracts_then_returns.json +28 -0
- package/dist/qa/brain_matrix/fixtures/scenario_coder_consults_nac_spec_before_writing.json +28 -0
- package/dist/qa/brain_matrix/fixtures/scenario_default_design_system_override_shadcn.json +45 -0
- package/dist/qa/brain_matrix/fixtures/scenario_default_design_system_use_card_template.json +44 -0
- package/dist/qa/brain_matrix/fixtures/scenario_director_delegates_to_analyst.json +26 -0
- package/dist/qa/brain_matrix/fixtures/scenario_director_no_self_delegation.json +20 -0
- package/dist/qa/brain_matrix/fixtures/scenario_director_terminal_done_marker.json +19 -0
- package/dist/qa/brain_matrix/fixtures/scenario_doctrine_discover_default_design_system.json +50 -0
- package/dist/qa/brain_matrix/fixtures/scenario_doctrine_discover_language_sovereignty.json +53 -0
- package/dist/qa/brain_matrix/fixtures/scenario_doctrine_discover_testing_policy.json +55 -0
- package/dist/qa/brain_matrix/fixtures/scenario_language_sovereignty_not_conducive_spa_python.json +61 -0
- package/dist/qa/brain_matrix/fixtures/scenario_language_sovereignty_user_picks_python.json +48 -0
- package/dist/qa/brain_matrix/fixtures/scenario_mcp_invoke_destructive_pauses_for_r8.json +42 -0
- package/dist/qa/brain_matrix/fixtures/scenario_mcp_invoke_happy_path_writes_pizarron.json +76 -0
- package/dist/qa/brain_matrix/fixtures/scenario_mcp_invoke_network_timeout_graceful.json +41 -0
- package/dist/qa/brain_matrix/fixtures/scenario_no_hardcoded_color_use_token.json +47 -0
- package/dist/qa/brain_matrix/fixtures/scenario_office_excel_update_cell_picks_right_verb.json +26 -0
- package/dist/qa/brain_matrix/fixtures/scenario_scaffolder_destructive_pauses_for_approval.json +23 -0
- package/dist/qa/brain_matrix/fixtures/scenario_sumie_icon_pick_from_catalog.json +56 -0
- package/dist/qa/brain_matrix/fixtures/scenario_tester_uses_shell_exec_only.json +27 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workflow/graph.d.ts +59 -0
- package/dist/workflow/graph.d.ts.map +1 -0
- package/dist/workflow/graph.js +137 -0
- package/dist/workflow/graph.js.map +1 -0
- package/dist/workflow/state.d.ts +34 -0
- package/dist/workflow/state.d.ts.map +1 -1
- package/dist/workflow/state.js.map +1 -1
- package/docs/doctrine/default-design-system.md +120 -0
- package/docs/doctrine/language-sovereignty.md +143 -0
- package/docs/doctrine/testing-policy.md +211 -0
- package/package.json +102 -100
- package/src/i18n/catalogs/ar.json +20 -1
- package/src/i18n/catalogs/de.json +20 -1
- package/src/i18n/catalogs/en.json +20 -21
- package/src/i18n/catalogs/es.json +20 -21
- package/src/i18n/catalogs/fr.json +20 -1
- package/src/i18n/catalogs/hi.json +20 -1
- package/src/i18n/catalogs/it.json +20 -1
- package/src/i18n/catalogs/ja.json +20 -1
- package/src/i18n/catalogs/pt.json +20 -1
- package/src/i18n/catalogs/zh.json +20 -1
- package/templates/sumi-context.template.md +57 -0
package/dist/chat/panel.js
CHANGED
|
@@ -46,6 +46,20 @@ export function renderPanelHtml(cfg) {
|
|
|
46
46
|
streamEmptyBody: tForLang(lang, 'panel.stream.empty.body'),
|
|
47
47
|
traceEmptyKanji: tForLang(lang, 'panel.trace.empty.kanji'),
|
|
48
48
|
traceEmptyBody: tForLang(lang, 'panel.trace.empty.body'),
|
|
49
|
+
/* Director sprint slice 6 -- agent overlay strings. */
|
|
50
|
+
agentsButton: tForLang(lang, 'panel.button.agents'),
|
|
51
|
+
agentsTitle: tForLang(lang, 'panel.agents.title'),
|
|
52
|
+
agentsSubtitle: tForLang(lang, 'panel.agents.subtitle'),
|
|
53
|
+
agentsEmptyTitle: tForLang(lang, 'panel.agents.empty.title'),
|
|
54
|
+
agentsEmptyBody: tForLang(lang, 'panel.agents.empty.body'),
|
|
55
|
+
agentsLegendEco: tForLang(lang, 'panel.agents.legend.eco'),
|
|
56
|
+
agentsLegendManaged: tForLang(lang, 'panel.agents.legend.managed'),
|
|
57
|
+
agentsLegendFrontier: tForLang(lang, 'panel.agents.legend.frontier'),
|
|
58
|
+
agentsStatusDisabled: tForLang(lang, 'panel.agents.status.disabled'),
|
|
59
|
+
agentsEventRunning: tForLang(lang, 'panel.agents.event.running'),
|
|
60
|
+
agentsEventIdle: tForLang(lang, 'panel.agents.event.idle'),
|
|
61
|
+
agentsTickLive: tForLang(lang, 'panel.agents.tick.live'),
|
|
62
|
+
agentsTickLost: tForLang(lang, 'panel.agents.tick.lost'),
|
|
49
63
|
};
|
|
50
64
|
const runtimeConfig = JSON.stringify({
|
|
51
65
|
projectName: cfg.projectName,
|
|
@@ -911,6 +925,297 @@ body {
|
|
|
911
925
|
font-size: 11px; opacity: 0.7;
|
|
912
926
|
}
|
|
913
927
|
|
|
928
|
+
/* ---- agents overlay (Director sprint slice 6) ---- */
|
|
929
|
+
.header .actions .agents-btn { font-family: inherit; font-size: 13px; padding: 4px 10px; }
|
|
930
|
+
#yf-agents-modal {
|
|
931
|
+
position: fixed; inset: 0;
|
|
932
|
+
background: rgba(26,26,26,0.55); backdrop-filter: blur(4px);
|
|
933
|
+
display: flex; align-items: center; justify-content: center;
|
|
934
|
+
z-index: 320;
|
|
935
|
+
}
|
|
936
|
+
#yf-agents-modal .agents-card {
|
|
937
|
+
width: 100%; max-width: 760px; max-height: 88vh;
|
|
938
|
+
background: var(--bg-1, #1a1d27); border: 1px solid var(--border);
|
|
939
|
+
border-radius: 12px; box-shadow: 0 12px 36px rgba(0,0,0,0.30);
|
|
940
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
941
|
+
}
|
|
942
|
+
#yf-agents-modal .a-header {
|
|
943
|
+
display: flex; align-items: flex-start; justify-content: space-between;
|
|
944
|
+
background: var(--indigo); color: white; padding: 12px 16px;
|
|
945
|
+
flex-shrink: 0; gap: 12px;
|
|
946
|
+
}
|
|
947
|
+
#yf-agents-modal .a-header h2 { font-size: 15px; margin: 0; font-weight: 600; }
|
|
948
|
+
#yf-agents-modal .a-header .a-subtitle {
|
|
949
|
+
font-size: 11px; margin: 4px 0 0 0; opacity: 0.85; font-weight: 400;
|
|
950
|
+
}
|
|
951
|
+
#yf-agents-modal .a-header-status { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
|
952
|
+
#yf-agents-modal .a-tick {
|
|
953
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
954
|
+
background: rgba(255,255,255,0.12); padding: 4px 10px; border-radius: 999px;
|
|
955
|
+
font-size: 11px; color: white;
|
|
956
|
+
}
|
|
957
|
+
#yf-agents-modal .a-tick .a-tick-dot {
|
|
958
|
+
width: 8px; height: 8px; border-radius: 50%; background: #ffb84d;
|
|
959
|
+
}
|
|
960
|
+
#yf-agents-modal .a-tick.live .a-tick-dot { background: #4dd28a; box-shadow: 0 0 6px rgba(77,210,138,0.7); }
|
|
961
|
+
#yf-agents-modal .a-header button {
|
|
962
|
+
background: rgba(255,255,255,0.1); border: 0; color: white;
|
|
963
|
+
padding: 6px 10px; border-radius: 4px; cursor: pointer; font-size: 12px;
|
|
964
|
+
}
|
|
965
|
+
#yf-agents-modal .a-body { flex: 1; overflow: hidden; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
|
|
966
|
+
#yf-agents-modal .a-canvas {
|
|
967
|
+
flex: 1; min-height: 360px; position: relative;
|
|
968
|
+
background: var(--bg-2, #20232f);
|
|
969
|
+
border: 1px solid var(--border-subtle, #2a2e3a);
|
|
970
|
+
border-radius: 8px; overflow: hidden;
|
|
971
|
+
}
|
|
972
|
+
#yf-agents-modal .a-canvas svg { width: 100%; height: 100%; display: block; }
|
|
973
|
+
#yf-agents-modal .a-empty, #yf-agents-modal .a-disabled {
|
|
974
|
+
position: absolute; inset: 0; display: flex; flex-direction: column;
|
|
975
|
+
align-items: center; justify-content: center; gap: 8px; padding: 24px;
|
|
976
|
+
color: var(--ink-muted); text-align: center;
|
|
977
|
+
}
|
|
978
|
+
#yf-agents-modal .a-empty-kanji { font-size: 48px; color: var(--ink-subtle); opacity: 0.5; }
|
|
979
|
+
#yf-agents-modal .a-empty-title { font-size: 15px; font-weight: 600; color: var(--ink); }
|
|
980
|
+
#yf-agents-modal .a-empty-body { font-size: 12px; max-width: 420px; }
|
|
981
|
+
#yf-agents-modal .a-legend { display: flex; gap: 8px; flex-wrap: wrap; padding: 0 4px; }
|
|
982
|
+
#yf-agents-modal .a-chip {
|
|
983
|
+
font-size: 11px; padding: 4px 10px; border-radius: 999px;
|
|
984
|
+
border: 1px solid currentColor; color: var(--ink-muted);
|
|
985
|
+
}
|
|
986
|
+
#yf-agents-modal .a-chip.tier-eco { color: #6c8aa6; }
|
|
987
|
+
#yf-agents-modal .a-chip.tier-managed { color: #7da06d; }
|
|
988
|
+
#yf-agents-modal .a-chip.tier-frontier { color: #c87c4a; }
|
|
989
|
+
/* SVG node + edge styles. */
|
|
990
|
+
.a-node-circle { fill: var(--bg-1, #1a1d27); stroke: var(--ink-muted); stroke-width: 2; transition: stroke 0.18s ease, fill 0.18s ease; }
|
|
991
|
+
.a-node-circle.tier-eco { stroke: #6c8aa6; }
|
|
992
|
+
.a-node-circle.tier-managed { stroke: #7da06d; }
|
|
993
|
+
.a-node-circle.tier-frontier { stroke: #c87c4a; }
|
|
994
|
+
.a-node-circle.status-paused { stroke-dasharray: 4 4; opacity: 0.65; }
|
|
995
|
+
.a-node-circle.status-deleted { opacity: 0.30; }
|
|
996
|
+
.a-node-circle.running { fill: rgba(255,184,77,0.18); stroke-width: 3; animation: a-pulse 1.4s ease-in-out infinite; }
|
|
997
|
+
.a-node-circle.paused-for-approval {
|
|
998
|
+
/* Director closure pendiente 5 -- the agent finished its run
|
|
999
|
+
* but is waiting on a human to resolve the R8 approval modal.
|
|
1000
|
+
* Distinct from .running (active) and from a clean succeeded
|
|
1001
|
+
* idle node: amber stroke + dashed + slow breath. */
|
|
1002
|
+
stroke: #ffb84d; stroke-width: 2.5;
|
|
1003
|
+
stroke-dasharray: 5 3;
|
|
1004
|
+
fill: rgba(255,184,77,0.10);
|
|
1005
|
+
animation: a-breath 2.2s ease-in-out infinite;
|
|
1006
|
+
}
|
|
1007
|
+
.a-node-label {
|
|
1008
|
+
fill: var(--ink, #d4d4d4); font-size: 11px;
|
|
1009
|
+
text-anchor: middle; pointer-events: none;
|
|
1010
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
1011
|
+
}
|
|
1012
|
+
.a-node-tier {
|
|
1013
|
+
fill: var(--ink-muted, #8a8a8a); font-size: 9px;
|
|
1014
|
+
text-anchor: middle; pointer-events: none;
|
|
1015
|
+
}
|
|
1016
|
+
.a-edge {
|
|
1017
|
+
stroke: var(--ink-subtle, #5a5e6a); stroke-width: 1.2;
|
|
1018
|
+
fill: none; opacity: 0.55;
|
|
1019
|
+
transition: stroke 0.18s ease, opacity 0.18s ease, stroke-width 0.18s ease;
|
|
1020
|
+
}
|
|
1021
|
+
.a-edge.active {
|
|
1022
|
+
stroke: #ffb84d; stroke-width: 2; opacity: 1;
|
|
1023
|
+
animation: a-edge-flash 0.9s ease-out;
|
|
1024
|
+
}
|
|
1025
|
+
@keyframes a-pulse {
|
|
1026
|
+
0% { stroke-width: 3; }
|
|
1027
|
+
50% { stroke-width: 5; }
|
|
1028
|
+
100% { stroke-width: 3; }
|
|
1029
|
+
}
|
|
1030
|
+
@keyframes a-breath {
|
|
1031
|
+
0% { opacity: 0.55; }
|
|
1032
|
+
50% { opacity: 1.00; }
|
|
1033
|
+
100% { opacity: 0.55; }
|
|
1034
|
+
}
|
|
1035
|
+
@keyframes a-edge-flash {
|
|
1036
|
+
0% { stroke: #ffe6a8; }
|
|
1037
|
+
100% { stroke: #ffb84d; }
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/* ---- MCP bridges modal (F101 slice 3) ---- */
|
|
1041
|
+
#yf-mcp-modal {
|
|
1042
|
+
position: fixed; inset: 0; background: rgba(8,10,18,0.78);
|
|
1043
|
+
z-index: 99; display: flex; align-items: center; justify-content: center; padding: 24px;
|
|
1044
|
+
-webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
|
|
1045
|
+
}
|
|
1046
|
+
#yf-mcp-modal .mcp-card {
|
|
1047
|
+
background: var(--bg-1, #1a1d27); color: var(--ink, #d4d4d4);
|
|
1048
|
+
border: 1px solid var(--ink-subtle, #5a5e6a); border-radius: 8px;
|
|
1049
|
+
width: 100%; max-width: 720px; max-height: 80vh; display: flex; flex-direction: column;
|
|
1050
|
+
}
|
|
1051
|
+
#yf-mcp-modal .mcp-header {
|
|
1052
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
1053
|
+
padding: 12px 16px; border-bottom: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1054
|
+
}
|
|
1055
|
+
#yf-mcp-modal .mcp-header h2 { font-size: 15px; margin: 0; font-weight: 600; }
|
|
1056
|
+
#yf-mcp-modal .mcp-header .mcp-subtitle { font-size: 11px; color: var(--ink-muted, #8a8a8a); margin: 2px 0 0 0; }
|
|
1057
|
+
#yf-mcp-modal .mcp-header button {
|
|
1058
|
+
background: transparent; color: inherit; border: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1059
|
+
border-radius: 4px; padding: 4px 10px; font-family: inherit; font-size: 13px;
|
|
1060
|
+
}
|
|
1061
|
+
#yf-mcp-modal .mcp-body { flex: 1; overflow: auto; padding: 12px 16px; }
|
|
1062
|
+
#yf-mcp-modal .mcp-empty {
|
|
1063
|
+
text-align: center; padding: 28px 12px; color: var(--ink-muted, #8a8a8a); font-size: 13px;
|
|
1064
|
+
}
|
|
1065
|
+
#yf-mcp-modal .mcp-bridge {
|
|
1066
|
+
display: flex; align-items: center; gap: 10px;
|
|
1067
|
+
padding: 10px 12px; margin-bottom: 8px;
|
|
1068
|
+
background: var(--bg-2, #232631); border: 1px solid var(--ink-subtle, #5a5e6a); border-radius: 6px;
|
|
1069
|
+
}
|
|
1070
|
+
#yf-mcp-modal .mcp-bridge .mcp-meta { flex: 1; min-width: 0; }
|
|
1071
|
+
#yf-mcp-modal .mcp-bridge .mcp-name {
|
|
1072
|
+
font-weight: 600; font-size: 13px;
|
|
1073
|
+
}
|
|
1074
|
+
#yf-mcp-modal .mcp-bridge .mcp-endpoint {
|
|
1075
|
+
font-size: 11px; color: var(--ink-muted, #8a8a8a);
|
|
1076
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
1077
|
+
}
|
|
1078
|
+
#yf-mcp-modal .mcp-bridge .mcp-source-chip {
|
|
1079
|
+
font-size: 10px; padding: 2px 6px; border-radius: 3px;
|
|
1080
|
+
background: rgba(108,138,166,0.18); color: #9bb4cc; text-transform: uppercase; letter-spacing: 0.4px;
|
|
1081
|
+
}
|
|
1082
|
+
#yf-mcp-modal .mcp-bridge .mcp-protocol-chip {
|
|
1083
|
+
font-size: 10px; padding: 2px 6px; border-radius: 3px;
|
|
1084
|
+
background: rgba(125,160,109,0.18); color: #a6c592; text-transform: uppercase; letter-spacing: 0.4px;
|
|
1085
|
+
}
|
|
1086
|
+
#yf-mcp-modal .mcp-bridge.disabled { opacity: 0.55; }
|
|
1087
|
+
#yf-mcp-modal .mcp-toggle {
|
|
1088
|
+
background: transparent; color: inherit; border: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1089
|
+
border-radius: 4px; padding: 4px 10px; font-family: inherit; font-size: 12px; cursor: pointer;
|
|
1090
|
+
}
|
|
1091
|
+
#yf-mcp-modal .mcp-toggle:hover { background: rgba(255,255,255,0.05); }
|
|
1092
|
+
#yf-mcp-modal .mcp-tools-btn {
|
|
1093
|
+
background: transparent; color: inherit; border: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1094
|
+
border-radius: 4px; padding: 4px 10px; font-family: inherit; font-size: 12px; cursor: pointer;
|
|
1095
|
+
}
|
|
1096
|
+
#yf-mcp-modal .mcp-tools-list {
|
|
1097
|
+
margin-top: 6px; padding: 8px 12px; background: var(--bg-0, #0f1118); border-radius: 4px;
|
|
1098
|
+
font-size: 12px; line-height: 1.5; max-height: 240px; overflow: auto;
|
|
1099
|
+
}
|
|
1100
|
+
#yf-mcp-modal .mcp-tool {
|
|
1101
|
+
padding: 4px 0; border-bottom: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1102
|
+
}
|
|
1103
|
+
#yf-mcp-modal .mcp-tool:last-child { border-bottom: 0; }
|
|
1104
|
+
#yf-mcp-modal .mcp-tool .mcp-tool-id { font-weight: 600; color: var(--ink, #d4d4d4); }
|
|
1105
|
+
#yf-mcp-modal .mcp-tool .mcp-tool-desc { color: var(--ink-muted, #8a8a8a); }
|
|
1106
|
+
#yf-mcp-modal .mcp-tools-error { color: #ff8b7a; padding: 6px 0; }
|
|
1107
|
+
#yf-mcp-modal .mcp-refresh {
|
|
1108
|
+
margin-bottom: 8px;
|
|
1109
|
+
background: transparent; color: inherit; border: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1110
|
+
border-radius: 4px; padding: 4px 10px; font-family: inherit; font-size: 12px; cursor: pointer;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/* ---- Activity overlay (visibilidad sprint -- alpha.59z.153) ---- */
|
|
1114
|
+
#yf-activity-modal {
|
|
1115
|
+
position: fixed; inset: 0; background: rgba(8,10,18,0.78);
|
|
1116
|
+
z-index: 99; display: flex; align-items: center; justify-content: center; padding: 24px;
|
|
1117
|
+
-webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
|
|
1118
|
+
}
|
|
1119
|
+
#yf-activity-modal .act-card {
|
|
1120
|
+
background: var(--bg-1, #1a1d27); color: var(--ink, #d4d4d4);
|
|
1121
|
+
border: 1px solid var(--ink-subtle, #5a5e6a); border-radius: 8px;
|
|
1122
|
+
width: 100%; max-width: 920px; max-height: 84vh; display: flex; flex-direction: column;
|
|
1123
|
+
}
|
|
1124
|
+
#yf-activity-modal .act-header {
|
|
1125
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
1126
|
+
padding: 12px 16px; border-bottom: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1127
|
+
}
|
|
1128
|
+
#yf-activity-modal .act-header h2 { font-size: 15px; margin: 0; font-weight: 600; }
|
|
1129
|
+
#yf-activity-modal .act-header .act-subtitle { font-size: 11px; color: var(--ink-muted, #8a8a8a); margin: 2px 0 0 0; }
|
|
1130
|
+
#yf-activity-modal .act-header button {
|
|
1131
|
+
background: transparent; color: inherit; border: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1132
|
+
border-radius: 4px; padding: 4px 10px; font-family: inherit; font-size: 13px; cursor: pointer;
|
|
1133
|
+
}
|
|
1134
|
+
#yf-activity-modal .act-tabs {
|
|
1135
|
+
display: flex; gap: 4px; padding: 8px 16px 0;
|
|
1136
|
+
border-bottom: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1137
|
+
}
|
|
1138
|
+
#yf-activity-modal .act-tab {
|
|
1139
|
+
background: transparent; color: var(--ink-muted, #8a8a8a);
|
|
1140
|
+
border: 1px solid transparent; border-bottom: 0;
|
|
1141
|
+
border-radius: 4px 4px 0 0; padding: 6px 12px;
|
|
1142
|
+
font-family: inherit; font-size: 12px; cursor: pointer;
|
|
1143
|
+
}
|
|
1144
|
+
#yf-activity-modal .act-tab.active {
|
|
1145
|
+
color: var(--ink, #d4d4d4); background: var(--bg-2, #232631);
|
|
1146
|
+
border-color: var(--ink-subtle, #5a5e6a);
|
|
1147
|
+
}
|
|
1148
|
+
#yf-activity-modal .act-body { flex: 1; overflow: auto; padding: 12px 16px; }
|
|
1149
|
+
#yf-activity-modal .act-empty { text-align: center; padding: 24px 12px; color: var(--ink-muted, #8a8a8a); font-size: 13px; }
|
|
1150
|
+
#yf-activity-modal .act-line {
|
|
1151
|
+
display: grid;
|
|
1152
|
+
grid-template-columns: 90px 130px 1fr; gap: 8px;
|
|
1153
|
+
padding: 4px 0; border-bottom: 1px dashed rgba(90,94,106,0.25);
|
|
1154
|
+
font-size: 12px; font-family: ui-monospace, Menlo, Consolas, monospace;
|
|
1155
|
+
}
|
|
1156
|
+
#yf-activity-modal .act-line:last-child { border-bottom: 0; }
|
|
1157
|
+
#yf-activity-modal .act-line .act-ts { color: var(--ink-muted, #8a8a8a); }
|
|
1158
|
+
#yf-activity-modal .act-line .act-kind {
|
|
1159
|
+
color: var(--ink, #d4d4d4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
1160
|
+
}
|
|
1161
|
+
#yf-activity-modal .act-line.kind-dispatch .act-kind { color: #9bb4cc; }
|
|
1162
|
+
#yf-activity-modal .act-line.kind-agent .act-kind { color: #c87c4a; }
|
|
1163
|
+
#yf-activity-modal .act-line.kind-bridge .act-kind { color: #a6c592; }
|
|
1164
|
+
#yf-activity-modal .act-line.kind-approval .act-kind { color: #ffb84d; }
|
|
1165
|
+
#yf-activity-modal .act-line .act-text { word-break: break-word; }
|
|
1166
|
+
#yf-activity-modal .act-controls {
|
|
1167
|
+
display: flex; gap: 8px; padding: 8px 16px;
|
|
1168
|
+
border-top: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1169
|
+
font-size: 12px; color: var(--ink-muted, #8a8a8a); align-items: center;
|
|
1170
|
+
}
|
|
1171
|
+
#yf-activity-modal .act-controls input[type="checkbox"] { accent-color: #c87c4a; }
|
|
1172
|
+
#yf-activity-modal .act-controls button {
|
|
1173
|
+
background: transparent; color: inherit; border: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1174
|
+
border-radius: 4px; padding: 4px 10px; cursor: pointer;
|
|
1175
|
+
}
|
|
1176
|
+
/* Bridge tab */
|
|
1177
|
+
#yf-activity-modal .act-pair {
|
|
1178
|
+
display: flex; align-items: center; gap: 10px;
|
|
1179
|
+
padding: 8px 12px; margin-bottom: 6px;
|
|
1180
|
+
background: var(--bg-2, #232631); border: 1px solid var(--ink-subtle, #5a5e6a); border-radius: 5px;
|
|
1181
|
+
cursor: pointer;
|
|
1182
|
+
}
|
|
1183
|
+
#yf-activity-modal .act-pair:hover { border-color: #c87c4a; }
|
|
1184
|
+
#yf-activity-modal .act-pair .act-pair-id { flex: 1; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; }
|
|
1185
|
+
#yf-activity-modal .act-pair .act-pair-count {
|
|
1186
|
+
font-size: 11px; padding: 2px 6px; border-radius: 3px;
|
|
1187
|
+
background: rgba(108,138,166,0.2); color: #9bb4cc;
|
|
1188
|
+
}
|
|
1189
|
+
#yf-activity-modal .act-bridge-msg {
|
|
1190
|
+
padding: 6px 10px; margin-bottom: 5px;
|
|
1191
|
+
background: var(--bg-0, #0f1118); border-radius: 4px;
|
|
1192
|
+
font-size: 12px;
|
|
1193
|
+
}
|
|
1194
|
+
#yf-activity-modal .act-bridge-msg .act-bridge-head {
|
|
1195
|
+
font-family: ui-monospace, Menlo, Consolas, monospace; color: var(--ink-muted, #8a8a8a);
|
|
1196
|
+
}
|
|
1197
|
+
#yf-activity-modal .act-bridge-msg .act-bridge-payload { color: var(--ink, #d4d4d4); margin-top: 2px; word-break: break-word; }
|
|
1198
|
+
/* Chat tab */
|
|
1199
|
+
#yf-activity-modal .act-chat-row {
|
|
1200
|
+
display: flex; gap: 8px; margin-bottom: 10px;
|
|
1201
|
+
}
|
|
1202
|
+
#yf-activity-modal .act-chat-row select,
|
|
1203
|
+
#yf-activity-modal .act-chat-row input[type="text"] {
|
|
1204
|
+
background: var(--bg-2, #232631); color: var(--ink, #d4d4d4);
|
|
1205
|
+
border: 1px solid var(--ink-subtle, #5a5e6a); border-radius: 4px;
|
|
1206
|
+
padding: 6px 10px; font-family: inherit; font-size: 13px;
|
|
1207
|
+
}
|
|
1208
|
+
#yf-activity-modal .act-chat-row input[type="text"] { flex: 1; }
|
|
1209
|
+
#yf-activity-modal .act-chat-row button {
|
|
1210
|
+
background: transparent; color: inherit; border: 1px solid var(--ink-subtle, #5a5e6a);
|
|
1211
|
+
border-radius: 4px; padding: 6px 14px; cursor: pointer;
|
|
1212
|
+
}
|
|
1213
|
+
#yf-activity-modal .act-chat-result {
|
|
1214
|
+
padding: 10px; background: var(--bg-0, #0f1118); border-radius: 4px;
|
|
1215
|
+
font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px;
|
|
1216
|
+
white-space: pre-wrap; word-break: break-word;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
914
1219
|
/* ---- hidden helper ---- */
|
|
915
1220
|
.hidden { display: none !important; }
|
|
916
1221
|
</style>
|
|
@@ -1000,6 +1305,132 @@ body {
|
|
|
1000
1305
|
</div>
|
|
1001
1306
|
</div>
|
|
1002
1307
|
|
|
1308
|
+
<!-- Director sprint slice 6 -- agent graph overlay.
|
|
1309
|
+
Topology: Director (centre) + 4 sub-agents (radial) +
|
|
1310
|
+
auxiliary agents (lower row). Live updates via SSE. -->
|
|
1311
|
+
<div id="yf-agents-modal" data-nac-id="yujin.agents.modal" class="hidden" role="dialog" aria-modal="true" aria-labelledby="agents-title">
|
|
1312
|
+
<div class="agents-card">
|
|
1313
|
+
<div class="a-header">
|
|
1314
|
+
<div>
|
|
1315
|
+
<h2 id="agents-title" data-nac-id="yujin.agents.title">${escapeHtml(tr.agentsTitle)}</h2>
|
|
1316
|
+
<p class="a-subtitle">${escapeHtml(tr.agentsSubtitle)}</p>
|
|
1317
|
+
</div>
|
|
1318
|
+
<div class="a-header-status">
|
|
1319
|
+
<span class="a-tick" data-agents-tick aria-live="polite" title="${escapeHtml(tr.agentsTickLive)}"><span class="a-tick-dot"></span><span class="a-tick-label">${escapeHtml(tr.agentsTickLost)}</span></span>
|
|
1320
|
+
<button data-agents-close aria-label="${escapeHtml(tr.closeButton)}" title="${escapeHtml(tr.closeButton)}">${escapeHtml(tr.closeButton)}</button>
|
|
1321
|
+
</div>
|
|
1322
|
+
</div>
|
|
1323
|
+
<div class="a-body">
|
|
1324
|
+
<div class="a-canvas" data-agents-canvas>
|
|
1325
|
+
<svg id="yf-agents-svg" viewBox="0 0 640 440" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${escapeHtml(tr.agentsTitle)}">
|
|
1326
|
+
<defs>
|
|
1327
|
+
<marker id="a-arrow" viewBox="0 -5 10 10" refX="10" refY="0" markerWidth="6" markerHeight="6" orient="auto">
|
|
1328
|
+
<path d="M0,-4 L8,0 L0,4 Z" fill="currentColor"></path>
|
|
1329
|
+
</marker>
|
|
1330
|
+
</defs>
|
|
1331
|
+
<g data-agents-edges></g>
|
|
1332
|
+
<g data-agents-nodes></g>
|
|
1333
|
+
</svg>
|
|
1334
|
+
<div class="a-empty hidden" data-agents-empty>
|
|
1335
|
+
<div class="a-empty-kanji">空</div>
|
|
1336
|
+
<div class="a-empty-title">${escapeHtml(tr.agentsEmptyTitle)}</div>
|
|
1337
|
+
<div class="a-empty-body">${escapeHtml(tr.agentsEmptyBody)}</div>
|
|
1338
|
+
</div>
|
|
1339
|
+
<div class="a-disabled hidden" data-agents-disabled>
|
|
1340
|
+
${escapeHtml(tr.agentsStatusDisabled)}
|
|
1341
|
+
</div>
|
|
1342
|
+
</div>
|
|
1343
|
+
<div class="a-legend">
|
|
1344
|
+
<span class="a-chip tier-eco">${escapeHtml(tr.agentsLegendEco)}</span>
|
|
1345
|
+
<span class="a-chip tier-managed">${escapeHtml(tr.agentsLegendManaged)}</span>
|
|
1346
|
+
<span class="a-chip tier-frontier">${escapeHtml(tr.agentsLegendFrontier)}</span>
|
|
1347
|
+
</div>
|
|
1348
|
+
</div>
|
|
1349
|
+
</div>
|
|
1350
|
+
</div>
|
|
1351
|
+
|
|
1352
|
+
<!-- F107 accessibility -- two ARIA live regions that the
|
|
1353
|
+
a11y.announce verb writes into. The polite region is the
|
|
1354
|
+
default; assertive is reserved for errors / urgent. Hidden
|
|
1355
|
+
visually but exposed to screen readers + voice loop. -->
|
|
1356
|
+
<div id="yf-a11y-live-polite" aria-live="polite" aria-atomic="true"
|
|
1357
|
+
style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;"></div>
|
|
1358
|
+
<div id="yf-a11y-live-assertive" aria-live="assertive" aria-atomic="true"
|
|
1359
|
+
style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;"></div>
|
|
1360
|
+
|
|
1361
|
+
<!-- F101 slice 3 -- MCP bridges modal. Lists discovered bridges +
|
|
1362
|
+
per-bridge enable toggle + tool catalog inline panel. -->
|
|
1363
|
+
<div id="yf-mcp-modal" data-nac-id="yujin.mcp.modal" class="hidden" role="dialog" aria-modal="true" aria-labelledby="mcp-title">
|
|
1364
|
+
<div class="mcp-card">
|
|
1365
|
+
<div class="mcp-header">
|
|
1366
|
+
<div>
|
|
1367
|
+
<h2 id="mcp-title">MCP Bridges</h2>
|
|
1368
|
+
<p class="mcp-subtitle">F101 -- Forge as transparent MCP aggregator</p>
|
|
1369
|
+
</div>
|
|
1370
|
+
<button data-mcp-close aria-label="${escapeHtml(tr.closeButton)}" title="${escapeHtml(tr.closeButton)}">${escapeHtml(tr.closeButton)}</button>
|
|
1371
|
+
</div>
|
|
1372
|
+
<div class="mcp-body">
|
|
1373
|
+
<button class="mcp-refresh" data-mcp-refresh title="Refrescar discovery">Refrescar</button>
|
|
1374
|
+
<div data-mcp-content>
|
|
1375
|
+
<div class="mcp-empty">Cargando...</div>
|
|
1376
|
+
</div>
|
|
1377
|
+
</div>
|
|
1378
|
+
</div>
|
|
1379
|
+
</div>
|
|
1380
|
+
|
|
1381
|
+
<!-- Activity overlay -- 3 tabs: feed live timestamped / bridge
|
|
1382
|
+
inspector / chat-per-agent. Alpha.59z.153 antidote anti-ansiedad. -->
|
|
1383
|
+
<div id="yf-activity-modal" data-nac-id="yujin.activity.modal" class="hidden" role="dialog" aria-modal="true" aria-labelledby="act-title">
|
|
1384
|
+
<div class="act-card">
|
|
1385
|
+
<div class="act-header">
|
|
1386
|
+
<div>
|
|
1387
|
+
<h2 id="act-title">Agent Activity</h2>
|
|
1388
|
+
<p class="act-subtitle">Feed timestamped + bridge inspector + chat-per-agent</p>
|
|
1389
|
+
</div>
|
|
1390
|
+
<div style="display:flex;align-items:center;gap:10px;">
|
|
1391
|
+
<select data-act-project-filter aria-label="Filter by project"
|
|
1392
|
+
style="background:var(--bg-2,#232631);color:var(--ink,#d4d4d4);border:1px solid var(--ink-subtle,#5a5e6a);border-radius:4px;padding:4px 8px;font-family:inherit;font-size:12px;">
|
|
1393
|
+
<option value="">All projects</option>
|
|
1394
|
+
</select>
|
|
1395
|
+
<span data-act-concurrency aria-label="Concurrent runs"
|
|
1396
|
+
title="Concurrent runs: global / per-project caps"
|
|
1397
|
+
style="font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;color:var(--ink-muted,#8a8a8a);white-space:nowrap;">-/-</span>
|
|
1398
|
+
<button data-activity-close aria-label="${escapeHtml(tr.closeButton)}" title="${escapeHtml(tr.closeButton)}">${escapeHtml(tr.closeButton)}</button>
|
|
1399
|
+
</div>
|
|
1400
|
+
</div>
|
|
1401
|
+
<div class="act-tabs" role="tablist">
|
|
1402
|
+
<button class="act-tab active" data-act-tab="feed" role="tab">Feed</button>
|
|
1403
|
+
<button class="act-tab" data-act-tab="bridge" role="tab">Bridge</button>
|
|
1404
|
+
<button class="act-tab" data-act-tab="chat" role="tab">Chat</button>
|
|
1405
|
+
</div>
|
|
1406
|
+
<div class="act-body">
|
|
1407
|
+
<div data-act-panel="feed">
|
|
1408
|
+
<div class="act-empty">Cargando feed...</div>
|
|
1409
|
+
</div>
|
|
1410
|
+
<div data-act-panel="bridge" class="hidden">
|
|
1411
|
+
<div class="act-empty">Cargando bridge...</div>
|
|
1412
|
+
</div>
|
|
1413
|
+
<div data-act-panel="chat" class="hidden">
|
|
1414
|
+
<div class="act-chat-row">
|
|
1415
|
+
<select data-act-chat-slug aria-label="Agent slug"></select>
|
|
1416
|
+
<input type="text" data-act-chat-text placeholder="Mensaje para el agente..." style="min-width:200px;" />
|
|
1417
|
+
<label style="display:flex;align-items:center;gap:4px;font-size:11px;color:var(--ink-muted,#8a8a8a);">
|
|
1418
|
+
<input type="checkbox" data-act-chat-dispatch checked /> dispatch
|
|
1419
|
+
</label>
|
|
1420
|
+
<button data-act-chat-send>Enviar</button>
|
|
1421
|
+
</div>
|
|
1422
|
+
<p style="font-size:11px;color:var(--ink-muted,#8a8a8a);margin:0 0 8px 0;">El mensaje se enrutara al proyecto seleccionado arriba en el filtro. Default: "self".</p>
|
|
1423
|
+
<div class="act-chat-result" data-act-chat-result>Selecciona un agente + escribe un mensaje + click Enviar.</div>
|
|
1424
|
+
</div>
|
|
1425
|
+
</div>
|
|
1426
|
+
<div class="act-controls">
|
|
1427
|
+
<label><input type="checkbox" data-act-autorefresh checked /> auto-refresh 3s</label>
|
|
1428
|
+
<button data-act-refresh>Refrescar ahora</button>
|
|
1429
|
+
<span data-act-last-update style="margin-left:auto;font-family:ui-monospace,Menlo,Consolas,monospace;"></span>
|
|
1430
|
+
</div>
|
|
1431
|
+
</div>
|
|
1432
|
+
</div>
|
|
1433
|
+
|
|
1003
1434
|
<div id="yf-full" data-nac-id="yujin.panel.full" class="hidden">
|
|
1004
1435
|
<div class="panel">
|
|
1005
1436
|
<div class="header">
|
|
@@ -1008,6 +1439,27 @@ body {
|
|
|
1008
1439
|
<div class="t"><span class="name">Yujin Forge</span><span class="proj" id="proj-full" data-nac-id="yujin.panel.project-full"></span></div>
|
|
1009
1440
|
</div>
|
|
1010
1441
|
<div class="actions">
|
|
1442
|
+
<button class="agents-btn"
|
|
1443
|
+
data-agents-open
|
|
1444
|
+
data-nac-id="yujin.panel.agents"
|
|
1445
|
+
data-nac-role="action"
|
|
1446
|
+
data-nac-action="open_agents"
|
|
1447
|
+
aria-label="${escapeHtml(tr.agentsButton)}"
|
|
1448
|
+
title="${escapeHtml(tr.agentsButton)}">${escapeHtml(tr.agentsButton)}</button>
|
|
1449
|
+
<button class="agents-btn"
|
|
1450
|
+
data-mcp-open
|
|
1451
|
+
data-nac-id="yujin.panel.mcp"
|
|
1452
|
+
data-nac-role="action"
|
|
1453
|
+
data-nac-action="open_mcp"
|
|
1454
|
+
aria-label="MCP"
|
|
1455
|
+
title="MCP Bridges (F101)">MCP</button>
|
|
1456
|
+
<button class="agents-btn"
|
|
1457
|
+
data-activity-open
|
|
1458
|
+
data-nac-id="yujin.panel.activity"
|
|
1459
|
+
data-nac-role="action"
|
|
1460
|
+
data-nac-action="open_activity"
|
|
1461
|
+
aria-label="Activity"
|
|
1462
|
+
title="Agent activity + bridge inspector + chat-per-agent">Activity</button>
|
|
1011
1463
|
<button class="settings-btn"
|
|
1012
1464
|
data-settings-toggle
|
|
1013
1465
|
data-nac-id="yujin.panel.settings"
|
|
@@ -3678,6 +4130,31 @@ async function send(text, opts) {
|
|
|
3678
4130
|
/* Fire-and-forget; do not block the intercept loop. */
|
|
3679
4131
|
try { playTtsForText(safeText).catch(() => undefined); } catch (e) { /* ignore */ }
|
|
3680
4132
|
}
|
|
4133
|
+
} else if (verbId === 'yujin.a11y.announce') {
|
|
4134
|
+
/* F107 -- push text into the live region. Screen readers
|
|
4135
|
+
* pick it up; politeness controls the assertive/polite
|
|
4136
|
+
* mode. When speak=true we also queue TTS so the voice
|
|
4137
|
+
* loop announces it audibly. */
|
|
4138
|
+
a11yAnnounce(String(a.text || ''), {
|
|
4139
|
+
politeness: String(a.politeness || 'polite').toLowerCase() === 'assertive' ? 'assertive' : 'polite',
|
|
4140
|
+
speak: a.speak === true,
|
|
4141
|
+
locale: a.locale ? String(a.locale) : undefined,
|
|
4142
|
+
});
|
|
4143
|
+
} else if (verbId === 'yujin.a11y.focus') {
|
|
4144
|
+
/* F107 -- move keyboard focus to the data-nac-slug
|
|
4145
|
+
* target, optionally scroll into view + announce. */
|
|
4146
|
+
a11yFocus(String(a.slug || ''), {
|
|
4147
|
+
scroll: a.scroll !== false,
|
|
4148
|
+
announce: a.announce !== false,
|
|
4149
|
+
});
|
|
4150
|
+
} else if (verbId === 'yujin.a11y.label') {
|
|
4151
|
+
/* F107 -- set aria-label / aria-describedby on the
|
|
4152
|
+
* NAC-3 element. Optional shortcut hint + role. */
|
|
4153
|
+
a11yLabel(String(a.slug || ''), {
|
|
4154
|
+
description: String(a.description || ''),
|
|
4155
|
+
shortcut: a.shortcut ? String(a.shortcut) : undefined,
|
|
4156
|
+
role: a.role ? String(a.role) : undefined,
|
|
4157
|
+
});
|
|
3681
4158
|
}
|
|
3682
4159
|
}
|
|
3683
4160
|
}
|
|
@@ -5875,7 +6352,888 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
5875
6352
|
document.addEventListener('keydown', (e) => {
|
|
5876
6353
|
if (e.key === 'Escape' && !$('#yf-vault-modal').classList.contains('hidden')) vaultClose();
|
|
5877
6354
|
});
|
|
6355
|
+
|
|
6356
|
+
/* ---- Director sprint slice 6 -- agent graph overlay ---- */
|
|
6357
|
+
agentsWireUp();
|
|
6358
|
+
mcpWireUp();
|
|
6359
|
+
activityWireUp();
|
|
5878
6360
|
});
|
|
6361
|
+
|
|
6362
|
+
/* Agent graph overlay state lives in a module-scoped closure so
|
|
6363
|
+
* SSE subscribes once + drops the connection on close. The wiring
|
|
6364
|
+
* is idempotent (multiple opens/closes are safe). */
|
|
6365
|
+
const yfAgents = {
|
|
6366
|
+
modal: null,
|
|
6367
|
+
svg: null,
|
|
6368
|
+
edgesG: null,
|
|
6369
|
+
nodesG: null,
|
|
6370
|
+
tickEl: null,
|
|
6371
|
+
emptyEl: null,
|
|
6372
|
+
disabledEl: null,
|
|
6373
|
+
evtSource: null,
|
|
6374
|
+
byslug: new Map(),
|
|
6375
|
+
edgeBySlugPair: new Map(),
|
|
6376
|
+
};
|
|
6377
|
+
|
|
6378
|
+
function agentsWireUp() {
|
|
6379
|
+
yfAgents.modal = document.getElementById('yf-agents-modal');
|
|
6380
|
+
yfAgents.svg = document.getElementById('yf-agents-svg');
|
|
6381
|
+
if (!yfAgents.modal || !yfAgents.svg) return;
|
|
6382
|
+
yfAgents.edgesG = yfAgents.svg.querySelector('[data-agents-edges]');
|
|
6383
|
+
yfAgents.nodesG = yfAgents.svg.querySelector('[data-agents-nodes]');
|
|
6384
|
+
yfAgents.tickEl = yfAgents.modal.querySelector('[data-agents-tick]');
|
|
6385
|
+
yfAgents.emptyEl = yfAgents.modal.querySelector('[data-agents-empty]');
|
|
6386
|
+
yfAgents.disabledEl = yfAgents.modal.querySelector('[data-agents-disabled]');
|
|
6387
|
+
|
|
6388
|
+
document.querySelectorAll('[data-agents-open]').forEach((btn) => {
|
|
6389
|
+
btn.addEventListener('click', agentsOpen);
|
|
6390
|
+
});
|
|
6391
|
+
document.querySelectorAll('[data-agents-close]').forEach((btn) => {
|
|
6392
|
+
btn.addEventListener('click', agentsClose);
|
|
6393
|
+
});
|
|
6394
|
+
document.addEventListener('keydown', (e) => {
|
|
6395
|
+
if (e.key === 'Escape' && yfAgents.modal && !yfAgents.modal.classList.contains('hidden')) {
|
|
6396
|
+
agentsClose();
|
|
6397
|
+
}
|
|
6398
|
+
});
|
|
6399
|
+
}
|
|
6400
|
+
|
|
6401
|
+
async function agentsOpen() {
|
|
6402
|
+
if (!yfAgents.modal) return;
|
|
6403
|
+
yfAgents.modal.classList.remove('hidden');
|
|
6404
|
+
try {
|
|
6405
|
+
const r = await fetch('/api/agent/state');
|
|
6406
|
+
if (r.status === 503) {
|
|
6407
|
+
agentsShowDisabled();
|
|
6408
|
+
return;
|
|
6409
|
+
}
|
|
6410
|
+
if (!r.ok) throw new Error('state ' + r.status);
|
|
6411
|
+
const body = await r.json();
|
|
6412
|
+
const agents = Array.isArray(body.agents) ? body.agents : [];
|
|
6413
|
+
if (agents.length === 0) {
|
|
6414
|
+
agentsShowEmpty();
|
|
6415
|
+
return;
|
|
6416
|
+
}
|
|
6417
|
+
agentsShowGraph();
|
|
6418
|
+
agentsRenderGraph(agents);
|
|
6419
|
+
agentsSubscribe();
|
|
6420
|
+
} catch (err) {
|
|
6421
|
+
agentsShowDisabled();
|
|
6422
|
+
}
|
|
6423
|
+
}
|
|
6424
|
+
|
|
6425
|
+
function agentsClose() {
|
|
6426
|
+
if (!yfAgents.modal) return;
|
|
6427
|
+
yfAgents.modal.classList.add('hidden');
|
|
6428
|
+
if (yfAgents.evtSource) {
|
|
6429
|
+
try { yfAgents.evtSource.close(); } catch { /* noop */ }
|
|
6430
|
+
yfAgents.evtSource = null;
|
|
6431
|
+
}
|
|
6432
|
+
agentsSetTick(false);
|
|
6433
|
+
}
|
|
6434
|
+
|
|
6435
|
+
function agentsShowDisabled() {
|
|
6436
|
+
yfAgents.disabledEl.classList.remove('hidden');
|
|
6437
|
+
yfAgents.emptyEl.classList.add('hidden');
|
|
6438
|
+
yfAgents.edgesG.innerHTML = '';
|
|
6439
|
+
yfAgents.nodesG.innerHTML = '';
|
|
6440
|
+
}
|
|
6441
|
+
|
|
6442
|
+
function agentsShowEmpty() {
|
|
6443
|
+
yfAgents.emptyEl.classList.remove('hidden');
|
|
6444
|
+
yfAgents.disabledEl.classList.add('hidden');
|
|
6445
|
+
yfAgents.edgesG.innerHTML = '';
|
|
6446
|
+
yfAgents.nodesG.innerHTML = '';
|
|
6447
|
+
}
|
|
6448
|
+
|
|
6449
|
+
function agentsShowGraph() {
|
|
6450
|
+
yfAgents.emptyEl.classList.add('hidden');
|
|
6451
|
+
yfAgents.disabledEl.classList.add('hidden');
|
|
6452
|
+
}
|
|
6453
|
+
|
|
6454
|
+
function agentsSetTick(live) {
|
|
6455
|
+
if (!yfAgents.tickEl) return;
|
|
6456
|
+
yfAgents.tickEl.classList.toggle('live', !!live);
|
|
6457
|
+
const labelEl = yfAgents.tickEl.querySelector('.a-tick-label');
|
|
6458
|
+
if (labelEl) labelEl.textContent = live ? yfPanelCfg().i18n.agentsTickLive : yfPanelCfg().i18n.agentsTickLost;
|
|
6459
|
+
}
|
|
6460
|
+
|
|
6461
|
+
function yfPanelCfg() {
|
|
6462
|
+
/* runtimeConfig is injected by the server as a const named CONFIG
|
|
6463
|
+
* at the top of this inline script. The try/catch covers the
|
|
6464
|
+
* edge case of the script firing before CONFIG resolves. */
|
|
6465
|
+
try { return CONFIG; } catch { return { i18n: {} }; }
|
|
6466
|
+
}
|
|
6467
|
+
|
|
6468
|
+
/** Pick the layout: agent with empty can_be_called_by AND highest
|
|
6469
|
+
* can_call fan-out is the hub. Others ring around. Auxiliary
|
|
6470
|
+
* agents (web-search etc.) go in a second ring. */
|
|
6471
|
+
function agentsLayout(agents) {
|
|
6472
|
+
let hub = null;
|
|
6473
|
+
let bestFan = -1;
|
|
6474
|
+
for (const a of agents) {
|
|
6475
|
+
if ((a.can_be_called_by || []).length > 0) continue;
|
|
6476
|
+
const fan = (a.can_call || []).length;
|
|
6477
|
+
if (fan > bestFan) { bestFan = fan; hub = a; }
|
|
6478
|
+
}
|
|
6479
|
+
if (!hub) hub = agents[0];
|
|
6480
|
+
const cx = 320, cy = 220;
|
|
6481
|
+
const ring = agents.filter((a) => a.slug !== hub.slug);
|
|
6482
|
+
/* Two-tier layout: first-class sub-agents (called BY the hub)
|
|
6483
|
+
* vs auxiliary (everyone else). */
|
|
6484
|
+
const firstTier = ring.filter((a) => (hub.can_call || []).includes(a.slug) || (hub.can_call || []).includes('*'));
|
|
6485
|
+
const secondTier = ring.filter((a) => !firstTier.includes(a));
|
|
6486
|
+
const positions = new Map();
|
|
6487
|
+
positions.set(hub.slug, { x: cx, y: cy });
|
|
6488
|
+
const r1 = 140;
|
|
6489
|
+
for (let i = 0; i < firstTier.length; i++) {
|
|
6490
|
+
const angle = (i / firstTier.length) * Math.PI * 2 - Math.PI / 2;
|
|
6491
|
+
positions.set(firstTier[i].slug, {
|
|
6492
|
+
x: cx + Math.cos(angle) * r1,
|
|
6493
|
+
y: cy + Math.sin(angle) * r1,
|
|
6494
|
+
});
|
|
6495
|
+
}
|
|
6496
|
+
const r2 = 195;
|
|
6497
|
+
for (let i = 0; i < secondTier.length; i++) {
|
|
6498
|
+
/* Offset by half-step so they sit between first-tier slots. */
|
|
6499
|
+
const angle = ((i + 0.5) / Math.max(1, secondTier.length)) * Math.PI * 2 - Math.PI / 2;
|
|
6500
|
+
positions.set(secondTier[i].slug, {
|
|
6501
|
+
x: cx + Math.cos(angle) * r2,
|
|
6502
|
+
y: cy + Math.sin(angle) * r2,
|
|
6503
|
+
});
|
|
6504
|
+
}
|
|
6505
|
+
return { hub, positions };
|
|
6506
|
+
}
|
|
6507
|
+
|
|
6508
|
+
function agentsRenderGraph(agents) {
|
|
6509
|
+
yfAgents.byslug.clear();
|
|
6510
|
+
yfAgents.edgeBySlugPair.clear();
|
|
6511
|
+
for (const a of agents) yfAgents.byslug.set(a.slug, a);
|
|
6512
|
+
const { hub, positions } = agentsLayout(agents);
|
|
6513
|
+
/* Edges first so they sit under the nodes. */
|
|
6514
|
+
yfAgents.edgesG.innerHTML = '';
|
|
6515
|
+
for (const a of agents) {
|
|
6516
|
+
const calls = a.can_call || [];
|
|
6517
|
+
for (const target of calls) {
|
|
6518
|
+
if (target === '*') continue;
|
|
6519
|
+
if (!positions.has(target)) continue;
|
|
6520
|
+
const from = positions.get(a.slug);
|
|
6521
|
+
const to = positions.get(target);
|
|
6522
|
+
if (!from || !to) continue;
|
|
6523
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
6524
|
+
const d = 'M ' + from.x.toFixed(1) + ' ' + from.y.toFixed(1)
|
|
6525
|
+
+ ' L ' + to.x.toFixed(1) + ' ' + to.y.toFixed(1);
|
|
6526
|
+
line.setAttribute('d', d);
|
|
6527
|
+
line.setAttribute('class', 'a-edge');
|
|
6528
|
+
line.setAttribute('marker-end', 'url(#a-arrow)');
|
|
6529
|
+
line.dataset.from = a.slug;
|
|
6530
|
+
line.dataset.to = target;
|
|
6531
|
+
yfAgents.edgesG.appendChild(line);
|
|
6532
|
+
yfAgents.edgeBySlugPair.set(a.slug + '->' + target, line);
|
|
6533
|
+
}
|
|
6534
|
+
}
|
|
6535
|
+
/* Nodes. */
|
|
6536
|
+
yfAgents.nodesG.innerHTML = '';
|
|
6537
|
+
for (const a of agents) {
|
|
6538
|
+
const pos = positions.get(a.slug);
|
|
6539
|
+
if (!pos) continue;
|
|
6540
|
+
const isHub = a.slug === hub.slug;
|
|
6541
|
+
const radius = isHub ? 34 : 26;
|
|
6542
|
+
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
6543
|
+
g.setAttribute('transform', 'translate(' + pos.x.toFixed(1) + ',' + pos.y.toFixed(1) + ')');
|
|
6544
|
+
g.dataset.slug = a.slug;
|
|
6545
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
6546
|
+
circle.setAttribute('r', String(radius));
|
|
6547
|
+
circle.setAttribute('class', 'a-node-circle tier-' + (a.brain_tier || 'eco') + ' status-' + (a.status || 'active'));
|
|
6548
|
+
circle.dataset.slug = a.slug;
|
|
6549
|
+
g.appendChild(circle);
|
|
6550
|
+
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
6551
|
+
label.setAttribute('y', '4');
|
|
6552
|
+
label.setAttribute('class', 'a-node-label');
|
|
6553
|
+
label.textContent = a.name || a.slug;
|
|
6554
|
+
g.appendChild(label);
|
|
6555
|
+
const tier = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
6556
|
+
tier.setAttribute('y', String(radius + 14));
|
|
6557
|
+
tier.setAttribute('class', 'a-node-tier');
|
|
6558
|
+
tier.textContent = a.brain_tier || '';
|
|
6559
|
+
g.appendChild(tier);
|
|
6560
|
+
g.style.cursor = 'pointer';
|
|
6561
|
+
g.addEventListener('click', () => agentsDispatch(a.slug));
|
|
6562
|
+
yfAgents.nodesG.appendChild(g);
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
|
|
6566
|
+
async function agentsDispatch(slug) {
|
|
6567
|
+
try {
|
|
6568
|
+
await fetch('/api/agent/run', {
|
|
6569
|
+
method: 'POST',
|
|
6570
|
+
headers: { 'content-type': 'application/json' },
|
|
6571
|
+
body: JSON.stringify({ slug, trigger_reason: 'panel-click' }),
|
|
6572
|
+
});
|
|
6573
|
+
} catch { /* the SSE will tell the user about failure via the run event. */ }
|
|
6574
|
+
}
|
|
6575
|
+
|
|
6576
|
+
function agentsSubscribe() {
|
|
6577
|
+
if (yfAgents.evtSource) return;
|
|
6578
|
+
try {
|
|
6579
|
+
yfAgents.evtSource = new EventSource('/api/agent/events');
|
|
6580
|
+
yfAgents.evtSource.onopen = () => { agentsSetTick(true); };
|
|
6581
|
+
yfAgents.evtSource.onerror = () => { agentsSetTick(false); };
|
|
6582
|
+
/* Listen by named event. The server sends event: <kind>. */
|
|
6583
|
+
yfAgents.evtSource.addEventListener('agent_run_started', (e) => agentsHandleRunStarted(e));
|
|
6584
|
+
yfAgents.evtSource.addEventListener('agent_run_finished', (e) => agentsHandleRunFinished(e));
|
|
6585
|
+
yfAgents.evtSource.addEventListener('agent_run_skipped', (e) => agentsHandleRunFinished(e));
|
|
6586
|
+
yfAgents.evtSource.addEventListener('bridge_drain', (e) => agentsHandleBridgeDrain(e));
|
|
6587
|
+
/* Director closure pendiente 5 followup -- the server fires
|
|
6588
|
+
* this when /api/agent/approve resolves an R8 pending row
|
|
6589
|
+
* tagged with an agent_slug. The handler clears the persistent
|
|
6590
|
+
* .paused-for-approval CSS marker on the SVG node so the
|
|
6591
|
+
* operator sees a clean idle state without re-dispatching the
|
|
6592
|
+
* agent. */
|
|
6593
|
+
yfAgents.evtSource.addEventListener('agent_run_paused_resolved', (e) => agentsHandlePausedResolved(e));
|
|
6594
|
+
} catch {
|
|
6595
|
+
agentsSetTick(false);
|
|
6596
|
+
}
|
|
6597
|
+
}
|
|
6598
|
+
|
|
6599
|
+
function agentsParseEvent(e) {
|
|
6600
|
+
try { return JSON.parse(e.data); } catch { return null; }
|
|
6601
|
+
}
|
|
6602
|
+
function agentsHandleRunStarted(e) {
|
|
6603
|
+
const ev = agentsParseEvent(e);
|
|
6604
|
+
if (!ev || !ev.slug) return;
|
|
6605
|
+
const circle = yfAgents.nodesG.querySelector('[data-slug="' + cssEscape(ev.slug) + '"] .a-node-circle')
|
|
6606
|
+
|| yfAgents.nodesG.querySelector('g[data-slug="' + cssEscape(ev.slug) + '"] .a-node-circle');
|
|
6607
|
+
if (circle) {
|
|
6608
|
+
circle.classList.add('running');
|
|
6609
|
+
/* A new run supersedes any prior paused-for-approval state. */
|
|
6610
|
+
circle.classList.remove('paused-for-approval');
|
|
6611
|
+
}
|
|
6612
|
+
}
|
|
6613
|
+
function agentsHandleRunFinished(e) {
|
|
6614
|
+
const ev = agentsParseEvent(e);
|
|
6615
|
+
if (!ev || !ev.slug) return;
|
|
6616
|
+
const circle = yfAgents.nodesG.querySelector('g[data-slug="' + cssEscape(ev.slug) + '"] .a-node-circle');
|
|
6617
|
+
if (!circle) return;
|
|
6618
|
+
circle.classList.remove('running');
|
|
6619
|
+
/* Director closure pendiente 5 -- when the runner concluded
|
|
6620
|
+
* the agent as paused_for_approval, leave a persistent visual
|
|
6621
|
+
* marker so the operator sees which agent is waiting. A later
|
|
6622
|
+
* /api/agent/approve -> agent_run_paused_resolved event (or a
|
|
6623
|
+
* fresh agent_run_started for the same slug) clears it. */
|
|
6624
|
+
if (ev.status === 'paused_for_approval') {
|
|
6625
|
+
circle.classList.add('paused-for-approval');
|
|
6626
|
+
} else {
|
|
6627
|
+
circle.classList.remove('paused-for-approval');
|
|
6628
|
+
}
|
|
6629
|
+
}
|
|
6630
|
+
function agentsHandlePausedResolved(e) {
|
|
6631
|
+
const ev = agentsParseEvent(e);
|
|
6632
|
+
if (!ev || !ev.slug) return;
|
|
6633
|
+
const circle = yfAgents.nodesG.querySelector('g[data-slug="' + cssEscape(ev.slug) + '"] .a-node-circle');
|
|
6634
|
+
if (circle) circle.classList.remove('paused-for-approval');
|
|
6635
|
+
}
|
|
6636
|
+
function agentsHandleBridgeDrain(e) {
|
|
6637
|
+
const ev = agentsParseEvent(e);
|
|
6638
|
+
if (!ev || !ev.slug) return;
|
|
6639
|
+
/* Highlight every edge that targets this slug -- visualises
|
|
6640
|
+
* the inbound message wave. */
|
|
6641
|
+
const target = ev.slug;
|
|
6642
|
+
for (const [pair, line] of yfAgents.edgeBySlugPair.entries()) {
|
|
6643
|
+
if (pair.endsWith('->' + target)) {
|
|
6644
|
+
line.classList.add('active');
|
|
6645
|
+
/* Drop the highlight after the keyframe animation completes
|
|
6646
|
+
* so a sustained drain shows a regular flash, not a stuck
|
|
6647
|
+
* coloured edge. */
|
|
6648
|
+
setTimeout(() => line.classList.remove('active'), 950);
|
|
6649
|
+
}
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
|
|
6653
|
+
/* ------------------------------------------------------------------
|
|
6654
|
+
* F101 slice 3 -- MCP bridges panel UI.
|
|
6655
|
+
* Open: click MCP button OR data-nac-action="open_mcp".
|
|
6656
|
+
* Calls GET /api/forge/mcp/bridges + renders cards.
|
|
6657
|
+
* Per-card: enable/disable toggle + "Ver tools" -> inline catalog.
|
|
6658
|
+
* ----------------------------------------------------------------*/
|
|
6659
|
+
const yfMcp = {
|
|
6660
|
+
modal: null,
|
|
6661
|
+
contentEl: null,
|
|
6662
|
+
};
|
|
6663
|
+
|
|
6664
|
+
function mcpWireUp() {
|
|
6665
|
+
yfMcp.modal = document.getElementById('yf-mcp-modal');
|
|
6666
|
+
yfMcp.contentEl = yfMcp.modal && yfMcp.modal.querySelector('[data-mcp-content]');
|
|
6667
|
+
if (!yfMcp.modal) return;
|
|
6668
|
+
document.querySelectorAll('[data-mcp-open]').forEach((btn) => {
|
|
6669
|
+
btn.addEventListener('click', mcpOpen);
|
|
6670
|
+
});
|
|
6671
|
+
document.querySelectorAll('[data-mcp-close]').forEach((btn) => {
|
|
6672
|
+
btn.addEventListener('click', mcpClose);
|
|
6673
|
+
});
|
|
6674
|
+
const refresh = yfMcp.modal.querySelector('[data-mcp-refresh]');
|
|
6675
|
+
if (refresh) refresh.addEventListener('click', () => mcpLoadBridges(true));
|
|
6676
|
+
document.addEventListener('keydown', (e) => {
|
|
6677
|
+
if (e.key === 'Escape' && yfMcp.modal && !yfMcp.modal.classList.contains('hidden')) {
|
|
6678
|
+
mcpClose();
|
|
6679
|
+
}
|
|
6680
|
+
});
|
|
6681
|
+
}
|
|
6682
|
+
|
|
6683
|
+
async function mcpOpen() {
|
|
6684
|
+
if (!yfMcp.modal) return;
|
|
6685
|
+
yfMcp.modal.classList.remove('hidden');
|
|
6686
|
+
await mcpLoadBridges(false);
|
|
6687
|
+
}
|
|
6688
|
+
|
|
6689
|
+
function mcpClose() {
|
|
6690
|
+
if (!yfMcp.modal) return;
|
|
6691
|
+
yfMcp.modal.classList.add('hidden');
|
|
6692
|
+
}
|
|
6693
|
+
|
|
6694
|
+
async function mcpLoadBridges(/* force */) {
|
|
6695
|
+
if (!yfMcp.contentEl) return;
|
|
6696
|
+
yfMcp.contentEl.innerHTML = '<div class="mcp-empty">Cargando bridges...</div>';
|
|
6697
|
+
try {
|
|
6698
|
+
const r = await fetch('/api/forge/mcp/bridges');
|
|
6699
|
+
const body = await r.json();
|
|
6700
|
+
const bridges = Array.isArray(body.bridges) ? body.bridges : [];
|
|
6701
|
+
if (bridges.length === 0) {
|
|
6702
|
+
yfMcp.contentEl.innerHTML = '<div class="mcp-empty">No hay bridges MCP discovered. Yujin escanea 4 sources al boot (loopback / manifest / file / env).</div>';
|
|
6703
|
+
return;
|
|
6704
|
+
}
|
|
6705
|
+
yfMcp.contentEl.innerHTML = '';
|
|
6706
|
+
for (const b of bridges) {
|
|
6707
|
+
const card = mcpRenderBridge(b);
|
|
6708
|
+
yfMcp.contentEl.appendChild(card);
|
|
6709
|
+
}
|
|
6710
|
+
} catch (err) {
|
|
6711
|
+
yfMcp.contentEl.innerHTML = '<div class="mcp-empty">Error: ' + (err && err.message ? err.message : String(err)) + '</div>';
|
|
6712
|
+
}
|
|
6713
|
+
}
|
|
6714
|
+
|
|
6715
|
+
function mcpRenderBridge(b) {
|
|
6716
|
+
const wrap = document.createElement('div');
|
|
6717
|
+
wrap.className = 'mcp-bridge' + (b.enabled ? '' : ' disabled');
|
|
6718
|
+
wrap.dataset.bridgeId = b.id;
|
|
6719
|
+
const meta = document.createElement('div');
|
|
6720
|
+
meta.className = 'mcp-meta';
|
|
6721
|
+
meta.innerHTML =
|
|
6722
|
+
'<div class="mcp-name">' + mcpEsc(b.display_name || b.id) + '</div>' +
|
|
6723
|
+
'<div class="mcp-endpoint">' + mcpEsc(b.endpoint || '-') + '</div>';
|
|
6724
|
+
const srcChip = document.createElement('span');
|
|
6725
|
+
srcChip.className = 'mcp-source-chip';
|
|
6726
|
+
srcChip.textContent = b.source || '?';
|
|
6727
|
+
const protoChip = document.createElement('span');
|
|
6728
|
+
protoChip.className = 'mcp-protocol-chip';
|
|
6729
|
+
protoChip.textContent = b.protocol || 'http_rest';
|
|
6730
|
+
const toggleBtn = document.createElement('button');
|
|
6731
|
+
toggleBtn.className = 'mcp-toggle';
|
|
6732
|
+
toggleBtn.textContent = b.enabled ? 'Disable' : 'Enable';
|
|
6733
|
+
toggleBtn.addEventListener('click', () => mcpToggleBridge(b.id, !b.enabled));
|
|
6734
|
+
const toolsBtn = document.createElement('button');
|
|
6735
|
+
toolsBtn.className = 'mcp-tools-btn';
|
|
6736
|
+
toolsBtn.textContent = 'Tools';
|
|
6737
|
+
toolsBtn.addEventListener('click', () => mcpLoadTools(b.id, wrap));
|
|
6738
|
+
wrap.appendChild(meta);
|
|
6739
|
+
wrap.appendChild(srcChip);
|
|
6740
|
+
wrap.appendChild(protoChip);
|
|
6741
|
+
wrap.appendChild(toolsBtn);
|
|
6742
|
+
wrap.appendChild(toggleBtn);
|
|
6743
|
+
return wrap;
|
|
6744
|
+
}
|
|
6745
|
+
|
|
6746
|
+
async function mcpToggleBridge(bridgeId, enabled) {
|
|
6747
|
+
try {
|
|
6748
|
+
const r = await fetch('/api/forge/mcp/toggle', {
|
|
6749
|
+
method: 'POST',
|
|
6750
|
+
headers: { 'content-type': 'application/json' },
|
|
6751
|
+
body: JSON.stringify({ bridge_id: bridgeId, enabled }),
|
|
6752
|
+
});
|
|
6753
|
+
if (!r.ok) throw new Error('toggle ' + r.status);
|
|
6754
|
+
} catch (_err) {
|
|
6755
|
+
/* Silent -- the reload below will reflect the persisted state. */
|
|
6756
|
+
}
|
|
6757
|
+
await mcpLoadBridges(true);
|
|
6758
|
+
}
|
|
6759
|
+
|
|
6760
|
+
async function mcpLoadTools(bridgeId, cardEl) {
|
|
6761
|
+
/* Toggle inline panel. */
|
|
6762
|
+
const existing = cardEl.parentElement && cardEl.parentElement.querySelector('[data-mcp-tools-for="' + cssEscape(bridgeId) + '"]');
|
|
6763
|
+
if (existing) {
|
|
6764
|
+
existing.parentElement.removeChild(existing);
|
|
6765
|
+
return;
|
|
6766
|
+
}
|
|
6767
|
+
const panel = document.createElement('div');
|
|
6768
|
+
panel.className = 'mcp-tools-list';
|
|
6769
|
+
panel.dataset.mcpToolsFor = bridgeId;
|
|
6770
|
+
panel.textContent = 'Cargando tools...';
|
|
6771
|
+
cardEl.parentElement.insertBefore(panel, cardEl.nextSibling);
|
|
6772
|
+
try {
|
|
6773
|
+
const r = await fetch('/api/forge/mcp/tools?bridge_id=' + encodeURIComponent(bridgeId));
|
|
6774
|
+
const body = await r.json();
|
|
6775
|
+
if (!body.ok) {
|
|
6776
|
+
panel.innerHTML = '<div class="mcp-tools-error">' + mcpEsc(body.error || 'unreachable') + '</div>';
|
|
6777
|
+
return;
|
|
6778
|
+
}
|
|
6779
|
+
const tools = Array.isArray(body.tools) ? body.tools : [];
|
|
6780
|
+
if (tools.length === 0) {
|
|
6781
|
+
panel.innerHTML = '<div class="mcp-empty">Sin tools.</div>';
|
|
6782
|
+
return;
|
|
6783
|
+
}
|
|
6784
|
+
panel.innerHTML = '';
|
|
6785
|
+
for (const t of tools) {
|
|
6786
|
+
const row = document.createElement('div');
|
|
6787
|
+
row.className = 'mcp-tool';
|
|
6788
|
+
row.innerHTML =
|
|
6789
|
+
'<div class="mcp-tool-id">' + mcpEsc(t.id || t.slug || t.name || '?') + '</div>' +
|
|
6790
|
+
'<div class="mcp-tool-desc">' + mcpEsc(t.description || t.desc || '') + '</div>';
|
|
6791
|
+
panel.appendChild(row);
|
|
6792
|
+
}
|
|
6793
|
+
} catch (err) {
|
|
6794
|
+
panel.innerHTML = '<div class="mcp-tools-error">' + mcpEsc(err && err.message ? err.message : String(err)) + '</div>';
|
|
6795
|
+
}
|
|
6796
|
+
}
|
|
6797
|
+
|
|
6798
|
+
function mcpEsc(s) {
|
|
6799
|
+
return String(s == null ? '' : s)
|
|
6800
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
6801
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
6802
|
+
}
|
|
6803
|
+
|
|
6804
|
+
/* ------------------------------------------------------------------
|
|
6805
|
+
* F107 accessibility -- handlers for the 3 yujin.a11y.* verbs.
|
|
6806
|
+
*
|
|
6807
|
+
* announce -- write text into the matching ARIA live region so
|
|
6808
|
+
* screen readers + the voice loop pick it up. When
|
|
6809
|
+
* speak=true, also queue TTS (locale optional).
|
|
6810
|
+
* focus -- find a [data-nac-slug] element + move focus +
|
|
6811
|
+
* optionally scroll into view + auto-announce the
|
|
6812
|
+
* new focus target.
|
|
6813
|
+
* label -- attach aria-label / aria-describedby + optional
|
|
6814
|
+
* role + shortcut hint to the [data-nac-slug] target.
|
|
6815
|
+
*
|
|
6816
|
+
* All three are idempotent and never throw -- a missing slug is
|
|
6817
|
+
* silent; the brain learns from no visible effect.
|
|
6818
|
+
* ----------------------------------------------------------------*/
|
|
6819
|
+
|
|
6820
|
+
function a11yAnnounce(text, opts) {
|
|
6821
|
+
const txt = String(text || '').slice(0, 1000).trim();
|
|
6822
|
+
if (!txt) return;
|
|
6823
|
+
const politeness = (opts && opts.politeness === 'assertive') ? 'assertive' : 'polite';
|
|
6824
|
+
const region = document.getElementById('yf-a11y-live-' + politeness);
|
|
6825
|
+
if (region) {
|
|
6826
|
+
/* Empty + re-fill forces some screen readers to re-announce
|
|
6827
|
+
* even identical text. */
|
|
6828
|
+
region.textContent = '';
|
|
6829
|
+
setTimeout(() => { region.textContent = txt; }, 30);
|
|
6830
|
+
}
|
|
6831
|
+
if (opts && opts.speak) {
|
|
6832
|
+
try {
|
|
6833
|
+
const locale = opts.locale;
|
|
6834
|
+
if (typeof playTtsForText === 'function') {
|
|
6835
|
+
playTtsForText(txt, locale ? { locale } : undefined).catch(() => undefined);
|
|
6836
|
+
}
|
|
6837
|
+
} catch (_err) { /* silent */ }
|
|
6838
|
+
}
|
|
6839
|
+
}
|
|
6840
|
+
|
|
6841
|
+
function a11yFocus(slug, opts) {
|
|
6842
|
+
const s = String(slug || '').trim();
|
|
6843
|
+
if (!s) return;
|
|
6844
|
+
/* The NAC-3 verb references the data-nac-slug attribute; some
|
|
6845
|
+
* components also expose data-nac-id with the same shape. Try
|
|
6846
|
+
* both for resilience. */
|
|
6847
|
+
const el =
|
|
6848
|
+
document.querySelector('[data-nac-slug="' + cssEscape(s) + '"]') ||
|
|
6849
|
+
document.querySelector('[data-nac-id="' + cssEscape(s) + '"]');
|
|
6850
|
+
if (!el) return;
|
|
6851
|
+
try {
|
|
6852
|
+
if (!opts || opts.scroll !== false) {
|
|
6853
|
+
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
6854
|
+
}
|
|
6855
|
+
/* Ensure the target accepts focus -- add tabindex=-1 when not
|
|
6856
|
+
* already focusable. This is the same trick screen-reader
|
|
6857
|
+
* patterns use for non-button skip targets. */
|
|
6858
|
+
if (!el.matches('a, button, input, textarea, select, [tabindex]')) {
|
|
6859
|
+
el.setAttribute('tabindex', '-1');
|
|
6860
|
+
}
|
|
6861
|
+
el.focus({ preventScroll: false });
|
|
6862
|
+
if (!opts || opts.announce !== false) {
|
|
6863
|
+
const lbl = el.getAttribute('aria-label')
|
|
6864
|
+
|| el.getAttribute('title')
|
|
6865
|
+
|| el.textContent || '';
|
|
6866
|
+
a11yAnnounce('Foco: ' + lbl.toString().trim().slice(0, 200), { politeness: 'polite' });
|
|
6867
|
+
}
|
|
6868
|
+
} catch (_err) { /* silent */ }
|
|
6869
|
+
}
|
|
6870
|
+
|
|
6871
|
+
function a11yLabel(slug, opts) {
|
|
6872
|
+
const s = String(slug || '').trim();
|
|
6873
|
+
if (!s) return;
|
|
6874
|
+
const el =
|
|
6875
|
+
document.querySelector('[data-nac-slug="' + cssEscape(s) + '"]') ||
|
|
6876
|
+
document.querySelector('[data-nac-id="' + cssEscape(s) + '"]');
|
|
6877
|
+
if (!el || !opts) return;
|
|
6878
|
+
try {
|
|
6879
|
+
if (opts.description) {
|
|
6880
|
+
el.setAttribute('aria-label', String(opts.description).slice(0, 500));
|
|
6881
|
+
}
|
|
6882
|
+
if (opts.role) {
|
|
6883
|
+
el.setAttribute('role', String(opts.role).slice(0, 40));
|
|
6884
|
+
}
|
|
6885
|
+
if (opts.shortcut) {
|
|
6886
|
+
el.setAttribute('aria-keyshortcuts', String(opts.shortcut).slice(0, 40));
|
|
6887
|
+
}
|
|
6888
|
+
} catch (_err) { /* silent */ }
|
|
6889
|
+
}
|
|
6890
|
+
|
|
6891
|
+
/* ------------------------------------------------------------------
|
|
6892
|
+
* Activity overlay -- 3 tabs: feed timestamped / bridge inspector
|
|
6893
|
+
* / chat-per-agent. Consumes /api/agent/activity, /bridge-tail,
|
|
6894
|
+
* /inject. Polls every 3s when auto-refresh is on.
|
|
6895
|
+
* ----------------------------------------------------------------*/
|
|
6896
|
+
const yfActivity = {
|
|
6897
|
+
modal: null,
|
|
6898
|
+
tabPanels: { feed: null, bridge: null, chat: null },
|
|
6899
|
+
tabBtns: [],
|
|
6900
|
+
refreshTimer: null,
|
|
6901
|
+
activeTab: 'feed',
|
|
6902
|
+
lastEvtTs: '',
|
|
6903
|
+
selectedPair: null,
|
|
6904
|
+
/* Slice 3 N-projects sprint -- project filter + concurrency
|
|
6905
|
+
* badge. project='' means "all". project='self' is the default
|
|
6906
|
+
* single-project legacy slug. */
|
|
6907
|
+
projectFilter: '',
|
|
6908
|
+
knownProjects: new Set(),
|
|
6909
|
+
};
|
|
6910
|
+
|
|
6911
|
+
function activityWireUp() {
|
|
6912
|
+
yfActivity.modal = document.getElementById('yf-activity-modal');
|
|
6913
|
+
if (!yfActivity.modal) return;
|
|
6914
|
+
yfActivity.tabPanels.feed = yfActivity.modal.querySelector('[data-act-panel="feed"]');
|
|
6915
|
+
yfActivity.tabPanels.bridge = yfActivity.modal.querySelector('[data-act-panel="bridge"]');
|
|
6916
|
+
yfActivity.tabPanels.chat = yfActivity.modal.querySelector('[data-act-panel="chat"]');
|
|
6917
|
+
yfActivity.tabBtns = Array.from(yfActivity.modal.querySelectorAll('[data-act-tab]'));
|
|
6918
|
+
document.querySelectorAll('[data-activity-open]').forEach((b) => b.addEventListener('click', activityOpen));
|
|
6919
|
+
document.querySelectorAll('[data-activity-close]').forEach((b) => b.addEventListener('click', activityClose));
|
|
6920
|
+
yfActivity.tabBtns.forEach((b) => b.addEventListener('click', () => activitySwitchTab(b.dataset.actTab)));
|
|
6921
|
+
const refreshBtn = yfActivity.modal.querySelector('[data-act-refresh]');
|
|
6922
|
+
if (refreshBtn) refreshBtn.addEventListener('click', () => activityRefresh(true));
|
|
6923
|
+
const autoBtn = yfActivity.modal.querySelector('[data-act-autorefresh]');
|
|
6924
|
+
if (autoBtn) autoBtn.addEventListener('change', activityToggleAuto);
|
|
6925
|
+
const sendBtn = yfActivity.modal.querySelector('[data-act-chat-send]');
|
|
6926
|
+
if (sendBtn) sendBtn.addEventListener('click', activityChatSend);
|
|
6927
|
+
const projSel = yfActivity.modal.querySelector('[data-act-project-filter]');
|
|
6928
|
+
if (projSel) projSel.addEventListener('change', (e) => {
|
|
6929
|
+
yfActivity.projectFilter = String((e.target && e.target.value) || '');
|
|
6930
|
+
yfActivity.lastEvtTs = '';
|
|
6931
|
+
activityRefresh(true);
|
|
6932
|
+
});
|
|
6933
|
+
document.addEventListener('keydown', (e) => {
|
|
6934
|
+
if (e.key === 'Escape' && yfActivity.modal && !yfActivity.modal.classList.contains('hidden')) {
|
|
6935
|
+
activityClose();
|
|
6936
|
+
}
|
|
6937
|
+
});
|
|
6938
|
+
}
|
|
6939
|
+
|
|
6940
|
+
async function activityOpen() {
|
|
6941
|
+
if (!yfActivity.modal) return;
|
|
6942
|
+
yfActivity.modal.classList.remove('hidden');
|
|
6943
|
+
yfActivity.lastEvtTs = '';
|
|
6944
|
+
await activityRefresh(true);
|
|
6945
|
+
activityPopulateChatSlugs();
|
|
6946
|
+
activityStartAutoRefresh();
|
|
6947
|
+
}
|
|
6948
|
+
|
|
6949
|
+
function activityClose() {
|
|
6950
|
+
if (!yfActivity.modal) return;
|
|
6951
|
+
yfActivity.modal.classList.add('hidden');
|
|
6952
|
+
activityStopAutoRefresh();
|
|
6953
|
+
}
|
|
6954
|
+
|
|
6955
|
+
function activitySwitchTab(tab) {
|
|
6956
|
+
if (!tab) return;
|
|
6957
|
+
yfActivity.activeTab = tab;
|
|
6958
|
+
yfActivity.tabBtns.forEach((b) => {
|
|
6959
|
+
if (b.dataset.actTab === tab) b.classList.add('active');
|
|
6960
|
+
else b.classList.remove('active');
|
|
6961
|
+
});
|
|
6962
|
+
for (const key of Object.keys(yfActivity.tabPanels)) {
|
|
6963
|
+
const p = yfActivity.tabPanels[key];
|
|
6964
|
+
if (!p) continue;
|
|
6965
|
+
if (key === tab) p.classList.remove('hidden');
|
|
6966
|
+
else p.classList.add('hidden');
|
|
6967
|
+
}
|
|
6968
|
+
activityRefresh(true);
|
|
6969
|
+
}
|
|
6970
|
+
|
|
6971
|
+
function activityStartAutoRefresh() {
|
|
6972
|
+
activityStopAutoRefresh();
|
|
6973
|
+
const auto = yfActivity.modal && yfActivity.modal.querySelector('[data-act-autorefresh]');
|
|
6974
|
+
if (auto && !auto.checked) return;
|
|
6975
|
+
yfActivity.refreshTimer = setInterval(() => activityRefresh(false), 3000);
|
|
6976
|
+
}
|
|
6977
|
+
function activityStopAutoRefresh() {
|
|
6978
|
+
if (yfActivity.refreshTimer) {
|
|
6979
|
+
clearInterval(yfActivity.refreshTimer);
|
|
6980
|
+
yfActivity.refreshTimer = null;
|
|
6981
|
+
}
|
|
6982
|
+
}
|
|
6983
|
+
function activityToggleAuto(e) {
|
|
6984
|
+
if (e.target && e.target.checked) activityStartAutoRefresh();
|
|
6985
|
+
else activityStopAutoRefresh();
|
|
6986
|
+
}
|
|
6987
|
+
|
|
6988
|
+
async function activityRefresh(force) {
|
|
6989
|
+
if (!yfActivity.modal) return;
|
|
6990
|
+
if (yfActivity.activeTab === 'feed') {
|
|
6991
|
+
await activityLoadFeed(force);
|
|
6992
|
+
} else if (yfActivity.activeTab === 'bridge') {
|
|
6993
|
+
if (yfActivity.selectedPair) await activityLoadBridgeMessages(yfActivity.selectedPair);
|
|
6994
|
+
else await activityLoadBridgePairs();
|
|
6995
|
+
}
|
|
6996
|
+
/* Concurrency badge -- always refreshed via /api/agent/state. */
|
|
6997
|
+
await activityLoadConcurrency();
|
|
6998
|
+
/* Project dropdown -- newly seen projects from the feed / bridge
|
|
6999
|
+
* get appended. */
|
|
7000
|
+
activitySyncProjectDropdown();
|
|
7001
|
+
const stamp = yfActivity.modal.querySelector('[data-act-last-update]');
|
|
7002
|
+
if (stamp) stamp.textContent = new Date().toLocaleTimeString();
|
|
7003
|
+
}
|
|
7004
|
+
|
|
7005
|
+
async function activityLoadConcurrency() {
|
|
7006
|
+
const badge = yfActivity.modal.querySelector('[data-act-concurrency]');
|
|
7007
|
+
if (!badge) return;
|
|
7008
|
+
try {
|
|
7009
|
+
const r = await fetch('/api/agent/state');
|
|
7010
|
+
if (r.status === 503) { badge.textContent = 'runtime off'; return; }
|
|
7011
|
+
if (!r.ok) return;
|
|
7012
|
+
const body = await r.json();
|
|
7013
|
+
const c = body && body.runtime && body.runtime.concurrency;
|
|
7014
|
+
if (!c) { badge.textContent = '0/?'; return; }
|
|
7015
|
+
badge.textContent = c.global_running + '/' + c.global_max + ' (cap ' + c.per_project_max + '/proj)';
|
|
7016
|
+
} catch { /* silent */ }
|
|
7017
|
+
}
|
|
7018
|
+
|
|
7019
|
+
function activitySyncProjectDropdown() {
|
|
7020
|
+
const sel = yfActivity.modal.querySelector('[data-act-project-filter]');
|
|
7021
|
+
if (!sel) return;
|
|
7022
|
+
/* Read current options into a set so we only append new ones. */
|
|
7023
|
+
const existing = new Set();
|
|
7024
|
+
for (const opt of sel.options) existing.add(opt.value);
|
|
7025
|
+
let appended = false;
|
|
7026
|
+
for (const slug of yfActivity.knownProjects) {
|
|
7027
|
+
if (!existing.has(slug)) {
|
|
7028
|
+
const opt = document.createElement('option');
|
|
7029
|
+
opt.value = slug;
|
|
7030
|
+
opt.textContent = slug;
|
|
7031
|
+
sel.appendChild(opt);
|
|
7032
|
+
appended = true;
|
|
7033
|
+
}
|
|
7034
|
+
}
|
|
7035
|
+
/* Preserve user's current selection across appends. */
|
|
7036
|
+
if (appended && yfActivity.projectFilter) sel.value = yfActivity.projectFilter;
|
|
7037
|
+
}
|
|
7038
|
+
|
|
7039
|
+
async function activityLoadFeed(force) {
|
|
7040
|
+
const panel = yfActivity.tabPanels.feed;
|
|
7041
|
+
if (!panel) return;
|
|
7042
|
+
try {
|
|
7043
|
+
let url = '/api/agent/activity?limit=200';
|
|
7044
|
+
if (yfActivity.lastEvtTs && !force) url += '&since=' + encodeURIComponent(yfActivity.lastEvtTs);
|
|
7045
|
+
if (yfActivity.projectFilter) url += '&project_slug=' + encodeURIComponent(yfActivity.projectFilter);
|
|
7046
|
+
const r = await fetch(url);
|
|
7047
|
+
const body = await r.json();
|
|
7048
|
+
const events = Array.isArray(body.events) ? body.events : [];
|
|
7049
|
+
if (events.length === 0 && force) {
|
|
7050
|
+
panel.innerHTML = '<div class="act-empty">Sin actividad todavia. Disparate un agente o un chat y vuelve.</div>';
|
|
7051
|
+
return;
|
|
7052
|
+
}
|
|
7053
|
+
if (force) panel.innerHTML = '';
|
|
7054
|
+
/* Newest at top. */
|
|
7055
|
+
for (const ev of events) {
|
|
7056
|
+
const line = document.createElement('div');
|
|
7057
|
+
const family = String(ev.kind || '').split('.')[0] || 'misc';
|
|
7058
|
+
line.className = 'act-line kind-' + family;
|
|
7059
|
+
const ts = activityFormatTs(ev.ts);
|
|
7060
|
+
line.innerHTML =
|
|
7061
|
+
'<div class="act-ts">' + actEsc(ts) + '</div>' +
|
|
7062
|
+
'<div class="act-kind">' + actEsc(ev.kind || '') + '</div>' +
|
|
7063
|
+
'<div class="act-text">' + actEsc(ev.text || '') + '</div>';
|
|
7064
|
+
/* Prepend so newest stays on top. */
|
|
7065
|
+
panel.insertBefore(line, panel.firstChild);
|
|
7066
|
+
if (ev.ts > yfActivity.lastEvtTs) yfActivity.lastEvtTs = ev.ts;
|
|
7067
|
+
/* Track project_slugs we have seen so the dropdown can list
|
|
7068
|
+
* them as filter options. */
|
|
7069
|
+
if (ev.project_slug && typeof ev.project_slug === 'string') {
|
|
7070
|
+
yfActivity.knownProjects.add(ev.project_slug);
|
|
7071
|
+
}
|
|
7072
|
+
}
|
|
7073
|
+
/* Cap DOM at 500 lines. */
|
|
7074
|
+
while (panel.childNodes.length > 500) panel.removeChild(panel.lastChild);
|
|
7075
|
+
} catch (err) {
|
|
7076
|
+
if (force) panel.innerHTML = '<div class="act-empty">Error cargando feed: ' + actEsc(err.message || String(err)) + '</div>';
|
|
7077
|
+
}
|
|
7078
|
+
}
|
|
7079
|
+
|
|
7080
|
+
async function activityLoadBridgePairs() {
|
|
7081
|
+
const panel = yfActivity.tabPanels.bridge;
|
|
7082
|
+
if (!panel) return;
|
|
7083
|
+
try {
|
|
7084
|
+
let url = '/api/agent/bridge-tail';
|
|
7085
|
+
if (yfActivity.projectFilter) url += '?project_slug=' + encodeURIComponent(yfActivity.projectFilter);
|
|
7086
|
+
const r = await fetch(url);
|
|
7087
|
+
const body = await r.json();
|
|
7088
|
+
const pairs = Array.isArray(body.pairs) ? body.pairs : [];
|
|
7089
|
+
for (const p of pairs) {
|
|
7090
|
+
if (p.project_slug) yfActivity.knownProjects.add(p.project_slug);
|
|
7091
|
+
}
|
|
7092
|
+
if (pairs.length === 0) {
|
|
7093
|
+
panel.innerHTML = '<div class="act-empty">No hay mensajes en el bridge todavia.</div>';
|
|
7094
|
+
return;
|
|
7095
|
+
}
|
|
7096
|
+
panel.innerHTML = '';
|
|
7097
|
+
for (const p of pairs) {
|
|
7098
|
+
const card = document.createElement('div');
|
|
7099
|
+
card.className = 'act-pair';
|
|
7100
|
+
const projBadge = p.project_slug && p.project_slug !== 'self'
|
|
7101
|
+
? '<span class="act-pair-count" style="background:rgba(200,124,74,0.2);color:#c87c4a;">' + actEsc(p.project_slug) + '</span>'
|
|
7102
|
+
: '';
|
|
7103
|
+
card.innerHTML =
|
|
7104
|
+
'<div class="act-pair-id">' + actEsc(p.from) + ' → ' + actEsc(p.to) + '</div>' +
|
|
7105
|
+
projBadge +
|
|
7106
|
+
'<span class="act-pair-count">' + actEsc(String(p.count)) + ' msg</span>' +
|
|
7107
|
+
'<span class="act-ts" style="font-size:11px;color:var(--ink-muted,#8a8a8a);">' + actEsc(activityFormatTs(p.last_ts)) + '</span>';
|
|
7108
|
+
card.addEventListener('click', () => {
|
|
7109
|
+
yfActivity.selectedPair = p.pair_id;
|
|
7110
|
+
activityLoadBridgeMessages(p.pair_id);
|
|
7111
|
+
});
|
|
7112
|
+
panel.appendChild(card);
|
|
7113
|
+
}
|
|
7114
|
+
} catch (err) {
|
|
7115
|
+
panel.innerHTML = '<div class="act-empty">Error bridge: ' + actEsc(err.message || String(err)) + '</div>';
|
|
7116
|
+
}
|
|
7117
|
+
}
|
|
7118
|
+
|
|
7119
|
+
async function activityLoadBridgeMessages(pairId) {
|
|
7120
|
+
const panel = yfActivity.tabPanels.bridge;
|
|
7121
|
+
if (!panel) return;
|
|
7122
|
+
try {
|
|
7123
|
+
const r = await fetch('/api/agent/bridge-tail?limit=200&pair_id=' + encodeURIComponent(pairId));
|
|
7124
|
+
const body = await r.json();
|
|
7125
|
+
const msgs = Array.isArray(body.messages) ? body.messages : [];
|
|
7126
|
+
panel.innerHTML = '';
|
|
7127
|
+
const back = document.createElement('button');
|
|
7128
|
+
back.textContent = '< Pares';
|
|
7129
|
+
back.style.cssText = 'background:transparent;color:inherit;border:1px solid var(--ink-subtle,#5a5e6a);border-radius:4px;padding:4px 10px;font-family:inherit;font-size:12px;cursor:pointer;margin-bottom:10px;';
|
|
7130
|
+
back.addEventListener('click', () => { yfActivity.selectedPair = null; activityLoadBridgePairs(); });
|
|
7131
|
+
panel.appendChild(back);
|
|
7132
|
+
const header = document.createElement('div');
|
|
7133
|
+
header.style.cssText = 'font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;color:var(--ink-muted,#8a8a8a);margin-bottom:8px;';
|
|
7134
|
+
header.textContent = pairId + ' -- ' + msgs.length + ' msg';
|
|
7135
|
+
panel.appendChild(header);
|
|
7136
|
+
for (const m of msgs) {
|
|
7137
|
+
const w = document.createElement('div');
|
|
7138
|
+
w.className = 'act-bridge-msg';
|
|
7139
|
+
const ts = activityFormatTs(m.sent_at ? new Date(Number(m.sent_at)).toISOString() : '');
|
|
7140
|
+
w.innerHTML =
|
|
7141
|
+
'<div class="act-bridge-head">' + actEsc(ts) + ' #' + actEsc(String(m.id || '?'))
|
|
7142
|
+
+ ' (' + actEsc(String(m.status || '?')) + ', ' + actEsc(String(m.tokens_used || 0)) + ' tok)</div>' +
|
|
7143
|
+
'<div class="act-bridge-payload">' + actEsc(String(m.payload || '').slice(0, 1200)) + '</div>';
|
|
7144
|
+
panel.appendChild(w);
|
|
7145
|
+
}
|
|
7146
|
+
if (msgs.length === 0) {
|
|
7147
|
+
const empty = document.createElement('div');
|
|
7148
|
+
empty.className = 'act-empty';
|
|
7149
|
+
empty.textContent = 'Sin mensajes en este par.';
|
|
7150
|
+
panel.appendChild(empty);
|
|
7151
|
+
}
|
|
7152
|
+
} catch (err) {
|
|
7153
|
+
panel.innerHTML = '<div class="act-empty">Error: ' + actEsc(err.message || String(err)) + '</div>';
|
|
7154
|
+
}
|
|
7155
|
+
}
|
|
7156
|
+
|
|
7157
|
+
async function activityPopulateChatSlugs() {
|
|
7158
|
+
const select = yfActivity.modal && yfActivity.modal.querySelector('[data-act-chat-slug]');
|
|
7159
|
+
if (!select) return;
|
|
7160
|
+
try {
|
|
7161
|
+
const r = await fetch('/api/agent/state');
|
|
7162
|
+
if (!r.ok) return;
|
|
7163
|
+
const body = await r.json();
|
|
7164
|
+
const agents = Array.isArray(body.agents) ? body.agents : [];
|
|
7165
|
+
select.innerHTML = '';
|
|
7166
|
+
for (const a of agents) {
|
|
7167
|
+
const opt = document.createElement('option');
|
|
7168
|
+
opt.value = a.slug;
|
|
7169
|
+
opt.textContent = (a.name || a.slug) + ' (' + (a.brain_tier || '?') + ')';
|
|
7170
|
+
select.appendChild(opt);
|
|
7171
|
+
}
|
|
7172
|
+
if (agents.length === 0) {
|
|
7173
|
+
const opt = document.createElement('option');
|
|
7174
|
+
opt.value = '';
|
|
7175
|
+
opt.textContent = 'no agents';
|
|
7176
|
+
select.appendChild(opt);
|
|
7177
|
+
}
|
|
7178
|
+
} catch (_err) { /* silent */ }
|
|
7179
|
+
}
|
|
7180
|
+
|
|
7181
|
+
async function activityChatSend() {
|
|
7182
|
+
const select = yfActivity.modal.querySelector('[data-act-chat-slug]');
|
|
7183
|
+
const input = yfActivity.modal.querySelector('[data-act-chat-text]');
|
|
7184
|
+
const dispBox = yfActivity.modal.querySelector('[data-act-chat-dispatch]');
|
|
7185
|
+
const result = yfActivity.modal.querySelector('[data-act-chat-result]');
|
|
7186
|
+
if (!select || !input || !result) return;
|
|
7187
|
+
const slug = String(select.value || '').trim();
|
|
7188
|
+
const text = String(input.value || '').trim();
|
|
7189
|
+
if (!slug || !text) { result.textContent = 'Elegi un agente + escribe texto antes de enviar.'; return; }
|
|
7190
|
+
result.textContent = 'Enviando...';
|
|
7191
|
+
try {
|
|
7192
|
+
const r = await fetch('/api/agent/inject', {
|
|
7193
|
+
method: 'POST',
|
|
7194
|
+
headers: { 'content-type': 'application/json' },
|
|
7195
|
+
body: JSON.stringify({
|
|
7196
|
+
slug, text,
|
|
7197
|
+
dispatch: dispBox ? dispBox.checked : true,
|
|
7198
|
+
/* Slice 3 N-projects -- enrutar al proyecto seleccionado
|
|
7199
|
+
* (default 'self'). */
|
|
7200
|
+
project_slug: yfActivity.projectFilter || 'self',
|
|
7201
|
+
}),
|
|
7202
|
+
});
|
|
7203
|
+
const body = await r.json();
|
|
7204
|
+
result.textContent = JSON.stringify(body, null, 2);
|
|
7205
|
+
input.value = '';
|
|
7206
|
+
/* Trigger a feed refresh so the user sees the new run. */
|
|
7207
|
+
if (yfActivity.activeTab === 'feed') setTimeout(() => activityRefresh(true), 800);
|
|
7208
|
+
} catch (err) {
|
|
7209
|
+
result.textContent = 'Error: ' + (err.message || String(err));
|
|
7210
|
+
}
|
|
7211
|
+
}
|
|
7212
|
+
|
|
7213
|
+
function activityFormatTs(ts) {
|
|
7214
|
+
if (!ts) return '';
|
|
7215
|
+
try {
|
|
7216
|
+
const d = new Date(ts);
|
|
7217
|
+
if (isNaN(d.getTime())) return String(ts);
|
|
7218
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
7219
|
+
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
7220
|
+
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
7221
|
+
return hh + ':' + mm + ':' + ss;
|
|
7222
|
+
} catch { return String(ts); }
|
|
7223
|
+
}
|
|
7224
|
+
|
|
7225
|
+
function actEsc(s) {
|
|
7226
|
+
return String(s == null ? '' : s)
|
|
7227
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
7228
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
7229
|
+
}
|
|
7230
|
+
|
|
7231
|
+
/* CSS.escape polyfill -- IE/legacy safe; modern browsers have it
|
|
7232
|
+
* native. Kebab-case slugs are safe under both. */
|
|
7233
|
+
function cssEscape(s) {
|
|
7234
|
+
if (typeof CSS !== 'undefined' && CSS && typeof CSS.escape === 'function') return CSS.escape(s);
|
|
7235
|
+
return String(s).replace(/[^a-z0-9_-]/gi, '\\$&');
|
|
7236
|
+
}
|
|
5879
7237
|
</script>
|
|
5880
7238
|
</body>
|
|
5881
7239
|
</html>`;
|