@clearlysh/beehaven 0.8.22 → 0.8.23
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/build-channel.js +2 -2
- package/dist/cli.js +37 -2
- package/dist/commands/agent-instructions.js +182 -0
- package/dist/commands/agent.js +5 -1
- package/dist/commands/workspace.js +16 -0
- package/dist/reserved-type.js +60 -0
- package/package.json +1 -1
- package/skills/clearly-agent/SKILL.md +5 -4
- package/skills/index.json +1 -1
package/dist/build-channel.js
CHANGED
|
@@ -53,9 +53,9 @@ export const PUBLIC_BUILD = true;
|
|
|
53
53
|
* ⚠ REWRITTEN IN `dist/` AT BUILD TIME, like `PUBLIC_BUILD`. `unstamped` is what a source checkout
|
|
54
54
|
* carries and what `tsx`/`vitest` see, and it is honest: nothing has stamped them.
|
|
55
55
|
*/
|
|
56
|
-
export const BUILD_SHA = '
|
|
56
|
+
export const BUILD_SHA = '8585349e92e9-dirty';
|
|
57
57
|
/** ISO-8601 minute precision, UTC. Rewritten in `dist/` by `scripts/stamp-build-channel.mjs`. */
|
|
58
|
-
export const BUILD_TIME = '2026-
|
|
58
|
+
export const BUILD_TIME = '2026-10-02T00:47Z';
|
|
59
59
|
/**
|
|
60
60
|
* The build identity as one short string, for `--version`.
|
|
61
61
|
*
|
package/dist/cli.js
CHANGED
|
@@ -309,7 +309,8 @@ ${DEV_ONLY_HELP ? ` PROJECT — build your own Durable Object backend (HIVE_DE
|
|
|
309
309
|
app setup <name> Re-run a hive's setup script
|
|
310
310
|
install <pkg> Shorthand for "app install <pkg>"
|
|
311
311
|
skills install Teach every agent about Clearly —
|
|
312
|
-
writes the shipped SKILL.md files
|
|
312
|
+
writes the shipped SKILL.md files + the
|
|
313
|
+
AGENTS.md / GEMINI.md / CLAUDE.md block
|
|
313
314
|
skills <list|sync|remove> Manage skills paired with installed hives
|
|
314
315
|
|
|
315
316
|
Deep dive: beehaven help hives Mental model + install/connect/call loop
|
|
@@ -437,7 +438,9 @@ ${DEV_ONLY_HELP ? `
|
|
|
437
438
|
ACCOUNT
|
|
438
439
|
─────────────────────────────────────────────────────────────────────
|
|
439
440
|
|
|
440
|
-
login Sign in (browser) — primary flow
|
|
441
|
+
login Sign in (browser) — primary flow; also teaches
|
|
442
|
+
your agents (skills + AGENTS.md block;
|
|
443
|
+
--no-agent-setup to skip)
|
|
441
444
|
login --email <e> --password <p> Self-hosted email/password (set BEEHAVEN_API_URL)
|
|
442
445
|
logout Clear local credentials + keychain
|
|
443
446
|
signup Open sign-up page in browser
|
|
@@ -1492,6 +1495,7 @@ async function run() {
|
|
|
1492
1495
|
// Paired Claude Code skill install policy (see commands/skills.ts)
|
|
1493
1496
|
'with-skills': { type: 'boolean', default: false },
|
|
1494
1497
|
'no-skills': { type: 'boolean', default: false },
|
|
1498
|
+
'no-agent-setup': { type: 'boolean', default: false },
|
|
1495
1499
|
// ── mobile-install metadata for `beehaven teleport <build>.ipa --composition <id>`
|
|
1496
1500
|
// (see commands/workspace.ts — wantMobileInstall branch).
|
|
1497
1501
|
// Future: parse Info.plist / AndroidManifest.xml automatically
|
|
@@ -3303,6 +3307,31 @@ async function run() {
|
|
|
3303
3307
|
*/
|
|
3304
3308
|
console.log(` ${color.green('✓')} Connected — your new sign-in is live`);
|
|
3305
3309
|
console.log(` Background service ${wasRunning ? 'restarted' : 'started'} (PID ${pid})`);
|
|
3310
|
+
/**
|
|
3311
|
+
* ⚠⚠ TEACH THE AGENTS AS PART OF SIGNING IN (PPO-190). "Install the CLI, log in, and your
|
|
3312
|
+
* agents handle the rest" needs the agents to KNOW about the CLI — the skills plus the
|
|
3313
|
+
* instruction block in ~/.codex/AGENTS.md, ~/.gemini/GEMINI.md and a pointer in
|
|
3314
|
+
* ~/.claude/CLAUDE.md. That used to be a separate `beehaven skills install` the installer's
|
|
3315
|
+
* closing text asked for, and a step people skip is a step that did not happen.
|
|
3316
|
+
*
|
|
3317
|
+
* ⚠ Idempotent and non-fatal: unchanged files are not rewritten, symlinked files are left
|
|
3318
|
+
* alone, and a failure here never fails the sign-in. Opt out with `--no-agent-setup` or
|
|
3319
|
+
* BEEHAVEN_NO_AGENT_SETUP=1; undo with `beehaven skills uninstall`.
|
|
3320
|
+
*/
|
|
3321
|
+
if (!values['no-agent-setup'] && process.env.BEEHAVEN_NO_AGENT_SETUP !== '1') {
|
|
3322
|
+
try {
|
|
3323
|
+
const { installAgentSkills } = await import('./commands/agent-skills.js');
|
|
3324
|
+
const { installAgentInstructions } = await import('./commands/agent-instructions.js');
|
|
3325
|
+
const sk = installAgentSkills();
|
|
3326
|
+
const ins = installAgentInstructions();
|
|
3327
|
+
const changed = [...sk.steps, ...ins.steps].filter((x) => /=(written|updated)$/.test(x)).length;
|
|
3328
|
+
console.log(` ${color.green('✓')} Your agents know about Clearly${changed ? '' : color.dim(' (already set up)')}`);
|
|
3329
|
+
console.log(color.dim(' Skills + instructions for Claude Code, Codex, Gemini and pi · undo: beehaven skills uninstall'));
|
|
3330
|
+
}
|
|
3331
|
+
catch (err) {
|
|
3332
|
+
console.log(color.dim(` Agent setup skipped: ${err?.message ?? err} — run: beehaven skills install`));
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3306
3335
|
console.log('');
|
|
3307
3336
|
break;
|
|
3308
3337
|
}
|
|
@@ -3897,7 +3926,13 @@ complete -F _beehaven_completions beehaven`);
|
|
|
3897
3926
|
*/
|
|
3898
3927
|
if (sub === 'install' || sub === 'uninstall') {
|
|
3899
3928
|
const { installAgentSkills, removeAgentSkills, AGENT_SKILL_DIRS } = await import('./commands/agent-skills.js');
|
|
3929
|
+
const { installAgentInstructions, removeAgentInstructions } = await import('./commands/agent-instructions.js');
|
|
3900
3930
|
const r = sub === 'install' ? installAgentSkills() : removeAgentSkills();
|
|
3931
|
+
// ⚠ The instruction block travels with the skills (PPO-190) — a skill is only OFFERED;
|
|
3932
|
+
// the block is what makes an agent reach for `beehaven` at all.
|
|
3933
|
+
const ins = sub === 'install' ? installAgentInstructions() : removeAgentInstructions();
|
|
3934
|
+
r.steps.push(...ins.steps);
|
|
3935
|
+
r.warnings.push(...ins.warnings);
|
|
3901
3936
|
const wrote = r.steps.filter((x) => /=(written|updated)$/.test(x)).length;
|
|
3902
3937
|
const current = r.steps.filter((x) => x.endsWith('=current')).length;
|
|
3903
3938
|
const removed = r.steps.filter((x) => x.endsWith('=removed')).length;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-instructions — the "use the beehaven CLI" block in each agent's GLOBAL instruction file.
|
|
3
|
+
*
|
|
4
|
+
* ⚠⚠ THE MISSING HALF OF "install the CLI, log in, and your agents handle the rest" (PPO-190).
|
|
5
|
+
* `skills install` put SKILL.md files on disk, but a skill is only OFFERED to a model when its
|
|
6
|
+
* description matches. The instruction block is what makes an agent reach for `beehaven` at all
|
|
7
|
+
* when the user says "put this on my board". The Mac app has written it on every launch
|
|
8
|
+
* (`apps/electron/electron/agent-skills-installer.ts`); the CLI never did, so a CLI-only user's
|
|
9
|
+
* Codex and Gemini had skills they never used.
|
|
10
|
+
*
|
|
11
|
+
* ⚠⚠ BYTE-IDENTICAL TO THE MAC APP'S BLOCK, MARKER LINE INCLUDED. Both writers splice between the
|
|
12
|
+
* same BLOCK_START / BLOCK_END. If their text differed by one character, a machine with both would
|
|
13
|
+
* rewrite the file on every app launch and every CLI install, each undoing the other. One text
|
|
14
|
+
* means both converge and either uninstaller removes it. `agentInstructionParity.test.mjs` in the
|
|
15
|
+
* root repo pins the two copies together.
|
|
16
|
+
*
|
|
17
|
+
* ⚠ CLAUDE GETS THE SHORT POINTER — `~/.claude/CLAUDE.md` is prepended to every Claude Code
|
|
18
|
+
* session in every repository, so it carries a pointer to the skills, not a manual. Same rule as
|
|
19
|
+
* the Mac app; see its note on `AGENT_INSTRUCTION_FILES`.
|
|
20
|
+
*
|
|
21
|
+
* ⚠ ONLY OUR BLOCK IS EVER TOUCHED. Text around it is preserved byte for byte; a file with no
|
|
22
|
+
* block gets one appended; removal deletes the block and nothing else.
|
|
23
|
+
*/
|
|
24
|
+
import fs from 'fs';
|
|
25
|
+
import os from 'os';
|
|
26
|
+
import path from 'path';
|
|
27
|
+
const MANAGED_MARKER = 'managed-by: clearly-mac-app';
|
|
28
|
+
export const BLOCK_START = '<!-- clearly:beehaven-cli:start -->';
|
|
29
|
+
export const BLOCK_END = '<!-- clearly:beehaven-cli:end -->';
|
|
30
|
+
/** Resolved at call time so tests can point HOME at a temp dir. */
|
|
31
|
+
export function agentInstructionFiles(home = os.homedir()) {
|
|
32
|
+
return [
|
|
33
|
+
{ agent: 'codex', file: path.join(home, '.codex', 'AGENTS.md') },
|
|
34
|
+
{ agent: 'gemini', file: path.join(home, '.gemini', 'GEMINI.md') },
|
|
35
|
+
{ agent: 'claude', file: path.join(home, '.claude', 'CLAUDE.md') },
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
/** The managed block appended to AGENTS.md / GEMINI.md. Keep identical to the Mac app's. */
|
|
39
|
+
export function instructionBlock() {
|
|
40
|
+
return [
|
|
41
|
+
BLOCK_START,
|
|
42
|
+
`<!-- ${MANAGED_MARKER}. Edits inside this block are overwritten on app launch; edit outside it. -->`,
|
|
43
|
+
'',
|
|
44
|
+
'## Clearly — drive it with the `beehaven` CLI',
|
|
45
|
+
'',
|
|
46
|
+
'Clearly (clearly.sh) is a workspace — documents, projects, boards, tickets and a design',
|
|
47
|
+
'canvas. Reach it from the shell with the `beehaven` CLI, NOT an HTTP API you construct.',
|
|
48
|
+
'',
|
|
49
|
+
'⚠ SIGN IN FIRST. Every call is attributed to an agent identity, and without one the CLI',
|
|
50
|
+
'refuses. `login` MINTS the name if it does not exist yet, and binds this terminal:',
|
|
51
|
+
'',
|
|
52
|
+
'```sh',
|
|
53
|
+
'beehaven agent login <your-name> # required — mints + binds, once per session',
|
|
54
|
+
'beehaven connect home # target the personal workspace',
|
|
55
|
+
'beehaven actions # list every action available there',
|
|
56
|
+
'beehaven call <action> \'{...}\'',
|
|
57
|
+
'```',
|
|
58
|
+
'',
|
|
59
|
+
'Read the full skills before driving it — they carry the exact verbs and node shapes:',
|
|
60
|
+
'',
|
|
61
|
+
'- `~/.<agent>/skills/clearly-agent/SKILL.md` — **read this first**: identity, sessions, your brief',
|
|
62
|
+
'- `~/.<agent>/skills/beehaven-cli/SKILL.md` — connect / call / sql / logs, the daemon',
|
|
63
|
+
'- `~/.<agent>/skills/clearly-workspace/SKILL.md` — audit what a workspace holds and who made it',
|
|
64
|
+
'- `~/.<agent>/skills/clearly-canvas/SKILL.md` — build + perceive a canvas, comm blocks, code diffs',
|
|
65
|
+
'- `~/.<agent>/skills/design-craft/SKILL.md` — **load before any visual work**: grid, type, colour, the studio pass',
|
|
66
|
+
'- `~/.<agent>/skills/layout-systems/SKILL.md` — grids and systems across many artboards',
|
|
67
|
+
'- `~/.<agent>/skills/pair-on-canvas/SKILL.md` — drive a coding task from a spec on the canvas',
|
|
68
|
+
'- `~/.<agent>/skills/codebase-map/SKILL.md` — draw a repo’s architecture on the canvas',
|
|
69
|
+
'',
|
|
70
|
+
'If a command reports the daemon is unreachable, `beehaven login` re-auths and',
|
|
71
|
+
'`beehaven start --headless` starts a daemon when the Mac app is not running.',
|
|
72
|
+
BLOCK_END,
|
|
73
|
+
].join('\n');
|
|
74
|
+
}
|
|
75
|
+
/** The short form, for `~/.claude/CLAUDE.md`. Keep identical to the Mac app's `claudeBlock()`. */
|
|
76
|
+
export function claudeBlock() {
|
|
77
|
+
return [
|
|
78
|
+
BLOCK_START,
|
|
79
|
+
`<!-- ${MANAGED_MARKER}. Edits inside this block are overwritten on app launch; edit outside it. -->`,
|
|
80
|
+
'',
|
|
81
|
+
'**Clearly workspace** — drive it from the shell with the `beehaven` CLI (documents, projects,',
|
|
82
|
+
'boards, tickets, canvas). Sign in once per session first: `beehaven agent login <name>` —',
|
|
83
|
+
'it mints the identity if new. Full detail is in the skills, `clearly-agent` first.',
|
|
84
|
+
BLOCK_END,
|
|
85
|
+
].join('\n');
|
|
86
|
+
}
|
|
87
|
+
/** Insert or replace our block, leaving everything around it untouched. Pure. */
|
|
88
|
+
export function upsertBlock(existing, block) {
|
|
89
|
+
const start = existing.indexOf(BLOCK_START);
|
|
90
|
+
const end = existing.indexOf(BLOCK_END);
|
|
91
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
92
|
+
return existing.slice(0, start) + block + existing.slice(end + BLOCK_END.length);
|
|
93
|
+
}
|
|
94
|
+
return existing.trim() ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
|
|
95
|
+
}
|
|
96
|
+
/** Remove our block (and the blank line it was appended after). Pure. */
|
|
97
|
+
export function stripBlock(existing) {
|
|
98
|
+
const start = existing.indexOf(BLOCK_START);
|
|
99
|
+
const end = existing.indexOf(BLOCK_END);
|
|
100
|
+
if (start === -1 || end === -1 || end < start)
|
|
101
|
+
return existing;
|
|
102
|
+
const before = existing.slice(0, start).replace(/\n{1,2}$/, '\n');
|
|
103
|
+
const after = existing.slice(end + BLOCK_END.length).replace(/^\n/, '');
|
|
104
|
+
const out = (before + after);
|
|
105
|
+
return out.trim() ? out : '';
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Write the block into every agent's global instruction file. Idempotent: an unchanged file is
|
|
109
|
+
* reported `=current` and not rewritten.
|
|
110
|
+
*
|
|
111
|
+
* ⚠ A SYMLINKED INSTRUCTION FILE IS LEFT ALONE. People keep dotfiles in a repo and link them in;
|
|
112
|
+
* writing through the link would silently edit their checkout. Same rule as the skills installer.
|
|
113
|
+
*/
|
|
114
|
+
export function installAgentInstructions(home = os.homedir()) {
|
|
115
|
+
const steps = [];
|
|
116
|
+
const warnings = [];
|
|
117
|
+
for (const { agent, file } of agentInstructionFiles(home)) {
|
|
118
|
+
try {
|
|
119
|
+
/**
|
|
120
|
+
* ⚠ ONLY FOR AGENTS THAT ARE INSTALLED. Creating `~/.gemini/GEMINI.md` for someone without
|
|
121
|
+
* Gemini invents an agent: the skills installer keys on that same directory existing, so
|
|
122
|
+
* the NEXT run would install skills for it too. Same rule as `installAgentSkills`.
|
|
123
|
+
*/
|
|
124
|
+
if (!fs.existsSync(path.dirname(file))) {
|
|
125
|
+
steps.push(`${agent}/instructions=agent-absent`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (isSymlink(file)) {
|
|
129
|
+
steps.push(`${agent}/instructions=symlink-skipped`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
let existing = '';
|
|
133
|
+
try {
|
|
134
|
+
existing = fs.readFileSync(file, 'utf8');
|
|
135
|
+
}
|
|
136
|
+
catch { /* first run */ }
|
|
137
|
+
const next = upsertBlock(existing, agent === 'claude' ? claudeBlock() : instructionBlock());
|
|
138
|
+
if (next === existing) {
|
|
139
|
+
steps.push(`${agent}/instructions=current`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
fs.writeFileSync(file, next, { mode: 0o644 });
|
|
143
|
+
steps.push(`${agent}/instructions=${existing ? 'updated' : 'written'}`);
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
warnings.push(`${agent}/instructions: ${e?.message ?? String(e)}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { steps, warnings };
|
|
150
|
+
}
|
|
151
|
+
/** Remove only our block. A file that becomes empty is deleted only if we created all of it. */
|
|
152
|
+
export function removeAgentInstructions(home = os.homedir()) {
|
|
153
|
+
const steps = [];
|
|
154
|
+
const warnings = [];
|
|
155
|
+
for (const { agent, file } of agentInstructionFiles(home)) {
|
|
156
|
+
try {
|
|
157
|
+
if (!fs.existsSync(file) || isSymlink(file))
|
|
158
|
+
continue;
|
|
159
|
+
const existing = fs.readFileSync(file, 'utf8');
|
|
160
|
+
const next = stripBlock(existing);
|
|
161
|
+
if (next === existing)
|
|
162
|
+
continue;
|
|
163
|
+
if (next)
|
|
164
|
+
fs.writeFileSync(file, next);
|
|
165
|
+
else
|
|
166
|
+
fs.unlinkSync(file);
|
|
167
|
+
steps.push(`${agent}/instructions=removed`);
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
warnings.push(`${agent}/instructions: ${e?.message ?? String(e)}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { steps, warnings };
|
|
174
|
+
}
|
|
175
|
+
function isSymlink(file) {
|
|
176
|
+
try {
|
|
177
|
+
return fs.lstatSync(file).isSymbolicLink();
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
package/dist/commands/agent.js
CHANGED
|
@@ -845,7 +845,11 @@ async function agentLogin(args, values) {
|
|
|
845
845
|
console.error(` ${yellow('⚠')} Signed in, but this workspace refused ${cyan(name)}: ${outcome.reason}`);
|
|
846
846
|
if (/revoked|not active|unknown agent|not admitted/i.test(`${outcome.scope} ${outcome.reason}`)) {
|
|
847
847
|
console.error(dim(` The agent is archived or not admitted here. Restore it in Settings → Agent access,`));
|
|
848
|
-
|
|
848
|
+
// ⚠ The workspace names the live identity in its refusal (PPO-190). The old advice —
|
|
849
|
+
// `beehaven call agent-list` — ran AS this refused identity and was refused in turn.
|
|
850
|
+
if (!/Log in as one:/.test(outcome.reason)) {
|
|
851
|
+
console.error(dim(` or log in as an active agent: beehaven agent login <name> (the Settings → Agent access page lists them).`));
|
|
852
|
+
}
|
|
849
853
|
}
|
|
850
854
|
console.error(dim(` Calls from this session run as ${name} and will be refused — they will NOT fall back to another agent.`));
|
|
851
855
|
process.exitCode = 1;
|
|
@@ -860,6 +860,22 @@ export async function cmdCall(action, rawInput, values) {
|
|
|
860
860
|
input = { ...input, action: canonical, name: canonical };
|
|
861
861
|
}
|
|
862
862
|
}
|
|
863
|
+
// ⚠ `type` IS THE WIRE'S ACTION NAME (PPO-190): a top-level `type` would be overwritten by the
|
|
864
|
+
// daemon's `{ ...input, type: action }` and lost with `ok: true`. Move it to the verb's own field
|
|
865
|
+
// or refuse with the right spelling — never drop it in silence. See reserved-type.ts.
|
|
866
|
+
{
|
|
867
|
+
const { reconcileReservedType } = await import('../reserved-type.js');
|
|
868
|
+
const r = reconcileReservedType(action, input);
|
|
869
|
+
if (r.kind === 'refused') {
|
|
870
|
+
console.error(` ${r.error}`);
|
|
871
|
+
process.exit(1);
|
|
872
|
+
}
|
|
873
|
+
if (r.kind === 'moved') {
|
|
874
|
+
input = r.input;
|
|
875
|
+
if (!values.json)
|
|
876
|
+
console.error(` · ${r.note}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
863
879
|
// Confirm spend BEFORE resolving a building or touching the daemon — a cancelled call must
|
|
864
880
|
// cost nothing and behave identically whether the input came from argv, a file, or stdin.
|
|
865
881
|
// It runs after the parse because the gate is input-aware: `engine:'handroll'` and any call
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reserved-type — `type` is the WIRE'S action name, so a top-level `type` in a call's input is lost.
|
|
3
|
+
*
|
|
4
|
+
* ⚠⚠ SILENT DATA LOSS (PPO-190). The daemon sends `{ ...input, type: action }`. An agent that calls
|
|
5
|
+
* `ticket-create '{"title":"…","type":"bug"}'` gets `ok: true` and a ticket that is NOT a bug: its
|
|
6
|
+
* `type` was overwritten by `"ticket-create"` before the workspace ever saw it. AGENTS.md has
|
|
7
|
+
* carried "use `issueType`, not `type`" as a warning, which is a rule every new agent has to be
|
|
8
|
+
* told and every one of them breaks once.
|
|
9
|
+
*
|
|
10
|
+
* The verbs already accept a non-colliding spelling (`issueType`, `linkType`, `proposalType`,
|
|
11
|
+
* `kind`). This moves `type` onto that spelling when the verb has one, and refuses with the right
|
|
12
|
+
* spelling when it does not — either way, nothing is dropped in silence.
|
|
13
|
+
*/
|
|
14
|
+
/** Verb → the field the workspace reads instead of `type`. */
|
|
15
|
+
export const TYPE_ALIASES = {
|
|
16
|
+
// Each entry is a verb whose handler reads this field (verified against apps/cloudflare/src).
|
|
17
|
+
'ticket-create': 'issueType',
|
|
18
|
+
'ticket-update': 'issueType',
|
|
19
|
+
'ticket-link-create': 'linkType',
|
|
20
|
+
'proposal-create': 'proposalType',
|
|
21
|
+
'composition-create': 'kind',
|
|
22
|
+
'record-thought': 'kind',
|
|
23
|
+
'thought-record': 'kind',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Reconcile a top-level `type` with the wire's use of the name. Pure.
|
|
27
|
+
*
|
|
28
|
+
* ⚠ A `type` EQUAL TO THE ACTION is left alone — that is a caller spelling the action twice, which
|
|
29
|
+
* the wire already agrees with. Nested `type`s (canvas nodes, `updates.type`) never collide and are
|
|
30
|
+
* never inspected.
|
|
31
|
+
*/
|
|
32
|
+
export function reconcileReservedType(action, input) {
|
|
33
|
+
if (!input || typeof input !== 'object' || !Object.prototype.hasOwnProperty.call(input, 'type'))
|
|
34
|
+
return { kind: 'none' };
|
|
35
|
+
const value = input.type;
|
|
36
|
+
if (value === undefined || value === action)
|
|
37
|
+
return { kind: 'none' };
|
|
38
|
+
const field = TYPE_ALIASES[action];
|
|
39
|
+
if (field) {
|
|
40
|
+
const { type: _dropped, ...rest } = input;
|
|
41
|
+
if (rest[field] !== undefined && rest[field] !== value) {
|
|
42
|
+
return {
|
|
43
|
+
kind: 'refused',
|
|
44
|
+
error: `both "type" (${JSON.stringify(value)}) and "${field}" (${JSON.stringify(rest[field])}) were given — `
|
|
45
|
+
+ `"type" is the action name on the wire and would be dropped; pass only "${field}".`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
kind: 'moved',
|
|
50
|
+
field,
|
|
51
|
+
input: { ...rest, [field]: value },
|
|
52
|
+
note: `"type" is reserved for the action name on the wire — sent as "${field}": ${JSON.stringify(value)}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
kind: 'refused',
|
|
57
|
+
error: `"type" is reserved for the action name on the wire, so ${action} would never see ${JSON.stringify(value)}. `
|
|
58
|
+
+ `Use the verb's own field (see: beehaven describe ${action}).`,
|
|
59
|
+
};
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -211,7 +211,8 @@ ask the owner.
|
|
|
211
211
|
`beehaven call agent-list '{}'` shows the live roster; log in as one of those.
|
|
212
212
|
- ⚠ **`workspace-sql` takes `query`**, not `sql`. The wrong key is silently dropped and you get the
|
|
213
213
|
SCHEMA back — `ok: true`, a confident list of tables, not your answer.
|
|
214
|
-
-
|
|
215
|
-
|
|
216
|
-
-
|
|
217
|
-
|
|
214
|
+
- **`document-create` takes `markdown` or `html`.** `content`, `body` and `text` are accepted as
|
|
215
|
+
markdown aliases, but prefer `markdown`.
|
|
216
|
+
- **`ticket-create` / `ticket-update` take `issueType` and `body`.** The wire uses `type` as the action
|
|
217
|
+
name, so the CLI moves a top-level `type` to `issueType` for you and refuses it on verbs with no
|
|
218
|
+
such field. `description` is accepted as `body`.
|