@prodero/curator 0.1.0 → 0.2.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/ARCHITECTURE.md +1 -1
- package/CHANGELOG.md +25 -2
- package/README.md +32 -33
- package/dist/cli.js +58 -20
- package/dist/contracts.js +1 -0
- package/dist/conversation-presentation.js +62 -27
- package/dist/core/agent-instructions.js +31 -18
- package/dist/core/curation-graphical.js +22 -10
- package/dist/core/curation-question.js +28 -0
- package/dist/core/curation-semantic-contract.js +4 -1
- package/dist/core/curation-semantic-jobs.js +78 -18
- package/dist/core/knowledge-graph.js +5 -4
- package/dist/core/local-preferences.js +76 -0
- package/dist/core/memory-index.js +2 -0
- package/dist/core/memory-link-repair.js +6 -2
- package/dist/core/memory-sync-curator.js +25 -5
- package/dist/core/memory-sync.js +124 -29
- package/dist/core/npm-update.js +289 -0
- package/dist/core/project.js +8 -8
- package/dist/core/session-index.js +10 -6
- package/dist/core/sessions.js +3 -3
- package/dist/core/settings.js +34 -16
- package/dist/core/skills.js +21 -2
- package/dist/core/storage.js +55 -1
- package/dist/core/sync-transaction.js +85 -49
- package/dist/core/update.js +9 -3
- package/dist/curation-review-adapter.js +1 -1
- package/dist/diagnostic-boundary.js +4 -3
- package/dist/memory-presentation.js +26 -16
- package/dist/memory-sync-operation.js +88 -0
- package/dist/presentation-server.js +25 -10
- package/dist/presentation.js +92 -61
- package/dist/server.js +59 -51
- package/dist/settings-definition.js +3 -3
- package/dist/status-presentation.js +20 -0
- package/dist/text-layout.js +278 -0
- package/dist/update-command.js +53 -0
- package/dist/update-instructions.js +4 -0
- package/dist-ui/index.html +117 -115
- package/docs/COMPATIBILITY.md +36 -15
- package/docs/MIGRATIONS.md +4 -0
- package/docs/RECOVERY.md +3 -1
- package/docs/RELEASING.md +69 -11
- package/docs/hosts.md +25 -7
- package/docs/images/prodero-context-ready-cropped.png +0 -0
- package/docs/images/prodero-overview-cropped.png +0 -0
- package/docs/images/prodero-session-learned-cropped.png +0 -0
- package/docs/ui-themes.md +10 -4
- package/instructions/AGENTS.md +24 -18
- package/package.json +6 -2
package/dist/presentation.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { conversationView,
|
|
1
|
+
import { renderProductText, entryKey } from './text-layout.js';
|
|
2
|
+
import { conversationView, PRESENTATION_REPLY, LIFECYCLE_PRESENTATION_REPLY } from './conversation-presentation.js';
|
|
3
3
|
import { renderReviewProjection } from './core/curation-review-presentation.js';
|
|
4
|
-
export { PRESENTATION_REPLY } from './conversation-presentation.js';
|
|
4
|
+
export { PRESENTATION_REPLY, LIFECYCLE_PRESENTATION_REPLY } from './conversation-presentation.js';
|
|
5
5
|
export { renderReviewProjection } from './core/curation-review-presentation.js';
|
|
6
6
|
import { applicabilityLabel } from "./core/applicability-label.js";
|
|
7
|
-
import { memoryOpenPresentation, memorySyncPresentation } from './memory-presentation.js';
|
|
7
|
+
import { memoryOpenPresentation, memorySyncPresentation, MEMORY_COPY } from './memory-presentation.js';
|
|
8
|
+
import { memorySyncOperation } from './memory-sync-operation.js';
|
|
9
|
+
import { MANUAL_UPDATE_COMMAND } from './update-instructions.js';
|
|
10
|
+
export { memorySyncOperation, syncClientFailure, syncClientNotice } from './memory-sync-operation.js';
|
|
8
11
|
export { memoryOpenPresentation, memorySyncPresentation, MEMORY_COPY } from './memory-presentation.js';
|
|
12
|
+
export { UPDATE_INSTRUCTIONS } from './update-instructions.js';
|
|
9
13
|
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 + '
|
|
14
|
+
export const PRESENTATION_ROUTING = PRESENTATION_REPLY + ' ' + LIFECYCLE_PRESENTATION_REPLY + ' Read effective settings with prodero_config_get for the current project before choosing each presentation route. Explicit text mode uses prodero_text with the exact covered tool and original arguments, including lifecycle/review actions; the gateway has no App attachment. Do not call an App-attached original with presentation=text. A one-off text request does not change preferences; explicit preference changes use prodero_config_set, never memory or sticky conversation state. Auto uses advertised MCP Apps capability, never inferred devices. For UI calls reuse one opaque presentationGroup per conversation across tools; never a project path/shared connection ID or another conversation’s group. Preserve each operation’s existing review/approval requirements.';
|
|
11
15
|
const list = (v) => Array.isArray(v) ? v : [];
|
|
12
16
|
export const tokenLabel = (n) => n >= 1000 ? `${(n / 1000).toFixed(2)}K` : String(n);
|
|
13
17
|
const human = (s) => s.replace(/[-_]/g, " ");
|
|
@@ -25,6 +29,19 @@ export function buildViewModel(payload) {
|
|
|
25
29
|
const normalized = payload.action === "initialized" ? { ...payload, project: payload.status?.project ?? payload.project } : payload;
|
|
26
30
|
const vm = buildDetailedViewModel(normalized);
|
|
27
31
|
vm.conversation = conversationView(normalized, vm);
|
|
32
|
+
const r = normalized.result ?? normalized, p = normalized.project ?? r.project;
|
|
33
|
+
const c = normalized.action === 'context' ? r : r.context;
|
|
34
|
+
const b = normalized.bootstrap;
|
|
35
|
+
const projection = normalized.curation?.projection;
|
|
36
|
+
vm.textScene = {
|
|
37
|
+
action: normalized.action,
|
|
38
|
+
project: p?.name ?? String(r.root ?? c?.projectRoot ?? normalized.root ?? normalized.projectSettings?.root ?? '').split(/[\\/]/).filter(Boolean).at(-1),
|
|
39
|
+
task: c?.query,
|
|
40
|
+
...(c?.metrics ? { budget: { used: c.metrics.contextTokens, total: c.metrics.contextBudgetTokens, source: 'Task', estimated: true } } : b?.loaded && b.metrics && b.limits ? { budget: { used: b.metrics.estimatedTokens, total: b.limits.budgetTokens, source: 'Bootstrap', estimated: true } } : {}),
|
|
41
|
+
...(['open', 'status', 'initialized'].includes(normalized.action) ? { home: { initialized: p?.initialized, records: p?.knowledgeCount, local: p?.localKnowledgeCount, versioned: p?.versionedKnowledgeCount, deferred: projection?.deferredQuestions, pending: projection?.readyChangeGroups } } : {}),
|
|
42
|
+
};
|
|
43
|
+
if (['open', 'status', 'status-details'].includes(normalized.action) && normalized.update?.available)
|
|
44
|
+
vm.conversation.attention.push(`Finish work and close hosts, then run ${MANUAL_UPDATE_COMMAND} in a terminal. Restart/reconnect afterwards; review project migrations separately.`);
|
|
28
45
|
return vm;
|
|
29
46
|
}
|
|
30
47
|
function buildDetailedViewModel(payload) {
|
|
@@ -37,7 +54,51 @@ function buildDetailedViewModel(payload) {
|
|
|
37
54
|
const section = (title, items) => { if (items.length)
|
|
38
55
|
vm.sections.push({ title, items: items.map(itemView) }); };
|
|
39
56
|
const knowledge = () => action("Show project knowledge", "prodero_knowledge");
|
|
40
|
-
|
|
57
|
+
const questionActions = (data, workflowId, candidateId) => {
|
|
58
|
+
const review = data.review, g = data.graphical;
|
|
59
|
+
if (!review || !g)
|
|
60
|
+
return;
|
|
61
|
+
const binding = { workflowId, candidateId, reviewId: review.snapshot.reviewId, reviewRevisionId: review.snapshot.revision.id, planHash: review.snapshot.planHash, decisionRevision: review.state.revision, answerLogHash: g.answerLogHash };
|
|
62
|
+
for (const q of g.questions.filter((q) => q.needsInput || q.state === 'answered_pending_analysis')) {
|
|
63
|
+
if (q.needsInput && !g.analysisPending)
|
|
64
|
+
for (const choice of q.question.choices)
|
|
65
|
+
action(choice.label, 'prodero_curation_question', { ...binding, action: 'answer', questionId: q.question.id, questionVersion: q.question.version, choiceId: choice.id, requestId: `chat_${globalThis.crypto.randomUUID()}`, ...(q.answer ? { replacesAnswerId: q.answer.id } : {}) });
|
|
66
|
+
action(q.relatedQuestions?.length > 1 ? `Defer ${q.relatedQuestions.length} related questions` : 'Answer later', 'prodero_curation_question', { ...binding, action: 'question_later', questionId: q.question.id, questionVersion: q.question.version });
|
|
67
|
+
}
|
|
68
|
+
if (g.canRetryAnalysis)
|
|
69
|
+
action('Retry saved answer analysis', 'prodero_curation_question', { ...binding, action: 'retry_analysis' });
|
|
70
|
+
else if (g.needsPreparation)
|
|
71
|
+
action('Analyze saved answer', 'prodero_curation_question', { ...binding, action: 'analyze' });
|
|
72
|
+
if (g.continuationJobId)
|
|
73
|
+
action('Continue bounded analysis', 'prodero_curation_reasoning_prepare', { jobId: g.continuationJobId });
|
|
74
|
+
};
|
|
75
|
+
if (payload.action === 'config-get') {
|
|
76
|
+
vm.title = 'Prodero · Project Settings';
|
|
77
|
+
const settings = payload.projectSettings;
|
|
78
|
+
vm.sections = settings.settings.map((s) => ({ title: s.label, items: [{ title: s.displayValue, body: s.description, notes: [`Source: ${s.source}`] }] }));
|
|
79
|
+
vm.notices.push('Display mode and theme are saved on this checkout. Context budget is shared project configuration.');
|
|
80
|
+
for (const d of settings.diagnostics ?? [])
|
|
81
|
+
vm.notices.push(`${d.code}: ${d.cause} ${d.recovery}`);
|
|
82
|
+
if (!settings.initialized)
|
|
83
|
+
vm.notices.push('Project is not initialized. Defaults are shown; initialize Prodero before saving settings.');
|
|
84
|
+
}
|
|
85
|
+
else if (payload.action === 'update-instructions') {
|
|
86
|
+
vm.title = 'Prodero · Update instructions';
|
|
87
|
+
vm.notices.push(r.message);
|
|
88
|
+
}
|
|
89
|
+
else if (payload.action === 'curation-question') {
|
|
90
|
+
vm.title = 'Saved curation question';
|
|
91
|
+
vm.simpleActions = true;
|
|
92
|
+
if (payload.answerAcknowledgement)
|
|
93
|
+
vm.notices.push(payload.answerAcknowledgement.message);
|
|
94
|
+
if (payload.analysisNotice)
|
|
95
|
+
vm.notices.push(payload.analysisNotice);
|
|
96
|
+
vm.notices.push('Chat answers are relayed evidence, not approval. Unresolved facts may still need clarification.');
|
|
97
|
+
vm.sections.push({ title: 'Project questions', items: payload.graphical.questions.map((q) => ({ title: q.question.title, body: q.question.prompt, notes: [q.state, ...q.question.choices.map((c) => c.label)] })) });
|
|
98
|
+
questionActions(payload, payload.syncWorkflowId, payload.candidateId);
|
|
99
|
+
action('Check saved curation progress', 'prodero_memory_sync', { request: { action: 'inspect', workflowId: payload.syncWorkflowId } });
|
|
100
|
+
}
|
|
101
|
+
else if (payload.action === "curation") {
|
|
41
102
|
const p = payload.plan, projection = payload.projection;
|
|
42
103
|
vm.title = "Knowledge cleanup";
|
|
43
104
|
if (!projection) {
|
|
@@ -160,7 +221,7 @@ function buildDetailedViewModel(payload) {
|
|
|
160
221
|
vm.notices.push(`${memory.conflicts} area${memory.conflicts === 1 ? ' needs' : 's need'} review. Prodero found incompatible current knowledge.`);
|
|
161
222
|
if (memory.areas.length)
|
|
162
223
|
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: [] }] }];
|
|
224
|
+
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: p.memoryHealth?.missingProvenanceItems ? [`${p.memoryHealth.missingProvenanceItems} current records have incomplete provenance metadata. ${MEMORY_COPY.incompleteProvenance}`] : [] }] }];
|
|
164
225
|
if (payload.phase === "initial") {
|
|
165
226
|
vm.notices.push("Lightweight status only; detailed statistics were not requested.");
|
|
166
227
|
action("Load detailed status", "prodero_status_details");
|
|
@@ -169,6 +230,8 @@ function buildDetailedViewModel(payload) {
|
|
|
169
230
|
action("Show project skills", "prodero_skills");
|
|
170
231
|
action(memory.button, "prodero_sync");
|
|
171
232
|
action("Check for updates", "prodero_check_update");
|
|
233
|
+
if (payload.update?.available)
|
|
234
|
+
action("Show update instructions", "prodero_update");
|
|
172
235
|
}
|
|
173
236
|
const b = payload.bootstrap;
|
|
174
237
|
if (b) {
|
|
@@ -193,36 +256,25 @@ function buildDetailedViewModel(payload) {
|
|
|
193
256
|
}
|
|
194
257
|
else if (payload.action === "memory-sync") {
|
|
195
258
|
const memory = memorySyncPresentation(r);
|
|
196
|
-
vm.title = `
|
|
259
|
+
vm.title = memory.headline === 'Curate Memory' ? 'Prodero · Curate Memory' : `Curate Memory · ${memory.headline}`;
|
|
197
260
|
vm.simpleActions = true;
|
|
198
261
|
vm.metrics = memory.metrics;
|
|
199
262
|
vm.notices = [...memory.notes, ...memory.warnings];
|
|
200
263
|
vm.sections = memory.sections;
|
|
201
264
|
if (r.state === 'awaiting_selection')
|
|
202
|
-
vm.sections.unshift({ title: '
|
|
265
|
+
vm.sections.unshift({ title: 'Curation 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
266
|
if (r.clarification?.graphical)
|
|
204
267
|
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
268
|
if (r.state === 'awaiting_promotion_selection')
|
|
206
269
|
section("Local Knowledge available for sharing review", r.promotionCandidates.map((c) => ({ title: c.title })));
|
|
207
270
|
vm.diagnostics = [{ title: "Memory diagnostics", items: [{ title: "Workflow evidence and exact effects", body: JSON.stringify({ ...r, semantic: undefined }, null, 2), notes: [] }] }];
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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 });
|
|
271
|
+
const operation = r.operation ?? memorySyncOperation(r);
|
|
272
|
+
vm.operation = operation;
|
|
273
|
+
for (const q of list(r.deferredQuestions)) {
|
|
274
|
+
const { title, expired, affectedQuestions } = q;
|
|
275
|
+
vm.sections.push({ title: expired ? 'Expired question review' : 'Deferred questions', items: [{ title: affectedQuestions.length > 1 ? `${affectedQuestions.length} related questions` : title, body: affectedQuestions.map((a) => a.title).join('\n'), notes: [...(affectedQuestions.length > 1 ? ['These related questions reopen together.'] : []), ...(expired ? ['Refresh this expired review before choosing Revisit. Refresh preserves deferral.'] : [])] }] });
|
|
276
|
+
}
|
|
277
|
+
vm.actions.push(...operation.actions);
|
|
226
278
|
}
|
|
227
279
|
else if (payload.action === "prepare") {
|
|
228
280
|
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";
|
|
@@ -287,20 +339,26 @@ function buildDetailedViewModel(payload) {
|
|
|
287
339
|
metric("Matches", r.total);
|
|
288
340
|
section(r.detail ? "Knowledge detail" : "Knowledge", r.detail ? [r.detail] : r.matches);
|
|
289
341
|
if (!r.detail)
|
|
290
|
-
vm.knowledgeTable = { offset: r.options?.offset ?? 0, total: r.total, rows: r.matches.map((i, index) => ({ key:
|
|
342
|
+
vm.knowledgeTable = { offset: r.options?.offset ?? 0, total: r.total, rows: r.matches.map((i, index) => ({ key: entryKey(index), id: i.id, layer: i.layer === 'versioned' ? 'Versioned' : 'Local', category: i.category ?? i.type ?? '', priority: i.priority ?? '', title: i.title ?? i.id })) };
|
|
291
343
|
if (r.detail) {
|
|
292
344
|
const i = r.detail;
|
|
293
345
|
action("Back to knowledge list", "prodero_knowledge", r.options);
|
|
294
346
|
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
347
|
action("Review an edit or retirement", "prodero_knowledge_mutate", { id: i.id, revision: i.revision, mode: i.layer === "versioned" ? "repository-maintenance" : "local" }, true);
|
|
348
|
+
vm.actions.at(-1).input = { field: 'action', prompt: 'Collect edit/retire and required fields. Show the complete proposal before asking for approval.' };
|
|
296
349
|
}
|
|
297
350
|
else {
|
|
298
|
-
|
|
299
|
-
|
|
351
|
+
const search = { label: 'Search knowledge', tool: 'prodero_knowledge', arguments: { ...(root ? { projectRoot: root } : {}), ...r.options, offset: 0 }, input: { field: 'query', prompt: 'What should I search for?' } };
|
|
352
|
+
vm.actions.push(search);
|
|
353
|
+
action('Show Local knowledge', 'prodero_knowledge', { ...r.options, layer: 'local', offset: 0 });
|
|
354
|
+
action('Show Versioned knowledge', 'prodero_knowledge', { ...r.options, layer: 'versioned', offset: 0 });
|
|
355
|
+
if (r.options?.offset)
|
|
356
|
+
action('Previous page', 'prodero_knowledge', { ...r.options, offset: Math.max(0, r.options.offset - (r.options.limit ?? 12)) });
|
|
300
357
|
}
|
|
301
358
|
if (!r.detail && r.nextOffset != null)
|
|
302
359
|
action("Next page", "prodero_knowledge", { ...r.options, offset: r.nextOffset });
|
|
303
|
-
action("
|
|
360
|
+
action("Curate Memory", "prodero_sync");
|
|
361
|
+
action("Back to Prodero", "prodero_open");
|
|
304
362
|
}
|
|
305
363
|
else if (payload.action === "instruction-review") {
|
|
306
364
|
vm.title = "Prodero · Required Instruction Review";
|
|
@@ -348,7 +406,7 @@ function buildDetailedViewModel(payload) {
|
|
|
348
406
|
action("Publish the exact reviewed preview", "prodero_promote", { approved: true }, true);
|
|
349
407
|
}
|
|
350
408
|
else if (payload.action === "promotion-applied") {
|
|
351
|
-
vm.title = "Prodero ·
|
|
409
|
+
vm.title = "Prodero · Versioned Files Prepared";
|
|
352
410
|
metric("Written to Versioned", r.published);
|
|
353
411
|
metric("Already covered", r.covered?.length);
|
|
354
412
|
metric("Conflicts remain local", r.conflicts?.length);
|
|
@@ -374,35 +432,8 @@ function buildDetailedViewModel(payload) {
|
|
|
374
432
|
return vm;
|
|
375
433
|
}
|
|
376
434
|
/** Short overview first; selected instruction/knowledge bodies remain available to the agent. */
|
|
377
|
-
export function renderText(vm, includeDiagnostics = false, expanded = includeDiagnostics) {
|
|
378
|
-
|
|
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");
|
|
435
|
+
export function renderText(vm, includeDiagnostics = false, expanded = includeDiagnostics, plain = false) {
|
|
436
|
+
return renderProductText(vm, includeDiagnostics, expanded, plain);
|
|
406
437
|
}
|
|
407
438
|
const escape = (s) => String(s).replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
408
439
|
/** Shared summary renderer, used by the existing interactive App. Actions keep its handlers. */
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AGENT_INSTRUCTIONS_PATH } from "./core/templates.js";
|
|
2
|
+
import { MCP_INITIALIZATION_INSTRUCTIONS } from "./core/agent-instructions.js";
|
|
2
3
|
import { SKILL_EXECUTION_ROUTING } from "./skill-instructions.js";
|
|
3
4
|
import { graphicalActionSchema, graphicalReviewAction, graphicalReviewPayload } from './core/curation-graphical.js';
|
|
5
|
+
import { curationQuestionToolSchema, runCurationQuestion } from './core/curation-question.js';
|
|
4
6
|
import { prepareSemanticCuration, inspectSemanticCuration, submitSemanticCuration, semanticPrepareSchema } from './core/curation-semantic-jobs.js';
|
|
5
7
|
import { executeCurationReview } from "./curation-review-adapter.js";
|
|
6
8
|
import { createCurationReview, curationReviewView, controlCurationReview, latestCurationReview, resumeCurationReview, stageCurationDecisions, CurationReviewError } from "./core/curation-review.js";
|
|
@@ -10,10 +12,9 @@ import { matchesCurrentUserRule, rememberCurrentUserRule, rememberChatRule } fro
|
|
|
10
12
|
import { planProjectMigration, applyProjectMigration, requireCurrentProject } from "./core/migrations.js";
|
|
11
13
|
import { runMemorySync, syncWorkflowForCuration, getMemorySyncOverview, memorySyncActionSchema } from './core/memory-sync.js';
|
|
12
14
|
import { prepareMemoryTransformation, validateMemoryTransformationProposal, memoryTransformationProposalSchema } from './core/memory-transformation-proposal.js';
|
|
13
|
-
import {
|
|
14
|
-
import { projectSettings, setProjectSetting, renderSettings } from "./core/settings.js";
|
|
15
|
+
import { projectSettings, setProjectSetting } from "./core/settings.js";
|
|
15
16
|
import { SETTINGS_ROUTING, settingKeySchema } from "./settings-definition.js";
|
|
16
|
-
import {
|
|
17
|
+
import { buildViewModel, renderText } from "./presentation.js";
|
|
17
18
|
import { presentationTools } from "./presentation-server.js";
|
|
18
19
|
import { installPresentationTrace } from "./presentation-trace.js";
|
|
19
20
|
import { installDiagnosticBoundary } from "./diagnostic-boundary.js";
|
|
@@ -25,20 +26,21 @@ import { knowledgeView, mutateKnowledge, knowledgeQuerySchema, knowledgeMutation
|
|
|
25
26
|
import { mcpSamplingProvider } from "./providers/mcp-sampling.js";
|
|
26
27
|
import { PluginRegistry } from "./plugins/registry.js";
|
|
27
28
|
import { registerPluginTools } from "./plugins/mcp.js";
|
|
28
|
-
import { readConfig } from "./core/storage.js";
|
|
29
|
+
import { readConfig, CANONICAL_ID_PATTERN } from "./core/storage.js";
|
|
29
30
|
import fs from "node:fs/promises";
|
|
30
31
|
import path from "node:path";
|
|
31
32
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
32
33
|
import { registerAppResource, getUiCapability, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server";
|
|
33
34
|
import { z } from "zod";
|
|
34
|
-
import { autoMigrateProject, applyPendingProjectIntegrationRefresh, findProjectRoot, closeProjectSession, emptyBootstrapContext, getDetailedProjectStatus, getFastProjectStatus, initializeProject, rememberProjectKnowledge, searchProjectMemory, forgetProjectKnowledge, previewProjectKnowledgePromotion, promoteProjectKnowledge, prepareProject, recallProject, PRODERO_SCHEMA_VERSION, PRODERO_VERSION, resolveProjectContext, resolveProjectBootstrapContext,
|
|
35
|
+
import { autoMigrateProject, applyPendingProjectIntegrationRefresh, findProjectRoot, closeProjectSession, emptyBootstrapContext, getDetailedProjectStatus, getFastProjectStatus, initializeProject, rememberProjectKnowledge, searchProjectMemory, forgetProjectKnowledge, previewProjectKnowledgePromotion, promoteProjectKnowledge, prepareProject, recallProject, PRODERO_SCHEMA_VERSION, PRODERO_VERSION, resolveProjectContext, resolveProjectBootstrapContext, recordProjectSession, } from "./core/project.js";
|
|
35
36
|
import { createProjectSkill, getProjectSkill, projectSkillsView, reviewProjectSkill, skillInputSchema, previewProjectSkill, skillMarkdown } from "./core/skills.js";
|
|
36
37
|
import { readSession } from "./core/sessions.js";
|
|
37
|
-
import { checkForUpdate, configuredReleaseSource,
|
|
38
|
+
import { checkForUpdate, configuredReleaseSource, updateInstructions } from "./core/update.js";
|
|
38
39
|
import { PRODERO_LIFECYCLE_RESOURCE_URI, PRODERO_RESOURCE_URI, PRODERO_TOOLS } from "./contracts.js";
|
|
39
40
|
import { promotionPreviewSchema } from "./core/promotion.js";
|
|
40
41
|
export { PRODERO_LIFECYCLE_RESOURCE_URI, PRODERO_RESOURCE_URI, PRODERO_TOOLS, PRODERO_TOOL_NAMES } from "./contracts.js";
|
|
41
|
-
export const PRODERO_MCP_INSTRUCTIONS =
|
|
42
|
+
export const PRODERO_MCP_INSTRUCTIONS = MCP_INITIALIZATION_INSTRUCTIONS;
|
|
43
|
+
const memoryIdSchema = z.string().regex(CANONICAL_ID_PATTERN);
|
|
42
44
|
const observationSchema = z.object({
|
|
43
45
|
applicability: applicabilitySchema.optional(), taskContext: taskContextInputSchema.optional(),
|
|
44
46
|
type: z.enum(["decision", "lesson", "discovery", "state_change", "global_instruction_candidate", "skill_candidate"]),
|
|
@@ -51,7 +53,7 @@ const observationSchema = z.object({
|
|
|
51
53
|
proposedReason: z.string().trim().min(1).max(1_000).optional(),
|
|
52
54
|
importanceHint: z.enum(["critical", "important", "normal", "contextual"]).optional(),
|
|
53
55
|
categoryHint: z.enum(["principle", "invariant", "decision", "constraint", "lesson", "state", "instruction"]).optional(),
|
|
54
|
-
supersedes: z.array(
|
|
56
|
+
supersedes: z.array(memoryIdSchema).max(20).optional(),
|
|
55
57
|
});
|
|
56
58
|
function text(data, companion) {
|
|
57
59
|
return {
|
|
@@ -112,16 +114,17 @@ export function createServer(options = {}) {
|
|
|
112
114
|
inputSchema: { projectRoot: projectRootSchema }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
113
115
|
}, async ({ projectRoot }) => {
|
|
114
116
|
const view = await projectSettings(await workspaceRoot(projectRoot));
|
|
115
|
-
|
|
117
|
+
const payload = { ok: true, action: "config-get", projectSettings: view };
|
|
118
|
+
return text(payload, renderText(buildViewModel(payload)));
|
|
116
119
|
});
|
|
117
120
|
server.registerTool(PRODERO_TOOLS.configSet, {
|
|
118
|
-
title: "Change Prodero Project Settings", description: SETTINGS_ROUTING + " Change one known setting. Value must be an allowed enum ID or an integer context budget between 1 and 1,000,000. Preserves unrelated
|
|
119
|
-
inputSchema: { projectRoot: projectRootSchema, key: settingKeySchema, value: z.union([z.string(), z.number()]) },
|
|
121
|
+
title: "Change Prodero Project Settings", description: SETTINGS_ROUTING + " Change one known setting. Value must be an allowed enum ID or an integer context budget between 1 and 1,000,000. Use reset:true without value to remove one local presentation override. Preserves unrelated fields; never stores settings as knowledge.",
|
|
122
|
+
inputSchema: { projectRoot: projectRootSchema, key: settingKeySchema, value: z.union([z.string(), z.number()]).optional(), reset: z.boolean().optional() },
|
|
120
123
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
121
|
-
}, async ({ projectRoot, key, value }) => {
|
|
122
|
-
const view = await setProjectSetting(await workspaceRoot(projectRoot), key, value);
|
|
124
|
+
}, async ({ projectRoot, key, value, reset }) => {
|
|
125
|
+
const view = await setProjectSetting(await workspaceRoot(projectRoot), key, value, { reset });
|
|
123
126
|
const updated = view.settings.find(s => s.key === key);
|
|
124
|
-
return text({ ok: true, action: "config-set", updated, projectSettings: view }, `${updated.label} saved: ${updated.displayValue}
|
|
127
|
+
return text({ ok: true, action: "config-set", updated, projectSettings: view }, reset ? `${updated.label} local override removed: ${updated.displayValue} (${updated.source}).` : `${updated.label} saved: ${updated.displayValue}.${key.startsWith('presentation.') ? ' Saved on this checkout.' : ''}`);
|
|
125
128
|
});
|
|
126
129
|
const workSessions = new Map();
|
|
127
130
|
const instructionSelections = new Map();
|
|
@@ -178,7 +181,12 @@ export function createServer(options = {}) {
|
|
|
178
181
|
...['defer_issue', 'defer_change'].map(action => z.object({ action: z.literal(action), ...reviewIdentity, decisionRevision: z.number().int().nonnegative(), unitId: z.string() }).strict()),
|
|
179
182
|
z.object({ action: z.literal('decide'), ...reviewIdentity, decisionRevision: z.number().int().nonnegative(), decisions: z.record(z.enum(['current', 'suggested', 'deferred'])) }).strict(),
|
|
180
183
|
]);
|
|
181
|
-
|
|
184
|
+
registerAppTool(server, PRODERO_TOOLS.curationQuestion, {
|
|
185
|
+
title: 'Answer a saved Sync question', description: 'Inspect, answer, defer or deliberately revisit an exactly bound question in the current Sync workflow. Analyze/retry prepares bounded reasoning only. Chat answers are server-stamped agent_relay evidence, not authenticated human intent, policy authority or approval. Save the user\'s actual answer with a unique requestId; never invent an answer. Inspect saved state after unknown outcomes before replay. No create, selection, summary, pause/unpause, protected Apply or promotion.',
|
|
186
|
+
inputSchema: curationQuestionToolSchema.extend({ projectRoot: projectRootSchema }).shape,
|
|
187
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
188
|
+
}, async ({ projectRoot, ...input }) => reviewResult(async () => runCurationQuestion(await workspaceRoot(projectRoot), input)));
|
|
189
|
+
// Protected review selection remains outside the text gateway and App-only.
|
|
182
190
|
server.registerTool(PRODERO_TOOLS.curationReview, {
|
|
183
191
|
title: "Review knowledge decisions", description: "Stage review choices, defer an issue or change, or pause/resume the whole review. Does not apply knowledge changes.",
|
|
184
192
|
// MCP tool schemas use an object root; enforce action-specific fields below.
|
|
@@ -307,7 +315,7 @@ export function createServer(options = {}) {
|
|
|
307
315
|
});
|
|
308
316
|
registerAppTool(server, PRODERO_TOOLS.prepare, {
|
|
309
317
|
title: "Prepare Prodero Session",
|
|
310
|
-
description: "Prepare substantial agent work: recover pending session observations, open a unique session memory file, and return compact task context.",
|
|
318
|
+
description: "Prepare substantial agent work: recover pending session observations, open a unique session memory file, and return compact task context. For lifecycle text delivery use prodero_text with tool=prodero_prepare and show its compact confirmation before continuing; healthy Prepare needs no additional approval or Continue step.",
|
|
311
319
|
inputSchema: { projectRoot: projectRootSchema, task: z.string().optional(), taskContext: taskContextInputSchema.optional(), purpose: z.enum(["explain", "work", "review", "debug"]).optional(), agent: z.string().optional(), sessionId: z.string().optional() },
|
|
312
320
|
_meta: { ui: { resourceUri: PRODERO_LIFECYCLE_RESOURCE_URI } },
|
|
313
321
|
}, async ({ projectRoot, task, taskContext, purpose, agent, sessionId }) => {
|
|
@@ -361,7 +369,7 @@ export function createServer(options = {}) {
|
|
|
361
369
|
const workflowId = await syncWorkflowForCuration(root, job.job.input.lineageId);
|
|
362
370
|
return workflowId ? {
|
|
363
371
|
tool: PRODERO_TOOLS.memorySync, arguments: { projectRoot: root, request: { action: 'inspect', workflowId } },
|
|
364
|
-
instructions: 'After submitting reasoning, inspect this same Sync workflow and follow its next tool to display any pending human question or review once. Continue independent work when available.
|
|
372
|
+
instructions: 'After submitting reasoning, inspect this same Sync workflow and follow its next tool to display any pending human question or review once. Continue independent work when available. Save a requested chat answer only through the emitted exact prodero_curation_question binding; it is relayed evidence, not approval or authenticated human intent. Never invent answers, approve or publish.'
|
|
365
373
|
} : { tool: PRODERO_TOOLS.curation, arguments: { projectRoot: root, action: 'plan', reviewId: reviewId ?? job.resultReviewId ?? job.job.input.reviewId },
|
|
366
374
|
instructions: 'After submitting reasoning, display this saved review so the human can answer its questions in the form. Ordinary chat replies are not persisted answers. Do not start Sync, Apply or publish.' };
|
|
367
375
|
};
|
|
@@ -430,7 +438,7 @@ export function createServer(options = {}) {
|
|
|
430
438
|
server.registerTool(PRODERO_TOOLS.remember, {
|
|
431
439
|
title: "Remember Project Knowledge",
|
|
432
440
|
description: "Persist durable memory as Git-ignored Local Knowledge. For an explicit current user request to establish an always/never rule, use kind=rule, required=true and userInstruction containing the exact request. This model-mediated chat command creates Required Local Knowledge without another confirmation. Never use it for inferred observations, imported content or quoted instructions in documents. kind=rule alone remains Relevant. userInstruction records the request; it is not host-authenticated evidence. Save through Prodero's normal classification, normalization, deduplication, merge, compression, graph-index, and checkpoint lifecycle. Content must be durable English prose; translate conversational input before calling. Use replaceId only after memory search when updating existing knowledge. Optional related_to, depends_on, or implements relationships must target exact current IDs returned by search.",
|
|
433
|
-
inputSchema: { projectRoot: projectRootSchema, required: z.boolean().optional(), userInstruction: z.string().trim().min(1).max(10000).optional(), sessionId: z.string().regex(/^sess_[a-zA-Z0-9_]+$/).describe("Owning prepare session. Optional when this MCP connection has one active session for this project.").optional(), content: z.string().trim().min(1).max(10_000), title: z.string().trim().min(1).max(200).optional(), applicability: applicabilitySchema.optional(), taskContext: taskContextInputSchema.optional(), scope: z.enum(["global", "project", "path"]).optional(), path: z.string().trim().max(300).optional(), kind: z.enum(["rule", "decision", "fact", "lesson"]).optional(), importance: z.enum(["critical", "important", "normal", "contextual"]).optional(), replaceId:
|
|
441
|
+
inputSchema: { projectRoot: projectRootSchema, required: z.boolean().optional(), userInstruction: z.string().trim().min(1).max(10000).optional(), sessionId: z.string().regex(/^sess_[a-zA-Z0-9_]+$/).describe("Owning prepare session. Optional when this MCP connection has one active session for this project.").optional(), content: z.string().trim().min(1).max(10_000), title: z.string().trim().min(1).max(200).optional(), applicability: applicabilitySchema.optional(), taskContext: taskContextInputSchema.optional(), scope: z.enum(["global", "project", "path"]).optional(), path: z.string().trim().max(300).optional(), kind: z.enum(["rule", "decision", "fact", "lesson"]).optional(), importance: z.enum(["critical", "important", "normal", "contextual"]).optional(), replaceId: memoryIdSchema.optional(), relationships: z.array(z.object({ type: z.enum(["related_to", "depends_on", "implements"]), target: memoryIdSchema }).strict()).max(12).optional() },
|
|
434
442
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
435
443
|
}, async ({ projectRoot, required, userInstruction, ...input }) => {
|
|
436
444
|
if (required && (input.kind !== "rule" || !userInstruction))
|
|
@@ -455,8 +463,8 @@ export function createServer(options = {}) {
|
|
|
455
463
|
});
|
|
456
464
|
server.registerTool(PRODERO_TOOLS.forget, {
|
|
457
465
|
title: "Forget Project Knowledge",
|
|
458
|
-
description: "
|
|
459
|
-
inputSchema: { projectRoot: projectRootSchema, id:
|
|
466
|
+
description: "Retire one exact Local Knowledge item after explicit user intent; use the id and layer returned by prodero_memory_search. Versioned Knowledge requires the existing reviewed prodero_knowledge_mutate repository-maintenance retire operation with its current revision. Never silently switch layers or operations. The canonical record is preserved as historical; no file is destructively deleted.",
|
|
467
|
+
inputSchema: { projectRoot: projectRootSchema, id: memoryIdSchema.optional(), query: z.string().trim().min(1).max(1_000).optional(), reason: z.string().trim().min(1).max(1_000).optional() },
|
|
460
468
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
461
469
|
}, async ({ projectRoot, id, query, reason }) => {
|
|
462
470
|
if (!id && !query)
|
|
@@ -467,7 +475,7 @@ export function createServer(options = {}) {
|
|
|
467
475
|
registerAppTool(server, PRODERO_TOOLS.promotionPreview, {
|
|
468
476
|
title: "Review Local Knowledge Promotion",
|
|
469
477
|
description: "Build a read-only semantic consolidation proposal from Local Knowledge into Versioned Knowledge. The proposal distinguishes new, merge, supersede, covered, and conflict outcomes. It never publishes knowledge or implies approval; leave the decision to the human in the attached App.",
|
|
470
|
-
inputSchema: { projectRoot: projectRootSchema, ids: z.array(
|
|
478
|
+
inputSchema: { projectRoot: projectRootSchema, ids: z.array(memoryIdSchema).max(50).optional(), includeAll: z.boolean().optional() },
|
|
471
479
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
472
480
|
_meta: { ui: { resourceUri: PRODERO_RESOURCE_URI } },
|
|
473
481
|
}, async ({ projectRoot, ids, includeAll }) => {
|
|
@@ -496,7 +504,7 @@ export function createServer(options = {}) {
|
|
|
496
504
|
});
|
|
497
505
|
registerAppTool(server, PRODERO_TOOLS.sessionClose, {
|
|
498
506
|
title: "Close Prodero Session",
|
|
499
|
-
description: "Finalize the current Prodero session, optionally with a final batch. Synchronizes session observations into Local Knowledge deterministically before returning success; prepare recovers pending work.",
|
|
507
|
+
description: "Finalize the current Prodero session, optionally with a final batch. Synchronizes session observations into Local Knowledge deterministically before returning success; prepare recovers pending work. Call before the final task answer. For lifecycle text delivery use prodero_text with tool=prodero_session_close and show its returned compact saved/deferred state; never claim Session Complete without a successful Close result.",
|
|
500
508
|
inputSchema: { projectRoot: projectRootSchema, sessionId: z.string(), finalEntries: z.array(observationSchema).max(100).optional(), sync: z.boolean().optional().describe("Defaults to true. Set false only to defer synchronization for legacy workflows; prepare recovers it.") },
|
|
501
509
|
_meta: { ui: { resourceUri: PRODERO_LIFECYCLE_RESOURCE_URI } },
|
|
502
510
|
}, async ({ projectRoot, sessionId, finalEntries, sync }) => {
|
|
@@ -532,22 +540,22 @@ export function createServer(options = {}) {
|
|
|
532
540
|
return text({ ok: true, action: "initialized", project, status: await detailedStatus(project.root, instrument) });
|
|
533
541
|
});
|
|
534
542
|
registerAppTool(server, PRODERO_TOOLS.sync, {
|
|
535
|
-
title: '
|
|
536
|
-
description: 'Open
|
|
543
|
+
title: 'Curate Memory',
|
|
544
|
+
description: 'Open Curate Memory or present a pending human step. With workflowId, reopen only that saved workflow; do not restart analysis. Recover pending deterministic observations, analyze transformation and promotion candidates, then await selection. Opening does not authorize semantic transformation or publication. This replaces the old manual session-flush action.',
|
|
537
545
|
inputSchema: { projectRoot: projectRootSchema, workflowId: z.string().uuid().optional().describe("Reopen this exact saved workflow without starting a new analysis.") }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
538
546
|
_meta: { ui: { resourceUri: PRODERO_RESOURCE_URI } },
|
|
539
547
|
}, async ({ projectRoot, workflowId }) => {
|
|
540
548
|
const result = await runMemorySync(await workspaceRoot(projectRoot), workflowId ? { action: 'inspect', workflowId } : { action: 'start' });
|
|
541
|
-
return text({ ok: true, action: 'memory-sync', result, uiState: { view: 'memory-sync' } }, `
|
|
549
|
+
return text({ ok: true, action: 'memory-sync', result, uiState: { view: 'memory-sync' } }, `Curate Memory: ${result.state}. The current saved step is displayed in the App.`);
|
|
542
550
|
});
|
|
543
551
|
registerAppTool(server, PRODERO_TOOLS.memorySync, {
|
|
544
|
-
title: 'Continue
|
|
545
|
-
description: 'Continue the existing
|
|
552
|
+
title: 'Continue Curate Memory',
|
|
553
|
+
description: 'Continue the existing Curate Memory UI in place without opening another App. Use prodero_sync to open the UI and once when next.tool requests a human handoff; pass next.arguments exactly before ending the turn. Do not reopen during intermediate analysis. Orchestrate one persisted Curate Memory workflow. start opens options; select with improve/share starts the same workflow (both default true; both false is invalid). Core chooses all improvement candidates. prepare returns the next bounded Curator job; use curation_reasoning_prepare/propose then inspect this same workflow and continue. Questions reuse Curator; after an answer or deferral inspect Sync to continue. Legacy candidateIds/prepare/propose remains supported for saved workflows. Clean Local plans apply only within this selected workflow. Continue candidates sequentially. awaiting_review requires explicit human approval of the exact reviewId: NEVER infer approval or click approval for the human. Mixed plans stay atomic. Promotion selection follows transformations; the UI initially checks all displayed candidates and lets the human select or deselect them before review. Preselection is not publication approval. approve is allowed only after human review; text hosts must show the full review and obtain explicit approval. retry handles failed candidates; skip/complete leave unresolved work untouched. No sampling, session lifecycle or extra memory scan by the agent.',
|
|
546
554
|
inputSchema: { projectRoot: projectRootSchema, request: memorySyncActionSchema },
|
|
547
555
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
548
556
|
}, async ({ projectRoot, request }) => {
|
|
549
557
|
const result = await runMemorySync(await workspaceRoot(projectRoot), request);
|
|
550
|
-
return text({ ok: true, action: 'memory-sync', result, uiState: { view: 'memory-sync' } }, `
|
|
558
|
+
return text({ ok: true, action: 'memory-sync', result, uiState: { view: 'memory-sync' } }, `Curate Memory: ${result.state}. ${result.next.action}. Workflow ${result.id}, revision ${result.revision}.`);
|
|
551
559
|
});
|
|
552
560
|
server.registerTool(PRODERO_TOOLS.skillCreate, {
|
|
553
561
|
title: "Create Project Skill",
|
|
@@ -585,23 +593,30 @@ export function createServer(options = {}) {
|
|
|
585
593
|
});
|
|
586
594
|
registerAppTool(server, PRODERO_TOOLS.skillPreview, {
|
|
587
595
|
title: "Preview Project Skill",
|
|
588
|
-
description: "Preview a candidate as a reusable Project Skill before human approval. Optional draft revisions are previewed, never activated. Optional Skill Curator uses bounded source observations, related knowledge and active skill evidence; failures fall back to a deterministic draft. Set recurate=true only on explicit re-curation intent, including proposed updates of active skills. Edited drafts bypass the model; pass previousPreview to retain the update target and stale-version protection.
|
|
596
|
+
description: "Preview a candidate as a reusable Project Skill before human approval. Optional draft revisions are previewed, never activated. Optional Skill Curator uses bounded source observations, related knowledge and active skill evidence; failures fall back to a deterministic draft. Set recurate=true only on explicit re-curation intent, including proposed updates of active skills. Edited drafts bypass the model; pass previousPreview to retain the update target and stale-version protection. Use the effective presentation: text mode or auto without Apps returns the full preview for explicit chat approval; UI uses the attached review App with Add Skill, Edit, and Dismiss. If the App cannot render, show the full preview in chat instead. Leave approval to the human; never infer it. Use prodero_text to guarantee no App attachment.",
|
|
589
597
|
inputSchema: { projectRoot: projectRootSchema, id: z.string(), draft: skillInputSchema.optional(), recurate: z.boolean().optional(), previousPreview: skillPreviewSchema.optional() },
|
|
590
598
|
_meta: { ui: { resourceUri: PRODERO_RESOURCE_URI } },
|
|
591
|
-
}, async ({ projectRoot, id, draft, recurate, previousPreview }) => {
|
|
599
|
+
}, async ({ projectRoot, id, draft, recurate, previousPreview }, _extra, presentation) => {
|
|
600
|
+
if (!presentation)
|
|
601
|
+
throw new Error("Skill preview requires its presentation context.");
|
|
592
602
|
const root = await findProjectRoot(await workspaceRoot(projectRoot));
|
|
593
603
|
const config = await readConfig(root);
|
|
594
604
|
const preview = await previewProjectSkill(root, id, { draft, recurate, previousPreview, provider: config?.knowledgeEngine?.mode === "assisted" ? mcpSamplingProvider(server) : undefined });
|
|
595
605
|
const skill = await getProjectSkill(root, id);
|
|
596
606
|
const appSupportAdvertised = Boolean(getUiCapability(server.server.getClientCapabilities())?.mimeTypes?.includes(RESOURCE_MIME_TYPE));
|
|
597
|
-
const
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
607
|
+
const chat = presentation.resolved === "text" || !appSupportAdvertised;
|
|
608
|
+
const operation = preview.disposition?.action ?? "create";
|
|
609
|
+
const covered = operation === "covered";
|
|
610
|
+
const operationLabel = { create: "Create Project Skill", update: "Update existing Project Skill", covered: "Already covered; no changes to apply", "keep-separate": "Create separate Project Skill" }[operation];
|
|
611
|
+
const summary = [`Operation: ${operationLabel}`, ...(preview.target ? [`Target: ${preview.target.title} (${preview.target.id})`] : []), ...(preview.notice ? [`Notice: ${preview.notice}`] : [])].join("\n");
|
|
612
|
+
const coveredGuidance = "Inspect the existing skill or dismiss this candidate. Promotion is unavailable; do not request approval to apply this preview.";
|
|
613
|
+
const guidance = covered ? coveredGuidance : chat ? "Show the complete preview in chat and obtain explicit human approval before calling prodero_skill_review with approved=true and this exact preview." : "Review this skill in the attached MCP App using Add Skill, Edit, or Dismiss. Do not ask for approval in chat when the App can render. Never approve on the human's behalf.";
|
|
614
|
+
const fallback = "Only if this host cannot render the MCP App: show the complete text preview below. " + (covered ? coveredGuidance : "Obtain explicit human approval before calling prodero_skill_review with approved=true and this exact preview.");
|
|
615
|
+
return text({ ok: true, action: "skill-preview", preview, skill, result: await projectSkillsView(root), approval: { required: !covered, surface: chat ? "chat" : "mcp-app", appSupportAdvertised, ...(!chat ? { chatFallback: "only-if-app-cannot-render" } : {}) }, uiState: { view: "skills" } }, `${summary}\n\n${guidance}\n\n${chat ? "" : fallback + "\n\n"}${skillMarkdown({ ...skill, ...preview.draft, candidateTitle: skill.title })}`);
|
|
601
616
|
});
|
|
602
617
|
registerAppTool(server, PRODERO_TOOLS.skillReview, {
|
|
603
618
|
title: "Review Project Skill Candidate",
|
|
604
|
-
description: "Promote only the exact preview the human approved: first call prodero_skill_preview
|
|
619
|
+
description: "Promote only the exact preview the human approved: first call prodero_skill_preview using the effective presentation, or prodero_text for a complete text preview. The review App or explicit chat approval submits approved=true and the exact preview as a workflow safeguard, not proof of human consent. Text mode and auto without Apps use chat; UI uses the App with chat fallback only when it cannot render. Never automatically promote inferred candidates. Dismiss retains procedure and provenance.",
|
|
605
620
|
inputSchema: { projectRoot: projectRootSchema, id: z.string(), action: z.enum(["promote", "dismiss"]), reason: z.string().trim().max(1_000).optional(), approved: z.boolean().optional(), preview: skillPreviewSchema.optional() },
|
|
606
621
|
_meta: { ui: { resourceUri: PRODERO_RESOURCE_URI } },
|
|
607
622
|
}, async ({ projectRoot, id, action, reason, approved, preview }) => {
|
|
@@ -620,24 +635,17 @@ export function createServer(options = {}) {
|
|
|
620
635
|
return text({ ok: true, update });
|
|
621
636
|
});
|
|
622
637
|
server.registerTool(PRODERO_TOOLS.update, {
|
|
623
|
-
title: "Update
|
|
624
|
-
description: "
|
|
638
|
+
title: "Show Prodero Update Instructions",
|
|
639
|
+
description: "Read-only instructions for updating Prodero in a terminal with prodero update. Finish work and close hosts first, then restart/reconnect after terminal completion and review project migrations separately. This tool never installs, builds, stages, switches runtimes or refreshes project integration, including development sources. Do not execute installation through chat tools.",
|
|
625
640
|
inputSchema: { projectRoot: projectRootSchema },
|
|
641
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
626
642
|
}, async ({ projectRoot }) => {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
if (root && (await readConfig(root))) {
|
|
634
|
-
if (update.restartRequired)
|
|
635
|
-
await scheduleProjectIntegrationRefresh(root, { version: update.availableVersion, source: update.source });
|
|
636
|
-
else
|
|
637
|
-
projectMaintenance = await refreshProjectIntegration(root);
|
|
638
|
-
}
|
|
639
|
-
const maintenanceMessage = projectMaintenance?.changedFiles.length ? ` Refreshed ${projectMaintenance.changedFiles.join(", ")}.` : projectMaintenance ? " Project integration is current." : update.restartRequired && root ? " Project integration will refresh automatically after restart." : "";
|
|
640
|
-
return text({ ...update, projectMaintenance, message: `${update.message}${maintenanceMessage}` });
|
|
643
|
+
// Preserve the selector contract without reading project state or invoking
|
|
644
|
+
// a release adapter. Instructions cannot stage even an injected dev source.
|
|
645
|
+
if (projectRoot !== undefined)
|
|
646
|
+
await workspaceRoot(projectRoot);
|
|
647
|
+
const result = updateInstructions(releaseSource);
|
|
648
|
+
return text(result, renderText(buildViewModel(result)));
|
|
641
649
|
});
|
|
642
650
|
registerAppResource(server, PRODERO_RESOURCE_URI, PRODERO_RESOURCE_URI, { mimeType: RESOURCE_MIME_TYPE }, async () => {
|
|
643
651
|
const htmlPath = path.resolve(import.meta.dirname, "../dist-ui/index.html");
|
|
@@ -13,10 +13,10 @@ export const modeSchema = z.enum(['auto', 'ui', 'text']);
|
|
|
13
13
|
export const themeSchema = z.enum(['prodero', 'dark-neon', 'pink-neon', 'blue-neon', 'purple-neon', 'light', 'system']);
|
|
14
14
|
export const presentationConfigSchema = z.object({ mode: modeSchema.optional(), theme: themeSchema.optional() }).passthrough();
|
|
15
15
|
export const SETTINGS = [
|
|
16
|
-
{ key: 'presentation.mode', group: 'Presentation', label: 'Display mode', description: 'Choose automatic display, interactive UI, or text
|
|
17
|
-
{ key: 'presentation.theme', group: 'Presentation', label: 'Theme', description: 'Visual theme used by Prodero
|
|
16
|
+
{ key: 'presentation.mode', group: 'Presentation', label: 'Display mode', description: 'Choose automatic display, interactive UI, or text on this checkout.', type: 'enum', defaultValue: 'auto', options: [{ id: 'auto', label: 'Automatic', description: 'Use MCP Apps when advertised, otherwise readable text.' }, { id: 'ui', label: 'UI', description: 'Prefer the interactive Prodero application on this checkout.' }, { id: 'text', label: 'Text', description: 'Use readable text on this checkout.' }], validation: modeSchema },
|
|
17
|
+
{ key: 'presentation.theme', group: 'Presentation', label: 'Theme', description: 'Visual theme used by Prodero on this checkout.', type: 'enum', defaultValue: 'prodero', options: THEMES, validation: themeSchema },
|
|
18
18
|
{ key: 'context.max_tokens', group: 'Context', label: 'Task context budget', description: 'Maximum Prodero context budget prepared for a task.', type: 'integer', defaultValue: DEFAULT_CONTEXT_BUDGET_TOKENS, unit: 'tokens', min: 1, max: 1000000, validation: z.number().int().min(1).max(1000000) },
|
|
19
19
|
];
|
|
20
20
|
export const settingKeySchema = z.enum(['presentation.mode', 'presentation.theme', 'context.max_tokens']);
|
|
21
|
-
export const SETTINGS_ROUTING = 'For requests to show Prodero settings, project settings, or Prodero config, call prodero_config_get and show its friendly settings view. For project preference changes use prodero_config_set: text-only/text mode → presentation.mode=text; switch back to UI → presentation.mode=ui; automatic presentation → presentation.mode=auto; blue/purple neon → presentation.theme=blue-neon/purple-neon; context budget 8000 → context.max_tokens=8000.
|
|
21
|
+
export const SETTINGS_ROUTING = 'For requests to show Prodero settings, project settings, or Prodero config, call prodero_config_get and show its friendly settings view. For project preference changes use prodero_config_set: text-only/text mode → presentation.mode=text; switch back to UI → presentation.mode=ui; automatic presentation → presentation.mode=auto; blue/purple neon → presentation.theme=blue-neon/purple-neon; context budget 8000 → context.max_tokens=8000. Display mode/theme are ignored checkout-local preferences; context budget remains shared project configuration. Legacy project presentation defaults remain fallback. Use reset:true without value to remove only the requested local presentation override. Never store settings as knowledge. Resolve the current project on each call. Read effective settings when choosing the text gateway; do not rely on a previous project or conversational preference.';
|
|
22
22
|
//# sourceMappingURL=settings-definition.js.map
|