@inetafrica/open-claudia 2.6.8 → 2.6.10
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/CHANGELOG.md +3 -0
- package/core/entities.js +3 -1
- package/core/pack-review.js +1 -0
- package/core/packs.js +3 -1
- package/core/scheduler.js +4 -1
- package/core/system-prompt.js +46 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.6.10
|
|
4
|
+
- **Sessions: wakeups join the live conversation instead of forking it.** A scheduled wakeup/cron captured the channel's `sessionId` at schedule time and resumed that *frozen* id when it fired — but every run writes its result id back to the shared `state.lastSessionId`, and a home-conversation compaction mints a brand-new id. So a pending wakeup carrying a pre-compaction id would yank the channel's live pointer onto a dead thread, and the user's next typed message landed on the stale session. Multiple concurrent wakeups froze different ids and forked the channel into parallel sessions. Fix (`core/scheduler.js`): a wakeup now resumes the channel's live `state.lastSessionId` (already ownership-guarded in the runner); the frozen `job.sessionId` is used only as a cold-start fallback when there is no live pointer. Wakeups scheduled after upgrade join the current context; already-pending wakeups still carry their old id until they fire.
|
|
5
|
+
|
|
3
6
|
## v2.6.8
|
|
4
7
|
- **Tasks: a plan can't be completed while subtasks are still open.** `task done` (and the loopback task-update path) now refuses to flip a parent to completed if any child is not yet completed — it returns a blocked result listing the open children, and the CLI prints a clean "Can't complete <id>: N subtask(s) still open" message instead of silently corrupting the tree. Standalone tasks and completing the last open child (which removes the whole plan) are unchanged. Prevents the inflated-count drift where parents showed done over still-open children.
|
|
5
8
|
|
package/core/entities.js
CHANGED
|
@@ -287,7 +287,9 @@ function queryTerms(text) {
|
|
|
287
287
|
const terms = [];
|
|
288
288
|
for (const raw of String(text || "").toLowerCase().split(/[^a-z0-9_.-]+/)) {
|
|
289
289
|
const t = raw.replace(/^[.-]+|[.-]+$/g, "");
|
|
290
|
-
|
|
290
|
+
// 2-char floor (not 3): keep technical acronyms like ip/ci/ha/ws/db/s3;
|
|
291
|
+
// common 2-letter words (is/it/in/of/on...) are caught by STOPWORDS.
|
|
292
|
+
if (t.length < 2 || STOPWORDS.has(t) || seen.has(t)) continue;
|
|
291
293
|
seen.add(t);
|
|
292
294
|
terms.push(t);
|
|
293
295
|
if (terms.length >= 40) break;
|
package/core/pack-review.js
CHANGED
|
@@ -90,6 +90,7 @@ ${clip(assistantText)}
|
|
|
90
90
|
Decide. Rules:
|
|
91
91
|
- Bias toward action: if the turn did real work on an identifiable topic (a named system, project, server, app, person, or domain), record it — at minimum a journal line. Most working turns deserve one. Reserve empty actions for small talk, pure status checks, and turns that contain nothing new.
|
|
92
92
|
- UPDATE an existing pack when the turn touched that topic: append a journal line, and rewrite State if where-things-stand changed. Update Stance when the user expressed a lasting preference or rule; Procedure when a verified working method emerged.
|
|
93
|
+
- PROMOTE durable conclusions out of the Journal. A confirmed root-cause diagnosis, an established fact, or a settled design decision must go into Stance/Procedure/State (the parts always injected in full), NOT only a Journal line — Journal is truncated to the last few lines on injection, so a conclusion left only there will silently fall out of recall over time.
|
|
93
94
|
- CREATE a pack when the turn worked on a durable topic no existing pack covers. Topics recur more than you expect — a named system or project is durable by default. Do not create packs for one-off trivia, and prefer update when an existing pack fits.
|
|
94
95
|
- Never store secrets, tokens, passwords, or credentials.
|
|
95
96
|
- State should be concise (under 150 words). Journal entries one sentence. Never start a journal or log sentence with a date — dates are prepended automatically.
|
package/core/packs.js
CHANGED
|
@@ -287,7 +287,9 @@ function queryTerms(text) {
|
|
|
287
287
|
const terms = [];
|
|
288
288
|
for (const raw of String(text || "").toLowerCase().split(/[^a-z0-9_.-]+/)) {
|
|
289
289
|
const t = raw.replace(/^[.-]+|[.-]+$/g, "");
|
|
290
|
-
|
|
290
|
+
// 2-char floor (not 3): keep technical acronyms like ip/ci/ha/ws/db/s3;
|
|
291
|
+
// common 2-letter words (is/it/in/of/on...) are caught by STOPWORDS.
|
|
292
|
+
if (t.length < 2 || STOPWORDS.has(t) || seen.has(t)) continue;
|
|
291
293
|
seen.add(t);
|
|
292
294
|
terms.push(t);
|
|
293
295
|
if (terms.length >= 40) break;
|
package/core/scheduler.js
CHANGED
|
@@ -106,7 +106,10 @@ async function fireJob(job, retry = 0) {
|
|
|
106
106
|
|
|
107
107
|
const { runClaude } = require("./runner");
|
|
108
108
|
const runOpts = {};
|
|
109
|
-
|
|
109
|
+
// Join the channel's live conversation. Only fall back to the wakeup's
|
|
110
|
+
// frozen sessionId when there's no live pointer (cold start) — otherwise
|
|
111
|
+
// a stale, compacted-away id would yank the channel onto a dead thread.
|
|
112
|
+
if (!state.lastSessionId && job.sessionId) runOpts.resumeSessionId = job.sessionId;
|
|
110
113
|
try {
|
|
111
114
|
await runClaude(wrappedPrompt, cwd, null, runOpts);
|
|
112
115
|
jobs.update(job.id, { lastFireAt: Date.now(), lastFireOk: true });
|
package/core/system-prompt.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const { SOUL_FILE, CRONS_FILE, VAULT_FILE, FILES_DIR, BOT_DIR, WHISPER_CLI, FFMPEG } = require("./config");
|
|
8
|
+
const CONFIG_DIR = require("../config-dir");
|
|
8
9
|
const { currentState } = require("./state");
|
|
9
10
|
const { currentAdapter, currentChannelId } = require("./context");
|
|
10
11
|
const { vault } = require("./vault-store");
|
|
@@ -311,11 +312,27 @@ function formatPackForContext(pack, packsLib) {
|
|
|
311
312
|
const body = (pack.sections[section] || "").trim();
|
|
312
313
|
if (body) parts.push(`#### ${section}\n${body}`);
|
|
313
314
|
}
|
|
314
|
-
const journal = (pack.sections.Journal || "").trim().split("\n").filter(Boolean).slice(-
|
|
315
|
+
const journal = (pack.sections.Journal || "").trim().split("\n").filter(Boolean).slice(-10).join("\n");
|
|
315
316
|
if (journal) parts.push(`#### Journal (recent)\n${journal}`);
|
|
316
317
|
return clip(parts.join("\n\n"), PACK_INJECT_MAX_CHARS);
|
|
317
318
|
}
|
|
318
319
|
|
|
320
|
+
// Compact anchor re-injected on later turns of the same session: the full
|
|
321
|
+
// pack was already shown once, but compaction can silently drop it from
|
|
322
|
+
// the rolling context, leaving a recall hole. A short Stance+State+latest
|
|
323
|
+
// reminder keeps the pack "present" without re-paying the full token cost.
|
|
324
|
+
function formatPackCompact(pack) {
|
|
325
|
+
const clip = (s, n) => (s.length > n ? s.slice(0, n) + "\n…[truncated]" : s);
|
|
326
|
+
const parts = [`### Pack: ${pack.name} (${pack.dir}) — recalled (full version shown earlier this session; read PACK.md for detail)`];
|
|
327
|
+
const stance = (pack.sections.Stance || "").trim();
|
|
328
|
+
if (stance) parts.push(`#### Stance\n${stance}`);
|
|
329
|
+
const state = (pack.sections.State || "").trim();
|
|
330
|
+
if (state) parts.push(`#### State\n${state}`);
|
|
331
|
+
const journal = (pack.sections.Journal || "").trim().split("\n").filter(Boolean).slice(-1).join("\n");
|
|
332
|
+
if (journal) parts.push(`#### Journal (latest)\n${journal}`);
|
|
333
|
+
return clip(parts.join("\n\n"), 1200);
|
|
334
|
+
}
|
|
335
|
+
|
|
319
336
|
function buildPackBlock(matches) {
|
|
320
337
|
try {
|
|
321
338
|
const packsLib = require("./packs");
|
|
@@ -332,9 +349,14 @@ function buildPackBlock(matches) {
|
|
|
332
349
|
used.push(m.dir);
|
|
333
350
|
const key = `${adapter?.id || "?"}:${channelId || "?"}:${m.dir}`;
|
|
334
351
|
const stamp = `${sess}:${pack.updated}`;
|
|
335
|
-
if (packsInjectedFor.get(key) === stamp) continue;
|
|
336
|
-
packsInjectedFor.set(key, stamp);
|
|
337
352
|
lastInjected.packs.push(pack.name || m.dir);
|
|
353
|
+
if (packsInjectedFor.get(key) === stamp) {
|
|
354
|
+
// Already injected the full pack this session-version; re-inject a
|
|
355
|
+
// compact anchor so compaction can't silently drop it.
|
|
356
|
+
blocks.push(formatPackCompact(pack));
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
packsInjectedFor.set(key, stamp);
|
|
338
360
|
blocks.push(formatPackForContext(pack, packsLib));
|
|
339
361
|
}
|
|
340
362
|
if (used.length > 0) setImmediate(() => { try { packsLib.touchUsed(used); } catch (e) {} });
|
|
@@ -458,6 +480,24 @@ function mergeMatches(primary, secondary, keyOf) {
|
|
|
458
480
|
return out;
|
|
459
481
|
}
|
|
460
482
|
|
|
483
|
+
// Append one JSONL line per turn recording what recall matched vs. what
|
|
484
|
+
// survived the relevance gate. This is the data we tune the match
|
|
485
|
+
// threshold with — disable via RECALL_DEBUG=off.
|
|
486
|
+
function logRecall(msg, candPacks, candEntities, keptPacks, keptEntities) {
|
|
487
|
+
if (String(process.env.RECALL_DEBUG || "on").toLowerCase() === "off") return;
|
|
488
|
+
try {
|
|
489
|
+
const rec = {
|
|
490
|
+
ts: new Date().toISOString(),
|
|
491
|
+
msg: String(msg || "").slice(0, 200),
|
|
492
|
+
candidatePacks: candPacks.map((m) => ({ dir: m.dir, score: m.score, origin: m.origin })),
|
|
493
|
+
keptPacks: keptPacks.map((m) => m.dir),
|
|
494
|
+
candidateEntities: candEntities.map((m) => ({ slug: m.slug, score: m.score, origin: m.origin })),
|
|
495
|
+
keptEntities: keptEntities.map((m) => m.slug),
|
|
496
|
+
};
|
|
497
|
+
fs.appendFileSync(path.join(CONFIG_DIR, "recall-debug.jsonl"), JSON.stringify(rec) + "\n");
|
|
498
|
+
} catch (e) {}
|
|
499
|
+
}
|
|
500
|
+
|
|
461
501
|
async function promptWithDynamicContext(prompt) {
|
|
462
502
|
lastInjected = { packs: [], entities: [] };
|
|
463
503
|
try {
|
|
@@ -485,9 +525,12 @@ async function promptWithDynamicContext(prompt) {
|
|
|
485
525
|
(m) => m.slug,
|
|
486
526
|
);
|
|
487
527
|
} catch (e) {}
|
|
528
|
+
const candPacks = packMatches;
|
|
529
|
+
const candEntities = entityMatches;
|
|
488
530
|
({ packMatches, entityMatches } = await filterMatches(userText, fullContext, packMatches, entityMatches));
|
|
489
531
|
packMatches = packMatches.slice(0, 3);
|
|
490
532
|
entityMatches = entityMatches.slice(0, 4);
|
|
533
|
+
logRecall(userText, candPacks, candEntities, packMatches, entityMatches);
|
|
491
534
|
return `${buildDynamicContextBlock()}${buildPackBlock(packMatches)}${buildEntityBlock(entityMatches)}\n\nCurrent user request:\n${prompt}`;
|
|
492
535
|
} catch (e) {
|
|
493
536
|
return prompt;
|
package/package.json
CHANGED