@dotdrelle/wiki-manager 0.15.98 → 0.15.100
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/package.json +2 -2
- package/src/agent/graph.js +7 -4
- package/src/contracts/schemas.js +8 -17
- package/src/contracts/schemas.test.js +15 -0
- package/src/core/agentEvents.js +45 -18
- package/src/core/agentEvents.test.js +40 -0
- package/src/core/buildInfo.json +2 -2
- package/src/core/mcp.js +1 -1
- package/src/core/runtimeEventAdapter.js +99 -1
- package/src/core/runtimeEventAdapter.test.js +92 -2
- package/src/core/skillCompiler.test.js +1 -1
- package/src/core/toolLoop.js +7 -1
- package/src/core/toolLoop.test.js +28 -0
- package/src/orchestrator/dispatcher.js +21 -1
- package/src/orchestrator/knowledgeSignals.js +260 -0
- package/src/orchestrator/knowledgeSignals.test.js +193 -0
- package/src/orchestrator/proactiveReviewScheduler.js +240 -0
- package/src/orchestrator/proactiveReviewScheduler.test.js +243 -0
- package/src/orchestrator/providers/deepAgentsProvider.js +134 -29
- package/src/orchestrator/providers/deepAgentsProvider.test.js +138 -3
- package/src/orchestrator/resultAggregator.js +115 -1
- package/src/orchestrator/resultAggregator.test.js +138 -0
- package/src/runtime/server.js +239 -1
- package/src/runtime/store.js +1 -1
- package/src/runtime/store.test.js +5 -1
- package/wiki-workspace +0 -1
package/src/runtime/server.js
CHANGED
|
@@ -10,6 +10,21 @@ import { tasksAwaitingApproval } from '../orchestrator/dependencyResolver.js';
|
|
|
10
10
|
import { isActive, isCancelled, isFailed, isSuccessful } from '../orchestrator/taskStatuses.js';
|
|
11
11
|
import { approvalClassForTask } from '../orchestrator/approvalPolicy.js';
|
|
12
12
|
import { RUNTIME_SHUTDOWN_ABORT_REASON } from '../orchestrator/dispatcher.js';
|
|
13
|
+
import {
|
|
14
|
+
PROACTIVE_REVIEW_CAPABILITY,
|
|
15
|
+
buildProactiveReviewObjective,
|
|
16
|
+
createProactiveReviewScheduler,
|
|
17
|
+
normalizeProactiveConfig,
|
|
18
|
+
} from '../orchestrator/proactiveReviewScheduler.js';
|
|
19
|
+
import {
|
|
20
|
+
conflictFingerprint,
|
|
21
|
+
detectConceptConflicts,
|
|
22
|
+
detectStaleKnowledge,
|
|
23
|
+
readConceptLeaves,
|
|
24
|
+
readSourceRegistry,
|
|
25
|
+
readWikiPages,
|
|
26
|
+
staleFingerprint,
|
|
27
|
+
} from '../orchestrator/knowledgeSignals.js';
|
|
13
28
|
import { matchSkillInvocation } from '../core/skillInvocation.js';
|
|
14
29
|
import { reconcileControlQueue } from './controlDrain.js';
|
|
15
30
|
import { cancelControlChain, cancelQueuedControlItem } from './controlCancellation.js';
|
|
@@ -74,6 +89,17 @@ export function startRuntimeServer({
|
|
|
74
89
|
exitOnShutdown = process.env.WIKI_MANAGER_RUNTIME_CHILD === '1',
|
|
75
90
|
} = {}) {
|
|
76
91
|
const clients = new Set();
|
|
92
|
+
// Deterministic dedup/cooldown/budget for proactive reviews; the run itself
|
|
93
|
+
// stays the normal control-lane path.
|
|
94
|
+
const proactiveScheduler = createProactiveReviewScheduler({
|
|
95
|
+
db: store?.db,
|
|
96
|
+
pendingReviews: store?.getProjection ? () => (store.getProjection().controlQueue ?? [])
|
|
97
|
+
.filter((item) => item.proactiveReview && ['queued', 'running'].includes(item.status))
|
|
98
|
+
.map((item) => item.proactiveReview) : null,
|
|
99
|
+
});
|
|
100
|
+
// runId -> the review record that started it, so the slot is released exactly
|
|
101
|
+
// once when the run reaches a terminal state (persisted or not).
|
|
102
|
+
const proactiveRuns = new Map();
|
|
77
103
|
// Compiled objectives are private execution material. They deliberately do
|
|
78
104
|
// not enter events, projections, SSE, audit output or the runs table.
|
|
79
105
|
// When this runtime process started — used by ensureRuntime to detect that
|
|
@@ -860,6 +886,26 @@ export function startRuntimeServer({
|
|
|
860
886
|
const loginAttemptPruneTimer = setInterval(() => pruneLoginAttempts(), 10 * 60 * 1000);
|
|
861
887
|
loginAttemptPruneTimer.unref?.();
|
|
862
888
|
|
|
889
|
+
// The corpus clock. `aged` is caused by TIME, and a workspace that stops
|
|
890
|
+
// ingesting — precisely the one whose knowledge ages — would otherwise never
|
|
891
|
+
// re-run the detector; conflicts and vanished paths are edit-caused, so the
|
|
892
|
+
// ingest moment covers them. This tick therefore reads only the source
|
|
893
|
+
// registry (staleOnly), and the opt-in gating happens inside the scan.
|
|
894
|
+
const corpusScanMs = Number(process.env.WIKI_MANAGER_CORPUS_SCAN_INTERVAL_MS ?? 15 * 60 * 1000);
|
|
895
|
+
const corpusScanTimer = Number.isFinite(corpusScanMs) && corpusScanMs > 0
|
|
896
|
+
? setInterval(() => {
|
|
897
|
+
void (async () => {
|
|
898
|
+
try {
|
|
899
|
+
const context = await resolvedGetContext(null);
|
|
900
|
+
if (!context?.session) return;
|
|
901
|
+
const workspace = context.workspace ?? context.session.workspace ?? null;
|
|
902
|
+
if (workspace) emitCorpusSignals(context, workspace, { staleOnly: true });
|
|
903
|
+
} catch { /* a clock tick never breaks the server */ }
|
|
904
|
+
})();
|
|
905
|
+
}, corpusScanMs)
|
|
906
|
+
: null;
|
|
907
|
+
corpusScanTimer?.unref?.();
|
|
908
|
+
|
|
863
909
|
return new Promise((resolve, reject) => {
|
|
864
910
|
server.once('error', reject);
|
|
865
911
|
server.listen(port, host, () => {
|
|
@@ -872,6 +918,7 @@ export function startRuntimeServer({
|
|
|
872
918
|
drainControl: (context) => drainControlQueue(context),
|
|
873
919
|
close: () => new Promise((closeResolve, closeReject) => {
|
|
874
920
|
clearInterval(loginAttemptPruneTimer);
|
|
921
|
+
if (corpusScanTimer) clearInterval(corpusScanTimer);
|
|
875
922
|
for (const client of clients) client.response.end();
|
|
876
923
|
clients.clear();
|
|
877
924
|
server.close((err) => (err ? closeReject(err) : closeResolve()));
|
|
@@ -1029,6 +1076,14 @@ export function startRuntimeServer({
|
|
|
1029
1076
|
announceControlLaunch(context.session, body.publicInput ?? body.input, runWorkspace);
|
|
1030
1077
|
}
|
|
1031
1078
|
}
|
|
1079
|
+
// The trigger hook and the proactive marker belong to THIS run on the
|
|
1080
|
+
// session; the review that opened it is remembered so its concurrency slot
|
|
1081
|
+
// is released exactly once, whatever terminal state it reaches.
|
|
1082
|
+
if (context.session) context.session._onKnowledgeTrigger = (payload) => handleKnowledgeTrigger(context, payload);
|
|
1083
|
+
if (body.proactiveReview) {
|
|
1084
|
+
if (context.session) context.session._proactiveReview = { ...body.proactiveReview, runId };
|
|
1085
|
+
proactiveRuns.set(runId, body.proactiveReview);
|
|
1086
|
+
}
|
|
1032
1087
|
const runPromise = run(context, runBody, { signal: context.currentAbortController.signal, runId });
|
|
1033
1088
|
runPromise
|
|
1034
1089
|
.catch((err) => {
|
|
@@ -1039,6 +1094,12 @@ export function startRuntimeServer({
|
|
|
1039
1094
|
context.session?._onRuntimeError?.(err, runId);
|
|
1040
1095
|
})
|
|
1041
1096
|
.finally(() => {
|
|
1097
|
+
const proactive = proactiveRuns.get(runId);
|
|
1098
|
+
if (proactive) {
|
|
1099
|
+
proactiveRuns.delete(runId);
|
|
1100
|
+
proactiveScheduler.release(proactive.workspace);
|
|
1101
|
+
}
|
|
1102
|
+
if (context.session?._proactiveReview?.runId === runId) context.session._proactiveReview = null;
|
|
1042
1103
|
context.running = false;
|
|
1043
1104
|
context.currentAbortController = null;
|
|
1044
1105
|
context.currentRunId = null;
|
|
@@ -1073,6 +1134,7 @@ export function startRuntimeServer({
|
|
|
1073
1134
|
// may still grant the pending run explicitly through --auto-approve.
|
|
1074
1135
|
...(item.chainId ? { requireApproval: true } : {}),
|
|
1075
1136
|
...(item.capabilityPlan !== undefined ? { capabilityPlan: item.capabilityPlan } : {}),
|
|
1137
|
+
...(item.proactiveReview ? { proactiveReview: item.proactiveReview } : {}),
|
|
1076
1138
|
...(item.chainId
|
|
1077
1139
|
? {
|
|
1078
1140
|
skillChain: {
|
|
@@ -1093,6 +1155,179 @@ export function startRuntimeServer({
|
|
|
1093
1155
|
});
|
|
1094
1156
|
}
|
|
1095
1157
|
|
|
1158
|
+
/*
|
|
1159
|
+
A knowledge fact is not an order: it is an opportunity, and the workspace
|
|
1160
|
+
says (opt-in) whether it wants one. The decision is deterministic — dedup,
|
|
1161
|
+
cooldown, budget, concurrency — and a refusal is said out loud, never a
|
|
1162
|
+
silent no. An accepted trigger queues a read-only `agent.review` through the
|
|
1163
|
+
normal control lane; nothing here mutates, creates a worktree or sends
|
|
1164
|
+
anything.
|
|
1165
|
+
*/
|
|
1166
|
+
function handleKnowledgeTrigger(context, payload) {
|
|
1167
|
+
const session = context?.session ?? null;
|
|
1168
|
+
const workspace = String(payload?.workspace ?? context?.workspace ?? '');
|
|
1169
|
+
const trigger = String(payload?.trigger ?? '');
|
|
1170
|
+
// The corpus detectors are reads that only make sense when the corpus just
|
|
1171
|
+
// changed. They feed this same scheduler — never a second one — and cannot
|
|
1172
|
+
// recurse, since a conflict/stale fact does not re-run the detectors.
|
|
1173
|
+
if (trigger === 'knowledge.ingested' || trigger === 'knowledge.rebuilt') {
|
|
1174
|
+
emitCorpusSignals(context, workspace);
|
|
1175
|
+
}
|
|
1176
|
+
const config = session?.wikircConfig?.proactiveReviews ?? null;
|
|
1177
|
+
let decision;
|
|
1178
|
+
try {
|
|
1179
|
+
decision = proactiveScheduler.decide({
|
|
1180
|
+
workspace, trigger, sourceVersion: payload?.sourceVersion ?? null, config,
|
|
1181
|
+
});
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
emitRuntimeLog(session, `proactive-review: skipped — budget state unavailable: ${error.message}`);
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
if (decision.action !== 'review') {
|
|
1187
|
+
const inFlight = proactiveScheduler.snapshot(workspace).inFlightTrigger;
|
|
1188
|
+
// The conflict detector runs first, so it can take the only slot; saying
|
|
1189
|
+
// so turns an effect of statement order into a stated precedence.
|
|
1190
|
+
const precedence = decision.reason === 'concurrency' && inFlight
|
|
1191
|
+
? ` — a ${inFlight} review already holds the slot`
|
|
1192
|
+
: '';
|
|
1193
|
+
emitRuntimeLog(
|
|
1194
|
+
session,
|
|
1195
|
+
`proactive-review: skipped (${decision.reason}${precedence}) for ${workspace || 'workspace'} [${trigger}]`,
|
|
1196
|
+
);
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
const evidence = payload?.evidence ?? null;
|
|
1200
|
+
const record = {
|
|
1201
|
+
id: `review-${randomUUID()}`,
|
|
1202
|
+
workspace,
|
|
1203
|
+
trigger,
|
|
1204
|
+
sourceVersion: decision.sourceVersion,
|
|
1205
|
+
createdAt: new Date().toISOString(),
|
|
1206
|
+
budget: proactiveScheduler.snapshot(workspace),
|
|
1207
|
+
// The deterministic facts, so the filed note says WHAT the scan knew —
|
|
1208
|
+
// not only that a review happened. Persisted with the item's marker.
|
|
1209
|
+
...(evidence ? { evidence } : {}),
|
|
1210
|
+
};
|
|
1211
|
+
const objective = buildProactiveReviewObjective({
|
|
1212
|
+
trigger,
|
|
1213
|
+
sourceVersion: decision.sourceVersion,
|
|
1214
|
+
evidence,
|
|
1215
|
+
});
|
|
1216
|
+
let item;
|
|
1217
|
+
try {
|
|
1218
|
+
item = enqueueControlRequest(context, objective, {
|
|
1219
|
+
publicInput: objective,
|
|
1220
|
+
// Routing is EXPLICIT. The objective names evidence paths, which could
|
|
1221
|
+
// contain another capability's alias (`report`, `plan`, `check`…), and
|
|
1222
|
+
// the free-text resolver returns null on two hits. Naming the
|
|
1223
|
+
// capability removes that risk entirely.
|
|
1224
|
+
capabilityPlan: { capability: PROACTIVE_REVIEW_CAPABILITY, operation: 'run' },
|
|
1225
|
+
proactiveReview: record,
|
|
1226
|
+
});
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
// A reservation that never became a queued item must be UNDONE: burning a
|
|
1229
|
+
// budget unit and marking the version seen would silently cancel an audit
|
|
1230
|
+
// that was never enqueued.
|
|
1231
|
+
proactiveScheduler.release(workspace, { undo: true, sourceVersion: decision.sourceVersion });
|
|
1232
|
+
emitRuntimeLog(
|
|
1233
|
+
session,
|
|
1234
|
+
`proactive-review: could not queue the review — ${error instanceof Error ? error.message : String(error)}`,
|
|
1235
|
+
);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
emitRuntimeLog(
|
|
1239
|
+
session,
|
|
1240
|
+
`proactive-review: queued ${item.id} (${trigger}) for ${workspace || 'workspace'} — ${PROACTIVE_REVIEW_CAPABILITY}, read-only`,
|
|
1241
|
+
);
|
|
1242
|
+
void startNextControlRequest(context);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/*
|
|
1246
|
+
The live-corpus read: two homonym leaves under one concept folder are a
|
|
1247
|
+
conflict the ingest plan never sees (they are already written). Pure fs +
|
|
1248
|
+
string work, no model. A conflict set is a stable fingerprint, so the same
|
|
1249
|
+
conflict dedups instead of re-firing every time the corpus is touched.
|
|
1250
|
+
*/
|
|
1251
|
+
function emitConflictSignal(context, workspace, session, workspacePath) {
|
|
1252
|
+
const { conflicts, total, dropped } = detectConceptConflicts(readConceptLeaves(workspacePath));
|
|
1253
|
+
if (total === 0) return;
|
|
1254
|
+
if (dropped > 0) {
|
|
1255
|
+
emitRuntimeLog(
|
|
1256
|
+
session,
|
|
1257
|
+
`knowledge-signals: ${total} conflict(s) found, ${dropped} beyond the ceiling are not listed (the fingerprint still counts them)`,
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
handleKnowledgeTrigger(context, {
|
|
1261
|
+
workspace,
|
|
1262
|
+
trigger: 'knowledge.conflict_detected',
|
|
1263
|
+
// The fingerprint includes the full count, so a conflict beyond the cap
|
|
1264
|
+
// still moves the version and is not deduped away.
|
|
1265
|
+
sourceVersion: conflictFingerprint(conflicts, total),
|
|
1266
|
+
// The exact facts travel to the agent and the filed note.
|
|
1267
|
+
evidence: { kind: 'conflict', items: conflicts },
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
function emitStaleSignal(context, workspace, session, workspacePath, config) {
|
|
1272
|
+
const { stale, total, dropped, counts } = detectStaleKnowledge(readSourceRegistry(workspacePath), {
|
|
1273
|
+
rootDir: workspacePath,
|
|
1274
|
+
staleAfterDays: config.staleAfterDays,
|
|
1275
|
+
wikiPages: readWikiPages(workspacePath),
|
|
1276
|
+
});
|
|
1277
|
+
if (total === 0) return;
|
|
1278
|
+
if (dropped > 0) {
|
|
1279
|
+
// Counted PER NATURE: the ceiling hides evidence of several kinds, and a
|
|
1280
|
+
// single "N source(s)" would be false for the others.
|
|
1281
|
+
const breakdown = [
|
|
1282
|
+
counts.aged ? `${counts.aged} aged` : null,
|
|
1283
|
+
counts.orphan ? `${counts.orphan} orphan page(s)` : null,
|
|
1284
|
+
counts.vanishedArchive ? `${counts.vanishedArchive} vanished archive(s)` : null,
|
|
1285
|
+
counts.vanishedPage ? `${counts.vanishedPage} vanished page(s)` : null,
|
|
1286
|
+
].filter(Boolean).join(', ');
|
|
1287
|
+
emitRuntimeLog(
|
|
1288
|
+
session,
|
|
1289
|
+
`knowledge-signals: stale knowledge — ${breakdown}; ${dropped} beyond the ceiling are not listed`,
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
handleKnowledgeTrigger(context, {
|
|
1293
|
+
workspace,
|
|
1294
|
+
trigger: 'knowledge.stale',
|
|
1295
|
+
sourceVersion: staleFingerprint(stale, total),
|
|
1296
|
+
evidence: { kind: 'stale', counts, items: stale },
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/*
|
|
1301
|
+
The live-corpus read, run when an ingest/rebuild completes AND on the
|
|
1302
|
+
runtime's clock. The clock matters for `aged`: it is caused by TIME, and a
|
|
1303
|
+
workspace that stops ingesting — precisely the one whose knowledge ages —
|
|
1304
|
+
would otherwise never re-run the detector. Conflicts and vanished paths are
|
|
1305
|
+
caused by edits, so the ingest moment is enough for them; `staleOnly` keeps
|
|
1306
|
+
the periodic tick from re-walking every leaf it does not need.
|
|
1307
|
+
*/
|
|
1308
|
+
function emitCorpusSignals(context, workspace, { staleOnly = false } = {}) {
|
|
1309
|
+
const session = context?.session ?? null;
|
|
1310
|
+
// Stay OFF the corpus until the workspace actually opted in: reading every
|
|
1311
|
+
// leaf synchronously on the event loop that also serves both chats' SSE is
|
|
1312
|
+
// a cost the default (disabled) must not pay.
|
|
1313
|
+
const config = normalizeProactiveConfig(session?.wikircConfig?.proactiveReviews);
|
|
1314
|
+
if (!config.enabled) return;
|
|
1315
|
+
const wantsConflicts = !staleOnly && config.triggers.includes('knowledge.conflict_detected');
|
|
1316
|
+
const wantsStale = config.triggers.includes('knowledge.stale');
|
|
1317
|
+
if (!wantsConflicts && !wantsStale) return;
|
|
1318
|
+
const workspacePath = session?.workspacePath;
|
|
1319
|
+
if (!workspacePath) return;
|
|
1320
|
+
try {
|
|
1321
|
+
if (wantsConflicts) emitConflictSignal(context, workspace, session, workspacePath);
|
|
1322
|
+
if (wantsStale) emitStaleSignal(context, workspace, session, workspacePath, config);
|
|
1323
|
+
} catch (error) {
|
|
1324
|
+
emitRuntimeLog(
|
|
1325
|
+
session,
|
|
1326
|
+
`knowledge-signals: a corpus scan failed — ${error instanceof Error ? error.message : String(error)}`,
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1096
1331
|
function takePrivateControlInput(session, item) {
|
|
1097
1332
|
const privateControlInputs = privateControlInputsFor(session);
|
|
1098
1333
|
const input = privateControlInputs.get(item.id) ?? item.input;
|
|
@@ -1592,7 +1827,7 @@ function announceControlLaunch(session, input, workspace) {
|
|
|
1592
1827
|
Une file est un passage de témoin : ce qui doit survivre au parent voyage avec
|
|
1593
1828
|
le message, pas dans l'état de celui qui l'a posté.
|
|
1594
1829
|
*/
|
|
1595
|
-
function enqueueControlRequest(context, input, { publicInput = null, capabilityPlan, chainId, chainSequence, skillName, skillExecution, skillStack, selectionKind, optional = false, continueOnFailure = false } = {}) {
|
|
1830
|
+
function enqueueControlRequest(context, input, { publicInput = null, capabilityPlan, chainId, chainSequence, skillName, skillExecution, skillStack, selectionKind, optional = false, continueOnFailure = false, proactiveReview = null } = {}) {
|
|
1596
1831
|
const now = new Date().toISOString();
|
|
1597
1832
|
const item = {
|
|
1598
1833
|
id: `control-${randomUUID()}`,
|
|
@@ -1609,6 +1844,9 @@ function enqueueControlRequest(context, input, { publicInput = null, capabilityP
|
|
|
1609
1844
|
...(skillExecution ? { skillExecution } : {}),
|
|
1610
1845
|
...(Array.isArray(skillStack) && skillStack.length ? { skillStack: [...skillStack] } : {}),
|
|
1611
1846
|
...(selectionKind ? { selectionKind } : {}),
|
|
1847
|
+
// The proactive marker rides on the ITEM, so it survives projection and a
|
|
1848
|
+
// runtime restart, and the drain can hand it back to the run it starts.
|
|
1849
|
+
...(proactiveReview ? { proactiveReview } : {}),
|
|
1612
1850
|
optional: optional === true,
|
|
1613
1851
|
continueOnFailure: continueOnFailure === true,
|
|
1614
1852
|
};
|
package/src/runtime/store.js
CHANGED
|
@@ -25,7 +25,7 @@ export { defaultRuntimeStateDir };
|
|
|
25
25
|
// turn's LLM context. A persisted progress note would be re-read by the model
|
|
26
26
|
// on every later turn as if it were something the user said or Donna answered,
|
|
27
27
|
// growing the context with commentary about work already finished.
|
|
28
|
-
const NON_PERSISTED_EVENT_TYPES = new Set(['runtime_log', 'assistant_progress']);
|
|
28
|
+
const NON_PERSISTED_EVENT_TYPES = new Set(['runtime_log', 'assistant_progress', 'runtime_heartbeat']);
|
|
29
29
|
export const RUNTIME_STORE_SCHEMA_VERSION = 1;
|
|
30
30
|
const RUNTIME_RETENTION_DAYS = 30;
|
|
31
31
|
const TERMINAL_RUN_STATUSES = ['done', 'error', 'cancelled', 'interrupted'];
|
|
@@ -770,13 +770,17 @@ test('runtime store backfills sequence for legacy event rows', () => {
|
|
|
770
770
|
store.close();
|
|
771
771
|
});
|
|
772
772
|
|
|
773
|
-
test('runtime store does not persist runtime logs', () => {
|
|
773
|
+
test('runtime store does not persist runtime logs or liveness beats', () => {
|
|
774
774
|
const stateDir = mkdtempSync(join(tmpdir(), 'wiki-manager-runtime-'));
|
|
775
775
|
const store = openRuntimeStore({ stateDir });
|
|
776
776
|
store.persistEvent(createAgentEvent('runtime_log', {
|
|
777
777
|
origin: 'runtime',
|
|
778
778
|
payload: { message: 'agentic-loop: turn 1/20' },
|
|
779
779
|
}));
|
|
780
|
+
store.persistEvent(createAgentEvent('runtime_heartbeat', {
|
|
781
|
+
origin: 'runtime_provider',
|
|
782
|
+
payload: { elapsedMs: 30_000 },
|
|
783
|
+
}));
|
|
780
784
|
|
|
781
785
|
assert.equal(store.listEvents().length, 0);
|
|
782
786
|
store.close();
|
package/wiki-workspace
CHANGED
|
@@ -138,7 +138,6 @@ wiki CLI commands reachable through `wiki <workspace> run`:
|
|
|
138
138
|
index Create or update the local vector index
|
|
139
139
|
refresh Regenerate only the stale deliverables
|
|
140
140
|
lint Static checks: dead links, orphans, stale deliverables
|
|
141
|
-
add-skill <source> Install a workspace method (directory, .zip, HTTPS .zip)
|
|
142
141
|
config Show the effective .wikirc.yaml
|
|
143
142
|
doctor, ingest, build, export Also available directly: wiki <workspace> <command>
|
|
144
143
|
|