castle-web-cli 0.4.178 → 0.4.180

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
@@ -432,9 +432,13 @@ function handleModelCaps(req, res) {
432
432
  }
433
433
  // Build the headless CLI invocation for a spawning backend/role (smith never
434
434
  // comes through here -- it has no CLI process; see runAgentSmith). Cursor's
435
- // router runs in read-only ask mode; claude runs permission-mode auto for
436
- // both roles (NOT plan mode -- that makes it emit plan tool calls) at medium
437
- // effort. claudeModel "openrouter" keeps the SAME claude binary/flags and
435
+ // router runs in read-only ask mode; claude runs permission-mode
436
+ // bypassPermissions for both roles (NOT plan mode -- that makes it emit plan
437
+ // tool calls) at medium effort. Not `auto`: auto mode runs a sonnet
438
+ // classifier call beside every turn, on Anthropic's own key, whatever --model
439
+ // says -- a second model's cost and a second billing line per turn. bypass
440
+ // asks no classifier. A cli fix is expected upstream; until then bypass is
441
+ // the mode. claudeModel "openrouter" keeps the SAME claude binary/flags and
438
442
  // swaps only the model slug and env (see envForOpenrouterSpawn) -- plus one
439
443
  // extra permission grant, see OPENROUTER_ALLOWED_TOOLS below.
440
444
  // claude CLI 2.1.198 SILENTLY resolves `--permission-mode auto` to "default"
@@ -507,11 +511,15 @@ function claudeSettingsArg(auth) {
507
511
  // new process, so that text (the kit guide: ~23k tokens on a kit deck) is sent
508
512
  // uncached on every call of every turn. The router already carries the deck's
509
513
  // quick reference and file tree in its cached system part, and can read a
510
- // guide on demand; tasks keep the CLI's own loading. Measured on a 30k-token
511
- // router prompt: a second turn goes from 40k tokens written to 4.6k.
514
+ // guide on demand; tasks keep the CLI's own loading. Auto memory is off for the
515
+ // same reason: the first process in a deck creates the memory store, the next
516
+ // one's system prompt gains a memory block, and the cached prefix only matches
517
+ // from the third turn on. Measured on a 30k-token router prompt: a second turn
518
+ // goes from 50k tokens written to 6k.
512
519
  const ROUTER_CLAUDE_ENV = {
513
520
  CLAUDE_CODE_DISABLE_CLAUDE_MDS: '1',
514
521
  CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS: '1',
522
+ CLAUDE_CODE_DISABLE_AUTO_MEMORY: '1',
515
523
  };
516
524
  function buildAgentInvocation(backend, role, prompt, claudeModel,
517
525
  // Already resolved for this role by the caller (router turns pass
@@ -549,7 +557,7 @@ systemPrompt) {
549
557
  'stream-json',
550
558
  '--include-partial-messages',
551
559
  '--permission-mode',
552
- 'auto',
560
+ 'bypassPermissions',
553
561
  ...(viaOpenrouter ? [openrouterAllowedToolsArg(role)] : []),
554
562
  '--model',
555
563
  viaOpenrouter ? openrouterModel : claudeModel,
@@ -2269,6 +2277,8 @@ function persistTaskFile(tasksDir, task) {
2269
2277
  // written whether or not the deck has the plan doc switched on: it is a disk
2270
2278
  // receipt, never injected into a prompt, so it costs an off deck nothing.
2271
2279
  const TASK_INDEX_FILE = 'index.md';
2280
+ const TASK_FEED_FILE = 'feed.json';
2281
+ const TASK_FEED_CAP = 80;
2272
2282
  const TASK_INDEX_HEADER = '# tasks\n# <task-id> | <item> | <title> | <created> | <status> | <touching>\n';
2273
2283
  // A pipe-delimited row can't carry a pipe. Titles come from the router's fence
2274
2284
  // and can hold anything.
@@ -3224,8 +3234,27 @@ function asClientMessage(message, signPath) {
3224
3234
  attachments: message.attachments.map((name) => clientUrl(`${AGENT_ATTACHMENT_PREFIX}${name}`, signPath)),
3225
3235
  };
3226
3236
  }
3227
- function createTaskFeeds(broadcast) {
3237
+ function loadTaskFeeds(tasksDir) {
3228
3238
  const map = new Map();
3239
+ for (const entry of fs.existsSync(tasksDir) ? fs.readdirSync(tasksDir) : []) {
3240
+ const loaded = readJsonFile(path.join(tasksDir, entry, TASK_FEED_FILE));
3241
+ if (!Array.isArray(loaded))
3242
+ continue;
3243
+ const lines = loaded.filter((line) => typeof line === 'string');
3244
+ if (lines.length === 0)
3245
+ continue;
3246
+ map.set(entry, lines.slice(-TASK_FEED_CAP));
3247
+ }
3248
+ return map;
3249
+ }
3250
+ function createTaskFeeds(broadcast, tasksDir) {
3251
+ const map = loadTaskFeeds(tasksDir);
3252
+ function persist(task) {
3253
+ const feed = map.get(task.id);
3254
+ if (!feed || feed.length === 0)
3255
+ return;
3256
+ atomicWriteFileSync(path.join(tasksDir, task.id, TASK_FEED_FILE), JSON.stringify(feed, null, 2) + '\n');
3257
+ }
3229
3258
  function push(task, entry) {
3230
3259
  let feed = map.get(task.id);
3231
3260
  if (!feed) {
@@ -3235,11 +3264,11 @@ function createTaskFeeds(broadcast) {
3235
3264
  if (feed[feed.length - 1] === entry)
3236
3265
  return;
3237
3266
  feed.push(entry);
3238
- if (feed.length > 80)
3239
- feed.splice(0, feed.length - 80);
3267
+ if (feed.length > TASK_FEED_CAP)
3268
+ feed.splice(0, feed.length - TASK_FEED_CAP);
3240
3269
  broadcast({ type: 'task-feed', id: task.id, entry });
3241
3270
  }
3242
- return { map, push };
3271
+ return { map, push, persist };
3243
3272
  }
3244
3273
  function createMessageThinking(broadcast) {
3245
3274
  const map = new Map();
@@ -4399,7 +4428,7 @@ export function createAgentServer(opts) {
4399
4428
  // Castle's), so the bar follows the change instead of the poll.
4400
4429
  usageFeed.refresh();
4401
4430
  };
4402
- const taskFeeds = createTaskFeeds(broadcast);
4431
+ const taskFeeds = createTaskFeeds(broadcast, tasksDir);
4403
4432
  const messageThinking = createMessageThinking(broadcast);
4404
4433
  const taskStore = createTaskStore({
4405
4434
  deckDir,
@@ -4421,7 +4450,7 @@ export function createAgentServer(opts) {
4421
4450
  onUpdate: (task) => broadcast({ type: 'task-update', task: asClientTask(task, opts.signPath) }),
4422
4451
  onStarted: () => undefined,
4423
4452
  onRetry: (task, attempt) => addLog(`agent died, retrying (${attempt}/${MAX_TASK_ATTEMPTS}): ${task.title}`),
4424
- onFinished: (task) => taskFeeds.map.delete(task.id),
4453
+ onFinished: (task) => taskFeeds.persist(task),
4425
4454
  onFeed: (task, entry) => taskFeeds.push(task, entry),
4426
4455
  });
4427
4456
  // The mid-run send queue (queue-by-default, "send now"/Stop interrupt, epoch