oh-my-llmwikimode 1.0.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/LICENSE +21 -0
- package/README.md +494 -0
- package/bin/llmwiki.js +1493 -0
- package/docs/INSTALLATION.md +228 -0
- package/docs/SCOPE_LOCK.md +79 -0
- package/docs/STAGE1_GUIDE.md +265 -0
- package/docs/STAGE2_AGENT_TEAM_GUIDE.md +141 -0
- package/docs/STAGE3_CONVERSATIONAL_GROWTH_GUIDE.md +50 -0
- package/docs/TEST_WORKSHEET.md +120 -0
- package/docs/github-private-bootstrap.md +53 -0
- package/docs/release.md +79 -0
- package/docs/stage4-slice1-manual-test.md +259 -0
- package/docs/stage4-slice1-user-guide.md +269 -0
- package/docs/user-guide-ko.md +452 -0
- package/package.json +76 -0
- package/scripts/install-llmwiki.ps1 +229 -0
- package/src/config.js +74 -0
- package/src/curator/browser-data.js +134 -0
- package/src/curator/queue.js +324 -0
- package/src/curator/schema.js +237 -0
- package/src/curator/scoring.js +83 -0
- package/src/hooks.js +199 -0
- package/src/librarian/schema.js +218 -0
- package/src/librarian/weekly-digest.js +478 -0
- package/src/security.js +127 -0
- package/src/server.js +860 -0
- package/src/stage4/graph-reasoning/analyzer.js +255 -0
- package/src/stage4/graph-reasoning/browser-data.js +130 -0
- package/src/stage4/graph-reasoning/index.js +35 -0
- package/src/stage4/graph-reasoning/loader.js +122 -0
- package/src/stage4/graph-reasoning/queue.js +154 -0
- package/src/stage4/graph-reasoning/schema.js +190 -0
- package/src/team/browser-data.js +142 -0
- package/src/team/capabilities.js +79 -0
- package/src/team/dispatch.js +108 -0
- package/src/team/queue.js +290 -0
- package/src/team/schema.js +225 -0
- package/src/team/shared-memory.js +183 -0
- package/src/todo/browser-data.js +71 -0
- package/src/todo/queue.js +159 -0
- package/src/todo/schema.js +90 -0
- package/src/utils/embedding-model.js +111 -0
- package/src/wiki/alias-suggestions.js +180 -0
- package/src/wiki/browser-data.js +284 -0
- package/src/wiki/doctor.js +218 -0
- package/src/wiki/entry-normalizer.js +139 -0
- package/src/wiki/ingest.js +443 -0
- package/src/wiki/lesson-proposal-analyzer.js +463 -0
- package/src/wiki/lesson-proposal-manager.js +331 -0
- package/src/wiki/lesson-template.js +182 -0
- package/src/wiki/lint.js +294 -0
- package/src/wiki/notebooklm-adapter.js +264 -0
- package/src/wiki/query.js +304 -0
- package/src/wiki/raw-manager.js +400 -0
- package/src/wiki/search-feedback.js +211 -0
- package/src/wiki/semantic-index.js +333 -0
- package/src/wiki/semantic-search.js +170 -0
- package/src/wiki/source-ledger.js +370 -0
- package/src/wiki/store.js +1329 -0
- package/src/wiki/usage-events.js +144 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_BUILT_AT,
|
|
5
|
+
validateAgentGroup,
|
|
6
|
+
validateAgentProfile,
|
|
7
|
+
validateEvidenceNote,
|
|
8
|
+
validateRole,
|
|
9
|
+
validateTaskCard,
|
|
10
|
+
} from "./schema.js";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_ROLES = [
|
|
13
|
+
{
|
|
14
|
+
id: "builder",
|
|
15
|
+
label: "Builder",
|
|
16
|
+
description: "Implements approved tasks and returns evidence.",
|
|
17
|
+
default_capabilities: ["read", "dispatch"],
|
|
18
|
+
dispatch_instructions: "Implement only the approved task and return evidence.",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "reviewer",
|
|
22
|
+
label: "Reviewer",
|
|
23
|
+
description: "Reviews evidence and flags gaps.",
|
|
24
|
+
default_capabilities: ["read"],
|
|
25
|
+
dispatch_instructions: "Review artifacts and report findings only.",
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const DEFAULT_AGENT_PROFILES = [
|
|
30
|
+
{
|
|
31
|
+
id: "main-opencode",
|
|
32
|
+
label: "Main OpenCode",
|
|
33
|
+
kind: "opencode",
|
|
34
|
+
default_role_id: "builder",
|
|
35
|
+
scope: "main",
|
|
36
|
+
capability_policy: { allowed: ["read", "dispatch"], requires_approval: ["shell", "browser", "network", "sync"], denied: ["git_push", "direct_agent_message"] },
|
|
37
|
+
dispatch_template: "Use an approval-first implementation packet and return evidence.",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "oracle-reviewer",
|
|
41
|
+
label: "Oracle Reviewer",
|
|
42
|
+
kind: "oracle",
|
|
43
|
+
default_role_id: "reviewer",
|
|
44
|
+
scope: "sub",
|
|
45
|
+
capability_policy: { allowed: ["read"], requires_approval: [], denied: ["shell", "git_push", "browser", "network", "sync", "direct_agent_message"] },
|
|
46
|
+
dispatch_template: "Review only. Do not execute actions.",
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const DEFAULT_AGENT_GROUPS = [
|
|
51
|
+
{
|
|
52
|
+
id: "core-team",
|
|
53
|
+
label: "Core Team",
|
|
54
|
+
agent_profile_ids: ["main-opencode", "oracle-reviewer"],
|
|
55
|
+
default_queue_policy: "approval-first",
|
|
56
|
+
description: "Default local Stage 2 team.",
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const ALLOWED_TRANSITIONS = new Map([
|
|
61
|
+
["proposed", new Set(["approved", "blocked"])],
|
|
62
|
+
["approved", new Set(["dispatched", "blocked", "proposed"])],
|
|
63
|
+
["dispatched", new Set(["evidence", "blocked", "review"])],
|
|
64
|
+
["evidence", new Set(["review", "blocked", "done"])],
|
|
65
|
+
["review", new Set(["done", "blocked", "evidence"])],
|
|
66
|
+
["blocked", new Set(["approved", "dispatched", "review", "done"])],
|
|
67
|
+
["done", new Set([])],
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
function nowIso() {
|
|
71
|
+
return new Date().toISOString();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function atomicWriteJson(filePath, data) {
|
|
75
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
76
|
+
const tmpFile = `${filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
77
|
+
fs.writeFileSync(tmpFile, JSON.stringify(data, null, 2));
|
|
78
|
+
fs.renameSync(tmpFile, filePath);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function sortById(items) {
|
|
82
|
+
return [...items].sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function sortTasks(items) {
|
|
86
|
+
return [...items].sort((a, b) => `${a.created_at}:${a.id}`.localeCompare(`${b.created_at}:${b.id}`));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeDefaults(items, validator) {
|
|
90
|
+
return items.map((item) => {
|
|
91
|
+
const result = validator(item);
|
|
92
|
+
if (!result.valid) throw new Error(result.errors.join("; "));
|
|
93
|
+
return result.value;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function createEmptyQueue() {
|
|
98
|
+
return {
|
|
99
|
+
version: 1,
|
|
100
|
+
agent_profiles: normalizeDefaults(DEFAULT_AGENT_PROFILES, validateAgentProfile),
|
|
101
|
+
agent_groups: normalizeDefaults(DEFAULT_AGENT_GROUPS, validateAgentGroup),
|
|
102
|
+
roles: normalizeDefaults(DEFAULT_ROLES, validateRole),
|
|
103
|
+
tasks: [],
|
|
104
|
+
evidence_notes: [],
|
|
105
|
+
meta: { built_at: DEFAULT_BUILT_AT },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeQueue(queue) {
|
|
110
|
+
const base = createEmptyQueue();
|
|
111
|
+
const profiles = [...base.agent_profiles];
|
|
112
|
+
const groups = [...base.agent_groups];
|
|
113
|
+
const roles = [...base.roles];
|
|
114
|
+
const tasks = [];
|
|
115
|
+
const evidenceNotes = [];
|
|
116
|
+
|
|
117
|
+
for (const profile of Array.isArray(queue?.agent_profiles) ? queue.agent_profiles : []) {
|
|
118
|
+
const result = validateAgentProfile(profile);
|
|
119
|
+
if (result.valid) profiles.push(result.value);
|
|
120
|
+
}
|
|
121
|
+
for (const group of Array.isArray(queue?.agent_groups) ? queue.agent_groups : []) {
|
|
122
|
+
const result = validateAgentGroup(group);
|
|
123
|
+
if (result.valid) groups.push(result.value);
|
|
124
|
+
}
|
|
125
|
+
for (const role of Array.isArray(queue?.roles) ? queue.roles : []) {
|
|
126
|
+
const result = validateRole(role);
|
|
127
|
+
if (result.valid) roles.push(result.value);
|
|
128
|
+
}
|
|
129
|
+
for (const task of Array.isArray(queue?.tasks) ? queue.tasks : []) {
|
|
130
|
+
const result = validateTaskCard(task);
|
|
131
|
+
if (result.valid) tasks.push(result.value);
|
|
132
|
+
}
|
|
133
|
+
for (const note of Array.isArray(queue?.evidence_notes) ? queue.evidence_notes : []) {
|
|
134
|
+
const result = validateEvidenceNote(note);
|
|
135
|
+
if (result.valid) evidenceNotes.push(result.value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
version: 1,
|
|
140
|
+
agent_profiles: sortById([...new Map(profiles.map((item) => [item.id, item])).values()]),
|
|
141
|
+
agent_groups: sortById([...new Map(groups.map((item) => [item.id, item])).values()]),
|
|
142
|
+
roles: sortById([...new Map(roles.map((item) => [item.id, item])).values()]),
|
|
143
|
+
tasks: sortTasks([...new Map(tasks.map((item) => [item.id, item])).values()]),
|
|
144
|
+
evidence_notes: sortById([...new Map(evidenceNotes.map((item) => [item.id, item])).values()]),
|
|
145
|
+
meta: { built_at: queue?.meta?.built_at || DEFAULT_BUILT_AT },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function writeQueue(wikiRoot, queue) {
|
|
150
|
+
const normalized = normalizeQueue({ ...queue, meta: { built_at: nowIso() } });
|
|
151
|
+
atomicWriteJson(getAgentTeamPaths(wikiRoot).queueFile, normalized);
|
|
152
|
+
return normalized;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function appendAudit(wikiRoot, action, entry = {}, options = {}) {
|
|
156
|
+
const paths = getAgentTeamPaths(wikiRoot);
|
|
157
|
+
fs.mkdirSync(paths.root, { recursive: true });
|
|
158
|
+
const record = {
|
|
159
|
+
timestamp: nowIso(),
|
|
160
|
+
action,
|
|
161
|
+
actor: options.actor || "system",
|
|
162
|
+
...entry,
|
|
163
|
+
};
|
|
164
|
+
fs.appendFileSync(paths.auditFile, `${JSON.stringify(record)}\n`);
|
|
165
|
+
return record;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function result(success, payload = {}) {
|
|
169
|
+
return { success, ...payload };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getAgentTeamPaths(wikiRoot) {
|
|
173
|
+
const systemDir = path.join(wikiRoot, ".system");
|
|
174
|
+
const root = path.join(systemDir, "agent-team");
|
|
175
|
+
return {
|
|
176
|
+
systemDir,
|
|
177
|
+
root,
|
|
178
|
+
queueFile: path.join(root, "queue.json"),
|
|
179
|
+
dispatchDir: path.join(root, "dispatch-packets"),
|
|
180
|
+
auditFile: path.join(root, "audit.jsonl"),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function ensureAgentTeamStructure(wikiRoot) {
|
|
185
|
+
const paths = getAgentTeamPaths(wikiRoot);
|
|
186
|
+
fs.mkdirSync(paths.root, { recursive: true });
|
|
187
|
+
fs.mkdirSync(paths.dispatchDir, { recursive: true });
|
|
188
|
+
if (!fs.existsSync(paths.queueFile)) writeQueue(wikiRoot, createEmptyQueue());
|
|
189
|
+
return paths;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function readAgentTeamQueue(wikiRoot) {
|
|
193
|
+
const paths = ensureAgentTeamStructure(wikiRoot);
|
|
194
|
+
try {
|
|
195
|
+
return normalizeQueue(JSON.parse(fs.readFileSync(paths.queueFile, "utf-8")));
|
|
196
|
+
} catch {
|
|
197
|
+
return createEmptyQueue();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function createAgentProfile(wikiRoot, profile, options = {}) {
|
|
202
|
+
const validation = validateAgentProfile(profile);
|
|
203
|
+
if (!validation.valid) return result(false, { error: validation.errors.join("; ") });
|
|
204
|
+
const queue = readAgentTeamQueue(wikiRoot);
|
|
205
|
+
queue.agent_profiles = [...queue.agent_profiles.filter((item) => item.id !== validation.value.id), validation.value];
|
|
206
|
+
writeQueue(wikiRoot, queue);
|
|
207
|
+
appendAudit(wikiRoot, "create_agent_profile", { agent_profile_id: validation.value.id }, options);
|
|
208
|
+
return result(true, { profile: validation.value });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function createAgentGroup(wikiRoot, group, options = {}) {
|
|
212
|
+
const validation = validateAgentGroup(group);
|
|
213
|
+
if (!validation.valid) return result(false, { error: validation.errors.join("; ") });
|
|
214
|
+
const queue = readAgentTeamQueue(wikiRoot);
|
|
215
|
+
queue.agent_groups = [...queue.agent_groups.filter((item) => item.id !== validation.value.id), validation.value];
|
|
216
|
+
writeQueue(wikiRoot, queue);
|
|
217
|
+
appendAudit(wikiRoot, "create_agent_group", { agent_group_id: validation.value.id }, options);
|
|
218
|
+
return result(true, { group: validation.value });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function createTaskCard(wikiRoot, task, options = {}) {
|
|
222
|
+
const validation = validateTaskCard({ ...task, created_at: task.created_at || nowIso(), updated_at: task.updated_at || nowIso() });
|
|
223
|
+
if (!validation.valid) return result(false, { error: validation.errors.join("; ") });
|
|
224
|
+
const queue = readAgentTeamQueue(wikiRoot);
|
|
225
|
+
queue.tasks = [...queue.tasks.filter((item) => item.id !== validation.value.id), validation.value];
|
|
226
|
+
writeQueue(wikiRoot, queue);
|
|
227
|
+
appendAudit(wikiRoot, "create_task", { task_id: validation.value.id, summary: validation.value.title }, options);
|
|
228
|
+
return result(true, { task: validation.value });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function updateTaskStatus(wikiRoot, taskId, status, options = {}) {
|
|
232
|
+
const queue = readAgentTeamQueue(wikiRoot);
|
|
233
|
+
const task = queue.tasks.find((item) => item.id === taskId);
|
|
234
|
+
if (!task) return result(false, { error: `Task not found: ${taskId}` });
|
|
235
|
+
|
|
236
|
+
let targetStatus;
|
|
237
|
+
try {
|
|
238
|
+
targetStatus = validateTaskCard({ ...task, status, blocked_reason: options.blocked_reason || task.blocked_reason }).value.status;
|
|
239
|
+
} catch (error) {
|
|
240
|
+
return result(false, { error: error.message });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (targetStatus === "blocked" && !options.blocked_reason && !task.blocked_reason) {
|
|
244
|
+
return result(false, { error: "Blocked tasks require blocked_reason" });
|
|
245
|
+
}
|
|
246
|
+
if (targetStatus === "approved" && !options.approval && !task.approval) {
|
|
247
|
+
return result(false, { error: "Approval metadata is required" });
|
|
248
|
+
}
|
|
249
|
+
if (targetStatus === "dispatched" && !task.approval && !options.approval) {
|
|
250
|
+
return result(false, { error: "Dispatch requires approval metadata" });
|
|
251
|
+
}
|
|
252
|
+
if (!ALLOWED_TRANSITIONS.get(task.status)?.has(targetStatus)) {
|
|
253
|
+
return result(false, { error: `Unsafe transition rejected: ${task.status} -> ${targetStatus}` });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const updated = {
|
|
257
|
+
...task,
|
|
258
|
+
status: targetStatus,
|
|
259
|
+
updated_at: nowIso(),
|
|
260
|
+
...(options.approval ? { approval: options.approval } : {}),
|
|
261
|
+
...(options.blocked_reason ? { blocked_reason: options.blocked_reason } : {}),
|
|
262
|
+
};
|
|
263
|
+
const validation = validateTaskCard(updated);
|
|
264
|
+
if (!validation.valid) return result(false, { error: validation.errors.join("; ") });
|
|
265
|
+
|
|
266
|
+
queue.tasks = queue.tasks.map((item) => item.id === taskId ? validation.value : item);
|
|
267
|
+
writeQueue(wikiRoot, queue);
|
|
268
|
+
appendAudit(wikiRoot, "update_task_status", { task_id: taskId, status: targetStatus }, options);
|
|
269
|
+
return result(true, { task: validation.value });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function appendTaskEvidence(wikiRoot, taskId, evidence, options = {}) {
|
|
273
|
+
const queue = readAgentTeamQueue(wikiRoot);
|
|
274
|
+
const task = queue.tasks.find((item) => item.id === taskId);
|
|
275
|
+
if (!task) return result(false, { error: `Task not found: ${taskId}` });
|
|
276
|
+
if (task.status !== "dispatched" && task.status !== "review") {
|
|
277
|
+
return result(false, { error: "Evidence can only be attached after dispatched or during review" });
|
|
278
|
+
}
|
|
279
|
+
if (!task.approval) return result(false, { error: "Evidence requires approval metadata" });
|
|
280
|
+
|
|
281
|
+
const validation = validateEvidenceNote({ ...evidence, task_id: taskId });
|
|
282
|
+
if (!validation.valid) return result(false, { error: validation.errors.join("; ") });
|
|
283
|
+
queue.evidence_notes = [...queue.evidence_notes.filter((item) => item.id !== validation.value.id), validation.value];
|
|
284
|
+
queue.tasks = queue.tasks.map((item) => item.id === taskId
|
|
285
|
+
? { ...item, status: "evidence", updated_at: nowIso() }
|
|
286
|
+
: item);
|
|
287
|
+
writeQueue(wikiRoot, queue);
|
|
288
|
+
appendAudit(wikiRoot, "append_evidence", { task_id: taskId, evidence_id: validation.value.id, summary: validation.value.summary }, options);
|
|
289
|
+
return result(true, { evidence: validation.value });
|
|
290
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { redactSecrets } from "../security.js";
|
|
2
|
+
|
|
3
|
+
export const QUEUE_STATUSES = ["proposed", "approved", "dispatched", "evidence", "review", "done", "blocked"];
|
|
4
|
+
export const AGENT_SCOPES = ["main", "sub", "custom"];
|
|
5
|
+
export const DEFAULT_BUILT_AT = "1970-01-01T00:00:00.000Z";
|
|
6
|
+
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeScalar(value, maxLength = 500) {
|
|
12
|
+
return redactTeamText(String(value ?? "")
|
|
13
|
+
.replace(/\r?\n/g, " ")
|
|
14
|
+
.replace(/\s+/g, " ")
|
|
15
|
+
.trim())
|
|
16
|
+
.slice(0, maxLength);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeId(value, label = "id") {
|
|
20
|
+
const id = normalizeScalar(value, 120).toLowerCase();
|
|
21
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,119}$/.test(id)) {
|
|
22
|
+
throw new Error(`Invalid ${label}: ${value}`);
|
|
23
|
+
}
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeStringList(value, maxLength = 120) {
|
|
28
|
+
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
29
|
+
return [...new Set(values.map((item) => normalizeScalar(item, maxLength)).filter(Boolean))]
|
|
30
|
+
.sort((a, b) => a.localeCompare(b));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeSafeRelativePath(value, label = "path", maxLength = 240) {
|
|
34
|
+
const normalized = normalizeScalar(value, maxLength).replace(/\\/g, "/");
|
|
35
|
+
if (!normalized) return "";
|
|
36
|
+
if (/^[a-z]:\//i.test(normalized) || normalized.startsWith("/") || normalized.startsWith("//")) {
|
|
37
|
+
throw new Error(`Unsafe ${label}: absolute paths are not allowed`);
|
|
38
|
+
}
|
|
39
|
+
if (normalized.split("/").some((segment) => segment === "..")) {
|
|
40
|
+
throw new Error(`Unsafe ${label}: traversal is not allowed`);
|
|
41
|
+
}
|
|
42
|
+
return normalized;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeArtifactList(value) {
|
|
46
|
+
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
47
|
+
return [...new Set(values.map((item) => normalizeSafeRelativePath(item, "artifact path", 240)).filter(Boolean))]
|
|
48
|
+
.sort((a, b) => a.localeCompare(b));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeContextRefs(value) {
|
|
52
|
+
const refs = Array.isArray(value) ? value : [];
|
|
53
|
+
return refs.flatMap((ref) => {
|
|
54
|
+
if (!isRecord(ref)) return [];
|
|
55
|
+
const type = normalizeScalar(ref.type, 40).toLowerCase();
|
|
56
|
+
const path = normalizeSafeRelativePath(ref.path, "context reference path", 240);
|
|
57
|
+
const query = normalizeScalar(ref.query, 240);
|
|
58
|
+
const graphId = normalizeSafeRelativePath(ref.graph_id || ref.graphId, "graph reference", 240);
|
|
59
|
+
if (!type || (!path && !query && !graphId)) return [];
|
|
60
|
+
return [{ type, ...(path ? { path } : {}), ...(query ? { query } : {}), ...(graphId ? { graph_id: graphId } : {}) }];
|
|
61
|
+
}).sort((a, b) => `${a.type}:${a.path || a.query || a.graph_id}`.localeCompare(`${b.type}:${b.path || b.query || b.graph_id}`));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeApproval(value) {
|
|
65
|
+
if (!isRecord(value)) return null;
|
|
66
|
+
const approvedBy = normalizeScalar(value.approved_by || value.approvedBy, 120);
|
|
67
|
+
const approvedAt = normalizeScalar(value.approved_at || value.approvedAt, 80);
|
|
68
|
+
return {
|
|
69
|
+
approved_by: approvedBy,
|
|
70
|
+
approved_at: approvedAt,
|
|
71
|
+
approved_capabilities: normalizeStringList(value.approved_capabilities || value.approvedCapabilities, 80),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function validationResult(builder) {
|
|
76
|
+
try {
|
|
77
|
+
return { valid: true, value: builder(), errors: [] };
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return { valid: false, value: null, errors: [error.message] };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function redactTeamText(value) {
|
|
84
|
+
return redactSecrets(String(value ?? ""));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function normalizeAgentScope(input) {
|
|
88
|
+
const scope = normalizeScalar(input || "custom", 40).toLowerCase();
|
|
89
|
+
if (!AGENT_SCOPES.includes(scope)) {
|
|
90
|
+
throw new Error(`Invalid agent scope: ${input}`);
|
|
91
|
+
}
|
|
92
|
+
return scope;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function normalizeQueueStatus(input) {
|
|
96
|
+
const status = normalizeScalar(input || "proposed", 40).toLowerCase();
|
|
97
|
+
if (!QUEUE_STATUSES.includes(status)) {
|
|
98
|
+
throw new Error(`Invalid queue status: ${input}`);
|
|
99
|
+
}
|
|
100
|
+
return status;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function normalizeCapabilityPolicy(value = {}) {
|
|
104
|
+
const policy = isRecord(value) ? value : {};
|
|
105
|
+
return {
|
|
106
|
+
allowed: normalizeStringList(policy.allowed, 80),
|
|
107
|
+
requires_approval: normalizeStringList(policy.requires_approval || policy.requiresApproval, 80),
|
|
108
|
+
denied: normalizeStringList(policy.denied, 80),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function validateAgentProfile(input) {
|
|
113
|
+
return validationResult(() => {
|
|
114
|
+
if (!isRecord(input)) throw new Error("Agent profile must be an object");
|
|
115
|
+
const profile = {
|
|
116
|
+
id: normalizeId(input.id, "agent profile id"),
|
|
117
|
+
label: normalizeScalar(input.label, 160),
|
|
118
|
+
kind: normalizeScalar(input.kind || "custom", 80).toLowerCase(),
|
|
119
|
+
default_role_id: normalizeId(input.default_role_id || input.defaultRoleId || "custom", "default role id"),
|
|
120
|
+
scope: normalizeAgentScope(input.scope),
|
|
121
|
+
capability_policy: normalizeCapabilityPolicy(input.capability_policy || input.capabilityPolicy),
|
|
122
|
+
dispatch_template: normalizeScalar(input.dispatch_template || input.dispatchTemplate, 2000),
|
|
123
|
+
};
|
|
124
|
+
if (!profile.label) throw new Error("Agent profile label is required");
|
|
125
|
+
return profile;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function validateAgentGroup(input) {
|
|
130
|
+
return validationResult(() => {
|
|
131
|
+
if (!isRecord(input)) throw new Error("Agent group must be an object");
|
|
132
|
+
const group = {
|
|
133
|
+
id: normalizeId(input.id, "agent group id"),
|
|
134
|
+
label: normalizeScalar(input.label, 160),
|
|
135
|
+
agent_profile_ids: normalizeStringList(input.agent_profile_ids || input.agentProfileIds, 120).map((item) => normalizeId(item, "agent profile id")),
|
|
136
|
+
default_queue_policy: normalizeScalar(input.default_queue_policy || input.defaultQueuePolicy || "approval-first", 120),
|
|
137
|
+
description: normalizeScalar(input.description, 1000),
|
|
138
|
+
};
|
|
139
|
+
if (!group.label) throw new Error("Agent group label is required");
|
|
140
|
+
return group;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function validateRole(input) {
|
|
145
|
+
return validationResult(() => {
|
|
146
|
+
if (!isRecord(input)) throw new Error("Role must be an object");
|
|
147
|
+
const role = {
|
|
148
|
+
id: normalizeId(input.id, "role id"),
|
|
149
|
+
label: normalizeScalar(input.label, 160),
|
|
150
|
+
description: normalizeScalar(input.description, 1000),
|
|
151
|
+
default_capabilities: normalizeStringList(input.default_capabilities || input.defaultCapabilities, 80),
|
|
152
|
+
dispatch_instructions: normalizeScalar(input.dispatch_instructions || input.dispatchInstructions, 2000),
|
|
153
|
+
};
|
|
154
|
+
if (!role.label) throw new Error("Role label is required");
|
|
155
|
+
return role;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function validateTaskCard(input) {
|
|
160
|
+
return validationResult(() => {
|
|
161
|
+
if (!isRecord(input)) throw new Error("Task card must be an object");
|
|
162
|
+
const now = new Date(0).toISOString();
|
|
163
|
+
const task = {
|
|
164
|
+
id: normalizeId(input.id, "task id"),
|
|
165
|
+
title: normalizeScalar(input.title, 220),
|
|
166
|
+
status: normalizeQueueStatus(input.status),
|
|
167
|
+
role_id: normalizeId(input.role_id || input.roleId || "custom", "role id"),
|
|
168
|
+
agent_profile_id: normalizeId(input.agent_profile_id || input.agentProfileId || "custom-agent", "agent profile id"),
|
|
169
|
+
goal: normalizeScalar(input.goal, 2000),
|
|
170
|
+
success_criteria: normalizeStringList(input.success_criteria || input.successCriteria, 400),
|
|
171
|
+
context_refs: normalizeContextRefs(input.context_refs || input.contextRefs),
|
|
172
|
+
capability_request: normalizeStringList(input.capability_request || input.capabilityRequest, 80),
|
|
173
|
+
approval: normalizeApproval(input.approval),
|
|
174
|
+
blocked_reason: normalizeScalar(input.blocked_reason || input.blockedReason, 500),
|
|
175
|
+
created_at: normalizeScalar(input.created_at || input.createdAt || now, 80),
|
|
176
|
+
updated_at: normalizeScalar(input.updated_at || input.updatedAt || now, 80),
|
|
177
|
+
};
|
|
178
|
+
if (!task.title) throw new Error("Task title is required");
|
|
179
|
+
if (!task.goal) throw new Error("Task goal is required");
|
|
180
|
+
if (task.success_criteria.length === 0) throw new Error("Task success criteria are required");
|
|
181
|
+
if (task.status === "blocked" && !task.blocked_reason) throw new Error("Blocked tasks require blocked_reason");
|
|
182
|
+
return task;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function validateDispatchPacket(input) {
|
|
187
|
+
return validationResult(() => {
|
|
188
|
+
if (!isRecord(input)) throw new Error("Dispatch packet must be an object");
|
|
189
|
+
const approval = normalizeApproval(input.approval);
|
|
190
|
+
if (!approval || !approval.approved_by || !approval.approved_at) {
|
|
191
|
+
throw new Error("Dispatch packet approval metadata is required");
|
|
192
|
+
}
|
|
193
|
+
const packet = {
|
|
194
|
+
id: normalizeId(input.id, "dispatch packet id"),
|
|
195
|
+
task_id: normalizeId(input.task_id || input.taskId, "task id"),
|
|
196
|
+
role_id: normalizeId(input.role_id || input.roleId, "role id"),
|
|
197
|
+
agent_profile_id: normalizeId(input.agent_profile_id || input.agentProfileId, "agent profile id"),
|
|
198
|
+
allowed_capabilities: normalizeStringList(input.allowed_capabilities || input.allowedCapabilities, 80),
|
|
199
|
+
forbidden_capabilities: normalizeStringList(input.forbidden_capabilities || input.forbiddenCapabilities, 80),
|
|
200
|
+
context_refs: normalizeContextRefs(input.context_refs || input.contextRefs),
|
|
201
|
+
prompt: normalizeScalar(input.prompt, 6000),
|
|
202
|
+
expected_output: normalizeScalar(input.expected_output || input.expectedOutput, 1000),
|
|
203
|
+
approval,
|
|
204
|
+
created_at: normalizeScalar(input.created_at || input.createdAt || new Date(0).toISOString(), 80),
|
|
205
|
+
};
|
|
206
|
+
if (!packet.prompt) throw new Error("Dispatch packet prompt is required");
|
|
207
|
+
return packet;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function validateEvidenceNote(input) {
|
|
212
|
+
return validationResult(() => {
|
|
213
|
+
if (!isRecord(input)) throw new Error("Evidence note must be an object");
|
|
214
|
+
const note = {
|
|
215
|
+
id: normalizeId(input.id, "evidence note id"),
|
|
216
|
+
task_id: normalizeId(input.task_id || input.taskId, "task id"),
|
|
217
|
+
agent_profile_id: normalizeId(input.agent_profile_id || input.agentProfileId || "custom-agent", "agent profile id"),
|
|
218
|
+
summary: normalizeScalar(input.summary, 2000),
|
|
219
|
+
artifacts: normalizeArtifactList(input.artifacts),
|
|
220
|
+
created_at: normalizeScalar(input.created_at || input.createdAt || new Date(0).toISOString(), 80),
|
|
221
|
+
};
|
|
222
|
+
if (!note.summary) throw new Error("Evidence summary is required");
|
|
223
|
+
return note;
|
|
224
|
+
});
|
|
225
|
+
}
|