castle-web-cli 0.4.34 → 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.
@@ -36,6 +36,7 @@ comma-separated titles or ids of the finished tasks
36
36
  \`\`\`
37
37
 
38
38
  - NEVER check a task off on your own judgment -- only a clear user statement that it works (or an explicit ask to clear it) counts. When in doubt, leave the row on the board.
39
+ - The background-tasks list below IS the board the user sees -- every row, with its id. To clear the WHOLE board at once (e.g. "clear all the tasks"), use \`all\` instead of listing ids: a \`castle-done\` with body \`all\` checks off every finished row, and a \`castle-stop\` with body \`all\` stops everything still running. Never claim the board is cleared without actually emitting the fence.
39
40
  - To STOP tasks (running or waiting) when the user asks or their work is clearly no longer wanted, include:
40
41
 
41
42
  \`\`\`castle-stop
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);
@@ -602,38 +603,58 @@ function createTaskStore(opts) {
602
603
  }
603
604
  }
604
605
  }
605
- // The router checks finished tasks off by title or id (castle-done fence).
606
+ // True when a fence body is the special token "all" / "*" (clear/stop
607
+ // everything, no per-task enumeration).
608
+ function meansAll(tokens) {
609
+ return tokens.some((t) => t.toLowerCase() === 'all' || t === '*');
610
+ }
611
+ // The router checks finished tasks off by title or id (castle-done fence),
612
+ // or "all" to clear every finished row off the board at once.
606
613
  function checkOff(tokens) {
607
- for (const id of resolveDeps(tasks, tokens))
614
+ const ids = meansAll(tokens)
615
+ ? [...tasks.values()].filter((t) => isTerminal(t.status) && !t.acknowledged).map((t) => t.id)
616
+ : resolveDeps(tasks, tokens);
617
+ for (const id of ids)
608
618
  acknowledge(id, false);
609
619
  }
610
- // The router stops tasks by title or id (castle-stop fence). Waiting tasks
611
- // are cancelled outright; running ones get their agent process killed and
612
- // finalize as interrupted via the stopRequested path.
613
- function stop(tokens) {
614
- for (const id of resolveDeps(tasks, tokens)) {
615
- const task = tasks.get(id);
616
- if (!task)
617
- continue;
618
- if (task.status === 'waiting') {
619
- task.status = 'interrupted';
620
- touch(task);
621
- }
622
- else if (task.status === 'running') {
623
- stopRequested.add(task.id);
624
- for (const child of children) {
625
- if (child.pid === task.pid) {
626
- try {
627
- child.kill('SIGKILL');
628
- }
629
- catch {
630
- /* already gone */
631
- }
620
+ // The router stops tasks by title or id (castle-stop fence), or "all" to
621
+ // stop everything still active. Waiting tasks are cancelled outright;
622
+ // running ones get their agent process killed and finalize as interrupted
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 */
632
641
  }
633
642
  }
634
643
  }
635
644
  }
636
645
  }
646
+ function stop(tokens) {
647
+ const ids = meansAll(tokens)
648
+ ? [...tasks.values()]
649
+ .filter((t) => t.status === 'running' || t.status === 'waiting')
650
+ .map((t) => t.id)
651
+ : resolveDeps(tasks, tokens);
652
+ for (const id of ids) {
653
+ const task = tasks.get(id);
654
+ if (task)
655
+ haltTask(task);
656
+ }
657
+ }
637
658
  return { sorted, get: (id) => tasks.get(id), spawnFromDirective, acknowledge, checkOff, stop, shutdown };
638
659
  }
639
660
  // -- attachments ----------------------------------------------------------------
@@ -764,7 +785,13 @@ function runRouterTurnIn(ctx, instruction) {
764
785
  messages: ctx.log.messages
765
786
  .filter((m) => m.role !== 'log' && m.id !== message.id && m.status !== 'streaming')
766
787
  .map((m) => ({ role: m.role, text: m.text })),
767
- tasks: ctx.taskStore.sorted().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),
768
795
  instruction,
769
796
  });
770
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 })))));
@@ -127,6 +127,7 @@ function buildFileTree(paths) {
127
127
  }
128
128
  }
129
129
  pruneChildMaps(root);
130
+ sortRootChildren(root);
130
131
  return root;
131
132
  }
132
133
  function pruneChildMaps(node) {
@@ -134,6 +135,17 @@ function pruneChildMaps(node) {
134
135
  for (const child of node.children) pruneChildMaps(child);
135
136
  delete node.childMap;
136
137
  }
138
+ // Top-level folders list in a fixed order: drawings, then scenes, then
139
+ // behaviors, then anything else alphabetically.
140
+ function sortRootChildren(root) {
141
+ const rank = (name) => {
142
+ if (name === 'drawings') return 0;
143
+ if (name === 'scenes') return 1;
144
+ if (name === 'behaviors') return 2;
145
+ return 3;
146
+ };
147
+ root.children.sort((a, b) => rank(a.name) - rank(b.name) || a.name.localeCompare(b.name));
148
+ }
137
149
  function collectDirectoryPaths(node) {
138
150
  if (node.type !== 'directory') return [];
139
151
  return [
@@ -44,6 +44,15 @@
44
44
  box-sizing: border-box;
45
45
  }
46
46
 
47
+ /* Hide scrollbars across the whole editor while keeping scroll functional. */
48
+ :global(*) {
49
+ scrollbar-width: none;
50
+ }
51
+
52
+ :global(*)::-webkit-scrollbar {
53
+ display: none;
54
+ }
55
+
47
56
  :global(html),
48
57
  :global(body),
49
58
  :global(#root) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "castle-web-cli",
3
- "version": "0.4.34",
3
+ "version": "0.4.36",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "castle-web": "./dist/index.js"