@inetafrica/open-claudia 2.2.22 → 2.2.23
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 +6 -0
- package/bin/task.js +10 -4
- package/core/loopback.js +4 -0
- package/core/system-prompt.js +19 -5
- package/core/tasks.js +43 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.2.23
|
|
4
|
+
- **Token economy: tasks (R2).** The per-channel todo list no longer rides every prompt at full size.
|
|
5
|
+
- **Done means gone**: completing a task now deletes it from the store instead of leaving an `[x]` row. Finishing the last subtask of a plan deletes the whole plan. `tasks.complete()` wraps the status flip plus a `prune()` pass; the loopback `task-update` handler routes `status: completed` through it and reports how many entries were removed, so the CLI can tell the agent what disappeared. `prune()` also retires legacy debris — plans whose subtasks are all completed but whose own status was never flipped.
|
|
6
|
+
- **Once-per-session tree injection**: the full pending-task tree is only injected into the first turn of a session (after restart, new conversation, or compaction) — exactly when the agent needs to rediscover where it left off. Later turns get a one-line count (`## Pending tasks: N open (M in progress)`) with a pointer to `open-claudia task list`. Previously a large tree (~60 plans on a busy channel) was re-sent uncached on every single user turn.
|
|
7
|
+
- `formatTree` gained a `hideCompleted` option so any injected tree only shows remaining work; CLI help and the system-prompt task docs updated to describe the new semantics.
|
|
8
|
+
|
|
3
9
|
## v2.2.22
|
|
4
10
|
- **Token economy: stable prompt-cache prefix (R1).** The per-turn churning lines — `Vault: unlocked/locked`, `Session: resuming/new`, and the full pending-tasks tree — moved out of the `--append-system-prompt` block into a "Runtime state (current turn)" header prepended to each user prompt (`promptWithDynamicContext`). The appended system prompt precedes the entire conversation in every API request, so any byte that changed between turns invalidated Anthropic's prompt-cache prefix and re-billed the whole history at 1.25x write price instead of 0.1x read price; on long sessions this churn was the dominant bot cost. `buildSystemPrompt()` is now byte-stable within a session (remaining interpolations — project path, channel, voice, team/speaker — only change on rare events), and the dynamic state rides at the end of the request where it is always uncached anyway. Transcripts still log the bare prompt; Cursor/Codex paths unchanged.
|
|
5
11
|
- Compaction is much less lossy. Three changes to `compactActiveSession`:
|
package/bin/task.js
CHANGED
|
@@ -12,13 +12,14 @@ Per-channel todo list with optional plan/subtask hierarchy.
|
|
|
12
12
|
open-claudia task plan "<title>" "<sub1>" "<sub2>" ... [--description "<...>"]
|
|
13
13
|
open-claudia task list [--status pending|in_progress|completed]
|
|
14
14
|
open-claudia task start <id>
|
|
15
|
-
open-claudia task done <id>
|
|
15
|
+
open-claudia task done <id> # completes AND removes; last subtask done removes the plan
|
|
16
16
|
open-claudia task remove <id> # removing a plan removes its subtasks
|
|
17
|
-
open-claudia task clear-completed #
|
|
17
|
+
open-claudia task clear-completed # prune any leftover completed entries
|
|
18
18
|
|
|
19
19
|
Use plans for any work with 3+ distinct steps. Mark subtasks in_progress
|
|
20
20
|
as you start them and done as you finish, so a resumed turn or a new
|
|
21
|
-
turn after compaction can pick up where you left off.
|
|
21
|
+
turn after compaction can pick up where you left off. Done means gone:
|
|
22
|
+
the list only ever shows remaining work.
|
|
22
23
|
`;
|
|
23
24
|
|
|
24
25
|
function statusMark(t) {
|
|
@@ -106,7 +107,12 @@ async function runUpdate(args, status) {
|
|
|
106
107
|
try {
|
|
107
108
|
const res = await postJson("task-update", { id, status });
|
|
108
109
|
if (!res.ok) { console.error(`Not found: ${id}`); process.exit(1); }
|
|
109
|
-
|
|
110
|
+
if (status === "completed") {
|
|
111
|
+
const n = res.removedCount || 0;
|
|
112
|
+
console.log(n > 1 ? `Task ${id} completed and removed (plan finished — ${n} entries removed).` : `Task ${id} completed and removed from the list.`);
|
|
113
|
+
} else {
|
|
114
|
+
console.log(`Task ${id} → ${status}.`);
|
|
115
|
+
}
|
|
110
116
|
process.exit(0);
|
|
111
117
|
} catch (e) { console.error(e.message); process.exit(1); }
|
|
112
118
|
}
|
package/core/loopback.js
CHANGED
|
@@ -224,6 +224,10 @@ async function handleJson(req, res, url, kind) {
|
|
|
224
224
|
|
|
225
225
|
if (kind === "task-update") {
|
|
226
226
|
if (!payload.id) return reply(res, 400, { error: "missing id" });
|
|
227
|
+
if (payload.status === "completed") {
|
|
228
|
+
const result = tasksStore.complete(adapterId, channelId, payload.id);
|
|
229
|
+
return reply(res, result ? 200 : 404, { ok: !!result, task: result ? result.task : null, removedCount: result ? result.removedCount : 0 });
|
|
230
|
+
}
|
|
227
231
|
const patch = {};
|
|
228
232
|
if (payload.status) patch.status = payload.status;
|
|
229
233
|
if (typeof payload.content === "string") patch.content = payload.content;
|
package/core/system-prompt.js
CHANGED
|
@@ -138,15 +138,14 @@ Persistent todo list with plans + subtasks (per channel; survives compaction and
|
|
|
138
138
|
|
|
139
139
|
Note: any \`<system-reminder>\` you see about "task tools" / TaskCreate / TaskUpdate is from the underlying Claude Code harness — that's a different, ephemeral todo system. Open Claudia work belongs in \`open-claudia task\` (the persistent, channel-scoped one). Don't double-track.
|
|
140
140
|
|
|
141
|
-
Use a plan when work is likely to outlive a single turn — i.e. it may hit a compaction, a restart, or a scheduled wakeup before completing, or its progress is something the user will want to see between turns. The plan is a parent task whose children are the steps. As you work, mark each subtask in_progress when you begin and completed when done — this is how a resumed turn sees where you left off.
|
|
141
|
+
Use a plan when work is likely to outlive a single turn — i.e. it may hit a compaction, a restart, or a scheduled wakeup before completing, or its progress is something the user will want to see between turns. The plan is a parent task whose children are the steps. As you work, mark each subtask in_progress when you begin and completed when done — this is how a resumed turn sees where you left off. Completed means removed: \`task done\` deletes the entry, and finishing the last subtask deletes the whole plan. The list only ever contains remaining work. The full pending tree is injected into the Runtime state block once per session (first turn after restart/compaction); later turns show just a count — run \`open-claudia task list\` when you need the tree again.
|
|
142
142
|
|
|
143
143
|
Skip the plan for work that visibly completes within the current turn (e.g. running a few CLI commands in sequence and replying). A plan there is overhead the user has to read past.
|
|
144
144
|
|
|
145
145
|
- \`open-claudia task plan "<plan title>" "<step 1>" "<step 2>" "<step 3>" [--description "<why / success criteria>"]\` — atomic create
|
|
146
146
|
- \`open-claudia task add "<content>" [--parent <plan-id>] [--description "..."]\` — add a single task or subtask
|
|
147
147
|
- \`open-claudia task list\` — renders as a tree
|
|
148
|
-
- \`open-claudia task start <id>\` / \`task done <id>\` / \`task remove <id>\` (removing a plan removes its subtasks)
|
|
149
|
-
- \`open-claudia task clear-completed\` — only clears plans whose subtasks are all done
|
|
148
|
+
- \`open-claudia task start <id>\` / \`task done <id>\` (completes AND removes; finishing the last subtask removes the plan) / \`task remove <id>\` (removing a plan removes its subtasks)
|
|
150
149
|
|
|
151
150
|
For one-off work under 3 steps, skip the plan and just track it in your own response.
|
|
152
151
|
|
|
@@ -208,6 +207,13 @@ If you tell the user "I'll check back in N minutes" or "I'll run this every morn
|
|
|
208
207
|
// the prompt-cache prefix and re-bills the full history at write price
|
|
209
208
|
// instead of 0.1x read price. Keep buildSystemPrompt() byte-stable
|
|
210
209
|
// within a session; put anything that flips between turns here.
|
|
210
|
+
// The full task tree is large and rides the (uncached) tail of every
|
|
211
|
+
// prompt, so inject it only once per session: on the first turn after a
|
|
212
|
+
// bot restart, a new conversation, or a compaction (lastSessionId
|
|
213
|
+
// changes in all three cases). Later turns get a one-line count; the
|
|
214
|
+
// agent runs `open-claudia task list` when it needs detail.
|
|
215
|
+
const taskTreeInjectedFor = new Map(); // `${adapterId}:${channelId}` -> sessionId
|
|
216
|
+
|
|
211
217
|
function buildDynamicContextBlock() {
|
|
212
218
|
const state = currentState();
|
|
213
219
|
const adapter = currentAdapter();
|
|
@@ -221,8 +227,16 @@ function buildDynamicContextBlock() {
|
|
|
221
227
|
try {
|
|
222
228
|
const pending = tasksStore.pendingSummary(adapter.id, channelId);
|
|
223
229
|
if (pending.length > 0) {
|
|
224
|
-
const
|
|
225
|
-
|
|
230
|
+
const key = `${adapter.id}:${channelId}`;
|
|
231
|
+
const sess = state.lastSessionId || "new";
|
|
232
|
+
if (taskTreeInjectedFor.get(key) !== sess) {
|
|
233
|
+
taskTreeInjectedFor.set(key, sess);
|
|
234
|
+
const tree = tasksStore.formatTree(adapter.id, channelId, { showIds: true, hideCompleted: true });
|
|
235
|
+
lines.push("", "## Pending tasks", "You may be resuming prior work. Before starting anything new, review these and decide whether to continue, update, or abandon them. Mark a subtask in_progress when you actually start it and completed when it's done. This tree is only shown once per session — run `open-claudia task list` to see it again later.", "", tree);
|
|
236
|
+
} else {
|
|
237
|
+
const inProgress = pending.filter((t) => t.status === "in_progress").length;
|
|
238
|
+
lines.push("", `## Pending tasks: ${pending.length} open${inProgress > 0 ? ` (${inProgress} in progress)` : ""} — run \`open-claudia task list\` for the tree. Keep statuses current as you work.`);
|
|
239
|
+
}
|
|
226
240
|
}
|
|
227
241
|
} catch (e) {}
|
|
228
242
|
}
|
package/core/tasks.js
CHANGED
|
@@ -92,6 +92,42 @@ function remove(adapter, channelId, id) {
|
|
|
92
92
|
return { removed: target, alsoRemoved: ids.size - 1 };
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// Done means gone: completed items leave the list instead of lingering
|
|
96
|
+
// as [x] rows that get re-injected into every prompt. Removes completed
|
|
97
|
+
// subtasks, completed standalone tasks, and any plan whose subtasks are
|
|
98
|
+
// all completed (the plan is implicitly finished even if its own status
|
|
99
|
+
// was never flipped). Returns the remaining list.
|
|
100
|
+
function prune(adapter, channelId) {
|
|
101
|
+
const all = load(adapter, channelId);
|
|
102
|
+
const drop = new Set();
|
|
103
|
+
for (const t of all) {
|
|
104
|
+
if (t.parentId) {
|
|
105
|
+
if (t.status === "completed") drop.add(t.id);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const kids = all.filter((c) => c.parentId === t.id);
|
|
109
|
+
const kidsAllDone = kids.length > 0 && kids.every((c) => c.status === "completed");
|
|
110
|
+
if (kidsAllDone || (t.status === "completed" && kids.length === 0)) {
|
|
111
|
+
drop.add(t.id);
|
|
112
|
+
for (const c of kids) drop.add(c.id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (drop.size === 0) return all;
|
|
116
|
+
const remaining = all.filter((t) => !drop.has(t.id));
|
|
117
|
+
save(adapter, channelId, remaining);
|
|
118
|
+
return remaining;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Mark a task completed, then prune. Reports how many entries left the
|
|
122
|
+
// list so callers can tell the agent what disappeared.
|
|
123
|
+
function complete(adapter, channelId, id) {
|
|
124
|
+
const t = update(adapter, channelId, id, { status: "completed" });
|
|
125
|
+
if (!t) return null;
|
|
126
|
+
const before = load(adapter, channelId).length;
|
|
127
|
+
const remaining = prune(adapter, channelId);
|
|
128
|
+
return { task: t, removedCount: before - remaining.length };
|
|
129
|
+
}
|
|
130
|
+
|
|
95
131
|
function clearCompleted(adapter, channelId) {
|
|
96
132
|
const all = load(adapter, channelId);
|
|
97
133
|
const drop = new Set();
|
|
@@ -146,7 +182,12 @@ function format(t, idx) {
|
|
|
146
182
|
|
|
147
183
|
function formatTree(adapter, channelId, opts = {}) {
|
|
148
184
|
const showIds = opts.showIds !== false;
|
|
149
|
-
|
|
185
|
+
let t = tree(adapter, channelId);
|
|
186
|
+
if (opts.hideCompleted) {
|
|
187
|
+
t = t
|
|
188
|
+
.map((r) => ({ ...r, children: r.children.filter((c) => c.status !== "completed") }))
|
|
189
|
+
.filter((r) => r.status !== "completed" || r.children.length > 0);
|
|
190
|
+
}
|
|
150
191
|
const lines = [];
|
|
151
192
|
t.forEach((root, i) => {
|
|
152
193
|
const head = `${i + 1}. ${statusMark(root)} ${root.content}` + (showIds ? ` (${root.id})` : "");
|
|
@@ -163,6 +204,7 @@ module.exports = {
|
|
|
163
204
|
filePathFor,
|
|
164
205
|
load, save,
|
|
165
206
|
add, plan, update, remove, list, tree, clearCompleted,
|
|
207
|
+
prune, complete,
|
|
166
208
|
pendingSummary,
|
|
167
209
|
format, formatTree, statusMark,
|
|
168
210
|
};
|
package/package.json
CHANGED