clawvault 2.5.1 → 2.5.3
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/README.md +159 -199
- package/bin/clawvault.js +111 -111
- package/bin/command-registration.test.js +166 -165
- package/bin/command-runtime.js +93 -77
- package/bin/command-runtime.test.js +154 -102
- package/bin/help-contract.test.js +39 -28
- package/bin/register-config-commands.js +153 -153
- package/bin/register-config-route-commands.test.js +121 -121
- package/bin/register-core-commands.js +237 -237
- package/bin/register-kanban-commands.js +56 -56
- package/bin/register-kanban-commands.test.js +83 -83
- package/bin/register-maintenance-commands.js +282 -248
- package/bin/register-project-commands.js +209 -209
- package/bin/register-project-commands.test.js +206 -201
- package/bin/register-query-commands.js +317 -312
- package/bin/register-query-commands.test.js +65 -0
- package/bin/register-resilience-commands.js +182 -182
- package/bin/register-resilience-commands.test.js +81 -81
- package/bin/register-route-commands.js +114 -114
- package/bin/register-session-lifecycle-commands.js +206 -206
- package/bin/register-tailscale-commands.js +106 -106
- package/bin/register-task-commands.js +348 -348
- package/bin/register-task-commands.test.js +69 -69
- package/bin/register-template-commands.js +72 -72
- package/bin/register-vault-operations-commands.js +300 -300
- package/bin/test-helpers/cli-command-fixtures.js +119 -119
- package/dashboard/lib/graph-diff.js +104 -104
- package/dashboard/lib/graph-diff.test.js +75 -75
- package/dashboard/lib/vault-parser.js +556 -556
- package/dashboard/lib/vault-parser.test.js +254 -254
- package/dashboard/public/app.js +796 -796
- package/dashboard/public/index.html +52 -52
- package/dashboard/public/styles.css +221 -221
- package/dashboard/server.js +374 -374
- package/dist/{chunk-G3OQJ2NQ.js → chunk-2YDBJS7M.js} +1 -1
- package/dist/chunk-3FP5BJ42.js +88 -0
- package/dist/{chunk-C3PF7WBA.js → chunk-4IV3R2F5.js} +2 -2
- package/dist/{chunk-7OHQFMJK.js → chunk-AY4PGUVL.js} +5 -4
- package/dist/chunk-FG6RJMCN.js +33 -0
- package/dist/{chunk-WIICLBNF.js → chunk-GFJ3LIIB.js} +1 -1
- package/dist/chunk-IZEY5S74.js +541 -0
- package/dist/chunk-LMEMZGUV.js +332 -0
- package/dist/{chunk-6RQPD7X6.js → chunk-M25QVSJM.js} +4 -3
- package/dist/{chunk-6B3JWM7J.js → chunk-O7XHXF7F.js} +34 -7
- package/dist/chunk-OSMS7QIG.js +406 -0
- package/dist/{chunk-PAYUH64O.js → chunk-QVMXF7FY.js} +11 -1
- package/dist/{chunk-TMZMN7OS.js → chunk-S2IG7VNM.js} +24 -12
- package/dist/{chunk-LMCC5OC7.js → chunk-TPDH3JPP.js} +1 -1
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +31 -0
- package/dist/commands/canvas.js +3 -3
- package/dist/commands/compat.js +1 -1
- package/dist/commands/context.js +4 -4
- package/dist/commands/doctor.js +16 -309
- package/dist/commands/embed.d.ts +17 -0
- package/dist/commands/embed.js +10 -0
- package/dist/commands/migrate-observations.js +2 -2
- package/dist/commands/observe.d.ts +1 -0
- package/dist/commands/observe.js +7 -6
- package/dist/commands/rebuild.js +5 -5
- package/dist/commands/reflect.js +3 -3
- package/dist/commands/replay.js +7 -7
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +2 -2
- package/dist/commands/sleep.d.ts +2 -1
- package/dist/commands/sleep.js +15 -15
- package/dist/commands/status.d.ts +9 -1
- package/dist/commands/status.js +33 -8
- package/dist/commands/wake.d.ts +1 -1
- package/dist/commands/wake.js +6 -6
- package/dist/index.d.ts +82 -5
- package/dist/index.js +127 -105
- package/dist/{types-jjuYN2Xn.d.ts → types-C74wgGL1.d.ts} +2 -0
- package/hooks/clawvault/HOOK.md +83 -74
- package/hooks/clawvault/handler.js +816 -812
- package/hooks/clawvault/handler.test.js +263 -263
- package/package.json +94 -125
- package/templates/checkpoint.md +19 -19
- package/templates/daily-note.md +19 -19
- package/templates/daily.md +19 -19
- package/templates/decision.md +17 -17
- package/templates/handoff.md +19 -19
- package/templates/lesson.md +16 -16
- package/templates/person.md +19 -19
- package/templates/project.md +23 -23
- package/dist/chunk-2RK2AG32.js +0 -743
- package/dist/{chunk-FW465EEA.js → chunk-VXEOHTSL.js} +3 -3
- package/dist/{chunk-KCCHROBR.js → chunk-YOSEUUNB.js} +4 -4
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSessionFile
|
|
3
|
+
} from "./chunk-P5EPF6MB.js";
|
|
4
|
+
import {
|
|
5
|
+
observeActiveSessions
|
|
6
|
+
} from "./chunk-IZEY5S74.js";
|
|
7
|
+
import {
|
|
8
|
+
Observer
|
|
9
|
+
} from "./chunk-S2IG7VNM.js";
|
|
10
|
+
import {
|
|
11
|
+
resolveVaultPath
|
|
12
|
+
} from "./chunk-MXSSG3QU.js";
|
|
13
|
+
import {
|
|
14
|
+
getObservationPath
|
|
15
|
+
} from "./chunk-Z2XBWN7A.js";
|
|
16
|
+
|
|
17
|
+
// src/commands/observe.ts
|
|
18
|
+
import * as fs2 from "fs";
|
|
19
|
+
import * as path2 from "path";
|
|
20
|
+
import { spawn } from "child_process";
|
|
21
|
+
|
|
22
|
+
// src/observer/watcher.ts
|
|
23
|
+
import * as fs from "fs";
|
|
24
|
+
import * as path from "path";
|
|
25
|
+
import chokidar from "chokidar";
|
|
26
|
+
var DEFAULT_FLUSH_THRESHOLD_CHARS = 500;
|
|
27
|
+
var SessionWatcher = class {
|
|
28
|
+
watchPath;
|
|
29
|
+
observer;
|
|
30
|
+
ignoreInitial;
|
|
31
|
+
debounceMs;
|
|
32
|
+
flushThresholdChars;
|
|
33
|
+
watcher = null;
|
|
34
|
+
fileOffsets = /* @__PURE__ */ new Map();
|
|
35
|
+
pendingPaths = /* @__PURE__ */ new Set();
|
|
36
|
+
debounceTimer = null;
|
|
37
|
+
processingQueue = Promise.resolve();
|
|
38
|
+
bufferedChars = 0;
|
|
39
|
+
constructor(watchPath, observer, options = {}) {
|
|
40
|
+
this.watchPath = path.resolve(watchPath);
|
|
41
|
+
this.observer = observer;
|
|
42
|
+
this.ignoreInitial = options.ignoreInitial ?? false;
|
|
43
|
+
this.debounceMs = options.debounceMs ?? 500;
|
|
44
|
+
this.flushThresholdChars = Math.max(1, options.flushThresholdChars ?? DEFAULT_FLUSH_THRESHOLD_CHARS);
|
|
45
|
+
}
|
|
46
|
+
async start() {
|
|
47
|
+
if (!fs.existsSync(this.watchPath)) {
|
|
48
|
+
throw new Error(`Watch path does not exist: ${this.watchPath}`);
|
|
49
|
+
}
|
|
50
|
+
this.watcher = chokidar.watch(this.watchPath, {
|
|
51
|
+
persistent: true,
|
|
52
|
+
ignoreInitial: this.ignoreInitial,
|
|
53
|
+
awaitWriteFinish: {
|
|
54
|
+
stabilityThreshold: 120,
|
|
55
|
+
pollInterval: 30
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const enqueue = (changedPath) => {
|
|
59
|
+
this.pendingPaths.add(path.resolve(changedPath));
|
|
60
|
+
this.scheduleDrain();
|
|
61
|
+
};
|
|
62
|
+
this.watcher.on("add", enqueue);
|
|
63
|
+
this.watcher.on("change", enqueue);
|
|
64
|
+
this.watcher.on("unlink", (deletedPath) => {
|
|
65
|
+
const resolved = path.resolve(deletedPath);
|
|
66
|
+
this.fileOffsets.delete(resolved);
|
|
67
|
+
this.pendingPaths.delete(resolved);
|
|
68
|
+
});
|
|
69
|
+
await new Promise((resolve3, reject) => {
|
|
70
|
+
this.watcher?.once("ready", () => resolve3());
|
|
71
|
+
this.watcher?.once("error", (error) => reject(error));
|
|
72
|
+
});
|
|
73
|
+
if (this.ignoreInitial) {
|
|
74
|
+
this.primeInitialOffsets();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async stop() {
|
|
78
|
+
if (this.debounceTimer) {
|
|
79
|
+
clearTimeout(this.debounceTimer);
|
|
80
|
+
this.debounceTimer = null;
|
|
81
|
+
this.drainPendingPaths();
|
|
82
|
+
}
|
|
83
|
+
await this.processingQueue.catch(() => void 0);
|
|
84
|
+
if (this.bufferedChars > 0) {
|
|
85
|
+
await this.observer.flush();
|
|
86
|
+
this.bufferedChars = 0;
|
|
87
|
+
}
|
|
88
|
+
this.pendingPaths.clear();
|
|
89
|
+
await this.watcher?.close();
|
|
90
|
+
this.watcher = null;
|
|
91
|
+
}
|
|
92
|
+
scheduleDrain() {
|
|
93
|
+
if (this.debounceTimer) {
|
|
94
|
+
clearTimeout(this.debounceTimer);
|
|
95
|
+
}
|
|
96
|
+
this.debounceTimer = setTimeout(() => {
|
|
97
|
+
this.debounceTimer = null;
|
|
98
|
+
this.drainPendingPaths();
|
|
99
|
+
}, this.debounceMs);
|
|
100
|
+
}
|
|
101
|
+
drainPendingPaths() {
|
|
102
|
+
const nextPaths = [...this.pendingPaths];
|
|
103
|
+
this.pendingPaths.clear();
|
|
104
|
+
for (const changedPath of nextPaths) {
|
|
105
|
+
this.processingQueue = this.processingQueue.then(() => this.consumeFile(changedPath)).catch(() => void 0);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async consumeFile(filePath) {
|
|
109
|
+
const resolved = path.resolve(filePath);
|
|
110
|
+
if (!fs.existsSync(resolved)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const stats = fs.statSync(resolved);
|
|
114
|
+
if (!stats.isFile()) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const previousOffset = this.fileOffsets.get(resolved) ?? 0;
|
|
118
|
+
const startOffset = stats.size < previousOffset ? 0 : previousOffset;
|
|
119
|
+
if (stats.size <= startOffset) {
|
|
120
|
+
this.fileOffsets.set(resolved, stats.size);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const bytesToRead = stats.size - startOffset;
|
|
124
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
125
|
+
const fd = fs.openSync(resolved, "r");
|
|
126
|
+
try {
|
|
127
|
+
fs.readSync(fd, buffer, 0, bytesToRead, startOffset);
|
|
128
|
+
} finally {
|
|
129
|
+
fs.closeSync(fd);
|
|
130
|
+
}
|
|
131
|
+
this.fileOffsets.set(resolved, stats.size);
|
|
132
|
+
const chunk = buffer.toString("utf-8");
|
|
133
|
+
const messages = chunk.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
134
|
+
if (messages.length === 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
await this.observer.processMessages(messages);
|
|
138
|
+
this.bufferedChars += chunk.length;
|
|
139
|
+
if (this.bufferedChars >= this.flushThresholdChars) {
|
|
140
|
+
await this.observer.flush();
|
|
141
|
+
this.bufferedChars = 0;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
primeInitialOffsets() {
|
|
145
|
+
for (const filePath of this.collectFiles(this.watchPath)) {
|
|
146
|
+
try {
|
|
147
|
+
const stats = fs.statSync(filePath);
|
|
148
|
+
if (stats.isFile()) {
|
|
149
|
+
this.fileOffsets.set(filePath, stats.size);
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
collectFiles(targetPath) {
|
|
156
|
+
if (!fs.existsSync(targetPath)) {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
const resolved = path.resolve(targetPath);
|
|
160
|
+
const stats = fs.statSync(resolved);
|
|
161
|
+
if (stats.isFile()) {
|
|
162
|
+
return [resolved];
|
|
163
|
+
}
|
|
164
|
+
if (!stats.isDirectory()) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
const collected = [];
|
|
168
|
+
for (const entry of fs.readdirSync(resolved, { withFileTypes: true })) {
|
|
169
|
+
const childPath = path.join(resolved, entry.name);
|
|
170
|
+
if (entry.isDirectory()) {
|
|
171
|
+
collected.push(...this.collectFiles(childPath));
|
|
172
|
+
} else if (entry.isFile()) {
|
|
173
|
+
collected.push(path.resolve(childPath));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return collected;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// src/commands/observe.ts
|
|
181
|
+
var ONE_KIB = 1024;
|
|
182
|
+
var ONE_MIB = ONE_KIB * ONE_KIB;
|
|
183
|
+
function parsePositiveInteger(raw, optionName) {
|
|
184
|
+
const parsed = Number.parseInt(raw, 10);
|
|
185
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
186
|
+
throw new Error(`Invalid ${optionName}: ${raw}`);
|
|
187
|
+
}
|
|
188
|
+
return parsed;
|
|
189
|
+
}
|
|
190
|
+
function buildDaemonArgs(options) {
|
|
191
|
+
const cliPath = process.argv[1];
|
|
192
|
+
if (!cliPath) {
|
|
193
|
+
throw new Error("Unable to resolve CLI script path for daemon mode.");
|
|
194
|
+
}
|
|
195
|
+
const args = [cliPath, "observe"];
|
|
196
|
+
if (options.watch) {
|
|
197
|
+
args.push("--watch", options.watch);
|
|
198
|
+
}
|
|
199
|
+
if (options.threshold) {
|
|
200
|
+
args.push("--threshold", String(options.threshold));
|
|
201
|
+
}
|
|
202
|
+
if (options.reflectThreshold) {
|
|
203
|
+
args.push("--reflect-threshold", String(options.reflectThreshold));
|
|
204
|
+
}
|
|
205
|
+
if (options.model) {
|
|
206
|
+
args.push("--model", options.model);
|
|
207
|
+
}
|
|
208
|
+
if (options.extractTasks === false) {
|
|
209
|
+
args.push("--no-extract-tasks");
|
|
210
|
+
}
|
|
211
|
+
if (options.vaultPath) {
|
|
212
|
+
args.push("--vault", options.vaultPath);
|
|
213
|
+
}
|
|
214
|
+
return args;
|
|
215
|
+
}
|
|
216
|
+
function formatByteSummary(bytes) {
|
|
217
|
+
const normalized = Number.isFinite(bytes) ? Math.max(0, bytes) : 0;
|
|
218
|
+
if (normalized === 0) {
|
|
219
|
+
return "0KB";
|
|
220
|
+
}
|
|
221
|
+
if (normalized >= ONE_MIB) {
|
|
222
|
+
return `${(normalized / ONE_MIB).toFixed(1)}MB`;
|
|
223
|
+
}
|
|
224
|
+
return `${Math.max(1, Math.round(normalized / ONE_KIB))}KB`;
|
|
225
|
+
}
|
|
226
|
+
function formatCronSummary(result) {
|
|
227
|
+
const decisionCount = result.routedCounts.decisions ?? 0;
|
|
228
|
+
return `observed ${result.observedSessions} sessions, ${formatByteSummary(result.observedNewBytes)} new content, ${decisionCount} decision${decisionCount === 1 ? "" : "s"} extracted`;
|
|
229
|
+
}
|
|
230
|
+
async function runOneShotCompression(observer, sourceFile, vaultPath) {
|
|
231
|
+
const resolved = path2.resolve(sourceFile);
|
|
232
|
+
if (!fs2.existsSync(resolved) || !fs2.statSync(resolved).isFile()) {
|
|
233
|
+
throw new Error(`Conversation file not found: ${resolved}`);
|
|
234
|
+
}
|
|
235
|
+
const messages = parseSessionFile(resolved);
|
|
236
|
+
const transcriptStat = fs2.statSync(resolved);
|
|
237
|
+
await observer.processMessages(messages, {
|
|
238
|
+
source: "openclaw",
|
|
239
|
+
transcriptId: path2.basename(resolved),
|
|
240
|
+
timestamp: transcriptStat.mtime
|
|
241
|
+
});
|
|
242
|
+
const { observations, routingSummary } = await observer.flush();
|
|
243
|
+
const outputPath = getObservationPath(vaultPath, /* @__PURE__ */ new Date());
|
|
244
|
+
console.log(`Observations updated: ${outputPath}`);
|
|
245
|
+
if (routingSummary) {
|
|
246
|
+
console.log(routingSummary);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async function watchSessions(observer, watchPath) {
|
|
250
|
+
const watcher = new SessionWatcher(watchPath, observer);
|
|
251
|
+
await watcher.start();
|
|
252
|
+
console.log(`Watching session updates: ${watchPath}`);
|
|
253
|
+
await new Promise((resolve3) => {
|
|
254
|
+
const shutdown = async () => {
|
|
255
|
+
process.off("SIGINT", onSigInt);
|
|
256
|
+
process.off("SIGTERM", onSigTerm);
|
|
257
|
+
await watcher.stop();
|
|
258
|
+
resolve3();
|
|
259
|
+
};
|
|
260
|
+
const onSigInt = () => {
|
|
261
|
+
void shutdown();
|
|
262
|
+
};
|
|
263
|
+
const onSigTerm = () => {
|
|
264
|
+
void shutdown();
|
|
265
|
+
};
|
|
266
|
+
process.once("SIGINT", onSigInt);
|
|
267
|
+
process.once("SIGTERM", onSigTerm);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async function observeCommand(options) {
|
|
271
|
+
if (options.cron && (options.active || options.watch || options.compress || options.daemon)) {
|
|
272
|
+
throw new Error("--cron cannot be combined with --active, --watch, --compress, or --daemon.");
|
|
273
|
+
}
|
|
274
|
+
if (options.cron && options.dryRun) {
|
|
275
|
+
throw new Error("--cron cannot be combined with --dry-run.");
|
|
276
|
+
}
|
|
277
|
+
if (options.active && (options.watch || options.compress || options.daemon)) {
|
|
278
|
+
throw new Error("--active cannot be combined with --watch, --compress, or --daemon.");
|
|
279
|
+
}
|
|
280
|
+
if (options.compress && options.daemon) {
|
|
281
|
+
throw new Error("--compress cannot be combined with --daemon.");
|
|
282
|
+
}
|
|
283
|
+
const vaultPath = resolveVaultPath({ explicitPath: options.vaultPath });
|
|
284
|
+
if (options.active || options.cron) {
|
|
285
|
+
const result = await observeActiveSessions({
|
|
286
|
+
vaultPath,
|
|
287
|
+
agentId: options.agent,
|
|
288
|
+
minNewBytes: options.minNew,
|
|
289
|
+
sessionsDir: options.sessionsDir,
|
|
290
|
+
dryRun: options.dryRun,
|
|
291
|
+
threshold: options.threshold,
|
|
292
|
+
reflectThreshold: options.reflectThreshold,
|
|
293
|
+
model: options.model,
|
|
294
|
+
extractTasks: options.extractTasks
|
|
295
|
+
});
|
|
296
|
+
const failedSessionCount = result.failedSessionCount ?? 0;
|
|
297
|
+
if (options.cron) {
|
|
298
|
+
if (failedSessionCount > 0) {
|
|
299
|
+
const firstFailure = result.failedSessions[0];
|
|
300
|
+
if (firstFailure) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`observer failed for ${failedSessionCount} session(s); first error: ${firstFailure.sessionKey} - ${firstFailure.error}`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
throw new Error(`observer failed for ${failedSessionCount} session(s).`);
|
|
306
|
+
}
|
|
307
|
+
if (result.candidateSessions === 0) {
|
|
308
|
+
console.log("nothing new");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
console.log(formatCronSummary({
|
|
312
|
+
observedSessions: result.observedSessions,
|
|
313
|
+
observedNewBytes: result.observedNewBytes ?? result.totalNewBytes,
|
|
314
|
+
routedCounts: result.routedCounts ?? {}
|
|
315
|
+
}));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (result.candidateSessions === 0) {
|
|
319
|
+
console.log(`No active sessions crossed threshold (${result.checkedSessions} checked).`);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (result.dryRun) {
|
|
323
|
+
console.log(
|
|
324
|
+
`Dry run: ${result.candidateSessions} session(s) would be observed (${result.totalNewBytes} new bytes).`
|
|
325
|
+
);
|
|
326
|
+
for (const candidate of result.candidates) {
|
|
327
|
+
console.log(
|
|
328
|
+
`- ${candidate.sessionKey} [${candidate.sourceLabel}] \u0394${candidate.newBytes}B (threshold ${candidate.thresholdBytes}B)`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
console.log(
|
|
334
|
+
`Active observation complete: ${result.observedSessions}/${result.candidateSessions} session(s) observed.${failedSessionCount > 0 ? ` ${failedSessionCount} failed.` : ""}`
|
|
335
|
+
);
|
|
336
|
+
if (failedSessionCount > 0) {
|
|
337
|
+
for (const failure of result.failedSessions) {
|
|
338
|
+
console.error(
|
|
339
|
+
`[observer] session failed ${failure.sessionKey} (${failure.sessionId}): ${failure.error}`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const observer = new Observer(vaultPath, {
|
|
346
|
+
tokenThreshold: options.threshold,
|
|
347
|
+
reflectThreshold: options.reflectThreshold,
|
|
348
|
+
model: options.model,
|
|
349
|
+
extractTasks: options.extractTasks
|
|
350
|
+
});
|
|
351
|
+
if (options.compress) {
|
|
352
|
+
await runOneShotCompression(observer, options.compress, vaultPath);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
let watchPath = options.watch ? path2.resolve(options.watch) : "";
|
|
356
|
+
if (!watchPath && options.daemon) {
|
|
357
|
+
watchPath = path2.join(vaultPath, "sessions");
|
|
358
|
+
}
|
|
359
|
+
if (!watchPath) {
|
|
360
|
+
throw new Error("Either --watch or --compress must be provided.");
|
|
361
|
+
}
|
|
362
|
+
if (!fs2.existsSync(watchPath)) {
|
|
363
|
+
if (options.daemon && !options.watch) {
|
|
364
|
+
fs2.mkdirSync(watchPath, { recursive: true });
|
|
365
|
+
} else {
|
|
366
|
+
throw new Error(`Watch path does not exist: ${watchPath}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (options.daemon) {
|
|
370
|
+
const daemonArgs = buildDaemonArgs({ ...options, watch: watchPath, vaultPath });
|
|
371
|
+
const child = spawn(process.execPath, daemonArgs, {
|
|
372
|
+
detached: true,
|
|
373
|
+
stdio: "ignore"
|
|
374
|
+
});
|
|
375
|
+
child.unref();
|
|
376
|
+
console.log(`Observer daemon started (pid: ${child.pid})`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
await watchSessions(observer, watchPath);
|
|
380
|
+
}
|
|
381
|
+
function registerObserveCommand(program) {
|
|
382
|
+
program.command("observe").description("Observe session files and build observational memory").option("--watch <path>", "Watch session file or directory").option("--active", "Observe active OpenClaw sessions incrementally").option("--cron", "Run one-shot active observation for cron hooks").option("--agent <id>", "OpenClaw agent ID (default: OPENCLAW_AGENT_ID or clawdious)").option("--min-new <bytes>", "Override minimum new-content threshold in bytes").option("--sessions-dir <path>", "Override OpenClaw sessions directory").option("--dry-run", "Show active observation candidates without compressing").option("--threshold <n>", "Compression token threshold", "30000").option("--reflect-threshold <n>", "Reflection token threshold", "40000").option("--model <model>", "LLM model override").option("--extract-tasks", "Extract task-like observations into backlog", true).option("--no-extract-tasks", "Disable task extraction from observations").option("--compress <file>", "One-shot compression for a conversation file").option("--daemon", "Run in detached background mode").option("-v, --vault <path>", "Vault path").action(async (rawOptions) => {
|
|
383
|
+
await observeCommand({
|
|
384
|
+
watch: rawOptions.watch,
|
|
385
|
+
active: rawOptions.active,
|
|
386
|
+
cron: rawOptions.cron,
|
|
387
|
+
agent: rawOptions.agent,
|
|
388
|
+
minNew: rawOptions.minNew ? parsePositiveInteger(rawOptions.minNew, "min-new") : void 0,
|
|
389
|
+
sessionsDir: rawOptions.sessionsDir,
|
|
390
|
+
dryRun: rawOptions.dryRun,
|
|
391
|
+
threshold: parsePositiveInteger(rawOptions.threshold, "threshold"),
|
|
392
|
+
reflectThreshold: parsePositiveInteger(rawOptions.reflectThreshold, "reflect-threshold"),
|
|
393
|
+
model: rawOptions.model,
|
|
394
|
+
extractTasks: rawOptions.extractTasks,
|
|
395
|
+
compress: rawOptions.compress,
|
|
396
|
+
daemon: rawOptions.daemon,
|
|
397
|
+
vaultPath: rawOptions.vault
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export {
|
|
403
|
+
SessionWatcher,
|
|
404
|
+
observeCommand,
|
|
405
|
+
registerObserveCommand
|
|
406
|
+
};
|
|
@@ -14,6 +14,16 @@ function readOptionalFile(filePath) {
|
|
|
14
14
|
return null;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
function findPackageRoot() {
|
|
18
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
while (dir !== path.dirname(dir)) {
|
|
20
|
+
if (fs.existsSync(path.join(dir, "package.json"))) {
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
dir = path.dirname(dir);
|
|
24
|
+
}
|
|
25
|
+
return path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
}
|
|
17
27
|
function resolveProjectFile(relativePath, baseDir) {
|
|
18
28
|
if (baseDir) {
|
|
19
29
|
return path.resolve(baseDir, relativePath);
|
|
@@ -22,7 +32,7 @@ function resolveProjectFile(relativePath, baseDir) {
|
|
|
22
32
|
if (fs.existsSync(fromCwd)) {
|
|
23
33
|
return fromCwd;
|
|
24
34
|
}
|
|
25
|
-
return
|
|
35
|
+
return path.resolve(findPackageRoot(), relativePath);
|
|
26
36
|
}
|
|
27
37
|
function checkOpenClawCli() {
|
|
28
38
|
const result = spawnSync("openclaw", ["--version"], { stdio: "ignore" });
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
listProjects
|
|
3
3
|
} from "./chunk-5GZFTAL7.js";
|
|
4
|
-
import {
|
|
5
|
-
listConfig,
|
|
6
|
-
listRouteRules,
|
|
7
|
-
matchRouteRule
|
|
8
|
-
} from "./chunk-ITPEXLHA.js";
|
|
9
|
-
import {
|
|
10
|
-
createBacklogItem,
|
|
11
|
-
listBacklogItems,
|
|
12
|
-
listTasks,
|
|
13
|
-
updateBacklogItem,
|
|
14
|
-
updateTask
|
|
15
|
-
} from "./chunk-IOALNTAN.js";
|
|
16
4
|
import {
|
|
17
5
|
DATE_HEADING_RE,
|
|
18
6
|
inferObservationType,
|
|
@@ -22,6 +10,11 @@ import {
|
|
|
22
10
|
renderObservationMarkdown,
|
|
23
11
|
renderScoredObservationLine
|
|
24
12
|
} from "./chunk-FHFUXL6G.js";
|
|
13
|
+
import {
|
|
14
|
+
listConfig,
|
|
15
|
+
listRouteRules,
|
|
16
|
+
matchRouteRule
|
|
17
|
+
} from "./chunk-ITPEXLHA.js";
|
|
25
18
|
import {
|
|
26
19
|
ensureLedgerStructure,
|
|
27
20
|
ensureParentDir,
|
|
@@ -30,6 +23,13 @@ import {
|
|
|
30
23
|
getRawTranscriptPath,
|
|
31
24
|
toDateKey
|
|
32
25
|
} from "./chunk-Z2XBWN7A.js";
|
|
26
|
+
import {
|
|
27
|
+
createBacklogItem,
|
|
28
|
+
listBacklogItems,
|
|
29
|
+
listTasks,
|
|
30
|
+
updateBacklogItem,
|
|
31
|
+
updateTask
|
|
32
|
+
} from "./chunk-IOALNTAN.js";
|
|
33
33
|
|
|
34
34
|
// src/observer/compressor.ts
|
|
35
35
|
var OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
@@ -759,6 +759,8 @@ var TYPE_TO_CATEGORY = {
|
|
|
759
759
|
relationship: "people",
|
|
760
760
|
project: "projects"
|
|
761
761
|
};
|
|
762
|
+
var PAST_TENSE_TASK_HINT_RE = /\b(completed|shipped|deployed|fixed|merged|finished|resolved|closed)\b/i;
|
|
763
|
+
var FUTURE_TASK_HINT_RE = /\b(need to|should|todo|must|plan to)\b/i;
|
|
762
764
|
var Router = class {
|
|
763
765
|
vaultPath;
|
|
764
766
|
extractTasks;
|
|
@@ -815,6 +817,10 @@ var Router = class {
|
|
|
815
817
|
return type === "task" || type === "todo" || type === "commitment-unresolved";
|
|
816
818
|
}
|
|
817
819
|
routeTaskObservation(item, context, knownWorkItems) {
|
|
820
|
+
if (this.shouldSkipCompletedTaskCandidate(item.content)) {
|
|
821
|
+
console.log("[observer] skipped likely-completed task candidate");
|
|
822
|
+
return { routedItem: null, dedupHit: false };
|
|
823
|
+
}
|
|
818
824
|
const title = this.deriveTaskTitle(item.content, item.type);
|
|
819
825
|
if (!title) {
|
|
820
826
|
return { routedItem: null, dedupHit: false };
|
|
@@ -943,6 +949,12 @@ var Router = class {
|
|
|
943
949
|
title = title.replace(/\s+/g, " ").replace(/^[^a-zA-Z0-9]+/, "").replace(/[.?!:;,]+$/, "").trim();
|
|
944
950
|
return title.slice(0, 120);
|
|
945
951
|
}
|
|
952
|
+
shouldSkipCompletedTaskCandidate(content) {
|
|
953
|
+
if (!PAST_TENSE_TASK_HINT_RE.test(content)) {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
return !FUTURE_TASK_HINT_RE.test(content);
|
|
957
|
+
}
|
|
946
958
|
buildTaskContextContent(item, context) {
|
|
947
959
|
const lines = ["Auto-extracted by observer from session transcript."];
|
|
948
960
|
if (context.sessionKey) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
registerCliCommands
|
|
3
|
+
} from "../chunk-FG6RJMCN.js";
|
|
4
|
+
import "../chunk-NZ4ZZNSR.js";
|
|
5
|
+
import "../chunk-4GBPTBFJ.js";
|
|
6
|
+
import "../chunk-CLE2HHNT.js";
|
|
7
|
+
import "../chunk-OSMS7QIG.js";
|
|
8
|
+
import "../chunk-P5EPF6MB.js";
|
|
9
|
+
import "../chunk-2YDBJS7M.js";
|
|
10
|
+
import "../chunk-YOSEUUNB.js";
|
|
11
|
+
import "../chunk-GFJ3LIIB.js";
|
|
12
|
+
import "../chunk-IZEY5S74.js";
|
|
13
|
+
import "../chunk-HRLWZGMA.js";
|
|
14
|
+
import "../chunk-S2IG7VNM.js";
|
|
15
|
+
import "../chunk-5GZFTAL7.js";
|
|
16
|
+
import "../chunk-AY4PGUVL.js";
|
|
17
|
+
import "../chunk-FHFUXL6G.js";
|
|
18
|
+
import "../chunk-3FP5BJ42.js";
|
|
19
|
+
import "../chunk-O7XHXF7F.js";
|
|
20
|
+
import "../chunk-GSD4ALSI.js";
|
|
21
|
+
import "../chunk-K3CDT7IH.js";
|
|
22
|
+
import "../chunk-ITPEXLHA.js";
|
|
23
|
+
import "../chunk-2CDEETQN.js";
|
|
24
|
+
import "../chunk-MQUJNOHK.js";
|
|
25
|
+
import "../chunk-MXSSG3QU.js";
|
|
26
|
+
import "../chunk-ZZA73MFY.js";
|
|
27
|
+
import "../chunk-Z2XBWN7A.js";
|
|
28
|
+
import "../chunk-IOALNTAN.js";
|
|
29
|
+
export {
|
|
30
|
+
registerCliCommands
|
|
31
|
+
};
|
package/dist/commands/canvas.js
CHANGED
|
@@ -7,15 +7,15 @@ import {
|
|
|
7
7
|
positionGroupsVertically,
|
|
8
8
|
truncateText
|
|
9
9
|
} from "../chunk-MDIH26GC.js";
|
|
10
|
-
import {
|
|
11
|
-
listTasks
|
|
12
|
-
} from "../chunk-IOALNTAN.js";
|
|
13
10
|
import {
|
|
14
11
|
loadMemoryGraphIndex
|
|
15
12
|
} from "../chunk-ZZA73MFY.js";
|
|
16
13
|
import {
|
|
17
14
|
listObservationFiles
|
|
18
15
|
} from "../chunk-Z2XBWN7A.js";
|
|
16
|
+
import {
|
|
17
|
+
listTasks
|
|
18
|
+
} from "../chunk-IOALNTAN.js";
|
|
19
19
|
|
|
20
20
|
// src/commands/canvas.ts
|
|
21
21
|
import * as fs2 from "fs";
|
package/dist/commands/compat.js
CHANGED
package/dist/commands/context.js
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
contextCommand,
|
|
4
4
|
formatContextMarkdown,
|
|
5
5
|
registerContextCommand
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import "../chunk-
|
|
8
|
-
import "../chunk-6B3JWM7J.js";
|
|
9
|
-
import "../chunk-2CDEETQN.js";
|
|
6
|
+
} from "../chunk-GFJ3LIIB.js";
|
|
7
|
+
import "../chunk-AY4PGUVL.js";
|
|
10
8
|
import "../chunk-FHFUXL6G.js";
|
|
9
|
+
import "../chunk-O7XHXF7F.js";
|
|
10
|
+
import "../chunk-2CDEETQN.js";
|
|
11
11
|
import "../chunk-ZZA73MFY.js";
|
|
12
12
|
import "../chunk-Z2XBWN7A.js";
|
|
13
13
|
export {
|