negotium 0.3.0 → 0.3.2
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-helpers.js +107 -6
- package/dist/agent-helpers.js.map +11 -11
- package/dist/background-bash.js +3 -2
- package/dist/background-bash.js.map +4 -4
- package/dist/browser-runtime.js +3 -2
- package/dist/browser-runtime.js.map +4 -4
- package/dist/chunk-s2gez3wg.js.map +1 -1
- package/dist/{chunk-1y2xw0xe.js → chunk-vvfwqxnh.js} +33 -3
- package/dist/{chunk-1y2xw0xe.js.map → chunk-vvfwqxnh.js.map} +8 -8
- package/dist/hosted-agent.js +34 -4
- package/dist/hosted-agent.js.map +8 -8
- package/dist/main.js +1179 -366
- package/dist/main.js.map +24 -21
- package/dist/mcp-catalog.js +2 -1
- package/dist/mcp-catalog.js.map +3 -3
- package/dist/mcp-factories.js +635 -256
- package/dist/mcp-factories.js.map +15 -13
- package/dist/mcp-servers.js +3 -1
- package/dist/mcp-servers.js.map +3 -3
- package/dist/prompts.js +8 -2
- package/dist/prompts.js.map +5 -5
- package/dist/query-runtime.js +3 -2
- package/dist/query-runtime.js.map +4 -4
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +2 -2
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/mcp/canonical-bridge-config.ts +1 -1
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +74 -1
- package/dist/runtime/src/mcp/decision-server.ts +21 -0
- package/dist/runtime/src/mcp/factories/decision.ts +178 -0
- package/dist/runtime/src/mcp/factories/index.ts +6 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +1 -0
- package/dist/runtime/src/node-host.ts +1 -0
- package/dist/runtime/src/platform/config.ts +2 -1
- package/dist/runtime/src/platform/mcp-catalog-policy.ts +1 -0
- package/dist/runtime/src/platform/mcp-config.ts +31 -1
- package/dist/runtime/src/prompts/builders.ts +5 -0
- package/dist/runtime/src/runtime/visual-html.ts +68 -2
- package/dist/runtime/src/storage/decisions.ts +231 -0
- package/dist/runtime/src/storage/storage-public.ts +2 -0
- package/dist/runtime/src/types.ts +13 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +71 -4
- package/dist/runtime-helpers.js.map +5 -5
- package/dist/storage.js +261 -56
- package/dist/storage.js.map +5 -4
- package/dist/types/apps/negotium/src/mcp-servers.d.ts +1 -1
- package/dist/types/packages/core/src/mcp/canonical-bridge-config.d.ts +1 -1
- package/dist/types/packages/core/src/mcp/factories/decision.d.ts +16 -0
- package/dist/types/packages/core/src/mcp/factories/index.d.ts +1 -0
- package/dist/types/packages/core/src/mcp/runtime-spec.d.ts +1 -1
- package/dist/types/packages/core/src/platform/config.d.ts +2 -1
- package/dist/types/packages/core/src/platform/mcp-catalog-policy.d.ts +4 -0
- package/dist/types/packages/core/src/storage/decisions.d.ts +45 -0
- package/dist/types/packages/core/src/storage/storage-public.d.ts +2 -0
- package/dist/types/packages/core/src/types.d.ts +12 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +3 -2
- package/dist/vault.js.map +4 -4
- package/install-browser-rs.mjs +3 -3
- package/package.json +2 -2
|
@@ -85,6 +85,72 @@ const taskTools: Tool[] = [
|
|
|
85
85
|
},
|
|
86
86
|
];
|
|
87
87
|
|
|
88
|
+
const decisionStatus = ["proposed", "accepted", "executed", "rejected", "superseded"];
|
|
89
|
+
const decisionFields = {
|
|
90
|
+
action: { type: "string" },
|
|
91
|
+
reasoning: { type: "string" },
|
|
92
|
+
status: { type: "string", enum: decisionStatus },
|
|
93
|
+
caused_by: { type: "array", items: { type: "string" } },
|
|
94
|
+
} as const;
|
|
95
|
+
const decisionTools: Tool[] = [
|
|
96
|
+
{
|
|
97
|
+
name: "decision_create",
|
|
98
|
+
description: "Record durable decisions and their causal predecessors in this topic.",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
decisions: {
|
|
103
|
+
type: "array",
|
|
104
|
+
minItems: 1,
|
|
105
|
+
items: {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: decisionFields,
|
|
108
|
+
required: ["action", "reasoning"],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
required: ["decisions"],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "decision_update",
|
|
117
|
+
description: "Update decisions or causal links in this topic.",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
updates: {
|
|
122
|
+
type: "array",
|
|
123
|
+
minItems: 1,
|
|
124
|
+
items: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: { id: { type: "string" }, ...decisionFields },
|
|
127
|
+
required: ["id"],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
required: ["updates"],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "decision_list",
|
|
136
|
+
description: "Read this topic's decisions.",
|
|
137
|
+
inputSchema: { type: "object", properties: {} },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "decision_get",
|
|
141
|
+
description: "Read one decision as JSON.",
|
|
142
|
+
inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "decision_delete",
|
|
146
|
+
description: "Delete decisions from this topic.",
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: { ids: { type: "array", items: { type: "string" } }, all: { type: "boolean" } },
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
|
|
88
154
|
const wikiTools: Tool[] = [
|
|
89
155
|
{
|
|
90
156
|
name: "wiki_query",
|
|
@@ -160,7 +226,14 @@ const wikiTools: Tool[] = [
|
|
|
160
226
|
},
|
|
161
227
|
];
|
|
162
228
|
|
|
163
|
-
const tools =
|
|
229
|
+
const tools =
|
|
230
|
+
surface === "task"
|
|
231
|
+
? taskTools
|
|
232
|
+
: surface === "decision"
|
|
233
|
+
? decisionTools
|
|
234
|
+
: surface === "wiki"
|
|
235
|
+
? wikiTools
|
|
236
|
+
: [];
|
|
164
237
|
const allowed = new Set(tools.map((tool) => tool.name));
|
|
165
238
|
|
|
166
239
|
function error(text: string): CallToolResult {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./stdio-protect";
|
|
3
|
+
import { createDecisionMcpServer } from "#mcp/factories/decision";
|
|
4
|
+
import { connectStdio, parseUserIdArg } from "#mcp/mcp-helpers";
|
|
5
|
+
import { isAgentKind } from "#types";
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const topic = args.find((arg) => arg.startsWith("--topic="))?.slice("--topic=".length) || "";
|
|
9
|
+
const topicId = args.find((arg) => arg.startsWith("--topic-id="))?.slice("--topic-id=".length);
|
|
10
|
+
const agentArg = args.find((arg) => arg.startsWith("--agent="))?.slice("--agent=".length);
|
|
11
|
+
const model = args.find((arg) => arg.startsWith("--model="))?.slice("--model=".length);
|
|
12
|
+
|
|
13
|
+
await connectStdio(
|
|
14
|
+
createDecisionMcpServer({
|
|
15
|
+
userId: parseUserIdArg(args),
|
|
16
|
+
topic,
|
|
17
|
+
topicId,
|
|
18
|
+
agent: agentArg && isAgentKind(agentArg) ? agentArg : "codex",
|
|
19
|
+
model,
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { errMsg } from "#platform/error";
|
|
4
|
+
import {
|
|
5
|
+
createDecisions,
|
|
6
|
+
DECISION_STATUS_VALUES,
|
|
7
|
+
decisionScopeKey,
|
|
8
|
+
deleteDecisions,
|
|
9
|
+
readDecisions,
|
|
10
|
+
renderDecisionList,
|
|
11
|
+
type StoredDecision,
|
|
12
|
+
updateDecisions,
|
|
13
|
+
writeDecisions,
|
|
14
|
+
} from "#storage/decisions";
|
|
15
|
+
import type { AgentKind } from "#types";
|
|
16
|
+
import { mcpError, mcpOk } from "../mcp-helpers";
|
|
17
|
+
|
|
18
|
+
export interface DecisionMcpContext {
|
|
19
|
+
userId: string;
|
|
20
|
+
topic: string;
|
|
21
|
+
topicId?: string;
|
|
22
|
+
agent: AgentKind;
|
|
23
|
+
model?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DecisionMcpHost {
|
|
27
|
+
readDecisions(userId: string, scopeKey: string): StoredDecision[];
|
|
28
|
+
writeDecisions(userId: string, scopeKey: string, decisions: StoredDecision[]): void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const defaultDecisionMcpHost: DecisionMcpHost = { readDecisions, writeDecisions };
|
|
32
|
+
|
|
33
|
+
export function createDecisionMcpServer(
|
|
34
|
+
context: DecisionMcpContext,
|
|
35
|
+
host: DecisionMcpHost = defaultDecisionMcpHost,
|
|
36
|
+
): McpServer {
|
|
37
|
+
const scopeKey = context.topic
|
|
38
|
+
? decisionScopeKey({ topicId: context.topicId, session: context.topic })
|
|
39
|
+
: "";
|
|
40
|
+
const server = new McpServer({ name: "decision", version: "1.0.0" });
|
|
41
|
+
const requireContext = (): ReturnType<typeof mcpError> | null =>
|
|
42
|
+
!context.userId || !scopeKey ? mcpError("Error: missing userId/topic context.") : null;
|
|
43
|
+
const statusEnum = z.enum(DECISION_STATUS_VALUES);
|
|
44
|
+
|
|
45
|
+
server.tool(
|
|
46
|
+
"decision_create",
|
|
47
|
+
"Record one or more durable decisions in this topic. Include the rationale and causal predecessors.",
|
|
48
|
+
{
|
|
49
|
+
decisions: z
|
|
50
|
+
.array(
|
|
51
|
+
z.object({
|
|
52
|
+
action: z.string().min(1).describe("Concise statement of what was decided"),
|
|
53
|
+
reasoning: z.string().min(1).describe("Why this choice was made"),
|
|
54
|
+
status: statusEnum.optional().describe("Defaults to accepted"),
|
|
55
|
+
caused_by: z.array(z.string()).optional().describe("Upstream decision ids"),
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
58
|
+
.min(1),
|
|
59
|
+
},
|
|
60
|
+
async ({ decisions: inputs }) => {
|
|
61
|
+
const guard = requireContext();
|
|
62
|
+
if (guard) return guard;
|
|
63
|
+
try {
|
|
64
|
+
const current = host.readDecisions(context.userId, scopeKey);
|
|
65
|
+
const { decisions, created } = createDecisions(
|
|
66
|
+
current,
|
|
67
|
+
inputs.map((input) => ({
|
|
68
|
+
action: input.action,
|
|
69
|
+
reasoning: input.reasoning,
|
|
70
|
+
status: input.status,
|
|
71
|
+
causedBy: input.caused_by,
|
|
72
|
+
agent: context.agent,
|
|
73
|
+
model: context.model,
|
|
74
|
+
})),
|
|
75
|
+
);
|
|
76
|
+
host.writeDecisions(context.userId, scopeKey, decisions);
|
|
77
|
+
return mcpOk(
|
|
78
|
+
`${created.length} decision(s) recorded (${created.map((item) => `#${item.id}`).join(", ")})\n\n${renderDecisionList(decisions)}`,
|
|
79
|
+
);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return mcpError(`decision_create failed: ${errMsg(error)}`);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
server.tool(
|
|
87
|
+
"decision_update",
|
|
88
|
+
"Update decisions or their causal links in this topic.",
|
|
89
|
+
{
|
|
90
|
+
updates: z
|
|
91
|
+
.array(
|
|
92
|
+
z.object({
|
|
93
|
+
id: z.string(),
|
|
94
|
+
action: z.string().min(1).optional(),
|
|
95
|
+
reasoning: z.string().min(1).optional(),
|
|
96
|
+
status: statusEnum.optional(),
|
|
97
|
+
caused_by: z.array(z.string()).optional().describe("Replacement upstream decision ids"),
|
|
98
|
+
}),
|
|
99
|
+
)
|
|
100
|
+
.min(1),
|
|
101
|
+
},
|
|
102
|
+
async ({ updates }) => {
|
|
103
|
+
const guard = requireContext();
|
|
104
|
+
if (guard) return guard;
|
|
105
|
+
try {
|
|
106
|
+
const current = host.readDecisions(context.userId, scopeKey);
|
|
107
|
+
const { decisions, missing } = updateDecisions(
|
|
108
|
+
current,
|
|
109
|
+
updates.map((update) => ({
|
|
110
|
+
...update,
|
|
111
|
+
causedBy: update.caused_by,
|
|
112
|
+
})),
|
|
113
|
+
);
|
|
114
|
+
host.writeDecisions(context.userId, scopeKey, decisions);
|
|
115
|
+
const warning = missing.length ? `\nMissing ids ignored: ${missing.join(", ")}` : "";
|
|
116
|
+
return mcpOk(`${renderDecisionList(decisions)}${warning}`);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return mcpError(`decision_update failed: ${errMsg(error)}`);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
server.tool(
|
|
124
|
+
"decision_list",
|
|
125
|
+
"Read this topic's decision graph as a concise list.",
|
|
126
|
+
{},
|
|
127
|
+
async () => {
|
|
128
|
+
const guard = requireContext();
|
|
129
|
+
if (guard) return guard;
|
|
130
|
+
try {
|
|
131
|
+
return mcpOk(renderDecisionList(host.readDecisions(context.userId, scopeKey)));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return mcpError(`decision_list failed: ${errMsg(error)}`);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
server.tool(
|
|
139
|
+
"decision_get",
|
|
140
|
+
"Read one topic decision as JSON.",
|
|
141
|
+
{ id: z.string() },
|
|
142
|
+
async ({ id }) => {
|
|
143
|
+
const guard = requireContext();
|
|
144
|
+
if (guard) return guard;
|
|
145
|
+
try {
|
|
146
|
+
const decision = host
|
|
147
|
+
.readDecisions(context.userId, scopeKey)
|
|
148
|
+
.find((item) => item.id === id);
|
|
149
|
+
return decision
|
|
150
|
+
? mcpOk(JSON.stringify(decision, null, 2))
|
|
151
|
+
: mcpError(`Decision #${id} not found.`);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return mcpError(`decision_get failed: ${errMsg(error)}`);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
server.tool(
|
|
159
|
+
"decision_delete",
|
|
160
|
+
"Delete decisions. Downstream causal references to deleted decisions are removed.",
|
|
161
|
+
{ ids: z.array(z.string()).optional(), all: z.boolean().optional() },
|
|
162
|
+
async ({ ids, all }) => {
|
|
163
|
+
const guard = requireContext();
|
|
164
|
+
if (guard) return guard;
|
|
165
|
+
if (!all && (!ids || ids.length === 0)) return mcpError("Provide ids or all=true.");
|
|
166
|
+
try {
|
|
167
|
+
const current = host.readDecisions(context.userId, scopeKey);
|
|
168
|
+
const { decisions, removed } = deleteDecisions(current, { ids, all });
|
|
169
|
+
host.writeDecisions(context.userId, scopeKey, decisions);
|
|
170
|
+
return mcpOk(`${removed} decision(s) deleted\n\n${renderDecisionList(decisions)}`);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return mcpError(`decision_delete failed: ${errMsg(error)}`);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
return server;
|
|
178
|
+
}
|
|
@@ -29,6 +29,12 @@ export {
|
|
|
29
29
|
type CompactionLogMcpContext,
|
|
30
30
|
createCompactionLogMcpServer,
|
|
31
31
|
} from "./compaction-log";
|
|
32
|
+
export {
|
|
33
|
+
createDecisionMcpServer,
|
|
34
|
+
type DecisionMcpContext,
|
|
35
|
+
type DecisionMcpHost,
|
|
36
|
+
defaultDecisionMcpHost,
|
|
37
|
+
} from "./decision";
|
|
32
38
|
export {
|
|
33
39
|
createSessionCommMcpServer,
|
|
34
40
|
type SessionCommMcpHost,
|
|
@@ -67,6 +67,7 @@ export { startSessionInboxWorker } from "#runtime/inbox";
|
|
|
67
67
|
export { startAiTurn, startDurableTurnRequestWorker } from "#runtime/turn-runner";
|
|
68
68
|
export { appendApiMessage, getApiMessage, listApiMessages } from "#storage/api-messages";
|
|
69
69
|
export { getTopic, upsertTopic } from "#storage/api-topics";
|
|
70
|
+
export { readDecisions, writeDecisionGraphSvg } from "#storage/decisions";
|
|
70
71
|
export type { StoredRuntimeEvent } from "#storage/runtime-events";
|
|
71
72
|
export {
|
|
72
73
|
latestRuntimeEventSeq,
|
|
@@ -99,7 +99,7 @@ export function resolveOutputLanguage(): string {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/** Browser.rs release tested with this Negotium version. */
|
|
102
|
-
export const BROWSER_RS_VERSION = "v0.1.
|
|
102
|
+
export const BROWSER_RS_VERSION = "v0.1.21";
|
|
103
103
|
/** Require the authenticated listener and the current Browser.rs tool contract. */
|
|
104
104
|
export const BROWSER_RS_MIN_SECURE_VERSION = "0.1.15";
|
|
105
105
|
|
|
@@ -277,6 +277,7 @@ export const TSCONFIG_PATH = resolve(PROJECT_ROOT, "tsconfig.json");
|
|
|
277
277
|
export const SESSION_COMM_SERVER = resolve(PROJECT_ROOT, "src/mcp/session-comm/server.ts");
|
|
278
278
|
|
|
279
279
|
export const TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
|
|
280
|
+
export const DECISION_SERVER = resolve(PROJECT_ROOT, "src/mcp/decision-server.ts");
|
|
280
281
|
export const BROWSER_MCP_SSE_PROXY_SERVER = resolve(
|
|
281
282
|
PROJECT_ROOT,
|
|
282
283
|
"src/mcp/browser-sse-proxy-server.ts",
|
|
@@ -11,6 +11,7 @@ export const COMMON_RUNTIME_MCP_POLICY = {
|
|
|
11
11
|
runtime: { scopes: ["forum", "manager", "fork", "cron"], forumRequired: true },
|
|
12
12
|
"token-stats": { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
13
13
|
task: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
14
|
+
decision: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
14
15
|
"session-comm": { scopes: ["forum", "fork", "manager"], forumRequired: true },
|
|
15
16
|
wiki: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
16
17
|
skills: { scopes: ["dm", "forum", "manager", "cron"], forumRequired: true },
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
AGENT_HEALTH_SERVER,
|
|
12
12
|
BROWSER_MCP_SSE_PROXY_SERVER,
|
|
13
13
|
CANONICAL_MCP_PROXY_SERVER,
|
|
14
|
+
DECISION_SERVER,
|
|
14
15
|
envText,
|
|
15
16
|
FALLBACK_AGENT,
|
|
16
17
|
resolveTopicWorkspaceDir,
|
|
@@ -417,6 +418,35 @@ const MCP_CATALOG: Record<string, RuntimeMcpCatalogEntry> = {
|
|
|
417
418
|
);
|
|
418
419
|
},
|
|
419
420
|
},
|
|
421
|
+
decision: {
|
|
422
|
+
...commonRuntimeMcpPolicy("decision"),
|
|
423
|
+
build(ctx) {
|
|
424
|
+
const { userId, session, topicId, queryId, agent, model, peerBridge } = ctx;
|
|
425
|
+
if (peerBridge) {
|
|
426
|
+
if (!topicId || !queryId) return null;
|
|
427
|
+
const env = canonicalMcpBridgeEnv({
|
|
428
|
+
surface: "decision",
|
|
429
|
+
userId,
|
|
430
|
+
topicId,
|
|
431
|
+
queryId,
|
|
432
|
+
peerBridge,
|
|
433
|
+
});
|
|
434
|
+
return env
|
|
435
|
+
? buildStdioMcpServer(agent, CANONICAL_MCP_PROXY_SERVER, ["--surface=decision"], env)
|
|
436
|
+
: null;
|
|
437
|
+
}
|
|
438
|
+
const args = [
|
|
439
|
+
`--user-id=${userId}`,
|
|
440
|
+
`--topic=${session}`,
|
|
441
|
+
`--agent=${agent ?? FALLBACK_AGENT}`,
|
|
442
|
+
];
|
|
443
|
+
if (topicId) args.push(`--topic-id=${topicId}`);
|
|
444
|
+
if (model) args.push(`--model=${model}`);
|
|
445
|
+
return buildBuiltinMcpServer("decision", ctx, () =>
|
|
446
|
+
buildStdioMcpServer(agent, DECISION_SERVER, args),
|
|
447
|
+
);
|
|
448
|
+
},
|
|
449
|
+
},
|
|
420
450
|
"session-comm": {
|
|
421
451
|
// Exposed in `manager` scope as well so the General-topic manager agent
|
|
422
452
|
// can wake fresh-created topics via tell_session/ask_session right after
|
|
@@ -818,7 +848,7 @@ export function getForumMcpServers(opts: {
|
|
|
818
848
|
} = opts;
|
|
819
849
|
|
|
820
850
|
const filter = (name: string) => {
|
|
821
|
-
if (silent && name === "task") return false;
|
|
851
|
+
if (silent && (name === "task" || name === "decision")) return false;
|
|
822
852
|
if (enabled === null) return true;
|
|
823
853
|
return (
|
|
824
854
|
enabled.includes(name) || (REQUIRED_FORUM_MCP_SERVERS as readonly string[]).includes(name)
|
|
@@ -274,6 +274,7 @@ function buildRuntimeToolSection(
|
|
|
274
274
|
} = opts;
|
|
275
275
|
const runtimeNamespace = "mcp__runtime";
|
|
276
276
|
const taskNamespace = "mcp__task";
|
|
277
|
+
const decisionNamespace = "mcp__decision";
|
|
277
278
|
const visualToolLine =
|
|
278
279
|
agentKind === "codex"
|
|
279
280
|
? `To display charts, tables, or interactive HTML results to the user, call the \`show_html\` function in the \`${runtimeNamespace}\` namespace with { html: "<complete HTML string>", title?: "optional title" }.`
|
|
@@ -302,6 +303,7 @@ function buildRuntimeToolSection(
|
|
|
302
303
|
agentKind === "codex"
|
|
303
304
|
? `For task tracking, use \`task_create\`, \`task_update\`, \`task_list\`, \`task_get\`, and \`task_delete\` functions in the \`${taskNamespace}\` namespace.`
|
|
304
305
|
: `For task tracking, use MCP tools "${taskNamespace}__task_create", "${taskNamespace}__task_update", "${taskNamespace}__task_list", "${taskNamespace}__task_get", and "${taskNamespace}__task_delete".`;
|
|
306
|
+
const decisionToolLine = `Use the shared Decision tools in the \`${decisionNamespace}\` namespace when an architectural, product, or operational choice establishes or changes a durable direction or constraint. Do not record routine task progress or temporary implementation details; link causal predecessors when relevant.`;
|
|
305
307
|
const runtimeToolRef = (name: string): string =>
|
|
306
308
|
agentKind === "codex" ? `\`${name}\`` : `"${runtimeNamespace}__${name}"`;
|
|
307
309
|
const spawnSubagentToolLine = `Use ${runtimeToolRef("spawn_subagent")} for self-contained parallel or long-running background work; keep quick work inline.`;
|
|
@@ -364,6 +366,9 @@ function buildRuntimeToolSection(
|
|
|
364
366
|
taskToolLine,
|
|
365
367
|
"Use this shared task store for plans, progress, and checklist updates; it is visible across claude/codex/maestro turns.",
|
|
366
368
|
nativeTaskPolicyLine,
|
|
369
|
+
"",
|
|
370
|
+
"## Shared Decisions",
|
|
371
|
+
decisionToolLine,
|
|
367
372
|
...extensions.render("after-shared-tasks"),
|
|
368
373
|
...fileDeliverySection,
|
|
369
374
|
...extensions.render("before-session-communication"),
|
|
@@ -55,6 +55,10 @@ export function buildMermaidHtml(
|
|
|
55
55
|
.controls button:focus-visible{outline:2px solid var(--celadon);outline-offset:1px}
|
|
56
56
|
.zoom-value{min-width:46px;color:var(--graphite);font:500 11px/1 Geist,system-ui,sans-serif;text-align:center;font-variant-numeric:tabular-nums}
|
|
57
57
|
.error{margin:0;white-space:pre-wrap;color:#7D2E2E;background:#FFF5F2;border:1px solid #E4B9B1;border-radius:6px;padding:14px;font:13px ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
58
|
+
.failure{max-width:56ch;margin:0 auto}
|
|
59
|
+
.failure p{margin:0 0 12px;color:var(--graphite);font:14px/1.6 Geist,system-ui,sans-serif}
|
|
60
|
+
.failure details{color:var(--graphite);font:12px/1.5 Geist,system-ui,sans-serif}
|
|
61
|
+
.failure summary{cursor:pointer;padding:4px 0}
|
|
58
62
|
@media(max-width:600px){.viewport{padding:54px 14px 18px}.controls{top:10px;right:10px}}
|
|
59
63
|
</style>
|
|
60
64
|
</head>
|
|
@@ -69,11 +73,58 @@ export function buildMermaidHtml(
|
|
|
69
73
|
<script data-otium-mermaid-runtime src="${safeScriptUrl}"></script>
|
|
70
74
|
<script>
|
|
71
75
|
(async () => {
|
|
76
|
+
// An unrendered document reports itself in more than one voice: Mermaid's
|
|
77
|
+
// own 0x0 guard, and the browser refusing geometry on a path that was
|
|
78
|
+
// never laid out. Both mean the same thing, so both are worth one retry
|
|
79
|
+
// and, if it still fails, the same explanation.
|
|
80
|
+
const unrendered = (error) => {
|
|
81
|
+
const message = String(error && error.message ? error.message : error);
|
|
82
|
+
return message.indexOf("not in render tree") !== -1 || message.indexOf("path is empty") !== -1;
|
|
83
|
+
};
|
|
72
84
|
try {
|
|
73
85
|
const runtime = globalThis.mermaid;
|
|
74
86
|
if (!runtime) throw new Error("Mermaid renderer failed to load.");
|
|
75
87
|
runtime.initialize({ startOnLoad: false, securityLevel: "strict", theme: ${safeTheme} });
|
|
76
|
-
|
|
88
|
+
const host = document.querySelector(".mermaid");
|
|
89
|
+
// Mermaid sizes every label by appending a probe <svg> to the body and
|
|
90
|
+
// reading getBBox(), and it throws "svg element not in render tree" the
|
|
91
|
+
// moment that comes back 0x0. That is what a hidden panel looks like from
|
|
92
|
+
// in here: the document exists but nothing is in the render tree, so the
|
|
93
|
+
// measurement has no geometry to report. Ask the same question Mermaid
|
|
94
|
+
// will ask, and only start once it has an answer.
|
|
95
|
+
const measurable = () => {
|
|
96
|
+
const probe = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
97
|
+
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
98
|
+
text.textContent = "M";
|
|
99
|
+
probe.appendChild(text);
|
|
100
|
+
document.body.appendChild(probe);
|
|
101
|
+
let box = { width: 0, height: 0 };
|
|
102
|
+
try { box = text.getBBox(); } catch (ignored) {}
|
|
103
|
+
probe.remove();
|
|
104
|
+
return box.width > 0 || box.height > 0;
|
|
105
|
+
};
|
|
106
|
+
// requestAnimationFrame is the right clock here: a hidden document stops
|
|
107
|
+
// being animated, so this waits without spinning and resumes on the frame
|
|
108
|
+
// the panel is shown. The cap counts rendered frames, not wall time.
|
|
109
|
+
const waitUntilMeasurable = async (maxFrames) => {
|
|
110
|
+
for (let frame = 0; frame < maxFrames; frame += 1) {
|
|
111
|
+
if (measurable()) return true;
|
|
112
|
+
await new Promise((next) => requestAnimationFrame(next));
|
|
113
|
+
}
|
|
114
|
+
return measurable();
|
|
115
|
+
};
|
|
116
|
+
await waitUntilMeasurable(600);
|
|
117
|
+
try {
|
|
118
|
+
await runtime.run({ querySelector: ".mermaid" });
|
|
119
|
+
} catch (firstAttempt) {
|
|
120
|
+
if (!unrendered(firstAttempt)) throw firstAttempt;
|
|
121
|
+
// The panel can be hidden again between the probe and the real measure.
|
|
122
|
+
// Clear the marker Mermaid leaves behind so the retry is not skipped as
|
|
123
|
+
// already done, then wait for the render tree once more.
|
|
124
|
+
host.removeAttribute("data-processed");
|
|
125
|
+
await waitUntilMeasurable(600);
|
|
126
|
+
await runtime.run({ querySelector: ".mermaid" });
|
|
127
|
+
}
|
|
77
128
|
const viewport = document.querySelector(".viewport");
|
|
78
129
|
const svg = document.querySelector(".mermaid svg");
|
|
79
130
|
const value = document.querySelector(".zoom-value");
|
|
@@ -100,7 +151,22 @@ export function buildMermaidHtml(
|
|
|
100
151
|
applyScale(scale, false);
|
|
101
152
|
} catch (error) {
|
|
102
153
|
document.querySelector(".controls")?.remove();
|
|
103
|
-
|
|
154
|
+
const raw = String(error && error.message ? error.message : error);
|
|
155
|
+
const escape = (value) => value.replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c]));
|
|
156
|
+
// Mermaid aborts the whole render when it cannot place an edge label:
|
|
157
|
+
// cardinality markers ask for a point a fixed distance along the edge,
|
|
158
|
+
// and a relation laid out shorter than that walks off the end. Nothing
|
|
159
|
+
// is wrong with the diagram, so the raw message sends authors looking
|
|
160
|
+
// for a syntax error that does not exist. Say what actually moves it.
|
|
161
|
+
const note = raw.indexOf("Could not find a suitable point") !== -1
|
|
162
|
+
? "Two nodes ended up too close together for Mermaid to fit a label on the edge between them. Renaming a node, adding another, or setting an explicit direction usually spreads the layout enough to render."
|
|
163
|
+
: unrendered(error)
|
|
164
|
+
? "The panel stayed hidden long enough that there was never a laid-out page to measure the diagram against. Reopening the panel renders it."
|
|
165
|
+
: "This diagram could not be rendered.";
|
|
166
|
+
document.querySelector(".viewport").innerHTML =
|
|
167
|
+
'<div class="failure"><p>' + escape(note) +
|
|
168
|
+
'</p><details><summary>Technical detail</summary><pre class="error">' +
|
|
169
|
+
escape(raw) + '</pre></details></div>';
|
|
104
170
|
}
|
|
105
171
|
})();
|
|
106
172
|
</script>
|