castle-web-cli 0.4.178 → 0.4.179

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/dist/agent.js CHANGED
@@ -2269,6 +2269,8 @@ function persistTaskFile(tasksDir, task) {
2269
2269
  // written whether or not the deck has the plan doc switched on: it is a disk
2270
2270
  // receipt, never injected into a prompt, so it costs an off deck nothing.
2271
2271
  const TASK_INDEX_FILE = 'index.md';
2272
+ const TASK_FEED_FILE = 'feed.json';
2273
+ const TASK_FEED_CAP = 80;
2272
2274
  const TASK_INDEX_HEADER = '# tasks\n# <task-id> | <item> | <title> | <created> | <status> | <touching>\n';
2273
2275
  // A pipe-delimited row can't carry a pipe. Titles come from the router's fence
2274
2276
  // and can hold anything.
@@ -3224,8 +3226,27 @@ function asClientMessage(message, signPath) {
3224
3226
  attachments: message.attachments.map((name) => clientUrl(`${AGENT_ATTACHMENT_PREFIX}${name}`, signPath)),
3225
3227
  };
3226
3228
  }
3227
- function createTaskFeeds(broadcast) {
3229
+ function loadTaskFeeds(tasksDir) {
3228
3230
  const map = new Map();
3231
+ for (const entry of fs.existsSync(tasksDir) ? fs.readdirSync(tasksDir) : []) {
3232
+ const loaded = readJsonFile(path.join(tasksDir, entry, TASK_FEED_FILE));
3233
+ if (!Array.isArray(loaded))
3234
+ continue;
3235
+ const lines = loaded.filter((line) => typeof line === 'string');
3236
+ if (lines.length === 0)
3237
+ continue;
3238
+ map.set(entry, lines.slice(-TASK_FEED_CAP));
3239
+ }
3240
+ return map;
3241
+ }
3242
+ function createTaskFeeds(broadcast, tasksDir) {
3243
+ const map = loadTaskFeeds(tasksDir);
3244
+ function persist(task) {
3245
+ const feed = map.get(task.id);
3246
+ if (!feed || feed.length === 0)
3247
+ return;
3248
+ atomicWriteFileSync(path.join(tasksDir, task.id, TASK_FEED_FILE), JSON.stringify(feed, null, 2) + '\n');
3249
+ }
3229
3250
  function push(task, entry) {
3230
3251
  let feed = map.get(task.id);
3231
3252
  if (!feed) {
@@ -3235,11 +3256,11 @@ function createTaskFeeds(broadcast) {
3235
3256
  if (feed[feed.length - 1] === entry)
3236
3257
  return;
3237
3258
  feed.push(entry);
3238
- if (feed.length > 80)
3239
- feed.splice(0, feed.length - 80);
3259
+ if (feed.length > TASK_FEED_CAP)
3260
+ feed.splice(0, feed.length - TASK_FEED_CAP);
3240
3261
  broadcast({ type: 'task-feed', id: task.id, entry });
3241
3262
  }
3242
- return { map, push };
3263
+ return { map, push, persist };
3243
3264
  }
3244
3265
  function createMessageThinking(broadcast) {
3245
3266
  const map = new Map();
@@ -4399,7 +4420,7 @@ export function createAgentServer(opts) {
4399
4420
  // Castle's), so the bar follows the change instead of the poll.
4400
4421
  usageFeed.refresh();
4401
4422
  };
4402
- const taskFeeds = createTaskFeeds(broadcast);
4423
+ const taskFeeds = createTaskFeeds(broadcast, tasksDir);
4403
4424
  const messageThinking = createMessageThinking(broadcast);
4404
4425
  const taskStore = createTaskStore({
4405
4426
  deckDir,
@@ -4421,7 +4442,7 @@ export function createAgentServer(opts) {
4421
4442
  onUpdate: (task) => broadcast({ type: 'task-update', task: asClientTask(task, opts.signPath) }),
4422
4443
  onStarted: () => undefined,
4423
4444
  onRetry: (task, attempt) => addLog(`agent died, retrying (${attempt}/${MAX_TASK_ATTEMPTS}): ${task.title}`),
4424
- onFinished: (task) => taskFeeds.map.delete(task.id),
4445
+ onFinished: (task) => taskFeeds.persist(task),
4425
4446
  onFeed: (task, entry) => taskFeeds.push(task, entry),
4426
4447
  });
4427
4448
  // The mid-run send queue (queue-by-default, "send now"/Stop interrupt, epoch