@prodero/curator 0.0.0-stage → 0.1.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/ARCHITECTURE.md +298 -0
- package/CHANGELOG.md +16 -0
- package/CONTRIBUTING.md +9 -0
- package/LICENSE +21 -0
- package/README.md +94 -2
- package/SECURITY.md +7 -0
- package/bin/prodero +7 -0
- package/dist/cli.js +228 -0
- package/dist/context-instructions.js +2 -0
- package/dist/contracts.js +50 -0
- package/dist/conversation-presentation.js +262 -0
- package/dist/core/agent-instructions.js +39 -0
- package/dist/core/applicability-label.js +11 -0
- package/dist/core/applicability.js +143 -0
- package/dist/core/bootstrap.js +129 -0
- package/dist/core/consolidation.js +315 -0
- package/dist/core/constants.js +21 -0
- package/dist/core/context-allocation.js +54 -0
- package/dist/core/context-snapshot.js +12 -0
- package/dist/core/context.js +489 -0
- package/dist/core/curation-clauses.js +68 -0
- package/dist/core/curation-evidence.js +98 -0
- package/dist/core/curation-graphical.js +105 -0
- package/dist/core/curation-review-presentation.js +4 -0
- package/dist/core/curation-review-state.js +62 -0
- package/dist/core/curation-review.js +573 -0
- package/dist/core/curation-revision.js +106 -0
- package/dist/core/curation-semantic-contract.js +239 -0
- package/dist/core/curation-semantic-jobs.js +280 -0
- package/dist/core/diagnostics.js +17 -0
- package/dist/core/format.js +114 -0
- package/dist/core/ids.js +6 -0
- package/dist/core/imports.js +169 -0
- package/dist/core/index.js +93 -0
- package/dist/core/instruction-review.js +86 -0
- package/dist/core/integrity-files.js +174 -0
- package/dist/core/intelligence.js +95 -0
- package/dist/core/knowledge-analysis.js +46 -0
- package/dist/core/knowledge-authority.js +143 -0
- package/dist/core/knowledge-curation.js +316 -0
- package/dist/core/knowledge-engine.js +475 -0
- package/dist/core/knowledge-graph.js +191 -0
- package/dist/core/knowledge-lifecycle.js +212 -0
- package/dist/core/knowledge-management.js +113 -0
- package/dist/core/knowledge-migration.js +84 -0
- package/dist/core/knowledge-policy.js +38 -0
- package/dist/core/knowledge-provenance.js +90 -0
- package/dist/core/knowledge-write-policy.js +85 -0
- package/dist/core/language.js +14 -0
- package/dist/core/matching.js +6 -0
- package/dist/core/memory-index.js +239 -0
- package/dist/core/memory-intelligence.js +288 -0
- package/dist/core/memory-link-repair.js +66 -0
- package/dist/core/memory-operations.js +122 -0
- package/dist/core/memory-pressure.js +212 -0
- package/dist/core/memory-sync-curator.js +62 -0
- package/dist/core/memory-sync.js +463 -0
- package/dist/core/memory-transformation-bundle.js +163 -0
- package/dist/core/memory-transformation-proposal.js +135 -0
- package/dist/core/memory-transformation.js +108 -0
- package/dist/core/memory.js +186 -0
- package/dist/core/migrations.js +569 -0
- package/dist/core/path-scope.js +27 -0
- package/dist/core/plugin-files.js +116 -0
- package/dist/core/project-root.js +39 -0
- package/dist/core/project.js +634 -0
- package/dist/core/promotion.js +279 -0
- package/dist/core/reconciliation.js +61 -0
- package/dist/core/secrets.js +67 -0
- package/dist/core/semantic-safety.js +33 -0
- package/dist/core/session-index.js +112 -0
- package/dist/core/session-review.js +150 -0
- package/dist/core/sessions.js +289 -0
- package/dist/core/settings.js +76 -0
- package/dist/core/skill-curation.js +239 -0
- package/dist/core/skill-polish.js +41 -0
- package/dist/core/skills.js +368 -0
- package/dist/core/storage.js +408 -0
- package/dist/core/sync-transaction.js +254 -0
- package/dist/core/telemetry.js +64 -0
- package/dist/core/templates.js +53 -0
- package/dist/core/types.js +2 -0
- package/dist/core/update.js +320 -0
- package/dist/curation-review-adapter.js +92 -0
- package/dist/diagnostic-boundary.js +54 -0
- package/dist/filesystem/permissions.js +39 -0
- package/dist/hosts/adapters.js +119 -0
- package/dist/hosts/files.js +133 -0
- package/dist/hosts/manager.js +150 -0
- package/dist/hosts/public-entry.js +47 -0
- package/dist/hosts/runtime-command.js +66 -0
- package/dist/hosts/runtime.js +139 -0
- package/dist/hosts/wizard.js +114 -0
- package/dist/knowledge-table.js +38 -0
- package/dist/mcp-development.js +69 -0
- package/dist/mcp-workspace.js +141 -0
- package/dist/memory-presentation.js +172 -0
- package/dist/plugins/api.js +2 -0
- package/dist/plugins/context.js +20 -0
- package/dist/plugins/mcp.js +72 -0
- package/dist/plugins/registry.js +106 -0
- package/dist/presentation-cards.js +32 -0
- package/dist/presentation-server.js +79 -0
- package/dist/presentation-trace.js +174 -0
- package/dist/presentation.js +412 -0
- package/dist/providers/mcp-sampling.js +45 -0
- package/dist/server.js +656 -0
- package/dist/settings-definition.js +22 -0
- package/dist/skill-instructions.js +2 -0
- package/dist-ui/index.html +214 -0
- package/docs/APPLICABILITY.md +151 -0
- package/docs/COMPATIBILITY.md +49 -0
- package/docs/CONTEXT-SELECTION-V2.md +61 -0
- package/docs/CURATION-APP-REVIEW.md +156 -0
- package/docs/CURATION-CLAUSE-REFINEMENT.md +45 -0
- package/docs/IMPORTER-CURATION.md +142 -0
- package/docs/KNOWLEDGE-AUTHORITY.md +165 -0
- package/docs/KNOWLEDGE-SCHEMA-V2.md +150 -0
- package/docs/MIGRATIONS.md +99 -0
- package/docs/PRIVACY.md +28 -0
- package/docs/RECOVERY.md +56 -0
- package/docs/RELEASING.md +40 -0
- package/docs/hosts.md +134 -0
- package/docs/plugins.md +83 -0
- package/docs/ui-themes.md +43 -0
- package/instructions/AGENTS.md +33 -0
- package/package.json +88 -4
- package/plugins/migrate_ai_studio/README.md +101 -0
- package/plugins/migrate_ai_studio/index.cjs +396 -0
- package/plugins/migrate_ai_studio/instructions/audit.md +25 -0
- package/plugins/migrate_ai_studio/instructions/classify.md +41 -0
- package/plugins/migrate_ai_studio/instructions/migrate.md +34 -0
- package/plugins/migrate_ai_studio/instructions/verify.md +20 -0
- package/plugins/migrate_ai_studio/plugin.json +11 -0
- package/plugins/migrate_ai_studio/schema.cjs +20 -0
- package/plugins/migrate_ai_studio/ui/dist/index.html +77 -0
- package/prodero.release.json +5 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/** Temporary, opt-in wire observation. No tool/resource payloads are recorded. */
|
|
2
|
+
import { createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
3
|
+
import { closeSync, constants, fstatSync, openSync, writeSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { getUiCapability, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
|
6
|
+
import { PRODERO_RESOURCE_URI, PRODERO_TOOLS } from "./contracts.js";
|
|
7
|
+
const publicKeys = new Set(["experimental", "extensions", "io.modelcontextprotocol/ui", "mimeTypes", "roots", "listChanged", "sampling", "context", "tools", "elicitation", "form", "url", "tasks", "requests", "call", "createMessage", "create", "list", "cancel", "subscribe", "name", "version", "title", "platform", "userAgent", "clientInfo", "hostInfo", "clientId", "sessionId", "progressToken", "io.modelcontextprotocol/related-task", "taskId", "openai/userAgent", "openai/userLocation", "openai/locale", "openai/widgetSessionId", "ui", "protocolVersion"]);
|
|
8
|
+
const publicNames = new Set(["codex", "codex-mcp-client", "Codex", "claude-desktop", "Claude", "Cursor", "cursor", "claude-code"]);
|
|
9
|
+
let processFingerprintSalt;
|
|
10
|
+
const versions = new Set(["2024-10-07", "2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25", "2026-01-26"]);
|
|
11
|
+
export function installPresentationTrace(server) {
|
|
12
|
+
if (process.env.PRODERO_DEBUG_PRESENTATION !== "1")
|
|
13
|
+
return;
|
|
14
|
+
const connect = server.connect.bind(server);
|
|
15
|
+
// Hashes permit equality checks within this process without storing unknown values.
|
|
16
|
+
const salt = processFingerprintSalt ??= randomBytes(32);
|
|
17
|
+
const fingerprint = (value) => createHmac("sha256", salt).update(JSON.stringify(value) ?? "undefined").digest("hex").slice(0, 24);
|
|
18
|
+
function safe(value, key = "", depth = 0) {
|
|
19
|
+
if (depth > 6)
|
|
20
|
+
return { redacted: true, fingerprint: fingerprint(value) };
|
|
21
|
+
if (value === undefined)
|
|
22
|
+
return null;
|
|
23
|
+
if (value === null || typeof value === "boolean")
|
|
24
|
+
return value;
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
if ((key === "mimeTypes" && value === RESOURCE_MIME_TYPE) || (key === "platform" && ["desktop", "mobile", "web"].includes(value)) || (key === "name" && publicNames.has(value)) || (key === "version" && /^\d+\.\d+\.\d+$/.test(value)) || (key === "protocolVersion" && versions.has(value)))
|
|
27
|
+
return value;
|
|
28
|
+
return { type: "string", fingerprint: fingerprint(value) };
|
|
29
|
+
}
|
|
30
|
+
if (typeof value === "number")
|
|
31
|
+
return { type: "number", fingerprint: fingerprint(value) };
|
|
32
|
+
if (Array.isArray(value))
|
|
33
|
+
return { items: value.slice(0, 32).map(item => safe(item, key, depth + 1)), truncated: value.length > 32, ...(value.length > 32 ? { fingerprint: fingerprint(value) } : {}) };
|
|
34
|
+
if (typeof value === "object")
|
|
35
|
+
return { ...Object.fromEntries(Object.entries(value).slice(0, 64).map(([k, v]) => [publicKeys.has(k) ? k : `key#${fingerprint(k)}`, safe(v, k, depth + 1)])), ...(Object.keys(value).length > 64 ? { traceTruncatedKeys: true, fingerprint: fingerprint(value) } : {}) };
|
|
36
|
+
return { redacted: true };
|
|
37
|
+
}
|
|
38
|
+
server.connect = async (transport) => {
|
|
39
|
+
const connection = randomUUID();
|
|
40
|
+
let fd;
|
|
41
|
+
let disabled = false;
|
|
42
|
+
let sequence = 0;
|
|
43
|
+
const destination = process.env.PRODERO_DEBUG_PRESENTATION_FILE;
|
|
44
|
+
try {
|
|
45
|
+
if (destination) {
|
|
46
|
+
if (!path.isAbsolute(destination))
|
|
47
|
+
throw new Error("absolute path required");
|
|
48
|
+
fd = openSync(destination, constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW, 0o600);
|
|
49
|
+
const stat = fstatSync(fd);
|
|
50
|
+
if (!stat.isFile() || (stat.mode & 0o077) !== 0 || (process.getuid && stat.uid !== process.getuid()))
|
|
51
|
+
throw new Error("private regular file required");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
if (fd !== undefined)
|
|
56
|
+
closeSync(fd);
|
|
57
|
+
fd = undefined;
|
|
58
|
+
disabled = true;
|
|
59
|
+
process.stderr.write("[prodero:presentation] Trace disabled: destination must be an owned private regular file at an absolute path.\n");
|
|
60
|
+
}
|
|
61
|
+
const emit = (event, details = {}) => {
|
|
62
|
+
if (disabled || sequence >= 10000)
|
|
63
|
+
return;
|
|
64
|
+
try {
|
|
65
|
+
let line = JSON.stringify({ trace: "prodero-presentation-v1", time: new Date().toISOString(), pid: process.pid, connection, sequence: ++sequence, event, ...details }) + "\n";
|
|
66
|
+
if (line.length > 32768)
|
|
67
|
+
line = JSON.stringify({ trace: "prodero-presentation-v1", time: new Date().toISOString(), pid: process.pid, connection, sequence, event, truncated: true }) + "\n";
|
|
68
|
+
if (fd !== undefined)
|
|
69
|
+
writeSync(fd, line);
|
|
70
|
+
else
|
|
71
|
+
process.stderr.write(line);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
disabled = true;
|
|
75
|
+
} // Diagnostics never change a protocol result.
|
|
76
|
+
};
|
|
77
|
+
const snapshot = () => {
|
|
78
|
+
const capabilities = server.server.getClientCapabilities();
|
|
79
|
+
const ui = getUiCapability(capabilities);
|
|
80
|
+
return { transportSession: safe(transport.sessionId), clientInfo: safe(server.server.getClientVersion()), capabilities: safe(capabilities), uiExtensionPresent: ui !== undefined, declaredMimeTypes: safe(ui?.mimeTypes, "mimeTypes"), supportsMcpAppHtml: Array.isArray(ui?.mimeTypes) && ui.mimeTypes.includes(RESOURCE_MIME_TYPE) };
|
|
81
|
+
};
|
|
82
|
+
const pending = new Map();
|
|
83
|
+
let call = 0;
|
|
84
|
+
function incoming(message) {
|
|
85
|
+
if (!("method" in message))
|
|
86
|
+
return;
|
|
87
|
+
const params = message.params;
|
|
88
|
+
let event;
|
|
89
|
+
if (message.method === "initialize")
|
|
90
|
+
event = "initialize.request";
|
|
91
|
+
else if (message.method === "tools/call" && params?.name === PRODERO_TOOLS.open)
|
|
92
|
+
event = "open.request";
|
|
93
|
+
else if (message.method === "tools/call" && params?.name === PRODERO_TOOLS.plugins)
|
|
94
|
+
event = "control.request";
|
|
95
|
+
else if (["tools/list", "resources/list", "resources/templates/list", "prompts/list", "ping"].includes(message.method))
|
|
96
|
+
event = "discovery.request";
|
|
97
|
+
else if (message.method === "tools/call") {
|
|
98
|
+
emit("other-tool.observed");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
else if (message.method === "resources/read" && params?.uri === PRODERO_RESOURCE_URI)
|
|
102
|
+
event = "status-resource.read";
|
|
103
|
+
else if (message.method === "notifications/initialized") {
|
|
104
|
+
emit("mcp.initialized", snapshot());
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
else if (message.method.startsWith("ui/")) {
|
|
108
|
+
emit("ui.message.observed", { method: ["ui/initialize", "ui/notifications/initialized", "ui/notifications/size-changed", "ui/notifications/host-context-changed"].includes(message.method) ? message.method : safe(message.method), meta: safe(params?._meta) });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!event)
|
|
112
|
+
return;
|
|
113
|
+
const number = ++call;
|
|
114
|
+
if ("id" in message) {
|
|
115
|
+
if (pending.size >= 256)
|
|
116
|
+
pending.delete(pending.keys().next().value);
|
|
117
|
+
pending.set(message.id, { event, call: number });
|
|
118
|
+
}
|
|
119
|
+
emit(event, { call: number, ...(event === "discovery.request" ? { method: message.method } : { meta: safe(params?._meta) }), ...(event === "initialize.request" ? { requestedProtocolVersion: safe(params?.protocolVersion, "protocolVersion"), clientInfo: safe(params?.clientInfo), capabilities: safe(params?.capabilities) } : snapshot()) });
|
|
120
|
+
}
|
|
121
|
+
function outgoing(message) {
|
|
122
|
+
if (!("id" in message) || message.id === undefined || "method" in message)
|
|
123
|
+
return;
|
|
124
|
+
const request = pending.get(message.id);
|
|
125
|
+
if (!request)
|
|
126
|
+
return;
|
|
127
|
+
pending.delete(message.id);
|
|
128
|
+
const result = "result" in message ? message.result : undefined;
|
|
129
|
+
emit(request.event.replace(/request$|read$/, "response"), { call: request.call, outcome: "error" in message || result?.isError === true ? "error" : "ok", ...(request.event === "initialize.request" ? { negotiatedProtocolVersion: safe(result?.protocolVersion, "protocolVersion"), ...snapshot() } : {}) });
|
|
130
|
+
}
|
|
131
|
+
// Observe the public transport boundary, including the SDK-owned initialize handler.
|
|
132
|
+
const wrapped = {
|
|
133
|
+
onmessage: transport.onmessage,
|
|
134
|
+
onerror: transport.onerror,
|
|
135
|
+
onclose: transport.onclose,
|
|
136
|
+
get sessionId() { return transport.sessionId; },
|
|
137
|
+
setProtocolVersion: transport.setProtocolVersion?.bind(transport),
|
|
138
|
+
async start() {
|
|
139
|
+
transport.onmessage = (message, extra) => { try {
|
|
140
|
+
incoming(message);
|
|
141
|
+
}
|
|
142
|
+
catch { /* fail open */ } wrapped.onmessage?.(message, extra); };
|
|
143
|
+
transport.onerror = error => { emit("transport.error"); wrapped.onerror?.(error); };
|
|
144
|
+
transport.onclose = () => { emit("connection.closed"); if (fd !== undefined) {
|
|
145
|
+
try {
|
|
146
|
+
closeSync(fd);
|
|
147
|
+
}
|
|
148
|
+
catch { }
|
|
149
|
+
fd = undefined;
|
|
150
|
+
} disabled = true; wrapped.onclose?.(); };
|
|
151
|
+
await transport.start();
|
|
152
|
+
},
|
|
153
|
+
async send(message, options) { try {
|
|
154
|
+
outgoing(message);
|
|
155
|
+
}
|
|
156
|
+
catch { /* fail open */ } await transport.send(message, options); },
|
|
157
|
+
close: () => transport.close(),
|
|
158
|
+
};
|
|
159
|
+
emit("connection.started", { handshakeObservation: "passive-server-wire-only; app-host handshake normally does not reach server", fingerprintScope: "process", controlTool: PRODERO_TOOLS.plugins });
|
|
160
|
+
try {
|
|
161
|
+
await connect(wrapped);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (fd !== undefined) {
|
|
165
|
+
try {
|
|
166
|
+
closeSync(fd);
|
|
167
|
+
}
|
|
168
|
+
catch { }
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=presentation-trace.js.map
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { renderKnowledgeTable } from './knowledge-table.js';
|
|
2
|
+
import { conversationView, renderConversation, renderActionMenu, PRESENTATION_REPLY } from './conversation-presentation.js';
|
|
3
|
+
import { renderReviewProjection } from './core/curation-review-presentation.js';
|
|
4
|
+
export { PRESENTATION_REPLY } from './conversation-presentation.js';
|
|
5
|
+
export { renderReviewProjection } from './core/curation-review-presentation.js';
|
|
6
|
+
import { applicabilityLabel } from "./core/applicability-label.js";
|
|
7
|
+
import { memoryOpenPresentation, memorySyncPresentation } from './memory-presentation.js';
|
|
8
|
+
export { memoryOpenPresentation, memorySyncPresentation, MEMORY_COPY } from './memory-presentation.js';
|
|
9
|
+
export const PRESENTATION_LAUNCHER = "When the user says `prodero` (ignoring surrounding whitespace and case), `open prodero`, or `show prodero`, immediately call prodero_config_get for the current workspace. If presentation.mode is text, call prodero_text with tool=prodero_open; otherwise call prodero_open. Pass the same projectRoot to config_get and the selected operation (inside arguments for prodero_text). Do not ask what the user wants to do with Prodero.";
|
|
10
|
+
export const PRESENTATION_ROUTING = PRESENTATION_REPLY + ' Use prodero_config_get for the current project before choosing a presentation route. For a project in text mode, call prodero_text (default tool prodero_open); pass original arguments in arguments. Apply this routing to every covered lifecycle and review operation, including prodero_prepare and prodero_session_close, and to available actions. Do not call the App-attached original with presentation=text: it can leave an empty host container. The gateway has no App attachment. Show human-readable content and actions in chat. Natural requests to change display mode update presentation.mode through prodero_config_set; do not retain a sticky conversation-only preference or record settings as knowledge. A one-off request to show a response as text may use prodero_text without changing config. Standard MCP Apps capabilities inform auto mode only; never infer a device from client metadata. Original tools retain static App descriptors even if they return text. For UI calls, generate one opaque presentationGroup per conversation (or use a supported host-provided conversation ID) and reuse it across Prodero UI tools, including open, prepare and session_close, so older cards can collapse. Never use a project path or a shared MCP connection ID as the group; never reuse it in another conversation. Keep it ephemeral, not in project settings or knowledge. Keep the existing review and approval workflow before mutations or promotion. v0.1 trust model: Local Knowledge is Git-ignored working state, not a security boundary against the connected agent. Versioned Knowledge is Git-shareable project state; the agent may create, promote and maintain it. Review/preview/approval steps are workflow safeguards, not authenticated human authorization; approved=true does not prove human consent. Curator does not commit or push. Users must review repository changes before committing or pushing.';
|
|
11
|
+
const list = (v) => Array.isArray(v) ? v : [];
|
|
12
|
+
export const tokenLabel = (n) => n >= 1000 ? `${(n / 1000).toFixed(2)}K` : String(n);
|
|
13
|
+
const human = (s) => s.replace(/[-_]/g, " ");
|
|
14
|
+
function itemView(item) {
|
|
15
|
+
return { title: item.title ?? item.id ?? "Knowledge", body: item.content ?? item.contentPreview ?? "", notes: [
|
|
16
|
+
item.applicability && `Applies to: ${applicabilityLabel(item)}`,
|
|
17
|
+
item.id && `ID: ${item.id}`, item.revision && `Revision: ${item.revision}`,
|
|
18
|
+
item.createdAt && `Created: ${item.createdAt}`, item.updatedAt && `Updated: ${item.updatedAt}`,
|
|
19
|
+
[item.layer, item.category ?? item.type, item.priority, item.scopeRef ?? item.scope, item.status].filter(Boolean).join(" · "),
|
|
20
|
+
...list(item.signals).map(s => `${s.kind}: ${s.detail}`),
|
|
21
|
+
item.derivedFrom?.length && `Sources: ${item.derivedFrom.join(", ")}`,
|
|
22
|
+
].filter(Boolean) };
|
|
23
|
+
}
|
|
24
|
+
export function buildViewModel(payload) {
|
|
25
|
+
const normalized = payload.action === "initialized" ? { ...payload, project: payload.status?.project ?? payload.project } : payload;
|
|
26
|
+
const vm = buildDetailedViewModel(normalized);
|
|
27
|
+
vm.conversation = conversationView(normalized, vm);
|
|
28
|
+
return vm;
|
|
29
|
+
}
|
|
30
|
+
function buildDetailedViewModel(payload) {
|
|
31
|
+
const r = payload.result ?? payload;
|
|
32
|
+
const root = r.root ?? r.project?.root ?? payload.project?.root ?? payload.root;
|
|
33
|
+
const vm = { version: 1, title: "Prodero · Project Intelligence", metrics: [], notices: [], sections: [], actions: [] };
|
|
34
|
+
const metric = (label, value, detail) => { if (value !== undefined && value !== null)
|
|
35
|
+
vm.metrics.push({ label, value: typeof value === "number" ? value : String(value), ...(detail ? { detail } : {}) }); };
|
|
36
|
+
const action = (label, tool, args = {}, approval = false) => vm.actions.push({ label, tool, arguments: { ...(root ? { projectRoot: root } : {}), ...args }, ...(approval ? { approval: true } : {}) });
|
|
37
|
+
const section = (title, items) => { if (items.length)
|
|
38
|
+
vm.sections.push({ title, items: items.map(itemView) }); };
|
|
39
|
+
const knowledge = () => action("Show project knowledge", "prodero_knowledge");
|
|
40
|
+
if (payload.action === "curation") {
|
|
41
|
+
const p = payload.plan, projection = payload.projection;
|
|
42
|
+
vm.title = "Knowledge cleanup";
|
|
43
|
+
if (!projection) {
|
|
44
|
+
vm.notices.push("Reload this review to obtain its saved state.");
|
|
45
|
+
action("Reload review", "prodero_curation", { action: "plan" });
|
|
46
|
+
return vm;
|
|
47
|
+
}
|
|
48
|
+
vm.notices.push(renderReviewProjection(projection), "Knowledge stays unchanged until explicit Apply.");
|
|
49
|
+
metric("Proposed change groups", projection.readyChangeGroups);
|
|
50
|
+
metric("Needs input", projection.needsInputNow);
|
|
51
|
+
metric("Deferred questions", projection.deferredQuestions);
|
|
52
|
+
metric("Deferred changes", projection.deferredChanges);
|
|
53
|
+
metric("Unchanged", projection.unchanged);
|
|
54
|
+
const decisions = projection.items.filter((v) => v.needsInput);
|
|
55
|
+
if (decisions.length)
|
|
56
|
+
vm.sections.push({ title: "Needs clarification", items: decisions.map((v) => ({ title: p.proposals.find((q) => q.id === v.proposalIds[0]).before.title, body: "Keep current or decide later. No actionable suggestion is available yet.", notes: [] })) });
|
|
57
|
+
for (const group of [...new Set(p.proposals.map((v) => v.group))])
|
|
58
|
+
vm.sections.push({ title: String(group), items: p.proposals.filter((v) => v.group === group).map((v) => ({ title: v.before.title, body: `Currently: ${v.before.contextPolicy} · ${applicabilityLabel(v.before)}\nSuggested: ${v.outputs.map((o) => `${o.category} · ${o.contextPolicy} · ${applicabilityLabel(o)}`).join("; ")}`, notes: [...v.attention, ...(v.investigation ? [v.investigation.outcome, v.investigation.explanation] : [])] })) });
|
|
59
|
+
vm.diagnostics = [{ title: "Record content and traceability", items: p.proposals.map((v) => ({ title: v.before.title, body: `Current: ${v.before.content}\n\nProposed: ${v.outputs.map((o) => o.content).join("\n\n")}`, notes: [`Source revision: ${v.revision}`, `Source: ${v.sourceId}`, ...v.evidence] })) }];
|
|
60
|
+
if (payload.result && Array.isArray(payload.result))
|
|
61
|
+
vm.sections.push({ title: "Context impact · safe batch", items: payload.result.map((row) => ({ title: row.task, body: `Required candidate tokens: ${row.before.requiredCandidateTokens} → ${row.after.requiredCandidateTokens}; omissions: ${row.before.requiredOmissions} → ${row.after.requiredOmissions}`, notes: [`Context needed: ${row.after.contextNeeded ? "yes" : "no"}`] })) });
|
|
62
|
+
if (!payload.trustedReviewAvailable)
|
|
63
|
+
vm.notices.push("Text review is preview-only. Open the graphical App to review and apply changes.");
|
|
64
|
+
if (projection.readyChangeGroups && !projection.paused)
|
|
65
|
+
action("Preview context impact", "prodero_curation", { projectRoot: payload.root, action: "simulate", planId: p.planId });
|
|
66
|
+
if ((projection.readyChangeGroups || projection.needsInputNow) && !projection.paused)
|
|
67
|
+
action("Pause review", "prodero_curation", { projectRoot: payload.root, action: "defer", planId: p.planId });
|
|
68
|
+
if (!projection.paused && (projection.readyChangeGroups || projection.needsInputNow))
|
|
69
|
+
action("Refresh suggestions", "prodero_curation", { projectRoot: payload.root, action: "plan" });
|
|
70
|
+
action("Back to Prodero", "prodero_open", { projectRoot: payload.root });
|
|
71
|
+
return vm;
|
|
72
|
+
}
|
|
73
|
+
if (payload.action === "migration") {
|
|
74
|
+
const p = payload.plan;
|
|
75
|
+
const titles = { current: "Project knowledge is up to date", available: "Knowledge update available", "review-required": "Knowledge update needs review", "recovery-required": "Knowledge update needs recovery", updating: "Updating project knowledge", "runtime-too-old": "Update Curator to continue", blocked: "Knowledge update unavailable" };
|
|
76
|
+
vm.title = payload.completed ? "Knowledge update completed" : titles[p.state] ?? "Project knowledge update";
|
|
77
|
+
if (p.state === "available")
|
|
78
|
+
vm.notices.push("This project needs a knowledge update. Existing knowledge will be preserved and a recovery backup will be created before changes.");
|
|
79
|
+
if (p.state === "review-required")
|
|
80
|
+
vm.notices.push("Some decisions need review. This update cannot be applied automatically; a supported review workflow is required.");
|
|
81
|
+
if (p.state === "runtime-too-old")
|
|
82
|
+
vm.notices.push("This project uses a newer knowledge or project format. Update Curator before continuing work.");
|
|
83
|
+
if (p.state === "recovery-required")
|
|
84
|
+
vm.notices.push("A previous update was interrupted. Restore the pre-update backup safely, then review the update again.");
|
|
85
|
+
if (p.state === "updating")
|
|
86
|
+
vm.notices.push("The project update is running. Check progress shortly.");
|
|
87
|
+
if (p.state === "blocked")
|
|
88
|
+
vm.notices.push("The project update cannot proceed safely. Inspect Details for recovery information.");
|
|
89
|
+
if (payload.completed)
|
|
90
|
+
vm.notices.push("Knowledge was preserved. The project is ready to continue.");
|
|
91
|
+
const args = { planId: p.planId, ...(payload.resume ? { resume: payload.resume } : {}) };
|
|
92
|
+
if (p.safeToApply)
|
|
93
|
+
action("Update project knowledge", "prodero_migrate", { ...args, action: "apply", approved: true }, true);
|
|
94
|
+
if (p.state === "recovery-required")
|
|
95
|
+
action("Restore pre-update backup", "prodero_migrate", { ...args, action: "recover", approved: true }, true);
|
|
96
|
+
if (p.state === "updating")
|
|
97
|
+
action("Check update progress", "prodero_migrate", { action: "status", ...(payload.resume ? { resume: payload.resume } : {}) });
|
|
98
|
+
if (p.state === "current") {
|
|
99
|
+
const resume = payload.resume ?? { tool: "prodero_open", arguments: { projectRoot: root } };
|
|
100
|
+
action("Continue", resume.tool, resume.arguments);
|
|
101
|
+
}
|
|
102
|
+
const formatVersion = (domain) => p.current?.[domain] == null
|
|
103
|
+
? `Current version unavailable · Target v${p.target[domain]}`
|
|
104
|
+
: p.current[domain] === p.target[domain] ? `v${p.current[domain]} · Up to date`
|
|
105
|
+
: `v${p.current[domain]} → v${p.target[domain]}`;
|
|
106
|
+
const stores = { memory: "Shared knowledge", "local/memory": "Local knowledge", archive: "Shared archive", "local/archive": "Local archive", "config.json": "Project settings", "index.md": "Shared index", "local/index.md": "Local index", ".cache": "Derived caches" };
|
|
107
|
+
const kinds = { structural: "Format update", "compatible-semantic": "Compatible knowledge update", "review-required": "Requires review" };
|
|
108
|
+
vm.diagnostics = [
|
|
109
|
+
{ title: "Format versions", items: [
|
|
110
|
+
{ title: "Project format", body: formatVersion("project"), notes: [] },
|
|
111
|
+
{ title: "Knowledge format", body: formatVersion("knowledge"), notes: [] },
|
|
112
|
+
] },
|
|
113
|
+
...(p.migrations.length ? [{ title: "Planned changes", items: list(p.migrations).map(step => ({
|
|
114
|
+
title: `${step.domain === "project" ? "Project" : "Knowledge"} · v${step.from} → v${step.to}`,
|
|
115
|
+
body: step.description,
|
|
116
|
+
notes: [kinds[step.kind] ?? human(step.kind), ...(step.affectedStores?.length ? [`Affects: ${step.affectedStores.map((store) => stores[store] ?? store).join(", ")}`] : [])],
|
|
117
|
+
})) }] : []),
|
|
118
|
+
{ title: "Backup and recovery", items: [
|
|
119
|
+
{ title: "Recovery backup", body: p.backupRequired ? "A recovery backup will be created before changes are applied." : "No new backup is required for the current state.", notes: [] },
|
|
120
|
+
...(p.recovery ? [{ title: "Recovery instructions", body: p.recovery, notes: [] }] : []),
|
|
121
|
+
...(p.diagnostic ? [{ title: "Update issue", body: p.diagnostic, notes: [] }] : []),
|
|
122
|
+
] },
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
else if (["open", "status", "status-details", "initialized"].includes(payload.action)) {
|
|
126
|
+
const p = payload.project;
|
|
127
|
+
if (payload.curation?.state === "unavailable")
|
|
128
|
+
vm.notices.push(payload.curation.message ?? "Saved review is unavailable.");
|
|
129
|
+
if (payload.curation?.prominent) {
|
|
130
|
+
vm.notices.push(renderReviewProjection(payload.curation.projection));
|
|
131
|
+
action("Review cleanup", "prodero_curation", { action: "plan" });
|
|
132
|
+
action("Pause review", "prodero_curation", { action: "defer", planId: payload.curation.planId });
|
|
133
|
+
}
|
|
134
|
+
if (payload.curation?.projection?.analyzedSourceCount > 0 && !payload.curation.prominent) {
|
|
135
|
+
vm.notices.push(renderReviewProjection(payload.curation.projection));
|
|
136
|
+
action("Review saved decisions", "prodero_curation", { action: "plan" });
|
|
137
|
+
}
|
|
138
|
+
if (typeof p?.initialized !== "boolean") {
|
|
139
|
+
vm.notices.push("Project state is not available yet.");
|
|
140
|
+
return vm;
|
|
141
|
+
}
|
|
142
|
+
vm.title = p.initialized ? "Prodero · Project Intelligence" : "Prodero · Initialize Project";
|
|
143
|
+
metric("Project", p.name);
|
|
144
|
+
metric("Git", p.git ? "Available" : "Not found");
|
|
145
|
+
metric("AGENTS.md", p.agentsFile ? "Found" : "Not found");
|
|
146
|
+
if (!p.initialized) {
|
|
147
|
+
vm.notices.push("Prodero is not initialized. Initialization creates .prodero/ and managed agent instructions.");
|
|
148
|
+
action("Initialize Prodero", "prodero_initialize");
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const memory = memoryOpenPresentation(p);
|
|
152
|
+
vm.simpleActions = true;
|
|
153
|
+
metric("Knowledge", p.knowledgeCount);
|
|
154
|
+
metric("Local Knowledge", p.localKnowledgeCount);
|
|
155
|
+
metric("Shared / Versioned Knowledge", p.versionedKnowledgeCount);
|
|
156
|
+
metric("Active skills", p.skillCount, p.skillCandidateCount ? `${p.skillCandidateCount} candidate${p.skillCandidateCount === 1 ? "" : "s"}` : undefined);
|
|
157
|
+
metric("Sessions", p.sessionCount);
|
|
158
|
+
vm.notices.push(memory.headline, ...memory.notes);
|
|
159
|
+
if (memory.conflicts)
|
|
160
|
+
vm.notices.push(`${memory.conflicts} area${memory.conflicts === 1 ? ' needs' : 's need'} review. Prodero found incompatible current knowledge.`);
|
|
161
|
+
if (memory.areas.length)
|
|
162
|
+
vm.sections.push({ title: "Memory", items: memory.areas.map((a) => ({ title: a.title, body: a.body, notes: a.reasons })) });
|
|
163
|
+
vm.diagnostics = [{ title: "Memory diagnostics", items: [{ title: "Project statistics", body: JSON.stringify({ memoryHealth: p.memoryHealth, graph: p.knowledgeGraph ? { ...p.knowledgeGraph, cacheState: undefined, ...(payload.action === "open" ? { brokenReferences: undefined } : {}) } : undefined, pendingSessions: p.pendingSessionCount, checkpoints: p.checkpointCount, overview: p.memoryOverview }, null, 2), notes: [] }] }];
|
|
164
|
+
if (payload.phase === "initial") {
|
|
165
|
+
vm.notices.push("Lightweight status only; detailed statistics were not requested.");
|
|
166
|
+
action("Load detailed status", "prodero_status_details");
|
|
167
|
+
}
|
|
168
|
+
knowledge();
|
|
169
|
+
action("Show project skills", "prodero_skills");
|
|
170
|
+
action(memory.button, "prodero_sync");
|
|
171
|
+
action("Check for updates", "prodero_check_update");
|
|
172
|
+
}
|
|
173
|
+
const b = payload.bootstrap;
|
|
174
|
+
if (b) {
|
|
175
|
+
metric("Required", b.instructions.length);
|
|
176
|
+
section("Required instructions · model-visible, not system-level", b.instructions);
|
|
177
|
+
vm.notices.push(...b.warnings);
|
|
178
|
+
}
|
|
179
|
+
if (payload.update?.available)
|
|
180
|
+
vm.notices.push(`Update available: ${payload.update.current} → ${payload.update.latest}.`);
|
|
181
|
+
}
|
|
182
|
+
else if (payload.action === "context") {
|
|
183
|
+
vm.title = "Prodero · View Context";
|
|
184
|
+
metric("Context", `${tokenLabel(r.metrics.contextTokens)} / ${tokenLabel(r.metrics.contextBudgetTokens)}`);
|
|
185
|
+
if (r.policyReadiness?.blocking)
|
|
186
|
+
vm.notices.push("Required context is incomplete or needs review. Do not continue work.");
|
|
187
|
+
vm.notices.push(...list(r.attention).map(a => `${a.title}: ${a.detail}`));
|
|
188
|
+
section("Required instructions not loaded", list(r.selection?.omittedRequired));
|
|
189
|
+
section("Required instructions", r.required);
|
|
190
|
+
section("Relevant knowledge", r.relevant);
|
|
191
|
+
section("Skills", r.skills);
|
|
192
|
+
vm.diagnostics = [{ title: "Context diagnostics", items: [{ title: "Selection", body: JSON.stringify({ metrics: r.metrics, selection: r.selection, policyReadiness: r.policyReadiness }, null, 2), notes: [] }] }];
|
|
193
|
+
}
|
|
194
|
+
else if (payload.action === "memory-sync") {
|
|
195
|
+
const memory = memorySyncPresentation(r);
|
|
196
|
+
vm.title = `Sync Memory · ${memory.headline}`;
|
|
197
|
+
vm.simpleActions = true;
|
|
198
|
+
vm.metrics = memory.metrics;
|
|
199
|
+
vm.notices = [...memory.notes, ...memory.warnings];
|
|
200
|
+
vm.sections = memory.sections;
|
|
201
|
+
if (r.state === 'awaiting_selection')
|
|
202
|
+
vm.sections.unshift({ title: 'Sync options', items: [{ title: 'Improve memory · enabled', body: 'Process, compress and organize memory.', notes: [] }, ...(r.brokenLinks ? [{ title: `Fix ${r.brokenLinks} broken ${r.brokenLinks === 1 ? 'link' : 'links'} · enabled`, body: 'Review and remove links to missing memories. Original records are saved in history.', notes: [] }] : []), { title: 'Share knowledge · enabled', body: 'Review Local knowledge for Versioned files. Your agent can also change these files; review the Git diff before commit/push.', notes: [] }] });
|
|
203
|
+
if (r.clarification?.graphical)
|
|
204
|
+
vm.sections.push({ title: 'Project questions', items: r.clarification.graphical.questions.filter((q) => q.needsInput).map((q) => ({ title: q.question.title, body: q.question.prompt, notes: q.question.choices.map((c) => c.label) })) });
|
|
205
|
+
if (r.state === 'awaiting_promotion_selection')
|
|
206
|
+
section("Local Knowledge available for sharing review", r.promotionCandidates.map((c) => ({ title: c.title })));
|
|
207
|
+
vm.diagnostics = [{ title: "Memory diagnostics", items: [{ title: "Workflow evidence and exact effects", body: JSON.stringify({ ...r, semantic: undefined }, null, 2), notes: [] }] }];
|
|
208
|
+
const args = { workflowId: r.id, revision: r.revision };
|
|
209
|
+
if (r.state === 'awaiting_selection')
|
|
210
|
+
action("Start Sync", "prodero_memory_sync", { request: { action: 'select', ...args, improve: true, share: true, ...(r.brokenLinks ? { repairLinks: true } : {}) } });
|
|
211
|
+
if (r.state === 'semantic_work')
|
|
212
|
+
action("Continue improving memory", "prodero_memory_sync", { request: { action: 'prepare', ...args, candidateId: r.next.candidateId } });
|
|
213
|
+
if (r.state === 'awaiting_review')
|
|
214
|
+
action(r.review.kind === 'promotion' ? "Share reviewed knowledge" : "Approve reviewed changes", "prodero_memory_sync", { request: { action: 'approve', ...args, reviewId: r.review.reviewId, approved: true } }, true);
|
|
215
|
+
if (r.state === 'awaiting_promotion_selection')
|
|
216
|
+
action("Review selected knowledge for sharing", "prodero_memory_sync", { request: { action: 'promotions', ...args, ids: [] } });
|
|
217
|
+
if (r.state === 'awaiting_review')
|
|
218
|
+
action("Skip this review", "prodero_memory_sync", { request: { action: 'skip', ...args, ...(r.review.kind === 'transformation' ? { candidateId: r.jobs.find((j) => j.status === 'awaiting_review')?.id } : {}) } });
|
|
219
|
+
if (r.state === 'awaiting_promotion_selection')
|
|
220
|
+
action("Finish without sharing", "prodero_memory_sync", { request: { action: 'complete', ...args } });
|
|
221
|
+
if (r.state === 'stale' || r.state === 'completed')
|
|
222
|
+
action("Re-analyze memory", "prodero_memory_sync", { request: { action: 'start', restart: true } });
|
|
223
|
+
else if (["semantic_work", "applying"].includes(r.state))
|
|
224
|
+
action("Check progress", "prodero_memory_sync", { request: { action: 'inspect', workflowId: r.id } });
|
|
225
|
+
action("Show details", "prodero_memory_sync", { request: { action: 'inspect', workflowId: r.id }, details: true });
|
|
226
|
+
}
|
|
227
|
+
else if (payload.action === "prepare") {
|
|
228
|
+
vm.title = r.context.policyReadiness?.state === "required-incomplete" ? "Prodero · Required context incomplete" : r.context.policyReadiness?.state === "context-needed" ? "Prodero · Task context needed" : r.context.policyReadiness?.blocking ? "Prodero · Required policy review" : "Prodero · Context Ready";
|
|
229
|
+
const c = r.context, m = c.metrics;
|
|
230
|
+
if (c.applicability)
|
|
231
|
+
vm.diagnostics = [{ title: "Applicability", items: Object.entries(c.applicability.decisions).map(([id, d]) => ({ title: id, body: d.state, notes: d.reasons })) }];
|
|
232
|
+
if (c.allocation)
|
|
233
|
+
(vm.diagnostics ??= []).push({ title: "Context allocation", items: [{ title: "Allocation decisions", body: JSON.stringify(c.allocation, null, 2), notes: [] }] });
|
|
234
|
+
metric("Required", c.required.length);
|
|
235
|
+
metric("Relevant", c.relevant.length);
|
|
236
|
+
metric("Skills", c.skills.length);
|
|
237
|
+
metric("Synced", r.automaticSync?.sessionsProcessed ?? r.automaticSync?.sessions ?? 0);
|
|
238
|
+
metric("Task context", `${tokenLabel(m.contextTokens)} / ${tokenLabel(m.contextBudgetTokens)} est. tokens`);
|
|
239
|
+
metric("Project knowledge", `${tokenLabel(m.projectKnowledgeTokens)} est. tokens`);
|
|
240
|
+
const omitted = m.requiredGlobalExcludedItems ?? c.selection?.omittedRequired?.length ?? 0;
|
|
241
|
+
if (omitted)
|
|
242
|
+
vm.notices.push(`Required context truncated: ${omitted} required instructions were not loaded. Mandatory delivery is incomplete. Inspect total capacity and safety-ceiling omissions before continuing.`);
|
|
243
|
+
vm.notices.push(...(r.warnings ?? []), ...list(r.attention ?? c.attention).map(a => `${a.title}: ${a.detail}`));
|
|
244
|
+
section("Required instructions", c.required);
|
|
245
|
+
section("Relevant knowledge", c.relevant);
|
|
246
|
+
section("Skills", c.skills);
|
|
247
|
+
section("Required instructions not loaded", list(c.selection?.omittedRequired).map(i => ({ ...i, content: human(i.reason ?? "not loaded") })));
|
|
248
|
+
section("Additional matches not loaded", list(c.selection?.omittedRelevant).map(i => ({ ...i, content: human(i.reason ?? "not loaded") })));
|
|
249
|
+
const additional = (c.selection?.omittedRelevantByBudget ?? 0) + (c.selection?.omittedRelevantByItemLimit ?? 0) + (c.selection?.omittedRelevantByMarginalValue ?? 0);
|
|
250
|
+
if (additional)
|
|
251
|
+
vm.notices.push(`${additional} additional relevant matches were not loaded; ${c.selection?.omittedRelevant?.length ?? 0} summaries supplied.`);
|
|
252
|
+
const reviewId = c.snapshot?.reviewId;
|
|
253
|
+
if (reviewId) {
|
|
254
|
+
action("Review truncated required instructions", "prodero_instruction_review", { reviewId });
|
|
255
|
+
action("Preview instruction consolidation", "prodero_instruction_optimize", { reviewId, action: "preview" });
|
|
256
|
+
}
|
|
257
|
+
knowledge();
|
|
258
|
+
}
|
|
259
|
+
else if (payload.action === "session-close") {
|
|
260
|
+
vm.title = "Prodero · Session Learned";
|
|
261
|
+
metric("Recorded", r.learned.observations);
|
|
262
|
+
metric("Session memory", `${tokenLabel(r.learned.rawSessionTokens)} est. tokens`);
|
|
263
|
+
for (const [key, value] of Object.entries(r.learned.byType ?? {}))
|
|
264
|
+
if (value)
|
|
265
|
+
metric(human(key), value);
|
|
266
|
+
metric("Needs review", r.review?.needsReview);
|
|
267
|
+
metric("Local Knowledge", r.review?.localKnowledge);
|
|
268
|
+
metric("Versioned Knowledge", r.review?.versionedKnowledge);
|
|
269
|
+
vm.notices.push(r.alreadyClosed ? "Session was already closed." : "Session closed.");
|
|
270
|
+
vm.notices.push(r.learned.syncState === "pending-auto-sync" ? "Saving was deferred. Prodero will recover it before the next work session." : "Useful knowledge was saved automatically to Local Knowledge.");
|
|
271
|
+
const s = r.learned.synchronization;
|
|
272
|
+
if (s)
|
|
273
|
+
for (const key of ["knowledgeCreated", "knowledgeConsolidated", "duplicates", "conflicts"])
|
|
274
|
+
metric(human(key), s[key]);
|
|
275
|
+
if (r.review?.error)
|
|
276
|
+
vm.notices.push(`Knowledge review unavailable: ${r.review.error}`);
|
|
277
|
+
action("Review session knowledge", "prodero_session_review", { sessionId: r.session.id });
|
|
278
|
+
knowledge();
|
|
279
|
+
}
|
|
280
|
+
else if (["knowledge", "knowledge-mutated"].includes(payload.action)) {
|
|
281
|
+
vm.title = "Prodero · Knowledge";
|
|
282
|
+
if (payload.action === "knowledge-mutated")
|
|
283
|
+
vm.notices.push("Knowledge maintenance completed. Identity and history are retained.");
|
|
284
|
+
metric("Local", r.counts.local);
|
|
285
|
+
metric("Versioned", r.counts.versioned);
|
|
286
|
+
metric("Available for sharing review", r.counts.recommended);
|
|
287
|
+
metric("Matches", r.total);
|
|
288
|
+
section(r.detail ? "Knowledge detail" : "Knowledge", r.detail ? [r.detail] : r.matches);
|
|
289
|
+
if (!r.detail)
|
|
290
|
+
vm.knowledgeTable = { offset: r.options?.offset ?? 0, total: r.total, rows: r.matches.map((i, index) => ({ key: String(index + 1), id: i.id, layer: i.layer === 'versioned' ? 'Versioned' : 'Local', category: i.category ?? i.type ?? '', priority: i.priority ?? '', title: i.title ?? i.id })) };
|
|
291
|
+
if (r.detail) {
|
|
292
|
+
const i = r.detail;
|
|
293
|
+
action("Back to knowledge list", "prodero_knowledge", r.options);
|
|
294
|
+
vm.notices.push("Edits preserve identity and history. Retirement is not secure erase. Repository maintenance modifies Git-shareable files through the review workflow. Approval arguments do not authenticate human consent; review the Git diff before commit/push.");
|
|
295
|
+
action("Review an edit or retirement", "prodero_knowledge_mutate", { id: i.id, revision: i.revision, mode: i.layer === "versioned" ? "repository-maintenance" : "local" }, true);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
vm.notices.push("Pass an item ID to inspect its complete content and current revision.");
|
|
299
|
+
action("Inspect knowledge by ID", "prodero_knowledge", r.options);
|
|
300
|
+
}
|
|
301
|
+
if (!r.detail && r.nextOffset != null)
|
|
302
|
+
action("Next page", "prodero_knowledge", { ...r.options, offset: r.nextOffset });
|
|
303
|
+
action("Sync Memory · improve and share", "prodero_sync");
|
|
304
|
+
}
|
|
305
|
+
else if (payload.action === "instruction-review") {
|
|
306
|
+
vm.title = "Prodero · Required Instruction Review";
|
|
307
|
+
metric("Omitted", r.total);
|
|
308
|
+
metric("Critical", r.counts.critical);
|
|
309
|
+
metric("Normal", r.counts.normal);
|
|
310
|
+
section("Instructions", r.items.map((row) => ({ ...row.item, ...(!row.item ? { title: row.selection.title, id: row.selection.id, content: "No longer available." } : {}), signals: [{ kind: "Omission", detail: human(row.selection.reason) }] })));
|
|
311
|
+
if (r.nextOffset != null)
|
|
312
|
+
action("Next page", "prodero_instruction_review", { reviewId: r.reviewId, offset: r.nextOffset, criticalOnly: r.criticalOnly });
|
|
313
|
+
action("Preview instruction consolidation", "prodero_instruction_optimize", { reviewId: r.reviewId, action: "preview" });
|
|
314
|
+
knowledge();
|
|
315
|
+
}
|
|
316
|
+
else if (payload.action === "instruction-optimize") {
|
|
317
|
+
vm.title = "Prodero · Instruction Consolidation";
|
|
318
|
+
metric("Before", r.beforeTokens);
|
|
319
|
+
metric("After", r.afterTokens);
|
|
320
|
+
metric("Instructions reduced", r.reducedBy);
|
|
321
|
+
metric("Inspected", r.inspected);
|
|
322
|
+
metric("Remaining", r.remaining);
|
|
323
|
+
if (r.notice)
|
|
324
|
+
vm.notices.push(r.notice);
|
|
325
|
+
if (r.repository)
|
|
326
|
+
vm.notices.push("This proposal changes Git-tracked Versioned Knowledge.");
|
|
327
|
+
section("Before", list(r.before));
|
|
328
|
+
section("After", list(r.after));
|
|
329
|
+
if (!r.applied && r.previewId && r.reducedBy > 0)
|
|
330
|
+
action("Apply this exact reviewed preview", "prodero_instruction_optimize", { reviewId: r.reviewId, previewId: r.previewId, action: "apply", approved: true }, true);
|
|
331
|
+
if (r.applied)
|
|
332
|
+
vm.notices.push("Applied. Prepare again to load the updated instructions.");
|
|
333
|
+
knowledge();
|
|
334
|
+
}
|
|
335
|
+
else if (payload.action === "promotion-preview") {
|
|
336
|
+
vm.title = "Prodero · Publication Review";
|
|
337
|
+
const preview = payload.preview;
|
|
338
|
+
if (preview.notice)
|
|
339
|
+
vm.notices.push(preview.notice);
|
|
340
|
+
vm.notices.push("Supply the complete structured preview unchanged when approving publication.");
|
|
341
|
+
metric("Items", preview.actions.length);
|
|
342
|
+
vm.notices.push("This preview changes no knowledge. Applying it writes Git-shareable Versioned files; Curator does not commit or push. Review the Git diff before sharing. Conflicts remain local.");
|
|
343
|
+
for (const a of preview.actions) {
|
|
344
|
+
vm.notices.push(`${a.disposition}: ${a.localIds.join(", ")} — ${a.reason}`);
|
|
345
|
+
section(`Before · ${a.targetId ?? "new item"}`, a.before ? [a.before] : []);
|
|
346
|
+
section("Proposed", a.draft ? [a.draft] : []);
|
|
347
|
+
}
|
|
348
|
+
action("Publish the exact reviewed preview", "prodero_promote", { approved: true }, true);
|
|
349
|
+
}
|
|
350
|
+
else if (payload.action === "promotion-applied") {
|
|
351
|
+
vm.title = "Prodero · Publication Complete";
|
|
352
|
+
metric("Written to Versioned", r.published);
|
|
353
|
+
metric("Already covered", r.covered?.length);
|
|
354
|
+
metric("Conflicts remain local", r.conflicts?.length);
|
|
355
|
+
knowledge();
|
|
356
|
+
}
|
|
357
|
+
else if (payload.action === "session-review") {
|
|
358
|
+
vm.title = "Prodero · Session Knowledge Review";
|
|
359
|
+
metric("Items", r.summary.total);
|
|
360
|
+
metric("Needs review", r.summary.needsReview);
|
|
361
|
+
metric("State", r.consolidated ? "Synchronized" : "Pending synchronization");
|
|
362
|
+
section("Observations", r.items);
|
|
363
|
+
section("Produced knowledge", [...r.items.flatMap((i) => i.outputs ?? []), ...r.knowledge]);
|
|
364
|
+
vm.notices.push(`Session: ${r.sessionId} · Revision: ${r.revision}`);
|
|
365
|
+
if (r.editable)
|
|
366
|
+
action("Review an edit or discard", "prodero_session_review_mutate", { sessionId: r.sessionId, revision: r.revision }, true);
|
|
367
|
+
else
|
|
368
|
+
vm.notices.push("Session history is not editable; maintain the resulting knowledge instead.");
|
|
369
|
+
knowledge();
|
|
370
|
+
}
|
|
371
|
+
if (payload.presentationWarning)
|
|
372
|
+
vm.notices.push(payload.presentationWarning);
|
|
373
|
+
vm.notices = [...new Set(vm.notices.filter(Boolean))];
|
|
374
|
+
return vm;
|
|
375
|
+
}
|
|
376
|
+
/** Short overview first; selected instruction/knowledge bodies remain available to the agent. */
|
|
377
|
+
export function renderText(vm, includeDiagnostics = false, expanded = includeDiagnostics) {
|
|
378
|
+
if (vm.conversation && vm.conversation.kind !== "details" && !expanded)
|
|
379
|
+
return renderConversation(vm.conversation);
|
|
380
|
+
const knowledgeDetail = vm.sections.find(s => s.title === "Knowledge detail");
|
|
381
|
+
if (knowledgeDetail) {
|
|
382
|
+
const record = knowledgeDetail.items.map(i => [i.title, i.body, ...i.notes].filter(Boolean).join("\n")).join("\n\n");
|
|
383
|
+
const hints = vm.notices.map(n => `• ${n}`).join("\n");
|
|
384
|
+
const diagnostics = includeDiagnostics ? (vm.diagnostics ?? []).map(s => `${s.title}:\n${s.items.map(i => [i.title, i.body, ...i.notes].filter(Boolean).join("\n")).join("\n\n")}`).join("\n\n") : '';
|
|
385
|
+
return [vm.title, `Knowledge detail:\n${record}`, "-------------", hints, renderActionMenu(vm.actions), diagnostics].filter(Boolean).join("\n\n");
|
|
386
|
+
}
|
|
387
|
+
const blocks = [vm.title, vm.metrics.map(m => `${m.label} ${m.value}${m.detail ? ` · ${m.detail}` : ""}`).join("\n"), vm.notices.map(n => `• ${n}`).join("\n")];
|
|
388
|
+
if (vm.knowledgeTable) {
|
|
389
|
+
const t = vm.knowledgeTable;
|
|
390
|
+
blocks.push(renderKnowledgeTable(t.rows));
|
|
391
|
+
if (t.rows.length)
|
|
392
|
+
blocks.push(`Showing ${t.offset + 1}-${t.offset + t.rows.length} of ${t.total}. Enter a record number (for example 1) to open it; numbers refer only to this displayed page.`);
|
|
393
|
+
}
|
|
394
|
+
if (vm.actions.length)
|
|
395
|
+
blocks.push(renderActionMenu(vm.actions, (vm.knowledgeTable?.rows.length ?? 0) + 1));
|
|
396
|
+
for (const s of [...vm.sections.filter(s => !vm.knowledgeTable || s.title !== "Knowledge" || expanded), ...(includeDiagnostics ? vm.diagnostics ?? [] : (vm.title === "Knowledge cleanup" || (expanded && vm.conversation?.title === "Prodero · Project update") ? vm.diagnostics ?? [] : []))])
|
|
397
|
+
blocks.push(`${s.title}:\n` + s.items.map(i => [i.title, i.body, ...i.notes].filter(Boolean).join("\n")).join("\n\n"));
|
|
398
|
+
if (expanded && vm.conversation?.reviewRequired && vm.actions.some(a => a.approval) && vm.conversation.title === "Prodero · Sync")
|
|
399
|
+
blocks.push("Approve these exact changes?");
|
|
400
|
+
if (expanded && vm.conversation?.title === "Prodero · Project update") {
|
|
401
|
+
const approval = vm.actions.find(a => a.approval);
|
|
402
|
+
if (approval)
|
|
403
|
+
blocks.push(`${approval.label}?`);
|
|
404
|
+
}
|
|
405
|
+
return blocks.filter(Boolean).join("\n\n");
|
|
406
|
+
}
|
|
407
|
+
const escape = (s) => String(s).replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
408
|
+
/** Shared summary renderer, used by the existing interactive App. Actions keep its handlers. */
|
|
409
|
+
export function renderMetricsHtml(vm) {
|
|
410
|
+
return vm.metrics.map(m => `<div class="metric"><strong>${escape(m.value)}</strong><span>${escape(m.label)}</span>${m.detail ? `<small>${escape(m.detail)}</small>` : ""}</div>`).join("");
|
|
411
|
+
}
|
|
412
|
+
//# sourceMappingURL=presentation.js.map
|