@inetafrica/open-claudia 3.0.25 → 3.0.27
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 +16 -0
- package/bin/agency-ledger.js +91 -0
- package/bin/cli.js +15 -0
- package/bin/prompt-budget.js +49 -0
- package/bin/recall.js +6 -0
- package/channels/spaces/adapter.js +33 -8
- package/channels/telegram/adapter.js +26 -3
- package/core/actions.js +25 -0
- package/core/agency/deliberate.js +332 -0
- package/core/agency/ledger.js +294 -0
- package/core/connected-apps.js +15 -0
- package/core/guardrail.js +251 -0
- package/core/handlers.js +41 -1
- package/core/prompt-budget.js +165 -0
- package/core/runner.js +26 -2
- package/core/state.js +16 -0
- package/core/system-prompt.js +29 -0
- package/package.json +6 -2
- package/test-agency-deliberate.js +119 -0
- package/test-agency-ledger.js +114 -0
- package/test-guardrail.js +126 -0
- package/test-prompt-budget.js +78 -0
- package/test-provider-language.js +12 -9
- package/test-telegram-poll-recovery.js +81 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// Track C — dynamic owner-guardrail capture. When the OWNER states a durable
|
|
2
|
+
// behavioural rule mid-chat ("from now on ...", "never ...", "stop ...ing"), a
|
|
3
|
+
// deterministic post-turn detector either captures it straight into lessons.md
|
|
4
|
+
// (always-loaded) or asks first, so the correction holds on the next turn with
|
|
5
|
+
// no code change and no deploy.
|
|
6
|
+
//
|
|
7
|
+
// Invariants:
|
|
8
|
+
// - Owner-only, HARD. Rules are NEVER captured from external speakers — the
|
|
9
|
+
// caller gates on relationship.guardActive(speaker) and consider() re-checks.
|
|
10
|
+
// - Advisory-tier. A captured rule layers ABOVE lessons but can never override
|
|
11
|
+
// the code-fixed hard rules (tool-first, enforcer). Safety rails stay in code.
|
|
12
|
+
// - No model call. Detection is phrase-based and default-silent; the ambiguous
|
|
13
|
+
// tier defers to the owner (a Y/N button), not to a model that could invent
|
|
14
|
+
// a rule. Clear "from now on / never / stop" phrasings auto-capture with an
|
|
15
|
+
// announce + one-tap undo; softer imperatives ask first.
|
|
16
|
+
// - Dedupe against existing lessons (normalized text) so the same rule
|
|
17
|
+
// reinforces instead of cluttering the always-on budget.
|
|
18
|
+
|
|
19
|
+
const fs = require("fs");
|
|
20
|
+
const path = require("path");
|
|
21
|
+
const crypto = require("crypto");
|
|
22
|
+
const { atomicWriteFileSync } = require("./fsutil");
|
|
23
|
+
|
|
24
|
+
const CONFIG_DIR = require("../config-dir");
|
|
25
|
+
const lessons = require("./lessons");
|
|
26
|
+
|
|
27
|
+
const PENDING_FILE = path.join(CONFIG_DIR, "guardrail-pending.json");
|
|
28
|
+
const PENDING_TTL_MS = Number(process.env.GUARDRAIL_PENDING_TTL_MS || 24 * 60 * 60 * 1000);
|
|
29
|
+
const MIN_RULE_CHARS = 10;
|
|
30
|
+
const MIN_RULE_WORDS = 3;
|
|
31
|
+
const MAX_RULE_CHARS = lessons.MAX_LESSON_CHARS || 240;
|
|
32
|
+
|
|
33
|
+
// Operability control (mirrors pack-review's PACK_REVIEW gate). Default ON —
|
|
34
|
+
// this is not a hidden kill-switch; it lets the capture path be disabled
|
|
35
|
+
// without a deploy if it ever misfires, and lets tests force it off.
|
|
36
|
+
function enabled() {
|
|
37
|
+
return String(process.env.GUARDRAIL_CAPTURE || "on").toLowerCase() !== "off";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── detection (pure, deterministic) ─────────────────────────────────
|
|
41
|
+
|
|
42
|
+
// Explicit durable markers → high confidence (auto-capture). Matched anywhere.
|
|
43
|
+
const HIGH_MARKERS = [
|
|
44
|
+
/\bfrom now on\b/i,
|
|
45
|
+
/\bfrom here on(?: out)?\b/i,
|
|
46
|
+
/\bgoing forward\b/i,
|
|
47
|
+
/\bin (?:the )?future\b/i,
|
|
48
|
+
/\bnever again\b/i,
|
|
49
|
+
/\bmake (?:it|this|that) a (?:standing )?rule\b/i,
|
|
50
|
+
/\bas a (?:standing )?rule\b/i,
|
|
51
|
+
/\balways remember to\b/i,
|
|
52
|
+
];
|
|
53
|
+
// Clear negative directive at the start of a (cleaned) clause → high. "stop"
|
|
54
|
+
// must be followed by a gerund so "stop summarizing" captures but "stop the
|
|
55
|
+
// server" (a one-off action) does not.
|
|
56
|
+
const HIGH_NEG = /^(?:never\s+\w+|stop\s+\w+ing|(?:don'?t|do not) ever\b)/i;
|
|
57
|
+
// Softer imperatives → medium (ask the owner first).
|
|
58
|
+
const MED_OPENER = /^(?:always|don'?t|do not|make sure|be sure|remember to|prefer|avoid)\b/i;
|
|
59
|
+
|
|
60
|
+
// Junk that pattern-matches a directive but is never a rule.
|
|
61
|
+
const STOP_RULES = new Set(["never mind", "nevermind", "stop it", "never ever", "don't worry", "do not worry"]);
|
|
62
|
+
|
|
63
|
+
function splitSentences(text) {
|
|
64
|
+
return String(text || "")
|
|
65
|
+
.split(/(?<=[.!?])\s+|\n+/)
|
|
66
|
+
.map((s) => s.trim())
|
|
67
|
+
.filter(Boolean);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function endsAsQuestion(s) {
|
|
71
|
+
return /\?\s*$/.test(String(s || ""));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function stripOpener(s) {
|
|
75
|
+
return String(s || "").replace(/^(?:ok(?:ay)?|hey|so|and|also|well|please|now)[,:\s]+/i, "").trim();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Turn a directive sentence into a clean, imperative one-line rule: drop soft
|
|
79
|
+
// openers, leading durable markers, and polite lead-ins; collapse whitespace;
|
|
80
|
+
// capitalize; cap length. Keeps "never/stop/always/make sure" intact — those
|
|
81
|
+
// are the rule's core and drive classification.
|
|
82
|
+
function cleanRule(sentence) {
|
|
83
|
+
let s = stripOpener(sentence);
|
|
84
|
+
s = s.replace(/^(?:from now on|from here on(?: out)?|going forward|in (?:the )?future)[,:\s]+/i, "");
|
|
85
|
+
s = s.replace(/^(?:can you|could you|i(?:'d| would)? (?:like|want) you to|i want you to)\s+/i, "");
|
|
86
|
+
s = s.replace(/\s+/g, " ").trim().replace(/[\s,;:]+$/g, "");
|
|
87
|
+
if (s) s = s[0].toUpperCase() + s.slice(1);
|
|
88
|
+
return s.slice(0, MAX_RULE_CHARS);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ruleOk(rule) {
|
|
92
|
+
if (!rule || rule.length < MIN_RULE_CHARS) return false;
|
|
93
|
+
const low = rule.toLowerCase();
|
|
94
|
+
if (STOP_RULES.has(low)) return false;
|
|
95
|
+
if (low.split(/\s+/).length < MIN_RULE_WORDS) return false;
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Returns { isRule, confidence: "high"|"medium", rule, trigger } or { isRule:false }.
|
|
100
|
+
// High short-circuits; a medium hit is remembered as the fallback.
|
|
101
|
+
function detect(text) {
|
|
102
|
+
let medium = null;
|
|
103
|
+
for (const s of splitSentences(text)) {
|
|
104
|
+
if (endsAsQuestion(s)) continue;
|
|
105
|
+
const rule = cleanRule(s);
|
|
106
|
+
if (!ruleOk(rule)) continue;
|
|
107
|
+
const hasMarker = HIGH_MARKERS.some((re) => re.test(s));
|
|
108
|
+
if (hasMarker || HIGH_NEG.test(rule)) {
|
|
109
|
+
return { isRule: true, confidence: "high", rule, trigger: hasMarker ? "marker" : "directive" };
|
|
110
|
+
}
|
|
111
|
+
if (!medium && MED_OPENER.test(rule)) medium = { isRule: true, confidence: "medium", rule, trigger: "soft" };
|
|
112
|
+
}
|
|
113
|
+
return medium || { isRule: false };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── capture (dedupe against existing lessons) ───────────────────────
|
|
117
|
+
|
|
118
|
+
function findDuplicate(rule) {
|
|
119
|
+
const norm = lessons.normalize(rule);
|
|
120
|
+
return lessons.readLessons().find((l) => lessons.normalize(l.text) === norm) || null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Add the rule as a lesson, or reinforce the existing twin. origin marks
|
|
124
|
+
// provenance (guardrail = owner stated it in chat); lessons meta carries the date.
|
|
125
|
+
function capture(rule, { origin = "guardrail" } = {}) {
|
|
126
|
+
const dup = findDuplicate(rule);
|
|
127
|
+
const target = dup ? dup.text : rule;
|
|
128
|
+
const r = lessons.addLesson({ text: target, origin });
|
|
129
|
+
return {
|
|
130
|
+
added: !!r.added,
|
|
131
|
+
reinforced: !!r.reinforced,
|
|
132
|
+
duplicate: !!dup,
|
|
133
|
+
id: r.id || (dup && dup.id) || lessons.lessonId(target),
|
|
134
|
+
text: target,
|
|
135
|
+
count: r.count,
|
|
136
|
+
overCap: !!r.overCap,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── pending store (medium-confidence Y/N confirms) ──────────────────
|
|
141
|
+
|
|
142
|
+
function readPending() {
|
|
143
|
+
try { return JSON.parse(fs.readFileSync(PENDING_FILE, "utf-8")) || {}; }
|
|
144
|
+
catch (e) { return {}; }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function writePending(obj) {
|
|
148
|
+
fs.mkdirSync(path.dirname(PENDING_FILE), { recursive: true, mode: 0o700 });
|
|
149
|
+
atomicWriteFileSync(PENDING_FILE, JSON.stringify(obj, null, 2) + "\n", { mode: 0o600 });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function prunePending(obj, now) {
|
|
153
|
+
for (const [tok, rec] of Object.entries(obj)) {
|
|
154
|
+
if (!rec || now - (rec.createdAt || 0) > PENDING_TTL_MS) delete obj[tok];
|
|
155
|
+
}
|
|
156
|
+
return obj;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function putPending(rule, meta = {}, now = Date.now()) {
|
|
160
|
+
const obj = prunePending(readPending(), now);
|
|
161
|
+
const token = crypto.randomBytes(5).toString("hex");
|
|
162
|
+
obj[token] = { rule, channel: meta.channel || null, createdAt: now };
|
|
163
|
+
writePending(obj);
|
|
164
|
+
return token;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function takePending(token, now = Date.now()) {
|
|
168
|
+
const obj = prunePending(readPending(), now);
|
|
169
|
+
const rec = obj[token] || null;
|
|
170
|
+
if (rec) { delete obj[token]; }
|
|
171
|
+
writePending(obj);
|
|
172
|
+
return rec;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── render (pure) ───────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
function clip(s) {
|
|
178
|
+
return String(s || "").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function renderCaptured(res) {
|
|
182
|
+
if (!res.added) return `📌 Already a standing rule — reinforced: "${clip(res.text)}"`;
|
|
183
|
+
return `📌 Got it — I'll keep this loaded as a standing rule from now on:\n"${clip(res.text)}"` +
|
|
184
|
+
`${res.overCap ? "\n(Over the lessons cap — the nightly dream will tidy.)" : ""}` +
|
|
185
|
+
`\nTap Undo if that's not what you meant.`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function renderAsk(rule) {
|
|
189
|
+
return `Want me to make this a standing rule (always loaded)?\n"${clip(rule)}"`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── orchestrator ────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
// Owner-gated. `announce(text, opts?)` sends to the chat (opts may carry a
|
|
195
|
+
// keyboard); omit it for a dry run. Returns a small result for testability.
|
|
196
|
+
async function consider({ text, speaker, announce, now = Date.now() } = {}) {
|
|
197
|
+
if (!enabled()) return { action: "disabled" };
|
|
198
|
+
const relationship = require("./relationship");
|
|
199
|
+
if (relationship.guardActive(speaker)) return { action: "external-skip" };
|
|
200
|
+
|
|
201
|
+
const d = detect(text);
|
|
202
|
+
if (!d.isRule) return { action: "silent" };
|
|
203
|
+
|
|
204
|
+
if (d.confidence === "high") {
|
|
205
|
+
const res = capture(d.rule);
|
|
206
|
+
if (typeof announce === "function") {
|
|
207
|
+
const opts = res.added
|
|
208
|
+
? { keyboard: { inline_keyboard: [[{ text: "↩️ Undo", callback_data: `grd:undo:${res.id}` }]] } }
|
|
209
|
+
: undefined;
|
|
210
|
+
try { await announce(renderCaptured(res), opts); } catch (e) {}
|
|
211
|
+
}
|
|
212
|
+
return { action: res.added ? "captured" : "reinforced", id: res.id, rule: res.text, duplicate: res.duplicate };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const token = putPending(d.rule, { channel: speaker && speaker.channelId }, now);
|
|
216
|
+
if (typeof announce === "function") {
|
|
217
|
+
const opts = { keyboard: { inline_keyboard: [[
|
|
218
|
+
{ text: "✅ Make it a rule", callback_data: `grd:save:${token}` },
|
|
219
|
+
{ text: "Skip", callback_data: `grd:no:${token}` },
|
|
220
|
+
]] } };
|
|
221
|
+
try { await announce(renderAsk(d.rule), opts); } catch (e) {}
|
|
222
|
+
}
|
|
223
|
+
return { action: "asked", token, rule: d.rule };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ── callback actions (owner-gated by the caller) ────────────────────
|
|
227
|
+
|
|
228
|
+
function confirmPending(token, now = Date.now()) {
|
|
229
|
+
const rec = takePending(token, now);
|
|
230
|
+
if (!rec) return { ok: false, reason: "expired" };
|
|
231
|
+
const res = capture(rec.rule);
|
|
232
|
+
return { ok: true, ...res };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function dropPending(token, now = Date.now()) {
|
|
236
|
+
const rec = takePending(token, now);
|
|
237
|
+
return { ok: !!rec, rule: rec ? rec.rule : "" };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function undo(lessonId) {
|
|
241
|
+
return { ok: !!lessons.removeLesson(lessonId) };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = {
|
|
245
|
+
PENDING_FILE, PENDING_TTL_MS,
|
|
246
|
+
enabled, detect, cleanRule, ruleOk, splitSentences,
|
|
247
|
+
findDuplicate, capture,
|
|
248
|
+
putPending, takePending, prunePending,
|
|
249
|
+
consider, confirmPending, dropPending, undo,
|
|
250
|
+
renderCaptured, renderAsk,
|
|
251
|
+
};
|
package/core/handlers.js
CHANGED
|
@@ -12,7 +12,8 @@ const {
|
|
|
12
12
|
CHAT_ID, discoverProviderExecutable, SOUL_FILE,
|
|
13
13
|
} = require("./config");
|
|
14
14
|
const { register } = require("./commands");
|
|
15
|
-
const { send, deleteMessage } = require("./io");
|
|
15
|
+
const { send, deleteMessage, typing } = require("./io");
|
|
16
|
+
const { currentAdapter, currentChannelId } = require("./context");
|
|
16
17
|
const {
|
|
17
18
|
currentState, saveState, rootSession,
|
|
18
19
|
freshUsage, getActiveProvider, getProjectKey, getProviderSession, setProviderSession, clearProviderSession,
|
|
@@ -1063,6 +1064,45 @@ register({
|
|
|
1063
1064
|
},
|
|
1064
1065
|
});
|
|
1065
1066
|
|
|
1067
|
+
register({
|
|
1068
|
+
name: "agenda", aliases: ["agency"], description: "Aggregated read-only work list (tasks + Spaces + notifications)",
|
|
1069
|
+
handler: async (env) => {
|
|
1070
|
+
if (!authorized(env)) return;
|
|
1071
|
+
const adapter = currentAdapter();
|
|
1072
|
+
const channelId = currentChannelId();
|
|
1073
|
+
const ledger = require("./agency/ledger");
|
|
1074
|
+
try { typing(); } catch (_) {}
|
|
1075
|
+
let result;
|
|
1076
|
+
try {
|
|
1077
|
+
result = await ledger.collect({ adapter: adapter && adapter.id, channelId });
|
|
1078
|
+
} catch (e) {
|
|
1079
|
+
return send(`Couldn't build the agenda: ${e.message}`);
|
|
1080
|
+
}
|
|
1081
|
+
send(ledger.render(result, { now: result.generatedAt, limitPerSource: 8 }));
|
|
1082
|
+
},
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
register({
|
|
1086
|
+
name: "next", description: "Advisory: what I'd pick to do next, and why (acts on nothing)",
|
|
1087
|
+
handler: async (env) => {
|
|
1088
|
+
if (!authorized(env)) return;
|
|
1089
|
+
const adapter = currentAdapter();
|
|
1090
|
+
const channelId = currentChannelId();
|
|
1091
|
+
const ledger = require("./agency/ledger");
|
|
1092
|
+
const deliberate = require("./agency/deliberate");
|
|
1093
|
+
try { typing(); } catch (_) {}
|
|
1094
|
+
let proposal;
|
|
1095
|
+
try {
|
|
1096
|
+
const result = await ledger.collect({ adapter: adapter && adapter.id, channelId });
|
|
1097
|
+
proposal = await deliberate.deliberate(result, { now: result.generatedAt });
|
|
1098
|
+
try { deliberate.record(proposal, { channel: adapter && adapter.id ? `${adapter.id}:${channelId}` : null }); } catch (_) {}
|
|
1099
|
+
} catch (e) {
|
|
1100
|
+
return send(`Couldn't deliberate: ${e.message}`);
|
|
1101
|
+
}
|
|
1102
|
+
send(deliberate.render(proposal));
|
|
1103
|
+
},
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1066
1106
|
register({
|
|
1067
1107
|
name: "toolmode", description: "Tooling enforcement mode (strict = forced CLI, relaxed = scripting allowed)", args: "[strict|relaxed]", ownerOnly: true,
|
|
1068
1108
|
handler: async (env, { tail }) => {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Always-on prompt budget instrumentation (Track X0). One JSONL line per real
|
|
2
|
+
// user turn plus a rolling summary, so we can baseline the fixed prompt floor
|
|
3
|
+
// (soul + lessons + runtime state + slash-command list + tool headlines +
|
|
4
|
+
// relationship/mandate) and watch it grow as new systems are bolted on. This
|
|
5
|
+
// is read-only telemetry: it never changes what is sent to the model, it only
|
|
6
|
+
// measures it. Mirrors the storage shape of core/recall/metrics.js.
|
|
7
|
+
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const CONFIG_DIR = require("../config-dir");
|
|
11
|
+
|
|
12
|
+
const LOG_FILE = path.join(CONFIG_DIR, "prompt-budget.jsonl");
|
|
13
|
+
const SUMMARY_FILE = path.join(CONFIG_DIR, "prompt-budget-summary.json");
|
|
14
|
+
let VERSION = "";
|
|
15
|
+
try { VERSION = require("../package.json").version || ""; } catch (e) {}
|
|
16
|
+
|
|
17
|
+
const MAX_LOG_BYTES = 2 * 1024 * 1024; // truncate the jsonl past ~2MB so telemetry never eats disk
|
|
18
|
+
|
|
19
|
+
function enabled() {
|
|
20
|
+
return String(process.env.PROMPT_BUDGET || "on").toLowerCase() !== "off";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Local, network-free token estimate. Markdown/English averages ~4 chars per
|
|
24
|
+
// token; this is deliberately an ESTIMATE — consistency across turns matters
|
|
25
|
+
// more than matching a specific tokenizer, since X0 tracks growth over time.
|
|
26
|
+
function estimateTokens(str) {
|
|
27
|
+
const chars = typeof str === "string" ? str.length : 0;
|
|
28
|
+
return Math.ceil(chars / 4);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function part(str) {
|
|
32
|
+
const chars = typeof str === "string" ? str.length : 0;
|
|
33
|
+
return { chars, tokens: estimateTokens(str) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function addSize(a, b) {
|
|
37
|
+
return { chars: a.chars + b.chars, tokens: a.tokens + b.tokens };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Which named prompt components form the fixed "always-on floor" vs the
|
|
41
|
+
// variable, on-demand recall payload and the turn's own user input.
|
|
42
|
+
const ALWAYS_ON_KEYS = [
|
|
43
|
+
"core", // soul + persona + lessons + skill index + tool index + fixed boilerplate
|
|
44
|
+
"runtime", // runtime state block
|
|
45
|
+
"speaker", // speaker + team roster + slash-command list
|
|
46
|
+
"speakerPersona",
|
|
47
|
+
"provider", // provider-native tools note
|
|
48
|
+
"voice",
|
|
49
|
+
"relationship", // external-mode / mandate
|
|
50
|
+
"spacesMode",
|
|
51
|
+
];
|
|
52
|
+
const VARIABLE_KEYS = ["transcript", "recall"];
|
|
53
|
+
|
|
54
|
+
// Break an assembled turn's named parts into sizes. `parts` keys mirror the
|
|
55
|
+
// components of buildTurnPrompt(); any subset is tolerated.
|
|
56
|
+
function measure(parts = {}) {
|
|
57
|
+
const components = {};
|
|
58
|
+
for (const [k, v] of Object.entries(parts)) components[k] = part(v);
|
|
59
|
+
|
|
60
|
+
let alwaysOn = { chars: 0, tokens: 0 };
|
|
61
|
+
for (const k of ALWAYS_ON_KEYS) if (components[k]) alwaysOn = addSize(alwaysOn, components[k]);
|
|
62
|
+
|
|
63
|
+
let variable = { chars: 0, tokens: 0 };
|
|
64
|
+
for (const k of VARIABLE_KEYS) if (components[k]) variable = addSize(variable, components[k]);
|
|
65
|
+
|
|
66
|
+
const recall = components.recall || { chars: 0, tokens: 0 };
|
|
67
|
+
const userPrompt = components.userPrompt || { chars: 0, tokens: 0 };
|
|
68
|
+
const total = addSize(addSize(alwaysOn, variable), userPrompt);
|
|
69
|
+
|
|
70
|
+
return { components, alwaysOn, recall, variable, userPrompt, total };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readSummary() {
|
|
74
|
+
try { return JSON.parse(fs.readFileSync(SUMMARY_FILE, "utf8")) || {}; } catch (e) { return {}; }
|
|
75
|
+
}
|
|
76
|
+
function writeSummary(s) {
|
|
77
|
+
try { fs.writeFileSync(SUMMARY_FILE, JSON.stringify(s, null, 2)); } catch (e) {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function bumpSummary(m, meta) {
|
|
81
|
+
const s = readSummary();
|
|
82
|
+
s.turns = (s.turns || 0) + 1;
|
|
83
|
+
s.alwaysOnTokensTotal = (s.alwaysOnTokensTotal || 0) + m.alwaysOn.tokens;
|
|
84
|
+
s.recallTokensTotal = (s.recallTokensTotal || 0) + m.recall.tokens;
|
|
85
|
+
s.totalTokensTotal = (s.totalTokensTotal || 0) + m.total.tokens;
|
|
86
|
+
s.alwaysOnTokensMax = Math.max(s.alwaysOnTokensMax || 0, m.alwaysOn.tokens);
|
|
87
|
+
s.componentTokensTotal = s.componentTokensTotal || {};
|
|
88
|
+
for (const [k, v] of Object.entries(m.components)) {
|
|
89
|
+
s.componentTokensTotal[k] = (s.componentTokensTotal[k] || 0) + v.tokens;
|
|
90
|
+
}
|
|
91
|
+
// Latest snapshot — the current per-component breakdown ("what is the floor
|
|
92
|
+
// right now"), distinct from the rolling averages above.
|
|
93
|
+
s.latest = {
|
|
94
|
+
ts: new Date().toISOString(),
|
|
95
|
+
v: VERSION,
|
|
96
|
+
provider: meta.provider || "",
|
|
97
|
+
alwaysOnTokens: m.alwaysOn.tokens,
|
|
98
|
+
recallTokens: m.recall.tokens,
|
|
99
|
+
totalTokens: m.total.tokens,
|
|
100
|
+
components: Object.fromEntries(Object.entries(m.components).map(([k, v]) => [k, v.tokens])),
|
|
101
|
+
};
|
|
102
|
+
s.updatedAt = new Date().toISOString();
|
|
103
|
+
writeSummary(s);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function appendLog(rec) {
|
|
107
|
+
try {
|
|
108
|
+
try {
|
|
109
|
+
const st = fs.statSync(LOG_FILE);
|
|
110
|
+
if (st.size > MAX_LOG_BYTES) fs.rmSync(LOG_FILE, { force: true });
|
|
111
|
+
} catch (e) {}
|
|
112
|
+
fs.appendFileSync(LOG_FILE, JSON.stringify(rec) + "\n");
|
|
113
|
+
} catch (e) {}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Record one real turn's prompt budget. Best-effort; never throws.
|
|
117
|
+
function record(measurement, meta = {}) {
|
|
118
|
+
if (!enabled()) return;
|
|
119
|
+
try {
|
|
120
|
+
const m = measurement && measurement.total ? measurement : null;
|
|
121
|
+
if (!m) return;
|
|
122
|
+
const rec = {
|
|
123
|
+
ts: new Date().toISOString(),
|
|
124
|
+
v: VERSION,
|
|
125
|
+
provider: meta.provider || "",
|
|
126
|
+
runId: meta.runId || null,
|
|
127
|
+
alwaysOnTokens: m.alwaysOn.tokens,
|
|
128
|
+
recallTokens: m.recall.tokens,
|
|
129
|
+
totalTokens: m.total.tokens,
|
|
130
|
+
components: Object.fromEntries(Object.entries(m.components).map(([k, v]) => [k, v.tokens])),
|
|
131
|
+
};
|
|
132
|
+
appendLog(rec);
|
|
133
|
+
bumpSummary(m, meta);
|
|
134
|
+
} catch (e) {}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Human-readable rolling view (rendered by `open-claudia prompt-budget`).
|
|
138
|
+
function summary() {
|
|
139
|
+
const s = readSummary();
|
|
140
|
+
const turns = s.turns || 0;
|
|
141
|
+
const avg = (total) => (turns ? Math.round((total || 0) / turns) : 0);
|
|
142
|
+
const componentAvg = {};
|
|
143
|
+
for (const [k, v] of Object.entries(s.componentTokensTotal || {})) componentAvg[k] = avg(v);
|
|
144
|
+
return {
|
|
145
|
+
turns,
|
|
146
|
+
avgAlwaysOnTokens: avg(s.alwaysOnTokensTotal),
|
|
147
|
+
maxAlwaysOnTokens: s.alwaysOnTokensMax || 0,
|
|
148
|
+
avgRecallTokens: avg(s.recallTokensTotal),
|
|
149
|
+
avgTotalTokens: avg(s.totalTokensTotal),
|
|
150
|
+
componentAvgTokens: componentAvg,
|
|
151
|
+
latest: s.latest || null,
|
|
152
|
+
updatedAt: s.updatedAt || "",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function _resetForTest() {
|
|
157
|
+
try { fs.rmSync(LOG_FILE, { force: true }); } catch (e) {}
|
|
158
|
+
try { fs.rmSync(SUMMARY_FILE, { force: true }); } catch (e) {}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
LOG_FILE, SUMMARY_FILE, VERSION, enabled,
|
|
163
|
+
ALWAYS_ON_KEYS, VARIABLE_KEYS,
|
|
164
|
+
estimateTokens, measure, record, summary, _resetForTest,
|
|
165
|
+
};
|
package/core/runner.js
CHANGED
|
@@ -761,7 +761,7 @@ function createRunnerService(dependencies = {}) {
|
|
|
761
761
|
let bundle;
|
|
762
762
|
let invocation;
|
|
763
763
|
try {
|
|
764
|
-
bundle = await promptBuilder(prompt, { runContext, provider });
|
|
764
|
+
bundle = await promptBuilder(prompt, { runContext, provider, recordBudget: !capture });
|
|
765
765
|
invocation = invocationBuilder(provider, invocationContextForRun(runContext, bundle, provider));
|
|
766
766
|
if (dependencies.decorateInvocation) {
|
|
767
767
|
invocation = dependencies.decorateInvocation(invocation, runContext, bundle, provider);
|
|
@@ -1860,6 +1860,23 @@ async function handoffMemoryReview(result, runContext, store, state) {
|
|
|
1860
1860
|
});
|
|
1861
1861
|
}
|
|
1862
1862
|
|
|
1863
|
+
// Track C — owner-guardrail capture. After a foreground turn, a deterministic
|
|
1864
|
+
// detector spots durable-rule statements ("from now on / never / stop") in the
|
|
1865
|
+
// owner's message and captures them into lessons.md (or asks first). Owner-only
|
|
1866
|
+
// and best-effort: it never blocks or breaks a turn.
|
|
1867
|
+
async function considerGuardrail(result, runContext, store) {
|
|
1868
|
+
try {
|
|
1869
|
+
if (!result.ok || runContext.purpose !== "foreground") return;
|
|
1870
|
+
const guardrail = require("./guardrail");
|
|
1871
|
+
if (!guardrail.enabled()) return;
|
|
1872
|
+
const relationship = require("./relationship");
|
|
1873
|
+
const speaker = relationship.speakerFor(store?.adapter?.type, store?.channelId, store?.userId);
|
|
1874
|
+
if (relationship.guardActive(speaker)) return; // external speakers never capture
|
|
1875
|
+
const announce = (text, opts) => chatContext.run(store, () => send(text, opts));
|
|
1876
|
+
await guardrail.consider({ text: runContext.userPrompt, speaker, announce });
|
|
1877
|
+
} catch (e) { /* best-effort — a guardrail miss must never break a turn */ }
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1863
1880
|
function deferredRunResult() {
|
|
1864
1881
|
let resolve;
|
|
1865
1882
|
let reject;
|
|
@@ -1920,6 +1937,7 @@ async function executeAdmittedRun(runContext, state, store, capture, ownsAdmissi
|
|
|
1920
1937
|
try { service.turnObserver.onTurnSettled(!!result.ok); } catch (e) { /* best-effort */ }
|
|
1921
1938
|
}
|
|
1922
1939
|
if (!internalCapture) await handoffMemoryReview(result, runContext, store, state);
|
|
1940
|
+
if (!internalCapture) await considerGuardrail(result, runContext, store);
|
|
1923
1941
|
if (!internalCapture && state.settings?.backend === runContext.provider && state.settings.budget) {
|
|
1924
1942
|
state.settings.budget = null;
|
|
1925
1943
|
try { saveState({ strict: true }); }
|
|
@@ -1982,7 +2000,13 @@ function runAgent(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1982
2000
|
}
|
|
1983
2001
|
const deferred = deferredRunResult();
|
|
1984
2002
|
state.messageQueue.push({ runContext, store, deferred, queuedAt: Date.now() });
|
|
1985
|
-
|
|
2003
|
+
// Transient queue/compaction acks are a chat-UX affordance (Telegram/Kazee).
|
|
2004
|
+
// On a Spaces task thread they'd post a throwaway "Queued." comment on the
|
|
2005
|
+
// task — noise. Spaces stays quiet and just posts the real reply when its
|
|
2006
|
+
// turn runs.
|
|
2007
|
+
if (currentAdapter()?.type !== "spaces") {
|
|
2008
|
+
send(state.isCompacting ? "Compacting context, will pick this up next…" : "Queued.", { replyTo: replyToMsgId }).catch(() => {});
|
|
2009
|
+
}
|
|
1986
2010
|
return deferred.promise;
|
|
1987
2011
|
}
|
|
1988
2012
|
return executeAdmittedRun(runContext, state, store, false, true);
|
package/core/state.js
CHANGED
|
@@ -652,6 +652,21 @@ function acquireRunLock(state = currentState()) {
|
|
|
652
652
|
return true;
|
|
653
653
|
}
|
|
654
654
|
|
|
655
|
+
// True when ANY user has a turn running, admitted-and-preparing, compacting, or
|
|
656
|
+
// waiting in its queue. Same busy predicate as acquireRunLock, but across every
|
|
657
|
+
// user state rather than one — the Telegram poll-wedge backstop consults this
|
|
658
|
+
// before a self-exit so a reboot never interrupts in-flight work. It fires on a
|
|
659
|
+
// timer outside any channel context (no "current" user) and a reboot kills the
|
|
660
|
+
// whole process, so it must scan all states, not just currentState().
|
|
661
|
+
function anyTurnActive() {
|
|
662
|
+
for (const s of userStates.values()) {
|
|
663
|
+
if (!s) continue;
|
|
664
|
+
if (s.runningProcess || s.preparingRun || s.isCompacting) return true;
|
|
665
|
+
if (Array.isArray(s.messageQueue) && s.messageQueue.length) return true;
|
|
666
|
+
}
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
|
|
655
670
|
function migrationSources() {
|
|
656
671
|
return defaultMigrationSources({
|
|
657
672
|
configDir: CONFIG_DIR,
|
|
@@ -1363,6 +1378,7 @@ module.exports = {
|
|
|
1363
1378
|
resetSessionUsage,
|
|
1364
1379
|
resetSettings,
|
|
1365
1380
|
acquireRunLock,
|
|
1381
|
+
anyTurnActive,
|
|
1366
1382
|
saveState,
|
|
1367
1383
|
loadSessions,
|
|
1368
1384
|
loadSessionsDocument,
|
package/core/system-prompt.js
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
const tasksStore = require("./tasks");
|
|
16
16
|
const people = require("./people");
|
|
17
17
|
const commandsRegistry = require("./commands");
|
|
18
|
+
const promptBudget = require("./prompt-budget");
|
|
18
19
|
|
|
19
20
|
function loadSoul() {
|
|
20
21
|
const soul = fs.readFileSync(SOUL_FILE, "utf-8");
|
|
@@ -1068,12 +1069,40 @@ function createPromptBuilder(dependencies = {}) {
|
|
|
1068
1069
|
spacesTaskMode,
|
|
1069
1070
|
].filter((part) => typeof part === "string" && part.trim()).join("\n\n");
|
|
1070
1071
|
|
|
1072
|
+
// Track X0: measure the fixed always-on floor vs the variable recall
|
|
1073
|
+
// payload for this turn. Pure measurement — attached to the bundle for
|
|
1074
|
+
// any caller; only recorded to telemetry when the caller opts in
|
|
1075
|
+
// (real user turns, never sub-agent/capture builds).
|
|
1076
|
+
let budget = null;
|
|
1077
|
+
try {
|
|
1078
|
+
budget = promptBudget.measure({
|
|
1079
|
+
core: coreInstructions,
|
|
1080
|
+
transcript: transcriptContext,
|
|
1081
|
+
runtime: runtimeContext,
|
|
1082
|
+
speaker: speakerContext,
|
|
1083
|
+
speakerPersona,
|
|
1084
|
+
provider: providerContext,
|
|
1085
|
+
recall: recallResult.dynamicContext,
|
|
1086
|
+
voice: voiceContext,
|
|
1087
|
+
relationship: relationshipContext,
|
|
1088
|
+
spacesMode: spacesTaskMode,
|
|
1089
|
+
userPrompt,
|
|
1090
|
+
});
|
|
1091
|
+
if (opts.recordBudget) {
|
|
1092
|
+
promptBudget.record(budget, {
|
|
1093
|
+
provider: opts.provider?.id,
|
|
1094
|
+
runId: opts.runContext?.runId,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
} catch (_) { budget = null; }
|
|
1098
|
+
|
|
1071
1099
|
return {
|
|
1072
1100
|
coreInstructions,
|
|
1073
1101
|
dynamicContext,
|
|
1074
1102
|
userPrompt,
|
|
1075
1103
|
transcriptPaths,
|
|
1076
1104
|
recallMetadata: recallResult.metadata,
|
|
1105
|
+
promptBudget: budget,
|
|
1077
1106
|
};
|
|
1078
1107
|
},
|
|
1079
1108
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.27",
|
|
4
4
|
"description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
|
|
5
5
|
"main": "bot.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"setup": "node setup.js",
|
|
11
11
|
"start": "node bot.js",
|
|
12
|
-
"pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js",
|
|
12
|
+
"pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js",
|
|
13
13
|
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
@@ -66,6 +66,10 @@
|
|
|
66
66
|
"test-identity-prune.js",
|
|
67
67
|
"test-kazee-message-id.js",
|
|
68
68
|
"test-kazee-channel-health.js",
|
|
69
|
+
"test-prompt-budget.js",
|
|
70
|
+
"test-agency-ledger.js",
|
|
71
|
+
"test-agency-deliberate.js",
|
|
72
|
+
"test-guardrail.js",
|
|
69
73
|
"test-delivery-contract.js",
|
|
70
74
|
"test-run-lock.js",
|
|
71
75
|
"test-web-sessions.js",
|