negotium 0.3.0 → 0.3.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/dist/agent-helpers.js +57 -5
- 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 +1129 -365
- 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 +585 -255
- 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 +18 -1
- 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 +21 -3
- 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
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { logger } from "#platform/logger";
|
|
4
|
+
import { sanitizeFileName } from "#security/sanitize";
|
|
5
|
+
import { resolveStorageDataDir } from "#storage/storage-host";
|
|
6
|
+
import type { AgentKind, DecisionSnapshot } from "#types";
|
|
7
|
+
|
|
8
|
+
export type StoredDecision = DecisionSnapshot;
|
|
9
|
+
|
|
10
|
+
export const DECISION_STATUS_VALUES = [
|
|
11
|
+
"proposed",
|
|
12
|
+
"accepted",
|
|
13
|
+
"executed",
|
|
14
|
+
"rejected",
|
|
15
|
+
"superseded",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
interface DecisionFileShape {
|
|
19
|
+
version: 1;
|
|
20
|
+
decisions: StoredDecision[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function safeDecisionScopeKey(scopeKey: string): string {
|
|
24
|
+
const safe = sanitizeFileName(scopeKey);
|
|
25
|
+
if (!safe || safe === "." || safe === "..") {
|
|
26
|
+
throw new Error(`decisions: refusing unsafe scope key: ${scopeKey}`);
|
|
27
|
+
}
|
|
28
|
+
return safe;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function decisionScopeKey(opts: { topicId?: string; session: string }): string {
|
|
32
|
+
return opts.topicId?.trim() || opts.session || "default";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getDecisionFilePath(userId: number | string, scopeKey: string): string {
|
|
36
|
+
void userId;
|
|
37
|
+
return join(resolveStorageDataDir(), "decisions", `${safeDecisionScopeKey(scopeKey)}.json`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getDecisionGraphSvgPath(userId: number | string, scopeKey: string): string {
|
|
41
|
+
void userId;
|
|
42
|
+
return join(
|
|
43
|
+
resolveStorageDataDir(),
|
|
44
|
+
"decision-renders",
|
|
45
|
+
safeDecisionScopeKey(scopeKey),
|
|
46
|
+
"latest.svg",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function writeDecisionGraphSvg(
|
|
51
|
+
userId: number | string,
|
|
52
|
+
scopeKey: string,
|
|
53
|
+
svg: string,
|
|
54
|
+
): string {
|
|
55
|
+
const path = getDecisionGraphSvgPath(userId, scopeKey);
|
|
56
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
57
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
58
|
+
writeFileSync(tmp, svg, "utf-8");
|
|
59
|
+
renameSync(tmp, path);
|
|
60
|
+
return path;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function readDecisions(userId: number | string, scopeKey: string): StoredDecision[] {
|
|
64
|
+
const path = getDecisionFilePath(userId, scopeKey);
|
|
65
|
+
if (!existsSync(path)) return [];
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8")) as DecisionFileShape;
|
|
68
|
+
return Array.isArray(parsed?.decisions) ? parsed.decisions : [];
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.warn({ err: error, path }, "decisions: failed to read decision store");
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function writeDecisions(
|
|
76
|
+
userId: number | string,
|
|
77
|
+
scopeKey: string,
|
|
78
|
+
decisions: StoredDecision[],
|
|
79
|
+
): void {
|
|
80
|
+
const path = getDecisionFilePath(userId, scopeKey);
|
|
81
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
82
|
+
const payload: DecisionFileShape = { version: 1, decisions };
|
|
83
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
84
|
+
writeFileSync(tmp, JSON.stringify(payload, null, 2), "utf-8");
|
|
85
|
+
renameSync(tmp, path);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface DecisionCreateInput {
|
|
89
|
+
action: string;
|
|
90
|
+
reasoning: string;
|
|
91
|
+
agent: AgentKind;
|
|
92
|
+
model?: string;
|
|
93
|
+
status?: StoredDecision["status"];
|
|
94
|
+
causedBy?: string[];
|
|
95
|
+
timestamp?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface DecisionUpdateInput {
|
|
99
|
+
id: string;
|
|
100
|
+
action?: string;
|
|
101
|
+
reasoning?: string;
|
|
102
|
+
status?: StoredDecision["status"];
|
|
103
|
+
causedBy?: string[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function nextDecisionId(decisions: StoredDecision[]): number {
|
|
107
|
+
let max = 0;
|
|
108
|
+
for (const decision of decisions) {
|
|
109
|
+
const id = Number(decision.id);
|
|
110
|
+
if (Number.isInteger(id) && id > max) max = id;
|
|
111
|
+
}
|
|
112
|
+
return max + 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizedIds(ids: string[] | undefined): string[] | undefined {
|
|
116
|
+
if (!ids) return undefined;
|
|
117
|
+
const unique = [...new Set(ids.map((id) => id.trim()).filter(Boolean))];
|
|
118
|
+
return unique.length > 0 ? unique : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function validateDecisionGraph(decisions: StoredDecision[]): void {
|
|
122
|
+
const ids = new Set(decisions.map((decision) => decision.id));
|
|
123
|
+
for (const decision of decisions) {
|
|
124
|
+
for (const upstream of decision.causedBy ?? []) {
|
|
125
|
+
if (!ids.has(upstream)) {
|
|
126
|
+
throw new Error(`Decision #${decision.id} references missing decision #${upstream}.`);
|
|
127
|
+
}
|
|
128
|
+
if (upstream === decision.id) {
|
|
129
|
+
throw new Error(`Decision #${decision.id} cannot cause itself.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const visiting = new Set<string>();
|
|
135
|
+
const visited = new Set<string>();
|
|
136
|
+
const byId = new Map(decisions.map((decision) => [decision.id, decision]));
|
|
137
|
+
const visit = (id: string): void => {
|
|
138
|
+
if (visited.has(id)) return;
|
|
139
|
+
if (visiting.has(id)) throw new Error(`Decision graph contains a cycle at #${id}.`);
|
|
140
|
+
visiting.add(id);
|
|
141
|
+
for (const upstream of byId.get(id)?.causedBy ?? []) visit(upstream);
|
|
142
|
+
visiting.delete(id);
|
|
143
|
+
visited.add(id);
|
|
144
|
+
};
|
|
145
|
+
for (const id of ids) visit(id);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function createDecisions(
|
|
149
|
+
decisions: StoredDecision[],
|
|
150
|
+
inputs: DecisionCreateInput[],
|
|
151
|
+
): { decisions: StoredDecision[]; created: StoredDecision[] } {
|
|
152
|
+
const out = [...decisions];
|
|
153
|
+
const created: StoredDecision[] = [];
|
|
154
|
+
let id = nextDecisionId(out);
|
|
155
|
+
for (const input of inputs) {
|
|
156
|
+
const decision: StoredDecision = {
|
|
157
|
+
id: String(id++),
|
|
158
|
+
action: input.action.trim(),
|
|
159
|
+
reasoning: input.reasoning.trim(),
|
|
160
|
+
agent: input.agent,
|
|
161
|
+
status: input.status ?? "accepted",
|
|
162
|
+
timestamp: input.timestamp ?? Date.now(),
|
|
163
|
+
...(input.model ? { model: input.model } : {}),
|
|
164
|
+
...(normalizedIds(input.causedBy) ? { causedBy: normalizedIds(input.causedBy) } : {}),
|
|
165
|
+
};
|
|
166
|
+
out.push(decision);
|
|
167
|
+
created.push(decision);
|
|
168
|
+
}
|
|
169
|
+
validateDecisionGraph(out);
|
|
170
|
+
return { decisions: out, created };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function updateDecisions(
|
|
174
|
+
decisions: StoredDecision[],
|
|
175
|
+
updates: DecisionUpdateInput[],
|
|
176
|
+
): { decisions: StoredDecision[]; missing: string[] } {
|
|
177
|
+
const out = decisions.map((decision) => ({
|
|
178
|
+
...decision,
|
|
179
|
+
...(decision.causedBy ? { causedBy: [...decision.causedBy] } : {}),
|
|
180
|
+
}));
|
|
181
|
+
const byId = new Map(out.map((decision) => [decision.id, decision]));
|
|
182
|
+
const missing: string[] = [];
|
|
183
|
+
for (const update of updates) {
|
|
184
|
+
const decision = byId.get(update.id);
|
|
185
|
+
if (!decision) {
|
|
186
|
+
missing.push(update.id);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (update.action !== undefined) decision.action = update.action.trim();
|
|
190
|
+
if (update.reasoning !== undefined) decision.reasoning = update.reasoning.trim();
|
|
191
|
+
if (update.status !== undefined) decision.status = update.status;
|
|
192
|
+
if (update.causedBy !== undefined) {
|
|
193
|
+
const ids = normalizedIds(update.causedBy);
|
|
194
|
+
if (ids) decision.causedBy = ids;
|
|
195
|
+
else delete decision.causedBy;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
validateDecisionGraph(out);
|
|
199
|
+
return { decisions: out, missing };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function deleteDecisions(
|
|
203
|
+
decisions: StoredDecision[],
|
|
204
|
+
opts: { ids?: string[]; all?: boolean },
|
|
205
|
+
): { decisions: StoredDecision[]; removed: number } {
|
|
206
|
+
if (opts.all) return { decisions: [], removed: decisions.length };
|
|
207
|
+
const ids = new Set(opts.ids ?? []);
|
|
208
|
+
const kept = decisions
|
|
209
|
+
.filter((decision) => !ids.has(decision.id))
|
|
210
|
+
.map((decision) => {
|
|
211
|
+
const causedBy = decision.causedBy?.filter((id) => !ids.has(id));
|
|
212
|
+
const next = { ...decision };
|
|
213
|
+
if (causedBy && causedBy.length > 0) next.causedBy = causedBy;
|
|
214
|
+
else delete next.causedBy;
|
|
215
|
+
return next;
|
|
216
|
+
});
|
|
217
|
+
return { decisions: kept, removed: decisions.length - kept.length };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function renderDecisionList(decisions: StoredDecision[]): string {
|
|
221
|
+
if (decisions.length === 0) return "Decisions (0 recorded)";
|
|
222
|
+
return [
|
|
223
|
+
`Decisions (${decisions.length} recorded)`,
|
|
224
|
+
...decisions.map((decision) => {
|
|
225
|
+
const causes = decision.causedBy?.length
|
|
226
|
+
? ` <- ${decision.causedBy.map((id) => `#${id}`).join(", ")}`
|
|
227
|
+
: "";
|
|
228
|
+
return `[${decision.status}] #${decision.id} ${decision.action}${causes}\n ${decision.reasoning}`;
|
|
229
|
+
}),
|
|
230
|
+
].join("\n");
|
|
231
|
+
}
|
|
@@ -20,6 +20,8 @@ export * as askUserGates from "#storage/ask-user-gates";
|
|
|
20
20
|
export * from "#storage/ask-user-gates";
|
|
21
21
|
export * as conversations from "#storage/conversations";
|
|
22
22
|
export * from "#storage/conversations";
|
|
23
|
+
export * as decisions from "#storage/decisions";
|
|
24
|
+
export * from "#storage/decisions";
|
|
23
25
|
export type {
|
|
24
26
|
ForumTopicInfo,
|
|
25
27
|
TopicRow,
|
|
@@ -97,6 +97,19 @@ export interface TaskSnapshot {
|
|
|
97
97
|
owner?: string;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/** One topic-scoped decision and its incoming causal edges. */
|
|
101
|
+
export interface DecisionSnapshot {
|
|
102
|
+
id: string;
|
|
103
|
+
action: string;
|
|
104
|
+
reasoning: string;
|
|
105
|
+
agent: AgentKind;
|
|
106
|
+
model?: string;
|
|
107
|
+
status: "proposed" | "accepted" | "executed" | "rejected" | "superseded";
|
|
108
|
+
/** Upstream decision ids. Each entry forms a directed upstream -> this edge. */
|
|
109
|
+
causedBy?: string[];
|
|
110
|
+
timestamp: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
100
113
|
export type UnifiedEvent =
|
|
101
114
|
| {
|
|
102
115
|
type: "user_message";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEGOTIUM_VERSION = "0.3.
|
|
1
|
+
export const NEGOTIUM_VERSION = "0.3.1";
|
package/dist/runtime-helpers.js
CHANGED
|
@@ -535,7 +535,7 @@ var DM_WORKSPACE_DIR = resolve(STATE_DIR, "data", "dm");
|
|
|
535
535
|
var SESSION_WORKSPACE_DIR = resolve(STATE_DIR, "data", "sessions");
|
|
536
536
|
var CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
|
|
537
537
|
var CLAUDE_EXECUTABLE = CLAUDE_EXECUTABLE_ENV ? resolve(CLAUDE_EXECUTABLE_ENV) : undefined;
|
|
538
|
-
var BROWSER_RS_VERSION = "v0.1.
|
|
538
|
+
var BROWSER_RS_VERSION = "v0.1.21";
|
|
539
539
|
var BROWSER_RS_MIN_SECURE_VERSION = "0.1.15";
|
|
540
540
|
function versionAtLeast(actualVersion, minimumVersion) {
|
|
541
541
|
const actual = actualVersion.split(".").map(Number);
|
|
@@ -605,6 +605,7 @@ var TSX_LOADER = createRequire(import.meta.url).resolve("tsx");
|
|
|
605
605
|
var TSCONFIG_PATH = resolve(PROJECT_ROOT, "tsconfig.json");
|
|
606
606
|
var SESSION_COMM_SERVER = resolve(PROJECT_ROOT, "src/mcp/session-comm/server.ts");
|
|
607
607
|
var TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
|
|
608
|
+
var DECISION_SERVER = resolve(PROJECT_ROOT, "src/mcp/decision-server.ts");
|
|
608
609
|
var BROWSER_MCP_SSE_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/browser-sse-proxy-server.ts");
|
|
609
610
|
var CANONICAL_MCP_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/canonical-proxy-server.ts");
|
|
610
611
|
var WIKI_SERVER = resolve(PROJECT_ROOT, "src/mcp/wiki-server.ts");
|
|
@@ -1439,6 +1440,10 @@ function buildMermaidHtml(code, theme, scriptUrl = MERMAID_BROWSER_ASSET_RELATIV
|
|
|
1439
1440
|
.controls button:focus-visible{outline:2px solid var(--celadon);outline-offset:1px}
|
|
1440
1441
|
.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}
|
|
1441
1442
|
.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}
|
|
1443
|
+
.failure{max-width:56ch;margin:0 auto}
|
|
1444
|
+
.failure p{margin:0 0 12px;color:var(--graphite);font:14px/1.6 Geist,system-ui,sans-serif}
|
|
1445
|
+
.failure details{color:var(--graphite);font:12px/1.5 Geist,system-ui,sans-serif}
|
|
1446
|
+
.failure summary{cursor:pointer;padding:4px 0}
|
|
1442
1447
|
@media(max-width:600px){.viewport{padding:54px 14px 18px}.controls{top:10px;right:10px}}
|
|
1443
1448
|
</style>
|
|
1444
1449
|
</head>
|
|
@@ -1484,7 +1489,20 @@ function buildMermaidHtml(code, theme, scriptUrl = MERMAID_BROWSER_ASSET_RELATIV
|
|
|
1484
1489
|
applyScale(scale, false);
|
|
1485
1490
|
} catch (error) {
|
|
1486
1491
|
document.querySelector(".controls")?.remove();
|
|
1487
|
-
|
|
1492
|
+
const raw = String(error && error.message ? error.message : error);
|
|
1493
|
+
const escape = (value) => value.replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c]));
|
|
1494
|
+
// Mermaid aborts the whole render when it cannot place an edge label:
|
|
1495
|
+
// cardinality markers ask for a point a fixed distance along the edge,
|
|
1496
|
+
// and a relation laid out shorter than that walks off the end. Nothing
|
|
1497
|
+
// is wrong with the diagram, so the raw message sends authors looking
|
|
1498
|
+
// for a syntax error that does not exist. Say what actually moves it.
|
|
1499
|
+
const note = raw.indexOf("Could not find a suitable point") !== -1
|
|
1500
|
+
? "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."
|
|
1501
|
+
: "This diagram could not be rendered.";
|
|
1502
|
+
document.querySelector(".viewport").innerHTML =
|
|
1503
|
+
'<div class="failure"><p>' + escape(note) +
|
|
1504
|
+
'</p><details><summary>Technical detail</summary><pre class="error">' +
|
|
1505
|
+
escape(raw) + '</pre></details></div>';
|
|
1488
1506
|
}
|
|
1489
1507
|
})();
|
|
1490
1508
|
</script>
|
|
@@ -1596,4 +1614,4 @@ export {
|
|
|
1596
1614
|
CLAUDE_EFFORT_VALUES
|
|
1597
1615
|
};
|
|
1598
1616
|
|
|
1599
|
-
//# debugId=
|
|
1617
|
+
//# debugId=958B6D1C218A4DA464756E2164756E21
|