agentlas 0.9.9 → 1.0.1
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/CHANGELOG.md +92 -0
- package/README.md +319 -342
- package/bin/agentlas.cjs +32 -9
- package/engine/agentlas-banner.cjs +24 -3
- package/engine/agentlas-composer.cjs +239 -31
- package/engine/agentlas-config.cjs +103 -7
- package/engine/agentlas-core-harness.cjs +48 -4
- package/engine/agentlas-evolution.cjs +3 -4
- package/engine/agentlas-i18n.cjs +88 -32
- package/engine/agentlas-input.cjs +234 -18
- package/engine/agentlas-memory-governance.cjs +3 -5
- package/engine/agentlas-memory-import.cjs +3 -3
- package/engine/agentlas-native-host.cjs +200 -9
- package/engine/agentlas-onboard.cjs +112 -20
- package/engine/agentlas-permissions.cjs +14 -7
- package/engine/agentlas-sqlite-policy.cjs +34 -0
- package/engine/agentlas-ui.cjs +93 -42
- package/engine/agentlas-workforce.cjs +318 -8
- package/engine/agentlas-workload-routing.cjs +8 -3
- package/engine/agentlas.cjs +140 -12336
- package/engine/agents/files.cjs +61 -0
- package/engine/agents/import-local.cjs +237 -0
- package/engine/agents/registry.cjs +158 -0
- package/engine/agents/router.cjs +565 -0
- package/engine/agents/routes.cjs +43 -0
- package/engine/architecture.data.json +2 -2
- package/engine/automation/daemon.cjs +335 -0
- package/engine/automation/schedule.cjs +181 -0
- package/engine/automation/store.cjs +209 -0
- package/engine/cloud/auth.cjs +279 -0
- package/engine/cloud/hub-client.cjs +239 -0
- package/engine/cloud-assets/cargo.cjs +49 -0
- package/engine/cloud-assets/cas.cjs +235 -0
- package/engine/cloud-assets/commands.cjs +273 -0
- package/engine/cloud-assets/package.cjs +936 -0
- package/engine/cloud-assets/restore.cjs +172 -0
- package/engine/cloud-assets/state.cjs +268 -0
- package/engine/commands/automation.cjs +195 -0
- package/engine/commands/billing.cjs +96 -0
- package/engine/commands/browser.cjs +20 -0
- package/engine/commands/build.cjs +33 -0
- package/engine/commands/call.cjs +24 -0
- package/engine/commands/career-graph.cjs +51 -0
- package/engine/commands/cd.cjs +22 -0
- package/engine/commands/chat.cjs +12 -0
- package/engine/commands/chats.cjs +28 -0
- package/engine/commands/cloud.cjs +17 -0
- package/engine/commands/connect.cjs +20 -0
- package/engine/commands/context.cjs +66 -0
- package/engine/commands/creds.cjs +203 -0
- package/engine/commands/doctor.cjs +68 -0
- package/engine/commands/env.cjs +33 -0
- package/engine/commands/evolve.cjs +23 -0
- package/engine/commands/experience.cjs +34 -0
- package/engine/commands/film.cjs +8 -0
- package/engine/commands/firm.cjs +115 -0
- package/engine/commands/help.cjs +65 -0
- package/engine/commands/hep.cjs +23 -0
- package/engine/commands/import.cjs +35 -0
- package/engine/commands/index.cjs +136 -0
- package/engine/commands/install.cjs +27 -0
- package/engine/commands/journal.cjs +31 -0
- package/engine/commands/legacy-network.cjs +29 -0
- package/engine/commands/list.cjs +49 -0
- package/engine/commands/login.cjs +68 -0
- package/engine/commands/logout.cjs +28 -0
- package/engine/commands/mcp.cjs +91 -0
- package/engine/commands/memory.cjs +21 -0
- package/engine/commands/multimodal.cjs +91 -0
- package/engine/commands/native.cjs +34 -0
- package/engine/commands/netadmin.cjs +31 -0
- package/engine/commands/oberon.cjs +70 -0
- package/engine/commands/ontology.cjs +24 -0
- package/engine/commands/open.cjs +48 -0
- package/engine/commands/plugin.cjs +101 -0
- package/engine/commands/project.cjs +47 -0
- package/engine/commands/research.cjs +36 -0
- package/engine/commands/route.cjs +37 -0
- package/engine/commands/run.cjs +149 -0
- package/engine/commands/search.cjs +55 -0
- package/engine/commands/setup.cjs +45 -0
- package/engine/commands/storm.cjs +75 -0
- package/engine/commands/swarm.cjs +75 -0
- package/engine/commands/telegram.cjs +32 -0
- package/engine/commands/uninstall.cjs +68 -0
- package/engine/commands/update.cjs +54 -0
- package/engine/commands/upload.cjs +18 -0
- package/engine/commands/usage.cjs +35 -0
- package/engine/commands/variant.cjs +25 -0
- package/engine/commands/version.cjs +9 -0
- package/engine/commands/whoami.cjs +38 -0
- package/engine/commands/workforce.cjs +103 -0
- package/engine/core/db.cjs +160 -0
- package/engine/core/paths.cjs +35 -0
- package/engine/experience/build.cjs +181 -0
- package/engine/experience/intents.cjs +492 -0
- package/engine/experience/runtime.cjs +242 -0
- package/engine/experience/variant.cjs +196 -0
- package/engine/firms/orchestrate.cjs +333 -0
- package/engine/hephaestus/runtime.cjs +697 -0
- package/engine/hub/install.cjs +872 -0
- package/engine/hub/plugins.cjs +213 -0
- package/engine/mcp/consent.cjs +289 -0
- package/engine/mcp/contract.cjs +202 -0
- package/engine/mcp/index.cjs +43 -0
- package/engine/mcp/inventory.cjs +322 -0
- package/engine/mcp/plan.cjs +286 -0
- package/engine/mcp/probe.cjs +151 -0
- package/engine/memory-cli/curate.cjs +163 -0
- package/engine/oberon/common.cjs +69 -0
- package/engine/oberon/manifest.cjs +164 -0
- package/engine/oberon/outputs.cjs +70 -0
- package/engine/oberon/render.cjs +164 -0
- package/engine/project/career-graph.cjs +249 -0
- package/engine/project/credentials.cjs +262 -0
- package/engine/project/env-file.cjs +46 -0
- package/engine/project/index.cjs +27 -0
- package/engine/project/memory-context.cjs +453 -0
- package/engine/project/ontology.cjs +467 -0
- package/engine/project/paths.cjs +39 -0
- package/engine/project/seed.cjs +200 -0
- package/engine/project/state.cjs +403 -0
- package/engine/project/super-ontology-seed.json +3288 -0
- package/engine/runtimes/detect.cjs +54 -0
- package/engine/runtimes/overrides.cjs +139 -0
- package/engine/runtimes/resolve.cjs +64 -0
- package/engine/sessions/apply-fences.cjs +188 -0
- package/engine/sessions/fences.cjs +362 -0
- package/engine/sessions/orchestrator.cjs +170 -0
- package/engine/sessions/prompt.cjs +212 -0
- package/engine/sessions/session.cjs +245 -0
- package/engine/sessions/sink.cjs +54 -0
- package/engine/sessions/store.cjs +79 -0
- package/engine/storm/deps.cjs +88 -0
- package/engine/storm/storm.cjs +218 -0
- package/engine/storm/swarm.cjs +422 -0
- package/engine/ui/palette.cjs +105 -0
- package/engine/ui/renderer.cjs +85 -0
- package/engine/ui/repl.cjs +444 -0
- package/engine/workforce/capture.cjs +701 -0
- package/engine/workforce/deps.cjs +472 -0
- package/package.json +3 -7
- package/engine/agentlas-experience-mcp.cjs +0 -1709
- package/engine/agentlas-parity.cjs +0 -1499
- package/engine/agentlas-repl.cjs +0 -1780
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* firms/orchestrate — 회사(firm) 3-tier 위임 실행: CEO PLAN → DELEGATE → SYNTHESIZE.
|
|
4
|
+
*
|
|
5
|
+
* 데스크탑 electron/mcp/firm-orchestrator.ts(읽기 전용 참조)의 계약을 v2 세션 계층
|
|
6
|
+
* 위에 포팅했다. 실행 경로는 세션 계층 하나뿐이다(제2 spawn 경로 금지):
|
|
7
|
+
* - CEO = orch.spawn 메인 세션 (kind='user' 챗 — 회사 챗)
|
|
8
|
+
* - 본부(division) = orch.spawn(parentKey=CEO) 서브세션 (kind='division' +
|
|
9
|
+
* parent_chat_id — 데스크탑 division 서브챗과 동일 패턴, 앱에서도 같은 트리로 보인다)
|
|
10
|
+
*
|
|
11
|
+
* 흐름:
|
|
12
|
+
* 1. PLAN: CEO에게 위임 프로토콜(## Delegate 펜스)을 주입하고 task를 보낸다.
|
|
13
|
+
* 2. DELEGATE: 회신의 ## Delegate 펜스를 파싱, 매칭된 본부만 병렬 실행(전원 강제 금지).
|
|
14
|
+
* 한 본부의 실패는 격리한다 — 오류 문자열을 status:failed 로 표기해 종합에 넘긴다.
|
|
15
|
+
* 3. SYNTHESIZE: CEO 세션의 두 번째 턴이 본부 결과를 종합 — CEO 챗에 자동 영속된다.
|
|
16
|
+
* 펜스가 없으면 PLAN 회신이 곧 최종 답(CEO 단독 처리 — 데스크탑 동형).
|
|
17
|
+
*/
|
|
18
|
+
const { maxParallel } = require("../sessions/orchestrator.cjs");
|
|
19
|
+
const { rowToAgent } = require("../agents/registry.cjs");
|
|
20
|
+
|
|
21
|
+
const DELEGATE_HEADING = "## Delegate";
|
|
22
|
+
|
|
23
|
+
// ── ## Delegate 펜스 파싱 ──────────────────────────────────
|
|
24
|
+
// 정본은 engine/sessions/fences.cjs parseDelegateBlock — loadDelegateParser()가
|
|
25
|
+
// 존재 시 자동으로 그쪽을 집는다(스왑 지점). 아래 로컬 구현은 fences.cjs 가 없는
|
|
26
|
+
// 체크아웃(다른 세션 작업 중)을 위한 동형 폴백: 데스크탑 electron/mcp/delegate.ts
|
|
27
|
+
// parseDelegations 의 CJS 포팅(workload allocation 필드 없이 target/brief만).
|
|
28
|
+
function parseDelegationsLocal(text) {
|
|
29
|
+
const source = String(text || "");
|
|
30
|
+
const idx = source.lastIndexOf(DELEGATE_HEADING);
|
|
31
|
+
if (idx < 0) return { delegations: [], cleanedText: source.trim() };
|
|
32
|
+
const after = source.slice(idx + DELEGATE_HEADING.length);
|
|
33
|
+
const fence = after.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
34
|
+
let delegations = [];
|
|
35
|
+
if (fence) {
|
|
36
|
+
try {
|
|
37
|
+
const data = JSON.parse(fence[1].trim());
|
|
38
|
+
const raw = Array.isArray(data)
|
|
39
|
+
? data
|
|
40
|
+
: data && typeof data === "object" && Array.isArray(data.delegations)
|
|
41
|
+
? data.delegations
|
|
42
|
+
: [];
|
|
43
|
+
delegations = raw
|
|
44
|
+
.map((d) => {
|
|
45
|
+
if (!d || typeof d !== "object") return null;
|
|
46
|
+
const target = typeof d.target === "string" ? d.target.trim() : "";
|
|
47
|
+
const brief = typeof d.brief === "string" ? d.brief.trim() : "";
|
|
48
|
+
return target ? { target, brief } : null;
|
|
49
|
+
})
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
} catch {
|
|
52
|
+
delegations = [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
let cut = source.length;
|
|
56
|
+
if (fence && fence.index != null) cut = idx + DELEGATE_HEADING.length + fence.index + fence[0].length;
|
|
57
|
+
else cut = idx; // 펜스 없으면 dangling heading도 제거
|
|
58
|
+
const cleanedText = (source.slice(0, idx) + source.slice(cut)).trim();
|
|
59
|
+
return { delegations, cleanedText };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function loadDelegateParser() {
|
|
63
|
+
try {
|
|
64
|
+
const fences = require("../sessions/fences.cjs");
|
|
65
|
+
if (fences && typeof fences.parseDelegateBlock === "function") return fences.parseDelegateBlock;
|
|
66
|
+
if (fences && typeof fences.parseDelegations === "function") return fences.parseDelegations;
|
|
67
|
+
} catch { /* fences.cjs 미존재 — 로컬 파서 사용 */ }
|
|
68
|
+
return parseDelegationsLocal;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 표시/전달용 텍스트에서 제어 펜스를 제거한다(파싱만 — 부작용 없음). 실패 시 원문. */
|
|
72
|
+
function cleanFenceText(text) {
|
|
73
|
+
const raw = String(text || "");
|
|
74
|
+
try {
|
|
75
|
+
const fences = require("../sessions/fences.cjs");
|
|
76
|
+
if (fences && typeof fences.parseReplyFences === "function") {
|
|
77
|
+
return fences.parseReplyFences(raw).cleanText;
|
|
78
|
+
}
|
|
79
|
+
} catch { /* fences 미존재/파서 실패 — 원문 보존 */ }
|
|
80
|
+
return parseDelegationsLocal(raw).cleanedText;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 리더(CEO) 시스템 프롬프트에 주입할 위임 가이드 (데스크탑 buildDelegateProtocol 동형 축약). */
|
|
84
|
+
function buildDelegateProtocol(reports) {
|
|
85
|
+
const list = reports
|
|
86
|
+
.map((r) => ` - ${r.role}${r.name && r.name !== r.role ? ` (${r.name})` : ""}`)
|
|
87
|
+
.join("\n");
|
|
88
|
+
return [
|
|
89
|
+
"## Delegation (you orchestrate a team)",
|
|
90
|
+
"",
|
|
91
|
+
"You lead a team. For THIS task, engage ONLY the direct reports actually needed —",
|
|
92
|
+
"never all of them. Give each a focused brief (goal + specifics). If none are needed,",
|
|
93
|
+
"do the work yourself and emit no Delegate block.",
|
|
94
|
+
"",
|
|
95
|
+
"Your direct reports:",
|
|
96
|
+
list,
|
|
97
|
+
"",
|
|
98
|
+
"To delegate, end your reply with exactly this block (omit entirely if delegating to none):",
|
|
99
|
+
"",
|
|
100
|
+
DELEGATE_HEADING,
|
|
101
|
+
"```json",
|
|
102
|
+
'{ "delegations": [ { "target": "<report role or name above>", "brief": "<what they should do>" } ] }',
|
|
103
|
+
"```",
|
|
104
|
+
"",
|
|
105
|
+
"After delegating, STOP — their results come back to you to synthesize. Don't do their work yourself.",
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 종합 노드에게 주는 상충/실패 처리 규칙 (데스크탑 CONFLICT_SYNTHESIS_GUIDANCE 동형):
|
|
110
|
+
// status:failed 결과는 오류 문자열이지 산출물이 아니다 — 지어내 메우면 안 된다.
|
|
111
|
+
const CONFLICT_SYNTHESIS_GUIDANCE = [
|
|
112
|
+
"Rules for this synthesis:",
|
|
113
|
+
'- A result marked "status: failed" is an error message, not a deliverable. Never treat it as findings, and never invent content to fill its gap.',
|
|
114
|
+
"- Resolve conflicts between results explicitly instead of averaging or silently picking one.",
|
|
115
|
+
"- If a failed or missing result means the goal was not met, say so plainly rather than presenting a partial answer as complete.",
|
|
116
|
+
].join("\n");
|
|
117
|
+
|
|
118
|
+
/** delegation 타깃을 본부(role/name)와 매칭 (데스크탑 matchTargets 동형). */
|
|
119
|
+
function matchTargets(delegations, candidates) {
|
|
120
|
+
const norm = (s) => String(s || "").trim().toLowerCase();
|
|
121
|
+
const picked = [];
|
|
122
|
+
const used = new Set();
|
|
123
|
+
for (const d of delegations) {
|
|
124
|
+
const t = norm(d.target);
|
|
125
|
+
if (!t) continue;
|
|
126
|
+
const node = candidates.find(
|
|
127
|
+
(c) =>
|
|
128
|
+
!used.has(c.key) &&
|
|
129
|
+
(norm(c.role) === t || norm(c.name) === t || norm(c.role).includes(t) || t.includes(norm(c.role))),
|
|
130
|
+
);
|
|
131
|
+
if (node) {
|
|
132
|
+
used.add(node.key);
|
|
133
|
+
picked.push({ node, brief: d.brief || "" });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return picked;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function deptLabel(name) {
|
|
140
|
+
return String(name || "").replace(/[-_]+/g, " ").split(" ").filter(Boolean)
|
|
141
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* org_chart_json → 본부(division) 노드 목록.
|
|
146
|
+
* 각 노드는 실제 설치 에이전트로 해석을 시도한다(agentId → agentSlug). 해석 실패 시
|
|
147
|
+
* 합성 노드: FK-safe 하게 CEO의 agent id 를 쓴다(데스크탑 runDivision fkAgentId 동형 —
|
|
148
|
+
* chats.agent_id FK 가 살아있는 행을 가리켜야 한다). 역할 프롬프트는 role 기반.
|
|
149
|
+
*/
|
|
150
|
+
function resolveDivisions(db, firm, ceoAgent) {
|
|
151
|
+
let chart = [];
|
|
152
|
+
try {
|
|
153
|
+
const parsed = JSON.parse(firm.org_chart_json || "[]");
|
|
154
|
+
if (Array.isArray(parsed)) chart = parsed;
|
|
155
|
+
} catch { chart = []; }
|
|
156
|
+
const nodes = [];
|
|
157
|
+
for (const node of chart) {
|
|
158
|
+
if (!node || !node.reportsTo) continue; // CEO 자신(reportsTo=null)은 본부가 아니다
|
|
159
|
+
const role = deptLabel(node.role || node.agentSlug || "Division");
|
|
160
|
+
let agent = null;
|
|
161
|
+
try {
|
|
162
|
+
const row = (node.agentId
|
|
163
|
+
? db.prepare("SELECT * FROM installed_agents WHERE id=?").get(node.agentId)
|
|
164
|
+
: null)
|
|
165
|
+
|| (node.agentSlug
|
|
166
|
+
? db.prepare("SELECT * FROM installed_agents WHERE slug=?").get(node.agentSlug)
|
|
167
|
+
: null);
|
|
168
|
+
if (row) agent = rowToAgent(row);
|
|
169
|
+
} catch { agent = null; }
|
|
170
|
+
const name = agent ? agent.name : role;
|
|
171
|
+
const roleContext = `\n\n## Firm role context\nYou are ${name}, the ${role} division of '${firm.name}'. Answer your CEO's brief for this division.`;
|
|
172
|
+
nodes.push({
|
|
173
|
+
key: `${firm.slug}:${role}`,
|
|
174
|
+
role,
|
|
175
|
+
name,
|
|
176
|
+
agent: agent
|
|
177
|
+
? { ...agent, systemPrompt: `${agent.systemPrompt || `You are ${name}.`}${roleContext}` }
|
|
178
|
+
// 합성 본부 — 실 에이전트가 없으면 CEO id로 FK-safe 하게 챗을 만들고 역할 프롬프트만 준다.
|
|
179
|
+
: { id: ceoAgent.id, slug: `${firm.slug}-${role.toLowerCase().replace(/\s+/g, "-")}`, name, systemPrompt: `You are ${name}, the ${role} division of '${firm.name}'.${roleContext}` },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return nodes;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** 간단한 동시성 풀 (데스크탑 parallelCap 동형) — 세션 동시 상한을 존중한다. */
|
|
186
|
+
async function parallelCap(items, cap, fn) {
|
|
187
|
+
const out = new Array(items.length);
|
|
188
|
+
let next = 0;
|
|
189
|
+
async function worker() {
|
|
190
|
+
while (next < items.length) {
|
|
191
|
+
const i = next++;
|
|
192
|
+
out[i] = await fn(items[i], i);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(cap, items.length)) }, () => worker()));
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function turnText(res) {
|
|
200
|
+
return ((res && (res.finalText || res.text)) || "").trim();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 회사 1태스크 실행: PLAN → DELEGATE → SYNTHESIZE.
|
|
205
|
+
* @param {object} p
|
|
206
|
+
* db, orch(Orchestrator), firm(firms 행), ceoAgent(rowToAgent 결과),
|
|
207
|
+
* task, runtime, permission, cwd, onEvent?({phase,...}),
|
|
208
|
+
* spawnImplFor?({kind:'ceo'|'division', role?}) — 계약 테스트 전용 fake spawn 주입,
|
|
209
|
+
* timeoutConfig? — 테스트 전용.
|
|
210
|
+
* @returns {ok, text, chatId, plan:{text,delegations}, divisions:[{role,name,ok,text,chatId}]}
|
|
211
|
+
*/
|
|
212
|
+
async function runFirmTurn(p) {
|
|
213
|
+
const { db, orch, firm, ceoAgent, task, runtime, permission, cwd } = p;
|
|
214
|
+
const onEvent = typeof p.onEvent === "function" ? p.onEvent : () => {};
|
|
215
|
+
const parseDelegations = loadDelegateParser();
|
|
216
|
+
const divisions = resolveDivisions(db, firm, ceoAgent);
|
|
217
|
+
|
|
218
|
+
// CEO 시스템 프롬프트 = CEO 페르소나 + 회사 컨텍스트 + (본부가 있으면) 위임 프로토콜.
|
|
219
|
+
let ceoSystem = `${ceoAgent.systemPrompt || `You are the CEO of ${firm.name}.`}\n\n[FIRM] You are the CEO of '${firm.name}'.`;
|
|
220
|
+
if (divisions.length) {
|
|
221
|
+
ceoSystem += `\n\n${buildDelegateProtocol(divisions.map((d) => ({ role: d.role, name: d.name })))}`;
|
|
222
|
+
}
|
|
223
|
+
const ceoSession = orch.spawn({
|
|
224
|
+
agent: { ...ceoAgent, systemPrompt: ceoSystem },
|
|
225
|
+
runtime,
|
|
226
|
+
permission,
|
|
227
|
+
cwd,
|
|
228
|
+
title: `${firm.name}: ${String(task).slice(0, 50)}`,
|
|
229
|
+
spawnImpl: p.spawnImplFor ? p.spawnImplFor({ kind: "ceo" }) : undefined,
|
|
230
|
+
timeoutConfig: p.timeoutConfig,
|
|
231
|
+
});
|
|
232
|
+
// ⚠️ 세션 계층의 generic 자동 위임(apply-fences.cjs)은 ## Delegate 를 보면 "부모와
|
|
233
|
+
// 같은 에이전트"의 자식을 스폰한다. firm 3-tier 는 본부별 실 에이전트 바인딩 + 종합
|
|
234
|
+
// 시퀀스가 필요하므로 이 turn 들의 위임 적용은 이 모듈이 소유한다 — CEO 세션에서
|
|
235
|
+
// 오르카 역참조를 떼어 이중 실행(CEO 페르소나 클론 + 본부 에이전트)을 막는다.
|
|
236
|
+
// apply-fences 는 orch 부재 시 'delegate-refused' 영수증만 남긴다(정직·무부작용).
|
|
237
|
+
ceoSession.orchestrator = null;
|
|
238
|
+
|
|
239
|
+
// 1) PLAN
|
|
240
|
+
onEvent({ phase: "plan", firm: firm.slug, ceo: ceoAgent.slug });
|
|
241
|
+
const planRes = await ceoSession.send(task);
|
|
242
|
+
const planRaw = turnText(planRes);
|
|
243
|
+
if (ceoSession.status === "failed") {
|
|
244
|
+
return {
|
|
245
|
+
ok: false,
|
|
246
|
+
text: planRaw || ceoSession.lastError || "CEO plan failed",
|
|
247
|
+
chatId: ceoSession.chatId,
|
|
248
|
+
plan: { text: planRaw, delegations: [] },
|
|
249
|
+
divisions: [],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const { delegations, cleanedText } = parseDelegations(planRaw);
|
|
253
|
+
const matched = divisions.length ? matchTargets(delegations, divisions) : [];
|
|
254
|
+
|
|
255
|
+
// CEO가 위임 안 함(또는 본부 없음) → PLAN 회신이 곧 최종 답 (데스크탑 동형).
|
|
256
|
+
// 이미 세션 계층이 CEO 챗에 영속했다.
|
|
257
|
+
if (!matched.length) {
|
|
258
|
+
onEvent({ phase: "final", delegated: false });
|
|
259
|
+
return {
|
|
260
|
+
ok: true,
|
|
261
|
+
text: cleanedText || planRaw,
|
|
262
|
+
chatId: ceoSession.chatId,
|
|
263
|
+
plan: { text: cleanedText || planRaw, delegations },
|
|
264
|
+
divisions: [],
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 2) DELEGATE — 매칭된 본부만 병렬 실행. 본부 세션은 CEO 세션의 자식으로 스폰되어
|
|
269
|
+
// kind='division' + parent_chat_id(CEO 챗)로 영속된다. 한 본부의 실패는 격리한다.
|
|
270
|
+
onEvent({ phase: "delegate", targets: matched.map((m) => ({ role: m.node.role, name: m.node.name, brief: m.brief })) });
|
|
271
|
+
const divisionResults = await parallelCap(matched, maxParallel(), async (m) => {
|
|
272
|
+
const session = orch.spawn({
|
|
273
|
+
agent: m.node.agent,
|
|
274
|
+
runtime,
|
|
275
|
+
permission,
|
|
276
|
+
cwd,
|
|
277
|
+
title: `division: ${m.node.role}`,
|
|
278
|
+
parentKey: ceoSession.key,
|
|
279
|
+
activate: false,
|
|
280
|
+
spawnImpl: p.spawnImplFor ? p.spawnImplFor({ kind: "division", role: m.node.role }) : undefined,
|
|
281
|
+
timeoutConfig: p.timeoutConfig,
|
|
282
|
+
});
|
|
283
|
+
// 본부 세션도 generic 자동 위임을 끈다 — 터미널 firm 은 아직 tier-3(전문가) 배선이
|
|
284
|
+
// 없고, 본부 회신의 펜스로 동일 에이전트 클론이 무통제 스폰되면 안 된다.
|
|
285
|
+
session.orchestrator = null;
|
|
286
|
+
let text = "";
|
|
287
|
+
let ok = false;
|
|
288
|
+
try {
|
|
289
|
+
const res = await session.send(m.brief || task);
|
|
290
|
+
text = cleanFenceText(turnText(res));
|
|
291
|
+
ok = session.status === "done";
|
|
292
|
+
if (!ok && !text) text = session.lastError || "no response";
|
|
293
|
+
} catch (e) {
|
|
294
|
+
text = (e && e.message) || String(e);
|
|
295
|
+
ok = false;
|
|
296
|
+
}
|
|
297
|
+
onEvent({ phase: "division-done", role: m.node.role, ok });
|
|
298
|
+
return { role: m.node.role, name: m.node.name, ok, text, chatId: session.chatId };
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// 3) SYNTHESIZE — CEO 세션의 두 번째 턴. status:failed 표기로 오류 문자열이 산출물로
|
|
302
|
+
// 오독되는 것을 막는다(데스크탑 CONFLICT_SYNTHESIS_GUIDANCE 계약).
|
|
303
|
+
onEvent({ phase: "synthesize" });
|
|
304
|
+
const synthPrompt =
|
|
305
|
+
`${task}\n\n[Results from your team — synthesize into one final answer for the user]\n` +
|
|
306
|
+
`${CONFLICT_SYNTHESIS_GUIDANCE}\n\n` +
|
|
307
|
+
divisionResults
|
|
308
|
+
.map((r) => `## ${r.name} (${r.role})\nstatus: ${r.ok ? "ok" : "failed"}\n${r.text}`)
|
|
309
|
+
.join("\n\n");
|
|
310
|
+
const finalRes = await ceoSession.send(synthPrompt);
|
|
311
|
+
const finalText = cleanFenceText(turnText(finalRes));
|
|
312
|
+
const finalOk = ceoSession.status === "done";
|
|
313
|
+
onEvent({ phase: "final", delegated: true, ok: finalOk });
|
|
314
|
+
|
|
315
|
+
// CEO 종합 턴의 성공은 팀의 성공이 아니다 — 자식 결과를 집계해 부분 완료가 성공으로
|
|
316
|
+
// 둔갑하지 않게 한다(데스크탑 동일 수리).
|
|
317
|
+
return {
|
|
318
|
+
ok: finalOk && divisionResults.every((r) => r.ok),
|
|
319
|
+
text: finalText,
|
|
320
|
+
chatId: ceoSession.chatId,
|
|
321
|
+
plan: { text: cleanedText, delegations },
|
|
322
|
+
divisions: divisionResults,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
module.exports = {
|
|
327
|
+
runFirmTurn,
|
|
328
|
+
parseDelegationsLocal,
|
|
329
|
+
buildDelegateProtocol,
|
|
330
|
+
matchTargets,
|
|
331
|
+
resolveDivisions,
|
|
332
|
+
DELEGATE_HEADING,
|
|
333
|
+
};
|