titan-agent 6.0.3 → 6.1.0-alpha.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.
- package/dist/agent/missionLifecycle.js +164 -0
- package/dist/agent/missionLifecycle.js.map +1 -0
- package/dist/agent/missionRoom.js +420 -0
- package/dist/agent/missionRoom.js.map +1 -0
- package/dist/agent/plays.js +286 -0
- package/dist/agent/plays.js.map +1 -0
- package/dist/agent/subAgent.js +18 -0
- package/dist/agent/subAgent.js.map +1 -1
- package/dist/gateway/routes/missions.js +258 -0
- package/dist/gateway/routes/missions.js.map +1 -0
- package/dist/gateway/server.js +7 -0
- package/dist/gateway/server.js.map +1 -1
- package/dist/providers/router.js +58 -17
- package/dist/providers/router.js.map +1 -1
- package/dist/utils/constants.js +5 -1
- package/dist/utils/constants.js.map +1 -1
- package/package.json +1 -1
- package/ui/dist/assets/{AuditPanel-DgKHxTPP.js → AuditPanel-DlrOwMmC.js} +1 -1
- package/ui/dist/assets/{AutonomyPanel-CGoGTTeQ.js → AutonomyPanel-t3AyhJuQ.js} +1 -1
- package/ui/dist/assets/{AutopilotPanel-BRB2iw62.js → AutopilotPanel-CkXUidc9.js} +1 -1
- package/ui/dist/assets/{AutoresearchPanel-C0CVdlZo.js → AutoresearchPanel-BRNJOGMA.js} +1 -1
- package/ui/dist/assets/{BackupPanel-DbGK-62d.js → BackupPanel-B2mY1UKX.js} +1 -1
- package/ui/dist/assets/{BrowserPanel-QBeYtxku.js → BrowserPanel-DXDRMGKz.js} +1 -1
- package/ui/dist/assets/{CPActivity-RKfcA14S.js → CPActivity-CVeHU0Au.js} +1 -1
- package/ui/dist/assets/{CPAgentDetail-DLQ-SLUv.js → CPAgentDetail-BRw7_tRm.js} +1 -1
- package/ui/dist/assets/{CPAgents-BvoWQVsN.js → CPAgents-CKhWzTcU.js} +1 -1
- package/ui/dist/assets/{CPApprovals-C4DBwT0d.js → CPApprovals-WFa6jiNt.js} +1 -1
- package/ui/dist/assets/{CPCosts-DWGuPs18.js → CPCosts-D93AnCj7.js} +1 -1
- package/ui/dist/assets/{CPDashboard-DLuJ3vMn.js → CPDashboard-CvhODeCV.js} +1 -1
- package/ui/dist/assets/{CPFiles-BeLI7pDx.js → CPFiles-CgtfEtsG.js} +1 -1
- package/ui/dist/assets/{CPGoals-9NeuC7Bf.js → CPGoals-DqgSsW6w.js} +1 -1
- package/ui/dist/assets/{CPInbox-QvoryAsM.js → CPInbox-DFj11Aru.js} +2 -2
- package/ui/dist/assets/{CPIssueDetail-e_XoGfNN.js → CPIssueDetail-Da5qqIwd.js} +1 -1
- package/ui/dist/assets/{CPIssues-CrfHaPp1.js → CPIssues-B189TcQh.js} +1 -1
- package/ui/dist/assets/{CPLayout-DFtDIu-l.js → CPLayout-BA2UX9Ti.js} +3 -3
- package/ui/dist/assets/{CPOrg-Bvgvg6TU.js → CPOrg-CHwXNgnE.js} +1 -1
- package/ui/dist/assets/{CPRuns-HSig_POk.js → CPRuns-Ccr53GNp.js} +1 -1
- package/ui/dist/assets/{CPSocial-DszBYcz3.js → CPSocial-1M785uYe.js} +2 -2
- package/ui/dist/assets/{ChannelsPanel-CA_33e6X.js → ChannelsPanel-Dsi042KM.js} +1 -1
- package/ui/dist/assets/{CheckpointsPanel-BWY5_she.js → CheckpointsPanel-pThrVMgP.js} +1 -1
- package/ui/dist/assets/{CommandPostHub-B9t1MQE-.js → CommandPostHub-BI6D__HO.js} +3 -3
- package/ui/dist/assets/{CronPanel-0GfnFx-2.js → CronPanel-qRCqXHme.js} +1 -1
- package/ui/dist/assets/{DataTable-Xubw7ZZ_.js → DataTable-B7N7fpsA.js} +1 -1
- package/ui/dist/assets/{DreamPanel-Cr_Qvz-y.js → DreamPanel-DeAVhHPQ.js} +1 -1
- package/ui/dist/assets/{EmptyState-SMpctu2L.js → EmptyState-DCUInvhw.js} +1 -1
- package/ui/dist/assets/{EvalHarnessPanel-mt32iK8b.js → EvalHarnessPanel-D0UIIxJJ.js} +2 -2
- package/ui/dist/assets/{EvalPanel-DxCpBDZ0.js → EvalPanel-C07RFM5W.js} +1 -1
- package/ui/dist/assets/{FilesPanel-GiPGeCFc.js → FilesPanel-B6rT7Uwh.js} +1 -1
- package/ui/dist/assets/{FleetPanel-BrXnJRTE.js → FleetPanel-DnUWKm7G.js} +1 -1
- package/ui/dist/assets/{HomelabPanel-Dvu42BHt.js → HomelabPanel-GGQd8Pdv.js} +1 -1
- package/ui/dist/assets/InfraView-D2wgTAiu.js +2 -0
- package/ui/dist/assets/{InlineEditableField-DLOUEEYu.js → InlineEditableField-DptCaN3d.js} +1 -1
- package/ui/dist/assets/{Input-B7EQI0I4.js → Input-r4--sBxM.js} +1 -1
- package/ui/dist/assets/{IntegrationsPanel-DZfRW7AO.js → IntegrationsPanel-Dyy1vSGD.js} +1 -1
- package/ui/dist/assets/IntelligenceView-CrKthu1Q.js +2 -0
- package/ui/dist/assets/{LearningPanel-BDHo-NOl.js → LearningPanel-B7S7fit-.js} +1 -1
- package/ui/dist/assets/{LogsPanel-Di2xx_p9.js → LogsPanel-_wLT8cme.js} +1 -1
- package/ui/dist/assets/{McpPanel-B4WBhj3t.js → McpPanel-C9v6p9lS.js} +1 -1
- package/ui/dist/assets/{MemoryGraphPanel-CVFpfV7R.js → MemoryGraphPanel-C5yVv0k1.js} +1 -1
- package/ui/dist/assets/{MemoryWikiPanel-BnzChGK9.js → MemoryWikiPanel-DotqXZUj.js} +1 -1
- package/ui/dist/assets/{MeshPanel-CWHtS8I_.js → MeshPanel-BuSqfgc9.js} +2 -2
- package/ui/dist/assets/MissionChat-F0r4jFwg.js +2 -0
- package/ui/dist/assets/MissionStart-BxroTO28.js +1 -0
- package/ui/dist/assets/{Modal-DvJUV13H.js → Modal-IUC2WVvY.js} +1 -1
- package/ui/dist/assets/{NvidiaPanel-95hBnxjC.js → NvidiaPanel-C8-3uH26.js} +1 -1
- package/ui/dist/assets/{OrganismPanel-BGjBnOvs.js → OrganismPanel-I-C_mXpG.js} +1 -1
- package/ui/dist/assets/{OverviewPanel-Dk1Q70Se.js → OverviewPanel---IJkSbz.js} +1 -1
- package/ui/dist/assets/{PageHeader-DDrb6I8n.js → PageHeader-DLF3sM0T.js} +1 -1
- package/ui/dist/assets/{PersonaProfilesPanel-DGejWnRB.js → PersonaProfilesPanel-DN1r3QFP.js} +3 -3
- package/ui/dist/assets/{PersonasPanel-DxrMDBTh.js → PersonasPanel-Di7CJ-Jt.js} +1 -1
- package/ui/dist/assets/{RecipesPanel-BHRS_0kN.js → RecipesPanel-CTLboeMF.js} +1 -1
- package/ui/dist/assets/{SecurityPanel-DkfHmxvT.js → SecurityPanel-BDuo7c8m.js} +1 -1
- package/ui/dist/assets/{SelfImprovePanel-DrsAjnc4.js → SelfImprovePanel-bpb1ASJc.js} +1 -1
- package/ui/dist/assets/{SelfProposalsPanel-BDCwQNxy.js → SelfProposalsPanel-B0ff_JH7.js} +1 -1
- package/ui/dist/assets/SessionsPanel-DoDBRcqv.js +1 -0
- package/ui/dist/assets/{SessionsTab-iqQ8K-uq.js → SessionsTab-C_skYx24.js} +1 -1
- package/ui/dist/assets/{SettingsPanel-FcAq4i6v.js → SettingsPanel-CRMpkEhE.js} +1 -1
- package/ui/dist/assets/SettingsView-CHp4_RNQ.js +2 -0
- package/ui/dist/assets/{SkeletonLoader-CRDu3-6Q.js → SkeletonLoader-SjxcUoQO.js} +1 -1
- package/ui/dist/assets/{SkillsPanel-C3ZK2ERk.js → SkillsPanel-CmleLPUA.js} +2 -2
- package/ui/dist/assets/{SomaView-BVQByvrz.js → SomaView-zMWl7un1.js} +1 -1
- package/ui/dist/assets/{StatCard-Bc0P7ZAM.js → StatCard-C0S9HSya.js} +1 -1
- package/ui/dist/assets/{StatusBadge-CD5xxHjI.js → StatusBadge-DwC4vVq_.js} +1 -1
- package/ui/dist/assets/{Tabs-CpP274I1.js → Tabs-MP7WGnSx.js} +1 -1
- package/ui/dist/assets/{TeamsPanel-Bko8t-L1.js → TeamsPanel-D5pmqVBh.js} +1 -1
- package/ui/dist/assets/{TelemetryPanel-BZQTTim6.js → TelemetryPanel-gEW6sknF.js} +1 -1
- package/ui/dist/assets/{TitanCanvas-CZ9SbUlP.js → TitanCanvas-D9ncG-Pn.js} +4 -4
- package/ui/dist/assets/{ToolsView-CFIz5Zz6.js → ToolsView-BzNl3_R_.js} +2 -2
- package/ui/dist/assets/{Tooltip-CVeAK0ZZ.js → Tooltip-BWy32ZNd.js} +1 -1
- package/ui/dist/assets/{TraceViewer-CTI2-r0i.js → TraceViewer-DgjJoOaY.js} +1 -1
- package/ui/dist/assets/{TrainingPanel-BFec-615.js → TrainingPanel-2HYwFSZe.js} +1 -1
- package/ui/dist/assets/{VoiceOverlay-LSUKsCN7.js → VoiceOverlay-CYhtmbjV.js} +1 -1
- package/ui/dist/assets/{VramPanel-CJneTxxb.js → VramPanel-DX50WQ5q.js} +1 -1
- package/ui/dist/assets/{WatchView-DsFOe9ns.js → WatchView-4MB9aOLd.js} +1 -1
- package/ui/dist/assets/{WorkTab-BzpzUgiy.js → WorkTab-D4T5ULLB.js} +1 -1
- package/ui/dist/assets/{WorkflowsPanel-CiM_4Cb3.js → WorkflowsPanel-1uxHOFjp.js} +1 -1
- package/ui/dist/assets/{arrow-left-B5CXNaIk.js → arrow-left-CdOv7kIt.js} +1 -1
- package/ui/dist/assets/{briefcase-CAsOYhH7.js → briefcase-TYgcuED2.js} +1 -1
- package/ui/dist/assets/{chart-column-C5TUkViO.js → chart-column-DQcPIU3h.js} +1 -1
- package/ui/dist/assets/{check-CgAgq8JZ.js → check-DfsOUz3Y.js} +1 -1
- package/ui/dist/assets/{chevron-down-HieTTETf.js → chevron-down-DRgMYEtF.js} +1 -1
- package/ui/dist/assets/{chevron-right-DonjB84W.js → chevron-right-C0YaHh6V.js} +1 -1
- package/ui/dist/assets/{chevron-up-BSRDbuA0.js → chevron-up-C2SLzSki.js} +1 -1
- package/ui/dist/assets/{circle-check-big-DzuchC-4.js → circle-check-big-DSBqvoJn.js} +1 -1
- package/ui/dist/assets/{clock-DaAt7jPK.js → clock-VjRAFhrr.js} +1 -1
- package/ui/dist/assets/{dollar-sign-CHo-CSz7.js → dollar-sign-HQolNaAR.js} +1 -1
- package/ui/dist/assets/{download-CLzgNsVN.js → download-DcG6V5_9.js} +1 -1
- package/ui/dist/assets/{external-link-Dq-gXAWi.js → external-link-Bxhf2WVL.js} +1 -1
- package/ui/dist/assets/{eye-off-CEY2IGhA.js → eye-off-D-_WNENc.js} +1 -1
- package/ui/dist/assets/{folder-BenNQiQE.js → folder-D2-iO0ab.js} +1 -1
- package/ui/dist/assets/{funnel-Dxmik6Ls.js → funnel-BZDXoKvs.js} +1 -1
- package/ui/dist/assets/{git-branch-C7Hc1_hh.js → git-branch-hg1pIJLh.js} +1 -1
- package/ui/dist/assets/{index-BNaZESGb.js → index-DRjETA57.js} +18 -18
- package/ui/dist/assets/index-kpX8IBEJ.css +1 -0
- package/ui/dist/assets/{layers-DvR5qUxC.js → layers-CDB9y2Vq.js} +1 -1
- package/ui/dist/assets/{legacy-Qcc8euuf.js → legacy-B1Y_SQxt.js} +1 -1
- package/ui/dist/assets/{lightbulb-ZbEGfVpn.js → lightbulb-CD7J4H8B.js} +1 -1
- package/ui/dist/assets/{list-todo-BFKOVzxo.js → list-todo-sdCn5iPf.js} +1 -1
- package/ui/dist/assets/{loader-circle-CpcxHf52.js → loader-circle-Q7cbISX7.js} +1 -1
- package/ui/dist/assets/missions-Cxrd_plx.js +1 -0
- package/ui/dist/assets/{network-xmc8fyEg.js → network-m2jGo-vd.js} +1 -1
- package/ui/dist/assets/{pause-DzuexF_t.js → pause-Gs7r3YYy.js} +1 -1
- package/ui/dist/assets/{play-BMx5X2Kb.js → play-C3GiJbmF.js} +1 -1
- package/ui/dist/assets/{plug-BII-8v4F.js → plug-D7OiODL1.js} +1 -1
- package/ui/dist/assets/{plus-Bc7CG41K.js → plus-D1-frTtD.js} +1 -1
- package/ui/dist/assets/{proxy-DkTAczox.js → proxy-BMBjtVer.js} +1 -1
- package/ui/dist/assets/{rotate-ccw-Bdg5wieT.js → rotate-ccw-BkYBGC9p.js} +1 -1
- package/ui/dist/assets/{save-DPl93H0B.js → save-CIjmkTDt.js} +1 -1
- package/ui/dist/assets/{search-Cj8ATqRF.js → search-CUnpLoKH.js} +1 -1
- package/ui/dist/assets/{send-DtssvT0u.js → send-BKnEov5w.js} +1 -1
- package/ui/dist/assets/{shield-check-pgaUFXYM.js → shield-check-Apij8nfc.js} +1 -1
- package/ui/dist/assets/{target-CUujoxoz.js → target-CQX_r1zc.js} +1 -1
- package/ui/dist/assets/{terminal-CIe7bf1C.js → terminal-Df5PAVO1.js} +1 -1
- package/ui/dist/assets/{toggle-right-Du7JxqNs.js → toggle-right-D9tzxF9r.js} +1 -1
- package/ui/dist/assets/{trash-2-qJTrlvIF.js → trash-2-xIA58TEg.js} +1 -1
- package/ui/dist/assets/{trending-up-zdi70rCI.js → trending-up-92t2CSmz.js} +1 -1
- package/ui/dist/assets/{trophy-BQWdmxE-.js → trophy-C46X_lBd.js} +1 -1
- package/ui/dist/assets/{users-BZoer_xv.js → users-CjUl0QSN.js} +1 -1
- package/ui/dist/assets/{wrench-zr5TlUlx.js → wrench-DRJuaP-O.js} +1 -1
- package/ui/dist/index.html +2 -2
- package/ui/dist/sw.js +1 -1
- package/ui/dist/assets/InfraView-UVUPjoCu.js +0 -2
- package/ui/dist/assets/IntelligenceView-Bk8rxP_6.js +0 -2
- package/ui/dist/assets/SessionsPanel-DZW5lmgf.js +0 -1
- package/ui/dist/assets/SettingsView-BgpaJGqz.js +0 -2
- package/ui/dist/assets/index-B6H_KNVX.css +0 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import logger from "../utils/logger.js";
|
|
3
|
+
import { createGoal } from "./goals.js";
|
|
4
|
+
import {
|
|
5
|
+
postAgentMessage,
|
|
6
|
+
setMemberState,
|
|
7
|
+
setStatus,
|
|
8
|
+
raiseQuestion,
|
|
9
|
+
updateArtifact,
|
|
10
|
+
recordCost
|
|
11
|
+
} from "./missionRoom.js";
|
|
12
|
+
const COMPONENT = "MissionLifecycle";
|
|
13
|
+
const lifecycles = /* @__PURE__ */ new Map();
|
|
14
|
+
async function startMissionWork(mission) {
|
|
15
|
+
try {
|
|
16
|
+
const goal = createGoal({
|
|
17
|
+
title: mission.goal,
|
|
18
|
+
description: `Mission ${mission.id}` + (mission.playId ? ` (play: ${mission.playId})` : ""),
|
|
19
|
+
tags: [
|
|
20
|
+
`mission:${mission.id}`,
|
|
21
|
+
mission.playId ? `play:${mission.playId}` : "play:generic"
|
|
22
|
+
]
|
|
23
|
+
});
|
|
24
|
+
logger.info(COMPONENT, `Mission ${mission.id} linked to goal ${goal.id} (play=${mission.playId})`);
|
|
25
|
+
for (const member of mission.team) {
|
|
26
|
+
setMemberState(mission.id, member.agentId, "working", "getting ready");
|
|
27
|
+
}
|
|
28
|
+
const cleanups = [];
|
|
29
|
+
cleanups.push(await wireAgentBusBridge(mission.id, mission.team.map((t) => t.agentId)));
|
|
30
|
+
cleanups.push(await wireApprovalBridge(mission.id, goal.id));
|
|
31
|
+
lifecycles.set(mission.id, cleanups);
|
|
32
|
+
return goal.id;
|
|
33
|
+
} catch (err) {
|
|
34
|
+
logger.error(COMPONENT, `Failed to start mission ${mission.id}: ${err.message}`);
|
|
35
|
+
setStatus(mission.id, "failed", `Couldn't start the team: ${err.message}`);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function handleUserMessage(missionId, content) {
|
|
40
|
+
try {
|
|
41
|
+
const { sendMessage } = await import("./messageBus.js");
|
|
42
|
+
const { getMission } = await import("./missionRoom.js");
|
|
43
|
+
const room = getMission(missionId);
|
|
44
|
+
const recipients = room?.team.map((t) => t.agentId) ?? [];
|
|
45
|
+
for (const to of recipients) {
|
|
46
|
+
sendMessage("user", to, content, { priority: "urgent" });
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
logger.debug(COMPONENT, `messageBus dispatch skipped: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function handleStatusChange(missionId, status) {
|
|
53
|
+
if (status !== "paused" && status !== "working") return;
|
|
54
|
+
try {
|
|
55
|
+
const room = (await import("./missionRoom.js")).getMission(missionId);
|
|
56
|
+
if (!room?.goalId) return;
|
|
57
|
+
const driver = await import("./goalDriver.js");
|
|
58
|
+
if (status === "paused" && typeof driver.pauseDriver === "function") {
|
|
59
|
+
driver.pauseDriver(room.goalId);
|
|
60
|
+
} else if (status === "working" && typeof driver.resumeDriverControl === "function") {
|
|
61
|
+
driver.resumeDriverControl(room.goalId);
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
logger.debug(COMPONENT, `Couldn't toggle driver state for ${missionId}: ${err.message}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function teardownMissionWork(missionId) {
|
|
68
|
+
const cleanups = lifecycles.get(missionId);
|
|
69
|
+
if (!cleanups) return;
|
|
70
|
+
for (const fn of cleanups) {
|
|
71
|
+
try {
|
|
72
|
+
fn();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
logger.debug(COMPONENT, `Cleanup threw: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
lifecycles.delete(missionId);
|
|
78
|
+
}
|
|
79
|
+
async function wireAgentBusBridge(missionId, expectedAgentIds) {
|
|
80
|
+
let unsubscribe = () => {
|
|
81
|
+
};
|
|
82
|
+
try {
|
|
83
|
+
const mod = await import("./subAgent.js");
|
|
84
|
+
if (typeof mod.onSubAgentEvent !== "function") {
|
|
85
|
+
return unsubscribe;
|
|
86
|
+
}
|
|
87
|
+
const known = new Set(expectedAgentIds);
|
|
88
|
+
unsubscribe = mod.onSubAgentEvent((ev) => {
|
|
89
|
+
const agentId = (ev.agentId ?? "").toLowerCase();
|
|
90
|
+
if (!known.has(agentId)) return;
|
|
91
|
+
switch (ev.type) {
|
|
92
|
+
case "agent_start":
|
|
93
|
+
setMemberState(missionId, agentId, "working", shortenActivity(ev.task));
|
|
94
|
+
break;
|
|
95
|
+
case "agent_progress":
|
|
96
|
+
if (ev.message) {
|
|
97
|
+
setMemberState(missionId, agentId, "working", shortenActivity(ev.message));
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
case "agent_message":
|
|
101
|
+
if (ev.content) {
|
|
102
|
+
postAgentMessage(missionId, agentId, ev.content, ev.actions);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case "artifact_chunk":
|
|
106
|
+
if (typeof ev.content === "string" && ev.content.length > 0) {
|
|
107
|
+
updateArtifact(missionId, agentId, ev.content, ev.summary ?? "updated");
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
case "cost":
|
|
111
|
+
if (typeof ev.tokens === "number" && typeof ev.usd === "number") {
|
|
112
|
+
recordCost(missionId, ev.tokens, ev.usd);
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
case "agent_done":
|
|
116
|
+
setMemberState(missionId, agentId, "idle", void 0);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
} catch (err) {
|
|
121
|
+
logger.debug(COMPONENT, `Agent bus bridge unavailable: ${err.message}`);
|
|
122
|
+
}
|
|
123
|
+
return unsubscribe;
|
|
124
|
+
}
|
|
125
|
+
async function wireApprovalBridge(missionId, goalId) {
|
|
126
|
+
let unsubscribe = () => {
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
const cp = await import("./commandPost.js");
|
|
130
|
+
if (typeof cp.onApprovalCreated !== "function") {
|
|
131
|
+
return unsubscribe;
|
|
132
|
+
}
|
|
133
|
+
unsubscribe = cp.onApprovalCreated((approval) => {
|
|
134
|
+
const payload = approval.payload ?? {};
|
|
135
|
+
if (payload.goalId !== goalId) return;
|
|
136
|
+
const agentId = String(payload.specialist ?? approval.requestedBy ?? "sage").toLowerCase();
|
|
137
|
+
const content = String(payload.question ?? approval.title ?? "Quick question.");
|
|
138
|
+
const quickReplies = Array.isArray(payload.quickReplies) ? payload.quickReplies.slice(0, 4) : [];
|
|
139
|
+
raiseQuestion({
|
|
140
|
+
missionId,
|
|
141
|
+
agentId,
|
|
142
|
+
content,
|
|
143
|
+
approvalId: approval.id,
|
|
144
|
+
quickReplies
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
} catch (err) {
|
|
148
|
+
logger.debug(COMPONENT, `Approval bridge unavailable: ${err.message}`);
|
|
149
|
+
}
|
|
150
|
+
return unsubscribe;
|
|
151
|
+
}
|
|
152
|
+
function shortenActivity(s) {
|
|
153
|
+
if (!s) return void 0;
|
|
154
|
+
const trimmed = s.trim();
|
|
155
|
+
if (trimmed.length <= 80) return trimmed;
|
|
156
|
+
return trimmed.slice(0, 77).trimEnd() + "\u2026";
|
|
157
|
+
}
|
|
158
|
+
export {
|
|
159
|
+
handleStatusChange,
|
|
160
|
+
handleUserMessage,
|
|
161
|
+
startMissionWork,
|
|
162
|
+
teardownMissionWork
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=missionLifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/agent/missionLifecycle.ts"],"sourcesContent":["/**\n * TITAN — Mission lifecycle adapter (v6.1.0)\n *\n * Connects a freshly-created Mission Room to the existing goal driver\n * + Command Post pipeline. When a mission is created:\n *\n * 1. Create a Goal (in the goals subsystem) with title = goal text,\n * tagged with the mission id and play id so the bridge below can\n * filter events back to the right room.\n * 2. Subscribe to the agent message bus so each specialist's tool\n * calls / answers / progress messages get rewritten as a chat\n * message in the mission thread.\n * 3. Subscribe to the Command Post approval queue so blocking\n * questions filed against this mission's goal show up as inline\n * question messages in the chat.\n *\n * The bridge is one-way (driver → mission room). User actions (replies\n * to questions, status toggles) go through the mission router and\n * propagate to the goal driver / Command Post via existing APIs.\n *\n * Idempotent: subscribing a second mission doesn't double-fire. We\n * track per-mission unsubscribe functions in `lifecycles`.\n */\nimport logger from '../utils/logger.js';\nimport { createGoal } from './goals.js';\nimport {\n postAgentMessage,\n setMemberState,\n setStatus,\n raiseQuestion,\n updateArtifact,\n recordCost,\n type MissionRoom,\n type MissionStatus,\n} from './missionRoom.js';\n\nconst COMPONENT = 'MissionLifecycle';\n\n// Per-mission cleanup handlers. When a mission completes, we tear down\n// these subscriptions so the bus doesn't leak listeners.\nconst lifecycles = new Map<string, Array<() => void>>();\n\n/** The wire that the gateway / missions router calls when a mission is\n * created. Returns the linked goal id. */\nexport async function startMissionWork(mission: MissionRoom): Promise<string | null> {\n try {\n // Tag the goal with the mission + play id so message-bus event\n // bridges can filter \"which mission did this belong to.\"\n const goal = createGoal({\n title: mission.goal,\n description: `Mission ${mission.id}` + (mission.playId ? ` (play: ${mission.playId})` : ''),\n tags: [\n `mission:${mission.id}`,\n mission.playId ? `play:${mission.playId}` : 'play:generic',\n ],\n });\n logger.info(COMPONENT, `Mission ${mission.id} linked to goal ${goal.id} (play=${mission.playId})`);\n\n // Set every team member to working so the team strip lights up\n // immediately. As the goal driver picks subtasks for real\n // specialists, the message bus bridge below will narrow each\n // member's `currentActivity` to what they're really doing.\n for (const member of mission.team) {\n setMemberState(mission.id, member.agentId, 'working', 'getting ready');\n }\n\n // Wire the agent message bus → mission room bridge for THIS mission.\n const cleanups: Array<() => void> = [];\n cleanups.push(await wireAgentBusBridge(mission.id, mission.team.map(t => t.agentId)));\n cleanups.push(await wireApprovalBridge(mission.id, goal.id));\n lifecycles.set(mission.id, cleanups);\n\n return goal.id;\n } catch (err) {\n logger.error(COMPONENT, `Failed to start mission ${mission.id}: ${(err as Error).message}`);\n setStatus(mission.id, 'failed', `Couldn't start the team: ${(err as Error).message}`);\n return null;\n }\n}\n\n/** Called when the UI posts a user message into an active mission. For\n * v1 the user message is treated as supplemental context (recorded in\n * the chat, queued for the next agent loop). Real-time injection into\n * a running specialist's prompt is a v6.1.1 follow-up. */\nexport async function handleUserMessage(missionId: string, content: string): Promise<void> {\n // For v1 we use the existing messageBus. We don't know which\n // specialist is \"current\" — broadcast to every registered member's\n // mailbox so whichever one is in-flight picks the note up at the\n // start of its next round. Mailbox names match the specialist ids.\n try {\n const { sendMessage } = await import('./messageBus.js');\n const { getMission } = await import('./missionRoom.js');\n const room = getMission(missionId);\n const recipients = room?.team.map(t => t.agentId) ?? [];\n for (const to of recipients) {\n // sendMessage(from, to, content, opts?)\n sendMessage('user', to, content, { priority: 'urgent' });\n }\n } catch (err) {\n logger.debug(COMPONENT, `messageBus dispatch skipped: ${(err as Error).message}`);\n }\n}\n\n/** Called when the UI toggles status (pause / resume). */\nexport async function handleStatusChange(missionId: string, status: MissionStatus): Promise<void> {\n if (status !== 'paused' && status !== 'working') return;\n try {\n // Look up the linked goal and toggle its driver state via the\n // existing user-controls surface. We import dynamically so the\n // lifecycle module doesn't have a top-level goal-driver\n // dependency (matches the rest of the file's pattern).\n const room = (await import('./missionRoom.js')).getMission(missionId);\n if (!room?.goalId) return;\n const driver = await import('./goalDriver.js');\n if (status === 'paused' && typeof driver.pauseDriver === 'function') {\n driver.pauseDriver(room.goalId);\n } else if (status === 'working' && typeof driver.resumeDriverControl === 'function') {\n driver.resumeDriverControl(room.goalId);\n }\n } catch (err) {\n logger.debug(COMPONENT, `Couldn't toggle driver state for ${missionId}: ${(err as Error).message}`);\n }\n}\n\n/** Tear down a mission's bridges. Called when the goal completes,\n * fails, or the mission is deleted. */\nexport function teardownMissionWork(missionId: string): void {\n const cleanups = lifecycles.get(missionId);\n if (!cleanups) return;\n for (const fn of cleanups) {\n try { fn(); }\n catch (err) { logger.debug(COMPONENT, `Cleanup threw: ${(err as Error).message}`); }\n }\n lifecycles.delete(missionId);\n}\n\n// ── Bridges ────────────────────────────────────────────────────────\n\n/** Subscribe to the agent event bus so specialist output becomes chat\n * messages in the mission room. Returns an unsubscribe. */\nasync function wireAgentBusBridge(missionId: string, expectedAgentIds: string[]): Promise<() => void> {\n let unsubscribe: () => void = () => { /* default no-op */ };\n try {\n // We import the existing sub-agent bus lazily; the gateway uses\n // it to broadcast specialist progress events for the dashboard.\n // We attach the same way, but filter to OUR mission's specialists\n // and route their outputs to postAgentMessage.\n const mod = await import('./subAgent.js') as unknown as {\n onSubAgentEvent?: (handler: (ev: SubAgentBusEvent) => void) => () => void;\n };\n if (typeof mod.onSubAgentEvent !== 'function') {\n return unsubscribe;\n }\n const known = new Set(expectedAgentIds);\n unsubscribe = mod.onSubAgentEvent((ev: SubAgentBusEvent) => {\n // Filter: only events from agents on THIS mission's team.\n const agentId = (ev.agentId ?? '').toLowerCase();\n if (!known.has(agentId)) return;\n switch (ev.type) {\n case 'agent_start':\n setMemberState(missionId, agentId, 'working', shortenActivity(ev.task));\n break;\n case 'agent_progress':\n if (ev.message) {\n setMemberState(missionId, agentId, 'working', shortenActivity(ev.message));\n }\n break;\n case 'agent_message':\n if (ev.content) {\n postAgentMessage(missionId, agentId, ev.content, ev.actions);\n }\n break;\n case 'artifact_chunk':\n if (typeof ev.content === 'string' && ev.content.length > 0) {\n // Caller passes the FULL latest content (caller knows\n // the artifact format, not us). We snapshot + diff\n // inside updateArtifact.\n updateArtifact(missionId, agentId, ev.content, ev.summary ?? 'updated');\n }\n break;\n case 'cost':\n if (typeof ev.tokens === 'number' && typeof ev.usd === 'number') {\n recordCost(missionId, ev.tokens, ev.usd);\n }\n break;\n case 'agent_done':\n setMemberState(missionId, agentId, 'idle', undefined);\n break;\n }\n });\n } catch (err) {\n logger.debug(COMPONENT, `Agent bus bridge unavailable: ${(err as Error).message}`);\n }\n return unsubscribe;\n}\n\n/** Subscribe to the Command Post approval queue and translate any\n * approval filed against this mission's goal into an inline question\n * message. */\nasync function wireApprovalBridge(missionId: string, goalId: string): Promise<() => void> {\n let unsubscribe: () => void = () => { /* default no-op */ };\n try {\n const cp = await import('./commandPost.js') as unknown as {\n onApprovalCreated?: (handler: (approval: CPApprovalLike) => void) => () => void;\n };\n if (typeof cp.onApprovalCreated !== 'function') {\n return unsubscribe;\n }\n unsubscribe = cp.onApprovalCreated((approval: CPApprovalLike) => {\n // Filter: only approvals tied to this mission's goal.\n const payload = (approval.payload ?? {}) as Record<string, unknown>;\n if (payload.goalId !== goalId) return;\n const agentId = String(payload.specialist ?? approval.requestedBy ?? 'sage').toLowerCase();\n const content = String(payload.question ?? approval.title ?? 'Quick question.');\n const quickReplies = Array.isArray(payload.quickReplies)\n ? (payload.quickReplies as string[]).slice(0, 4)\n : [];\n raiseQuestion({\n missionId,\n agentId,\n content,\n approvalId: approval.id,\n quickReplies,\n });\n });\n } catch (err) {\n logger.debug(COMPONENT, `Approval bridge unavailable: ${(err as Error).message}`);\n }\n return unsubscribe;\n}\n\n// ── Types shared with the subAgent / commandPost modules ──────────\n//\n// We don't import their full types here because that would couple the\n// mission lifecycle to internal shapes the rest of the codebase might\n// reshape. We declare a minimal surface and rely on duck-typing.\n\ninterface SubAgentBusEvent {\n type: 'agent_start' | 'agent_progress' | 'agent_message' | 'artifact_chunk' | 'cost' | 'agent_done';\n agentId?: string;\n task?: string;\n message?: string;\n content?: string;\n summary?: string;\n actions?: { name: string; detail?: string }[];\n tokens?: number;\n usd?: number;\n}\n\ninterface CPApprovalLike {\n id: string;\n title?: string;\n requestedBy?: string;\n payload?: Record<string, unknown>;\n}\n\n// ── Helpers ────────────────────────────────────────────────────────\n\nfunction shortenActivity(s: string | undefined): string | undefined {\n if (!s) return undefined;\n const trimmed = s.trim();\n if (trimmed.length <= 80) return trimmed;\n return trimmed.slice(0, 77).trimEnd() + '…';\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, renameSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { EventEmitter } from "events";
|
|
5
|
+
import logger from "../utils/logger.js";
|
|
6
|
+
import { MISSIONS_DIR } from "../utils/constants.js";
|
|
7
|
+
import { shortId } from "../utils/helpers.js";
|
|
8
|
+
const COMPONENT = "MissionRoom";
|
|
9
|
+
const SCHEMA_VERSION = 1;
|
|
10
|
+
const MEMBER_META = {
|
|
11
|
+
scout: { role: "the researcher", color: "#6ea8ff" },
|
|
12
|
+
builder: { role: "the writer", color: "#ff9a4a" },
|
|
13
|
+
// mockup-friendly: in code-missions Builder writes code
|
|
14
|
+
writer: { role: "the wordsmith", color: "#ff9a4a" },
|
|
15
|
+
analyst: { role: "the numbers nerd", color: "#4adbb5" },
|
|
16
|
+
sage: { role: "the skeptic", color: "#ff5d6c" },
|
|
17
|
+
default: { role: "the generalist", color: "#b07cff" }
|
|
18
|
+
};
|
|
19
|
+
function memberMetaFor(agentId) {
|
|
20
|
+
return MEMBER_META[agentId] ?? MEMBER_META.default;
|
|
21
|
+
}
|
|
22
|
+
const bus = new EventEmitter();
|
|
23
|
+
bus.setMaxListeners(256);
|
|
24
|
+
function onMissionEvent(handler) {
|
|
25
|
+
const wrapped = (ev) => {
|
|
26
|
+
try {
|
|
27
|
+
handler(ev);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
logger.warn(COMPONENT, `Mission event handler threw: ${err.message}`);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
bus.on("mission", wrapped);
|
|
33
|
+
return () => bus.off("mission", wrapped);
|
|
34
|
+
}
|
|
35
|
+
function emit(kind, missionId, data) {
|
|
36
|
+
const ev = { kind, missionId, at: (/* @__PURE__ */ new Date()).toISOString(), data };
|
|
37
|
+
bus.emit("mission", ev);
|
|
38
|
+
}
|
|
39
|
+
function ensureDir() {
|
|
40
|
+
try {
|
|
41
|
+
mkdirSync(MISSIONS_DIR, { recursive: true });
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function missionPath(id) {
|
|
46
|
+
return join(MISSIONS_DIR, `${id}.json`);
|
|
47
|
+
}
|
|
48
|
+
function saveRoom(room) {
|
|
49
|
+
ensureDir();
|
|
50
|
+
room.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
51
|
+
const path = missionPath(room.id);
|
|
52
|
+
try {
|
|
53
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
54
|
+
writeFileSync(path + ".tmp", JSON.stringify(room, null, 2));
|
|
55
|
+
renameSync(path + ".tmp", path);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
logger.warn(COMPONENT, `Persist mission ${room.id} failed: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function loadRoomFromDisk(id) {
|
|
61
|
+
const path = missionPath(id);
|
|
62
|
+
if (!existsSync(path)) return null;
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
65
|
+
if (parsed.schemaVersion !== SCHEMA_VERSION) {
|
|
66
|
+
logger.warn(COMPONENT, `Mission ${id} has unknown schemaVersion=${parsed.schemaVersion} \u2014 ignoring`);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return parsed;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
logger.warn(COMPONENT, `Could not parse mission ${id}: ${err.message}`);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const cache = /* @__PURE__ */ new Map();
|
|
76
|
+
function getOrLoad(id) {
|
|
77
|
+
const cached = cache.get(id);
|
|
78
|
+
if (cached) return cached;
|
|
79
|
+
const fresh = loadRoomFromDisk(id);
|
|
80
|
+
if (fresh) cache.set(id, fresh);
|
|
81
|
+
return fresh;
|
|
82
|
+
}
|
|
83
|
+
function commit(room) {
|
|
84
|
+
cache.set(room.id, room);
|
|
85
|
+
saveRoom(room);
|
|
86
|
+
return room;
|
|
87
|
+
}
|
|
88
|
+
function createMission(opts) {
|
|
89
|
+
const id = shortId();
|
|
90
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
91
|
+
const team = (opts.members ?? []).map((m) => {
|
|
92
|
+
const meta = memberMetaFor(m.agentId);
|
|
93
|
+
return {
|
|
94
|
+
agentId: m.agentId,
|
|
95
|
+
name: m.name,
|
|
96
|
+
role: meta.role,
|
|
97
|
+
color: meta.color,
|
|
98
|
+
state: "idle"
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
const room = {
|
|
102
|
+
schemaVersion: SCHEMA_VERSION,
|
|
103
|
+
id,
|
|
104
|
+
goal: opts.goal,
|
|
105
|
+
status: team.length > 0 ? "working" : "forming",
|
|
106
|
+
playId: opts.playId,
|
|
107
|
+
ownerId: opts.ownerId,
|
|
108
|
+
team,
|
|
109
|
+
artifact: {
|
|
110
|
+
format: "markdown",
|
|
111
|
+
content: "",
|
|
112
|
+
snapshots: [],
|
|
113
|
+
updatedAt: now
|
|
114
|
+
},
|
|
115
|
+
messages: [
|
|
116
|
+
// Always seed with the user's mission as the first message.
|
|
117
|
+
{
|
|
118
|
+
id: shortId(),
|
|
119
|
+
at: now,
|
|
120
|
+
kind: "user",
|
|
121
|
+
content: opts.goal
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
cost: { tokens: 0, usd: 0 },
|
|
125
|
+
createdAt: now,
|
|
126
|
+
updatedAt: now
|
|
127
|
+
};
|
|
128
|
+
if (team.length > 0) {
|
|
129
|
+
const names = team.map((t) => t.name).join(", ");
|
|
130
|
+
room.messages.push({
|
|
131
|
+
id: shortId(),
|
|
132
|
+
at: now,
|
|
133
|
+
kind: "system",
|
|
134
|
+
tag: "team_formed",
|
|
135
|
+
content: `Team formed \u2014 ${names}.`
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
commit(room);
|
|
139
|
+
emit("mission_created", id, { goal: opts.goal, playId: opts.playId });
|
|
140
|
+
if (team.length > 0) emit("team_formed", id, { team: team.map((t) => t.agentId) });
|
|
141
|
+
return room;
|
|
142
|
+
}
|
|
143
|
+
function getMission(id) {
|
|
144
|
+
return getOrLoad(id);
|
|
145
|
+
}
|
|
146
|
+
function listMissions(opts = {}) {
|
|
147
|
+
ensureDir();
|
|
148
|
+
let entries;
|
|
149
|
+
try {
|
|
150
|
+
entries = readdirSync(MISSIONS_DIR).filter((f) => f.endsWith(".json"));
|
|
151
|
+
} catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const f of entries) {
|
|
156
|
+
const id = f.slice(0, -5);
|
|
157
|
+
const room = getOrLoad(id);
|
|
158
|
+
if (!room) continue;
|
|
159
|
+
if (opts.ownerId && room.ownerId && room.ownerId !== opts.ownerId) continue;
|
|
160
|
+
if (!opts.full) {
|
|
161
|
+
out.push({
|
|
162
|
+
...room,
|
|
163
|
+
messages: [],
|
|
164
|
+
artifact: { ...room.artifact, content: "", snapshots: [] }
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
out.push(room);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return out.sort((a, b) => a.updatedAt < b.updatedAt ? 1 : -1);
|
|
171
|
+
}
|
|
172
|
+
function setTeam(missionId, members) {
|
|
173
|
+
const room = getOrLoad(missionId);
|
|
174
|
+
if (!room) return null;
|
|
175
|
+
room.team = members.map((m) => {
|
|
176
|
+
const meta = memberMetaFor(m.agentId);
|
|
177
|
+
return {
|
|
178
|
+
agentId: m.agentId,
|
|
179
|
+
name: m.name,
|
|
180
|
+
role: meta.role,
|
|
181
|
+
color: meta.color,
|
|
182
|
+
state: "idle"
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
if (room.status === "forming") room.status = "working";
|
|
186
|
+
room.messages.push({
|
|
187
|
+
id: shortId(),
|
|
188
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
189
|
+
kind: "system",
|
|
190
|
+
tag: "team_formed",
|
|
191
|
+
content: `Team formed \u2014 ${room.team.map((t) => t.name).join(", ")}.`
|
|
192
|
+
});
|
|
193
|
+
commit(room);
|
|
194
|
+
emit("team_formed", missionId, { team: room.team.map((t) => t.agentId) });
|
|
195
|
+
return room;
|
|
196
|
+
}
|
|
197
|
+
function setLinkedGoal(missionId, goalId) {
|
|
198
|
+
const room = getOrLoad(missionId);
|
|
199
|
+
if (!room) return null;
|
|
200
|
+
room.goalId = goalId;
|
|
201
|
+
commit(room);
|
|
202
|
+
return room;
|
|
203
|
+
}
|
|
204
|
+
function setMemberState(missionId, agentId, state, currentActivity) {
|
|
205
|
+
const room = getOrLoad(missionId);
|
|
206
|
+
if (!room) return null;
|
|
207
|
+
const member = room.team.find((m) => m.agentId === agentId);
|
|
208
|
+
if (!member) return room;
|
|
209
|
+
const changed = member.state !== state || member.currentActivity !== currentActivity;
|
|
210
|
+
member.state = state;
|
|
211
|
+
if (currentActivity !== void 0) member.currentActivity = currentActivity;
|
|
212
|
+
member.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
213
|
+
if (changed) {
|
|
214
|
+
commit(room);
|
|
215
|
+
emit("agent_state_changed", missionId, { agentId, state, currentActivity });
|
|
216
|
+
}
|
|
217
|
+
return room;
|
|
218
|
+
}
|
|
219
|
+
function postAgentMessage(missionId, agentId, content, actions) {
|
|
220
|
+
const room = getOrLoad(missionId);
|
|
221
|
+
if (!room) return null;
|
|
222
|
+
const member = room.team.find((m) => m.agentId === agentId);
|
|
223
|
+
const meta = memberMetaFor(agentId);
|
|
224
|
+
const msg = {
|
|
225
|
+
id: shortId(),
|
|
226
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
227
|
+
kind: "agent",
|
|
228
|
+
from: {
|
|
229
|
+
agentId,
|
|
230
|
+
name: member?.name ?? agentId,
|
|
231
|
+
role: meta.role,
|
|
232
|
+
color: meta.color
|
|
233
|
+
},
|
|
234
|
+
content,
|
|
235
|
+
actions
|
|
236
|
+
};
|
|
237
|
+
room.messages.push(msg);
|
|
238
|
+
commit(room);
|
|
239
|
+
emit("message_added", missionId, { message: msg });
|
|
240
|
+
return msg;
|
|
241
|
+
}
|
|
242
|
+
function postUserMessage(missionId, content) {
|
|
243
|
+
const room = getOrLoad(missionId);
|
|
244
|
+
if (!room) return null;
|
|
245
|
+
const msg = {
|
|
246
|
+
id: shortId(),
|
|
247
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
248
|
+
kind: "user",
|
|
249
|
+
content
|
|
250
|
+
};
|
|
251
|
+
room.messages.push(msg);
|
|
252
|
+
commit(room);
|
|
253
|
+
emit("message_added", missionId, { message: msg });
|
|
254
|
+
return msg;
|
|
255
|
+
}
|
|
256
|
+
function postSystemMessage(missionId, content, tag) {
|
|
257
|
+
const room = getOrLoad(missionId);
|
|
258
|
+
if (!room) return null;
|
|
259
|
+
const msg = {
|
|
260
|
+
id: shortId(),
|
|
261
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
262
|
+
kind: "system",
|
|
263
|
+
content,
|
|
264
|
+
tag
|
|
265
|
+
};
|
|
266
|
+
room.messages.push(msg);
|
|
267
|
+
commit(room);
|
|
268
|
+
emit("message_added", missionId, { message: msg });
|
|
269
|
+
return msg;
|
|
270
|
+
}
|
|
271
|
+
function raiseQuestion(opts) {
|
|
272
|
+
const room = getOrLoad(opts.missionId);
|
|
273
|
+
if (!room) return null;
|
|
274
|
+
const member = room.team.find((m) => m.agentId === opts.agentId);
|
|
275
|
+
const meta = memberMetaFor(opts.agentId);
|
|
276
|
+
const msg = {
|
|
277
|
+
id: shortId(),
|
|
278
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
279
|
+
kind: "question",
|
|
280
|
+
from: {
|
|
281
|
+
agentId: opts.agentId,
|
|
282
|
+
name: member?.name ?? opts.agentId,
|
|
283
|
+
role: meta.role,
|
|
284
|
+
color: meta.color
|
|
285
|
+
},
|
|
286
|
+
content: opts.content,
|
|
287
|
+
approvalId: opts.approvalId,
|
|
288
|
+
quickReplies: opts.quickReplies ?? []
|
|
289
|
+
};
|
|
290
|
+
room.messages.push(msg);
|
|
291
|
+
if (member) {
|
|
292
|
+
member.state = "blocked";
|
|
293
|
+
member.currentActivity = "needs you";
|
|
294
|
+
}
|
|
295
|
+
room.status = "blocked";
|
|
296
|
+
commit(room);
|
|
297
|
+
emit("question_raised", opts.missionId, { message: msg });
|
|
298
|
+
emit("agent_state_changed", opts.missionId, { agentId: opts.agentId, state: "blocked" });
|
|
299
|
+
emit("status_changed", opts.missionId, { status: "blocked" });
|
|
300
|
+
return msg;
|
|
301
|
+
}
|
|
302
|
+
function answerQuestion(missionId, approvalId, answer) {
|
|
303
|
+
const room = getOrLoad(missionId);
|
|
304
|
+
if (!room) return null;
|
|
305
|
+
const q = room.messages.find(
|
|
306
|
+
(m) => m.kind === "question" && m.approvalId === approvalId
|
|
307
|
+
);
|
|
308
|
+
if (!q) return room;
|
|
309
|
+
q.answer = { content: answer, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
310
|
+
const member = room.team.find((m) => m.agentId === q.from.agentId);
|
|
311
|
+
if (member) {
|
|
312
|
+
member.state = "working";
|
|
313
|
+
member.currentActivity = void 0;
|
|
314
|
+
}
|
|
315
|
+
const stillBlocked = room.team.some((m) => m.state === "blocked");
|
|
316
|
+
if (!stillBlocked && room.status === "blocked") room.status = "working";
|
|
317
|
+
commit(room);
|
|
318
|
+
emit("question_answered", missionId, { approvalId, answer });
|
|
319
|
+
if (member) emit("agent_state_changed", missionId, { agentId: member.agentId, state: "working" });
|
|
320
|
+
if (!stillBlocked) emit("status_changed", missionId, { status: room.status });
|
|
321
|
+
return room;
|
|
322
|
+
}
|
|
323
|
+
function updateArtifact(missionId, agentId, content, summary) {
|
|
324
|
+
const room = getOrLoad(missionId);
|
|
325
|
+
if (!room) return null;
|
|
326
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
327
|
+
const member = room.team.find((m) => m.agentId === agentId);
|
|
328
|
+
const wordCount = content.trim() === "" ? 0 : content.trim().split(/\s+/).length;
|
|
329
|
+
const priorSnapshot = {
|
|
330
|
+
at: room.artifact.updatedAt,
|
|
331
|
+
content: room.artifact.content,
|
|
332
|
+
by: agentId,
|
|
333
|
+
summary,
|
|
334
|
+
wordCount: room.artifact.content.trim() === "" ? 0 : room.artifact.content.trim().split(/\s+/).length
|
|
335
|
+
};
|
|
336
|
+
room.artifact.snapshots.push(priorSnapshot);
|
|
337
|
+
if (room.artifact.snapshots.length > 16) {
|
|
338
|
+
room.artifact.snapshots = room.artifact.snapshots.slice(-16);
|
|
339
|
+
}
|
|
340
|
+
room.artifact.content = content;
|
|
341
|
+
room.artifact.updatedAt = now;
|
|
342
|
+
const marker = {
|
|
343
|
+
id: shortId(),
|
|
344
|
+
at: now,
|
|
345
|
+
kind: "artifact_update",
|
|
346
|
+
by: { agentId, name: member?.name ?? agentId },
|
|
347
|
+
summary,
|
|
348
|
+
wordCount
|
|
349
|
+
};
|
|
350
|
+
room.messages.push(marker);
|
|
351
|
+
commit(room);
|
|
352
|
+
emit("artifact_updated", missionId, { summary, by: agentId, wordCount });
|
|
353
|
+
emit("message_added", missionId, { message: marker });
|
|
354
|
+
return room;
|
|
355
|
+
}
|
|
356
|
+
function recordCost(missionId, tokens, usd) {
|
|
357
|
+
const room = getOrLoad(missionId);
|
|
358
|
+
if (!room) return null;
|
|
359
|
+
room.cost.tokens += Math.max(0, Math.floor(tokens));
|
|
360
|
+
room.cost.usd += Math.max(0, usd);
|
|
361
|
+
commit(room);
|
|
362
|
+
emit("cost_changed", missionId, { ...room.cost });
|
|
363
|
+
return room;
|
|
364
|
+
}
|
|
365
|
+
function setStatus(missionId, status, note) {
|
|
366
|
+
const room = getOrLoad(missionId);
|
|
367
|
+
if (!room) return null;
|
|
368
|
+
if (room.status === status) return room;
|
|
369
|
+
room.status = status;
|
|
370
|
+
if (note) {
|
|
371
|
+
room.messages.push({
|
|
372
|
+
id: shortId(),
|
|
373
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
374
|
+
kind: "system",
|
|
375
|
+
tag: status,
|
|
376
|
+
content: note
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
commit(room);
|
|
380
|
+
emit("status_changed", missionId, { status, note });
|
|
381
|
+
return room;
|
|
382
|
+
}
|
|
383
|
+
function deleteMission(missionId) {
|
|
384
|
+
const path = missionPath(missionId);
|
|
385
|
+
if (!existsSync(path)) return false;
|
|
386
|
+
try {
|
|
387
|
+
const tomb = path + ".deleted";
|
|
388
|
+
renameSync(path, tomb);
|
|
389
|
+
cache.delete(missionId);
|
|
390
|
+
emit("mission_deleted", missionId, {});
|
|
391
|
+
return true;
|
|
392
|
+
} catch (err) {
|
|
393
|
+
logger.warn(COMPONENT, `Delete mission ${missionId} failed: ${err.message}`);
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function _resetMissionCacheForTests() {
|
|
398
|
+
cache.clear();
|
|
399
|
+
}
|
|
400
|
+
export {
|
|
401
|
+
_resetMissionCacheForTests,
|
|
402
|
+
answerQuestion,
|
|
403
|
+
createMission,
|
|
404
|
+
deleteMission,
|
|
405
|
+
getMission,
|
|
406
|
+
listMissions,
|
|
407
|
+
memberMetaFor,
|
|
408
|
+
onMissionEvent,
|
|
409
|
+
postAgentMessage,
|
|
410
|
+
postSystemMessage,
|
|
411
|
+
postUserMessage,
|
|
412
|
+
raiseQuestion,
|
|
413
|
+
recordCost,
|
|
414
|
+
setLinkedGoal,
|
|
415
|
+
setMemberState,
|
|
416
|
+
setStatus,
|
|
417
|
+
setTeam,
|
|
418
|
+
updateArtifact
|
|
419
|
+
};
|
|
420
|
+
//# sourceMappingURL=missionRoom.js.map
|