@ctrl-spc/cs 0.7.0 → 0.7.2
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/README.md +1 -1
- package/dist/codex-home.js +72 -7
- package/dist/config.js +17 -0
- package/dist/index.js +93 -12
- package/dist/mcp.js +15 -6
- package/dist/panel3/answer.js +12 -12
- package/dist/panel3/checkout.js +522 -2
- package/dist/panel3/cli.js +49 -62
- package/dist/panel3/client.js +53 -16
- package/dist/panel3/presence.js +1 -1
- package/dist/panel3/prompt.js +473 -62
- package/dist/panel3/run.js +1206 -111
- package/dist/panel3/say.js +10 -10
- package/dist/panel3/session.js +128 -0
- package/dist/panel3/show.js +197 -23
- package/dist/panel3/spawn.js +85 -20
- package/dist/panel3/tools.js +677 -51
- package/dist/presence.js +8 -0
- package/dist/skills.js +165 -0
- package/dist/workflows.js +68 -0
- package/package.json +2 -3
package/dist/presence.js
CHANGED
|
@@ -2,6 +2,7 @@ import { platform } from 'node:os';
|
|
|
2
2
|
import { getClient } from './supabase.js';
|
|
3
3
|
import { getMachineIdentity, mcpToken, supersededMachineIds, clearSupersededMachineIds } from './config.js';
|
|
4
4
|
import { detectAgents } from './agents.js';
|
|
5
|
+
import { installSkills } from './skills.js';
|
|
5
6
|
import { startToolsServer, stopToolsServer, toolsServerStatus, registerWithClaude, registerWithCodex, unregisterFromClaude, unregisterFromCodex, agentRegStatus, heartbeatOpenSessions, setToolsClient, } from './mcp.js';
|
|
6
7
|
import { HEARTBEAT_INTERVAL_MS, COMMAND_POLL_INTERVAL_MS, ORCHESTRATOR_POLL_INTERVAL_MS } from './env.js';
|
|
7
8
|
import { buildPresenceHeartbeatPayload } from './presence-heartbeat.js';
|
|
@@ -317,6 +318,13 @@ export async function startPresence() {
|
|
|
317
318
|
// presence heartbeat above, so it's caught and warned like everything here.
|
|
318
319
|
try {
|
|
319
320
|
await startToolsServer({ client, userId, machineId: identity.id });
|
|
321
|
+
/* The instructions belong to the release, so they are rewritten beside
|
|
322
|
+
registration on every start: the pair is "make this machine's agents
|
|
323
|
+
ready", and a machine registered against current tools while reading a
|
|
324
|
+
stale document is the exact failure this rewrite exists to kill. */
|
|
325
|
+
const unwritten = installSkills(agents);
|
|
326
|
+
if (unwritten.length)
|
|
327
|
+
console.warn(`Could not write the ctrl-spc skill: ${unwritten.join(', ')}`);
|
|
320
328
|
if (agents.includes('claude'))
|
|
321
329
|
registerWithClaude(toolsServerStatus().port);
|
|
322
330
|
if (agents.includes('codex'))
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* THE INSTRUCTIONS THE AGENT FOLLOWS ARE THE RELEASE'S, NOT THE MACHINE'S.
|
|
6
|
+
*
|
|
7
|
+
* These files used to be written only by the deprecated v1 CLI, so on a machine
|
|
8
|
+
* that stopped running v1 they froze: every installed copy still ends in
|
|
9
|
+
* `Tools live at http://localhost:4590/mcp`, a port this product has NEVER used
|
|
10
|
+
* (the real one is 4579, and an agent reaches the tools BY NAME, with no address
|
|
11
|
+
* at all). An agent whose tools were missing therefore probed a dead port,
|
|
12
|
+
* concluded the server was down, and told the user to repair a machine that was
|
|
13
|
+
* working. Writing them from `cs` on every start is what makes an install unable
|
|
14
|
+
* to disagree with itself.
|
|
15
|
+
*/
|
|
16
|
+
// Verbatim from the web/skill contract the copied `/ctrl-spc work <id>`
|
|
17
|
+
// invocation depends on.
|
|
18
|
+
const SKILL_DESCRIPTION = 'Work a CTRL+SPC work item or artifact by ID — delegates read-only exploration and writes analysis/plans/specs/diagrams/mocks/wireframes back via the ctrl-spc MCP tools. Use when the user pastes /ctrl-spc work <id> or /ctrl-spc artifact <id>.';
|
|
19
|
+
/** Home the skill files are written under. `CTRL_SPC_HOME` is the existing
|
|
20
|
+
* documented name for exactly this (docs/cli-surface-catalog.md), reused
|
|
21
|
+
* rather than renamed so one machine has one sandbox-home concept. */
|
|
22
|
+
function skillHome() {
|
|
23
|
+
return process.env.CTRL_SPC_HOME || homedir();
|
|
24
|
+
}
|
|
25
|
+
function claudeSkillPath() {
|
|
26
|
+
return join(skillHome(), '.claude', 'skills', 'ctrl-spc', 'SKILL.md');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Codex's three. `.agents` is NOT optional and not a legacy leftover: it is a
|
|
30
|
+
* second skill root that no isolation reaches (not a per-run `CODEX_HOME`, not
|
|
31
|
+
* `--ignore-user-config`, not `skill_search = false`, all three measured in
|
|
32
|
+
* codex-home.ts), and a resolved bug recorded that in 9 of 9 isolated runs
|
|
33
|
+
* Codex's FIRST action was to read that exact file. The leak cannot be closed
|
|
34
|
+
* here; what can be fixed is that the document it leaks is current.
|
|
35
|
+
*/
|
|
36
|
+
function codexTargets() {
|
|
37
|
+
return [
|
|
38
|
+
{ path: join(skillHome(), '.agents', 'skills', 'ctrl-spc', 'SKILL.md'), text: SKILL_TEXT },
|
|
39
|
+
{ path: join(skillHome(), '.codex', 'skills', 'ctrl-spc', 'SKILL.md'), text: SKILL_TEXT },
|
|
40
|
+
// The deprecated custom-prompt file, kept as a fallback for users who still
|
|
41
|
+
// invoke it explicitly as `/prompts:ctrl-spc ...`. No frontmatter.
|
|
42
|
+
{ path: join(skillHome(), '.codex', 'prompts', 'ctrl-spc.md'), text: PROMPT_TEXT },
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Write the ctrl-spc skill for each detected agent. ALWAYS OVERWRITES (Lane,
|
|
47
|
+
* 2026-08-26): no content comparison, no prompt, no backup, so a hand-edited or
|
|
48
|
+
* stale file cannot outlive the release that contradicts it.
|
|
49
|
+
*
|
|
50
|
+
* Returns the paths it could not write rather than only warning, and never
|
|
51
|
+
* throws: a SILENT write failure leaves the agent reading the stale file while
|
|
52
|
+
* the daemon reports itself healthy, which is precisely the failure this exists
|
|
53
|
+
* to kill. Registration must not break because a skill file could not be
|
|
54
|
+
* written, so the caller warns and carries on.
|
|
55
|
+
*/
|
|
56
|
+
export function installSkills(agents) {
|
|
57
|
+
const targets = [
|
|
58
|
+
...(agents.includes('claude') ? [{ path: claudeSkillPath(), text: SKILL_TEXT }] : []),
|
|
59
|
+
...(agents.includes('codex') ? codexTargets() : []),
|
|
60
|
+
];
|
|
61
|
+
const failed = [];
|
|
62
|
+
for (const { path, text } of targets) {
|
|
63
|
+
try {
|
|
64
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
65
|
+
writeFileSync(path, text, 'utf8');
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
failed.push(path);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return failed;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The working protocol, then the section that matters here: what an agent does
|
|
75
|
+
* when the tools are NOT in its list. It names no address, because there is
|
|
76
|
+
* none to name — `cs status` is the only diagnosis.
|
|
77
|
+
*/
|
|
78
|
+
const PROTOCOL_BODY = `Resolve a CTRL+SPC work item or artifact pasted as \`/ctrl-spc work <id>\` or \`/ctrl-spc artifact <id>\`, then follow the ctrl-spc working protocol:
|
|
79
|
+
|
|
80
|
+
WORKFLOW AUTHORITY — when \`get_task.workflow.enabled\` is true, follow only its current stage, stored instructions, capabilities, requirements, and gate. Never infer or skip a stage. Persist each required artifact, then call \`hand_off_stage\` with the work item id and this request id, and stop: the next stage is worked by a fresh agent reading the record. It refuses a stage with unfinished steps and the last stage of the process, where you answer and stop instead. Review gates advance only after explicit user approval in the web app or conversation; requested changes stay in the same stage. Only final approval sets Done.
|
|
81
|
+
|
|
82
|
+
HARD STOP — unclear novel feature (No workflow only): when \`workflow.enabled\` is false, after \`record_context_exploration\` succeeds, if the task does not explicitly request a build or name a deliverable, the next tool call MUST be \`ask_question\` with category \`intent\`, the exact question “What should I produce for this feature?”, \`answer_mode = multi_select\`, and options \`build\`, \`plan\`, \`spec\`, \`diagram\`, \`mock\`, and \`wireframe\`. Then call \`end_work\` with reason \`pending_user_answer\` and outcome \`blocked\`. In that run, never call \`get_task\` again, \`update_task\`, \`create_artifact\`, \`reserve_work_paths\`, or any other tool between the context artifact and \`ask_question\`. The context artifact is the only allowed artifact. Do not copy findings into the task description before the user answers. A read-only execution sandbox is not a missing checkout and must not change this intent question. The user may select one or more: build, plan, spec, diagram, mock, wireframe.
|
|
83
|
+
|
|
84
|
+
Before the context artifact exists, never state or imply a requested deliverable and never mention repository counts, contents, tests, findings, or likely impact. If progress commentary is required, use only: “The required CTRL+SPC context review is in progress; no decision or repository change has been made.” After the context artifact and before the intent question, either say nothing or use only: “The required context review is saved to the work item. I am applying the task's explicit intent gate now.”
|
|
85
|
+
|
|
86
|
+
1. Resolve without side effects — call \`get_task\` before anything else. It is read-only: it does not change status, presence, claims, or reservations. The ID is the only context in the paste; everything current lives in ctrl-spc.
|
|
87
|
+
2. Begin explicitly — call \`begin_work\` after reading the topology. If the user supplied an exact instruction alongside the copied command, pass it as \`prompt_instruction\`; never invent one. The first live run is coordinator and \`begin_work\` moves Backlog to In Progress; later runs join as collaborators. Only the coordinator changes status. Never mark an item \`done\` autonomously.
|
|
88
|
+
3. Repair missing context — if \`begin_work\` reports an unavailable checkout, warn the user and offer to fetch or link it. Never fetch without approval. If the user explicitly continues without it, call \`acknowledge_unavailable_checkout\`, then call \`begin_work\` again after every missing scope is fetched or acknowledged.
|
|
89
|
+
4. Delegate exploration — every pasted work item MUST use at least one read-only subagent. After \`begin_work\` joins, partition every available repository scope exactly once across bounded subagent invocations. Every child prompt must explicitly list its assigned \`repository_scope_ids\`; across the batch each available ID appears exactly once. When using Codex \`spawn_agent\`, MUST pass \`fork_turns: "none"\`; never pass \`all\` or a recent-turn count. Give each child a self-contained assignment containing only its exact scope IDs, checkout paths, inspection focus, read-only restrictions, and required report shape. Never include or inherit the parent \`/ctrl-spc work\` command. The child must not call any ctrl-spc tool—including \`get_task\` or \`begin_work\`—or pick up the work item; it only inspects assigned paths and returns its report. If zero scopes are available after explicit acknowledgements, one child inspects the task/artifact context and reports those unavailable scopes without pretending they were reviewed. Children inspect only: no file mutation, path reservation, ctrl-spc call, \`tee\`, shell redirection, temp/capture file, install, formatter, cache-generating command, or any command that can write. Inspection-first is sufficient; do not decide intent before reports are persisted. Each report uses \`{ agent, focus, repository_scope_ids, inspected_paths, findings, likely_impact, unresolved_questions }\`. If no subagent is available, do not explore directly: call \`record_context_exploration\` with \`blocked_reason = subagents_unavailable\`, tell the user, and stop.
|
|
90
|
+
5. Persist context before deciding — the parent consolidates every child report and MUST call \`record_context_exploration\` to create or update the work item's stable-purpose context artifact before deciding what to do, asking a task question, planning, reserving, or editing. Every agent and workflow stage revises the same task-level context artifact instead of creating another card. Pass the structured reports plus a separate coverage array; include every active repository scope exactly once, and give unavailable scopes their acknowledgement. Before the tool succeeds, commentary may state process only: never disclose findings, conclusions, likely impact, or decisions. After persistence, terminal summaries may reference the persisted artifact/question but must add no unpersisted facts.
|
|
91
|
+
6. Decide or ask deterministically — assigned workflow stage data decides the work. Without a workflow, classify intent only from the work item's explicit wording and accepted answers, never from findings. Findings do not authorize a deliverable. Infer and perform the action only when the item explicitly names it. Otherwise persist only the necessary question with \`ask_question\`. Choose \`free_text\` for an open response, \`single_select\` for exactly one choice, or \`multi_select\` for one or more choices; choice modes require 2–12 concise, unique options. For a bug, ask for reproduction steps, expected behavior, or actual behavior only when each is missing and not discoverable. For a novel feature with no explicit build action or deliverable, apply the HARD STOP above: the mandatory next mutating call after \`record_context_exploration\` is the specified multi-select \`ask_question\`. Generic goals such as “improve,” “coordinate,” “prepare,” or “support” are not deliverables. In this state do not call \`update_task\`, \`create_artifact\`, \`reserve_work_paths\`, or \`create_task\`; do not decide, edit, or complete. The context analysis is the only allowed artifact before the answer. Ask before overriding an unmet dependency, splitting work, fetching, or accepting a collision handoff. After \`ask_question\` succeeds, do no more work in that run: call \`end_work\` with reason \`pending_user_answer\` and outcome \`blocked\`, then wait for a later pasted run.
|
|
92
|
+
7. Persist every accepted answer and finding — \`ask_question\` creates first-class decisions and returns decision IDs for the web app to display and answer. When the user answers in the agent conversation instead, use \`record_user_input\` with one structured response per Decision: \`decision_id\`, \`selected_options\`, and optional \`answer_note\`. The legacy decision IDs plus exact-answer shape is free-text only. Decisions create no answer artifact and are not copied into the task description. Do not use progress comments; do not leave findings only in the terminal.
|
|
93
|
+
8. Reserve before writing — read-only stages never reserve writes or edit files. When the current stage allows writes, call \`reserve_work_paths\` for the exact checkout and paths; never edit through a collision. Never edit a conflicting path; narrow the reservation or use an isolated Git worktree.
|
|
94
|
+
9. Persist every substantive non-question output — give every artifact a clear title and stable \`purpose_key\`. Call \`create_artifact\` for a new purpose and \`update_artifact\` with the latest revision when the same purpose already exists; a different purpose creates a different artifact. Every artifact must have exactly one coverage disposition for every active repository scope. For workflow tasks, persist every required artifact first and then call \`hand_off_stage\`; it refuses while any step of the stage is unfinished. Plan approval is displayed on the submitted plan and is not a decision artifact. Never write planning documents into a repository.
|
|
95
|
+
10. Split only with approval — when work is too large, use \`ask_question\`; on approval create dependency-linked items with \`create_task\`, a shared feature tag, and matching board order.
|
|
96
|
+
11. Keep the item self-sufficient — questions and accepted answers are persisted as first-class decisions through \`ask_question\` and \`record_user_input\`; substantive outputs live in artifacts. Do not use progress comments; \`add_comment\` is not for routine progress. A fresh agent must need no terminal, chat, or subagent history.
|
|
97
|
+
12. End with proof — release finished reservations with \`release_work_paths\`, then call \`end_work\` with \`persistence_receipt = { final_task_revision, artifact_ids, outcome }\`. \`artifact_ids\` must exactly match every live artifact produced by this run and include the context-exploration artifact. Use outcome \`completed\` only for finished work, \`blocked\` for a persisted question or unavailable subagents, and \`aborted\` only when intentionally abandoning the run. A missing, stale, foreign, duplicate, or incomplete receipt is rejected; ending releases only this session's work.Full protocol: MCP prompt \`ctrl-spc-protocol\`.
|
|
98
|
+
|
|
99
|
+
## If the ctrl-spc tools are not in your tool list
|
|
100
|
+
|
|
101
|
+
First check your own tool list. **If the ctrl-spc tools are there, this section
|
|
102
|
+
does not apply**: do the work, and do not run \`cs status\` at all. Read on only if
|
|
103
|
+
they are actually missing.
|
|
104
|
+
|
|
105
|
+
The tools are registered into your agent by the CTRL+SPC daemon and are called by
|
|
106
|
+
name. There is no address to connect to.
|
|
107
|
+
|
|
108
|
+
**Diagnose it yourself. Do not ask the user to.** Run \`cs status\` and follow the
|
|
109
|
+
single next step it prints. Never probe a network address, never name a port,
|
|
110
|
+
never report the CTRL+SPC server down, and never hand the user a command whose
|
|
111
|
+
purpose is to find out what is wrong. The only things you ask the user for are
|
|
112
|
+
their password, permission to run something, and restarting their agent.
|
|
113
|
+
|
|
114
|
+
**Never suggest project configuration.** This is not per-project setup. Do not
|
|
115
|
+
create or suggest \`.mcp.json\`, a \`.claude/\` directory, or any file in the
|
|
116
|
+
project. If the tools are missing, the machine needs setup, not the codebase.
|
|
117
|
+
|
|
118
|
+
**Do one step per turn, in order.** Act only on the step \`cs status\` just
|
|
119
|
+
printed, never on one you expect to come next. The order is forced: sign in, then
|
|
120
|
+
start, then restart, and an earlier step must succeed before a later one can
|
|
121
|
+
work. Never ask for two things at once.
|
|
122
|
+
|
|
123
|
+
Act on what it says:
|
|
124
|
+
|
|
125
|
+
- **\`cs\` is not found at all.** CTRL+SPC is not installed on this machine. Tell
|
|
126
|
+
the user, and offer to run \`npm i -g @ctrl-spc/cs\`. Ask before running it.
|
|
127
|
+
- **It says to sign in.** Tell the user to run \`cs login\` themselves, and that a
|
|
128
|
+
browser will open where they sign in with the same email and password they use
|
|
129
|
+
on ctrl-spc.com. Do not run \`cs login\` yourself: it waits up to five minutes
|
|
130
|
+
for that browser. Then stop your turn and hand control back, saying you will
|
|
131
|
+
continue when they tell you sign-in is done. When they say it is done, run
|
|
132
|
+
\`cs status\` again yourself, tell them which account it shows, and carry on with
|
|
133
|
+
the step it now prints.
|
|
134
|
+
- **It says to run \`cs start\`.** Ask permission first, and tell the user it stays
|
|
135
|
+
running in the background to keep their computer online for CTRL+SPC.
|
|
136
|
+
\`cs start\` never exits on its own, so never run it as a command you wait on:
|
|
137
|
+
run exactly \`nohup cs start > /dev/null 2>&1 &\` (on Windows,
|
|
138
|
+
\`start /b cs start\`). Wait a few seconds, then run \`cs status\` again. If your
|
|
139
|
+
tools cannot leave a process running after a command returns, say so and ask
|
|
140
|
+
the user to run \`cs start\` in their own terminal window instead.
|
|
141
|
+
- **It says the tools are not registered into an agent.** Ask permission to
|
|
142
|
+
restart the background daemon, then start it again the same way, and run
|
|
143
|
+
\`cs status\` again. Do not ask the user to press Ctrl-C.
|
|
144
|
+
- **It says to restart your agent.** Ask the user to restart it, pass on the
|
|
145
|
+
reason \`cs status\` gave, and tell them the pasted work item will run once it
|
|
146
|
+
comes back.
|
|
147
|
+
|
|
148
|
+
If a command you ran fails, say which command failed and what it said. \`cs status\`
|
|
149
|
+
exiting non-zero is **not** one of those failures: it exits non-zero whenever
|
|
150
|
+
setup is not yet finished, which only means there is another step. Judge it by
|
|
151
|
+
what it prints, never by its exit code, and never treat a non-zero \`cs status\`
|
|
152
|
+
after a repair as proof the repair failed. Never report setup as complete on the
|
|
153
|
+
strength of a command you did not confirm with a fresh \`cs status\`.
|
|
154
|
+
`;
|
|
155
|
+
/** The three SKILL.md files. Byte-identical for Claude and Codex: the protocol
|
|
156
|
+
* is the same document, and one constant means they cannot drift apart the way
|
|
157
|
+
* v1's two copies did (Codex's still named a tool that no longer exists). */
|
|
158
|
+
const SKILL_TEXT = `---
|
|
159
|
+
name: ctrl-spc
|
|
160
|
+
description: ${SKILL_DESCRIPTION}
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
${PROTOCOL_BODY}`;
|
|
164
|
+
/** The Codex prompt file is the same body with no frontmatter. */
|
|
165
|
+
const PROMPT_TEXT = PROTOCOL_BODY;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
async function read(query, subject) {
|
|
2
|
+
const { data, error } = await query;
|
|
3
|
+
if (error)
|
|
4
|
+
throw new Error(`could not read ${subject}: ${error.message}`);
|
|
5
|
+
return data ?? [];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A workflow, its stages in order, and the exits off them.
|
|
9
|
+
*
|
|
10
|
+
* SORTED HERE RATHER THAN TRUSTED, which is the web reader's own rule: order is
|
|
11
|
+
* the whole point of a workflow, positions are deliberately not unique (a
|
|
12
|
+
* reorder rewrites the set), and a tie has to break the same way twice.
|
|
13
|
+
*
|
|
14
|
+
* FOUR READS RATHER THAN ONE EMBED. `cliv2_workflow_stages_in_workflow` carries
|
|
15
|
+
* two foreign keys into `cliv2_workflow_stages` (`stage_id` and
|
|
16
|
+
* `loop_to_stage_id`), so a nested embed of the stage document would need
|
|
17
|
+
* disambiguating by constraint name — a coupling to a constraint's spelling for
|
|
18
|
+
* the sake of saving a round trip on a tool call an agent makes once.
|
|
19
|
+
*/
|
|
20
|
+
export async function readWorkflow(client, workflowId) {
|
|
21
|
+
const found = await read(client.from('cliv2_workflows').select('id, name, description, ending').eq('id', workflowId), `workflow ${workflowId}`);
|
|
22
|
+
if (found.length === 0) {
|
|
23
|
+
throw new Error(`could not read workflow ${workflowId}: there is no such row, or it is not yours`);
|
|
24
|
+
}
|
|
25
|
+
const row = found[0];
|
|
26
|
+
const [links, exits] = await Promise.all([
|
|
27
|
+
read(client.from('cliv2_workflow_stages_in_workflow').select('stage_id, position')
|
|
28
|
+
.eq('workflow_id', workflowId), `the stages of workflow ${workflowId}`),
|
|
29
|
+
read(client.from('cliv2_workflow_stage_exits').select('stage_id, to_stage_id, condition, position')
|
|
30
|
+
.eq('workflow_id', workflowId), `the exits of workflow ${workflowId}`),
|
|
31
|
+
]);
|
|
32
|
+
const ordered = [...links].sort((a, b) => a.position - b.position || a.stage_id.localeCompare(b.stage_id));
|
|
33
|
+
const stageRows = ordered.length === 0 ? [] : await read(client.from('cliv2_workflow_stages').select('id, name, description, body')
|
|
34
|
+
.in('id', ordered.map((link) => link.stage_id)), `the stage documents of workflow ${workflowId}`);
|
|
35
|
+
const byId = new Map(stageRows.map((stage) => [stage.id, stage]));
|
|
36
|
+
return {
|
|
37
|
+
id: row.id,
|
|
38
|
+
name: row.name,
|
|
39
|
+
description: row.description ?? '',
|
|
40
|
+
/* Defaulted here as well as in the column, for the web reader's reason: a
|
|
41
|
+
workflow written before the ending existed reads back as 'end'. */
|
|
42
|
+
ending: row.ending ?? 'end',
|
|
43
|
+
stages: ordered.map((link) => {
|
|
44
|
+
const stage = byId.get(link.stage_id);
|
|
45
|
+
/* A LISTED STAGE THAT DID NOT COME BACK IS AN ERROR, NEVER A GAP. The
|
|
46
|
+
join cascades on delete and archiving is not deletion, so this cannot
|
|
47
|
+
happen through the product — and a stage silently missing from the
|
|
48
|
+
middle of a recipe would be a different process presented as this one. */
|
|
49
|
+
if (!stage) {
|
|
50
|
+
throw new Error(`could not read the stage documents of workflow ${workflowId}: stage ${link.stage_id} `
|
|
51
|
+
+ 'is in the workflow but its document did not come back');
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
id: stage.id,
|
|
55
|
+
name: stage.name,
|
|
56
|
+
description: stage.description ?? '',
|
|
57
|
+
body: stage.body ?? '',
|
|
58
|
+
};
|
|
59
|
+
}),
|
|
60
|
+
exits: [...exits]
|
|
61
|
+
.sort((a, b) => a.position - b.position || a.to_stage_id.localeCompare(b.to_stage_id))
|
|
62
|
+
.map((exit) => ({
|
|
63
|
+
stageId: exit.stage_id,
|
|
64
|
+
toStageId: exit.to_stage_id,
|
|
65
|
+
condition: exit.condition,
|
|
66
|
+
})),
|
|
67
|
+
};
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ctrl-spc/cs",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
9
|
"bin": {
|
|
10
|
-
"cs": "dist/index.js"
|
|
11
|
-
"cs3": "dist/panel3/cli.js"
|
|
10
|
+
"cs": "dist/index.js"
|
|
12
11
|
},
|
|
13
12
|
"files": [
|
|
14
13
|
"dist"
|