@ctrl-spc/cs 0.7.5 → 0.7.6

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.
Files changed (2) hide show
  1. package/dist/panel3/run.js +26 -2
  2. package/package.json +1 -1
@@ -148,6 +148,7 @@ import { listCodebases } from '../codebases.js';
148
148
  import { killTree, processIsAlive } from '../win-shell.js';
149
149
  import { hostname, uptime } from 'node:os';
150
150
  import { execFileSync } from 'node:child_process';
151
+ import { existsSync } from 'node:fs';
151
152
  import { mkdir, writeFile } from 'node:fs/promises';
152
153
  import { join } from 'node:path';
153
154
  const USAGE = 'usage: run [--once]';
@@ -1030,6 +1031,14 @@ export async function picturesOnDisk(client, cardId, where, level) {
1030
1031
  .eq('card_id', cardId).order('created_at'), 'read', `the pictures on card ${cardId}`);
1031
1032
  if (images.length === 0)
1032
1033
  return [];
1034
+ /* ═══ A CWD THAT IS GONE IS A FAILURE, NOT A FOLDER TO MAKE. ═══ `recursive`
1035
+ below would rebuild a removed worktree as a plain directory holding one
1036
+ `ctrl-spc-images/`, which is exactly the folder `worktreeForCard` then
1037
+ refuses on every start (.bugs/.resolved/20260905-card-stuck-not-a-working-copy). */
1038
+ if (!existsSync(where.cwd)) {
1039
+ throw new Error('This card\'s working copy went away while its agent was starting. Send your message '
1040
+ + 'again and it is made afresh.');
1041
+ }
1033
1042
  const folder = join(gitDirOf(where.cwd) ?? where.cwd, 'ctrl-spc-images');
1034
1043
  await mkdir(folder, { recursive: true });
1035
1044
  const lines = [];
@@ -2847,6 +2856,8 @@ export async function takeHandedBack(client, tools, machineId, mine, hold) {
2847
2856
  * normalised into a constant getter below, so there is one shape inside `run`
2848
2857
  * and no branch anywhere near the poll.
2849
2858
  */
2859
+ /** The card states in which nobody will start again on the card's copy. */
2860
+ const CARD_IS_OVER = ['done', 'failed', 'stopped'];
2850
2861
  /**
2851
2862
  * ═══ THE CARD IS OVER, SO ITS COPY GOES — AND THE BRANCH STAYS. ═══
2852
2863
  *
@@ -2865,7 +2876,7 @@ export async function takeHandedBack(client, tools, machineId, mine, hold) {
2865
2876
  * A folder no row explains is LEFT ALONE. Nothing here deletes something it
2866
2877
  * cannot account for.
2867
2878
  */
2868
- async function sweepFinishedWorktrees(client) {
2879
+ export async function sweepFinishedWorktrees(client) {
2869
2880
  const held = worktreesOnThisMachine();
2870
2881
  if (held.length === 0)
2871
2882
  return;
@@ -2889,6 +2900,17 @@ async function sweepFinishedWorktrees(client) {
2889
2900
  const cards = [...new Set([...cardOfFolder.values()].flat())];
2890
2901
  if (cards.length === 0)
2891
2902
  return;
2903
+ /* ═══ THE CARD IS ASKED, NOT ONLY ITS RUNS. ═══ A conversational owner is
2904
+ parked between wakes with `ended_at` set, so the second its last worker
2905
+ finishes, no run on the card is open, while the card itself still reads
2906
+ `working`. Reading the runs alone called that card finished and took the
2907
+ copy out from under the owner as it was starting
2908
+ (.bugs/.resolved/20260905-card-stuck-not-a-working-copy). The card's own
2909
+ state is the record's word for whether it is over; the runs still guard a
2910
+ card that is over on paper while a process of it is going. */
2911
+ const over = new Set((await returned(client.from('panel3_cards')
2912
+ .select('id, state')
2913
+ .in('id', cards), 'read', 'which of those cards are over')).filter((row) => CARD_IS_OVER.includes(row.state)).map((row) => row.id));
2892
2914
  const live = new Set((await returned(client.from('panel3_runs')
2893
2915
  .select('card_id')
2894
2916
  .in('card_id', cards)
@@ -2896,7 +2918,9 @@ async function sweepFinishedWorktrees(client) {
2896
2918
  const touched = new Set();
2897
2919
  for (const one of held) {
2898
2920
  const cardIds = cardOfFolder.get(one.folder) ?? [];
2899
- if (cardIds.length === 0 || cardIds.some((cardId) => live.has(cardId)))
2921
+ if (cardIds.length === 0)
2922
+ continue;
2923
+ if (!cardIds.every((cardId) => over.has(cardId) && !live.has(cardId)))
2900
2924
  continue;
2901
2925
  try {
2902
2926
  settleCardWorktree(one.folder);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl-spc/cs",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "description": "CTRL+SPC — minimal, reliable per-machine agent presence. Sign-in, auto-start, agent detection, heartbeat presence, and ping acknowledgement.",
5
5
  "engines": {
6
6
  "node": ">=22"