castle-web-cli 0.4.35 → 0.4.36

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
@@ -543,13 +543,14 @@ function createTaskStore(opts) {
543
543
  });
544
544
  }
545
545
  function spawnFromDirective(directive, originMessageId) {
546
- // A fix/redo task sweeps the rows it obsoletes off the user's board. A
547
- // superseded task that never started must not start later either.
546
+ // A fix/redo task sweeps the rows it obsoletes off the user's board. Halt
547
+ // first -- a superseded task that is still running must have its process
548
+ // killed (not just hidden, which left an invisible zombie the rest of the
549
+ // board could depend on), and a waiting one must not start later.
548
550
  for (const id of resolveDeps(tasks, directive.supersedes)) {
549
551
  const old = tasks.get(id);
550
552
  if (old && !old.acknowledged) {
551
- if (old.status === 'waiting')
552
- old.status = 'interrupted';
553
+ haltTask(old);
553
554
  old.acknowledged = true;
554
555
  touch(old);
555
556
  opts.onSuperseded(old);
@@ -620,6 +621,28 @@ function createTaskStore(opts) {
620
621
  // stop everything still active. Waiting tasks are cancelled outright;
621
622
  // running ones get their agent process killed and finalize as interrupted
622
623
  // via the stopRequested path.
624
+ // Halt an active task: a waiting one is cancelled (-> interrupted); a
625
+ // running one gets its agent process killed and finalizes as interrupted
626
+ // via the stopRequested path. No-op on terminal tasks.
627
+ function haltTask(task) {
628
+ if (task.status === 'waiting') {
629
+ task.status = 'interrupted';
630
+ touch(task);
631
+ }
632
+ else if (task.status === 'running') {
633
+ stopRequested.add(task.id);
634
+ for (const child of children) {
635
+ if (child.pid === task.pid) {
636
+ try {
637
+ child.kill('SIGKILL');
638
+ }
639
+ catch {
640
+ /* already gone */
641
+ }
642
+ }
643
+ }
644
+ }
645
+ }
623
646
  function stop(tokens) {
624
647
  const ids = meansAll(tokens)
625
648
  ? [...tasks.values()]
@@ -628,25 +651,8 @@ function createTaskStore(opts) {
628
651
  : resolveDeps(tasks, tokens);
629
652
  for (const id of ids) {
630
653
  const task = tasks.get(id);
631
- if (!task)
632
- continue;
633
- if (task.status === 'waiting') {
634
- task.status = 'interrupted';
635
- touch(task);
636
- }
637
- else if (task.status === 'running') {
638
- stopRequested.add(task.id);
639
- for (const child of children) {
640
- if (child.pid === task.pid) {
641
- try {
642
- child.kill('SIGKILL');
643
- }
644
- catch {
645
- /* already gone */
646
- }
647
- }
648
- }
649
- }
654
+ if (task)
655
+ haltTask(task);
650
656
  }
651
657
  }
652
658
  return { sorted, get: (id) => tasks.get(id), spawnFromDirective, acknowledge, checkOff, stop, shutdown };
@@ -779,10 +785,13 @@ function runRouterTurnIn(ctx, instruction) {
779
785
  messages: ctx.log.messages
780
786
  .filter((m) => m.role !== 'log' && m.id !== message.id && m.status !== 'streaming')
781
787
  .map((m) => ({ role: m.role, text: m.text })),
782
- // Only the live board (unacknowledged tasks) -- match what the user sees.
783
- // Showing every already-cleared task confused the router about what was
784
- // actually on the board to clear / act on.
785
- tasks: ctx.taskStore.sorted().filter((t) => !t.acknowledged).map(asPromptTask),
788
+ // Only the live board -- match what the user sees. Hide tasks that are
789
+ // BOTH acknowledged AND finished; an active (running/waiting) task always
790
+ // shows even if somehow acked, so nothing can ever go invisible mid-work.
791
+ tasks: ctx.taskStore
792
+ .sorted()
793
+ .filter((t) => !(t.acknowledged && isTerminal(t.status)))
794
+ .map(asPromptTask),
786
795
  instruction,
787
796
  });
788
797
  const backend = ctx.backend();
@@ -227,7 +227,10 @@ function TaskRow(props) {
227
227
  expanded && task.status !== 'running' ? (React.createElement("div", { className: "task-notes", dangerouslySetInnerHTML: renderMarkdown(notes) })) : null));
228
228
  }
229
229
  function TaskBoard(props) {
230
- const visible = props.tasks.filter((t) => !t.acknowledged);
230
+ // Hide only tasks that are acknowledged AND finished. An active
231
+ // (running/waiting) task always shows, even if somehow acked -- a running
232
+ // task must never go invisible while it (and its dependents) are live.
233
+ const visible = props.tasks.filter((t) => !(t.acknowledged && TERMINAL_TASK_STATUSES.includes(t.status)));
231
234
  if (visible.length === 0)
232
235
  return null;
233
236
  return (React.createElement("div", { id: "chat-strip" }, visible.map((task) => (React.createElement(TaskRow, { key: task.id, task: task, feed: props.feeds[task.id], onAck: props.onAck })))));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "castle-web-cli",
3
- "version": "0.4.35",
3
+ "version": "0.4.36",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "castle-web": "./dist/index.js"