beehaven 0.8.24 → 0.8.27
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-state.js +19 -0
- package/dist/browser-login.js +283 -0
- package/dist/build-channel.js +2 -2
- package/dist/cli.js +13 -265
- package/dist/commands/agent.js +186 -59
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/compose.js +2 -2
- package/dist/commands/help.js +2 -0
- package/dist/commands/self-update.js +2 -2
- package/dist/commands/workspace.js +8 -3
- package/dist/daemon.js +9 -9
- package/dist/folder-sync.js +1 -1
- package/dist/local-app-manager.js +1 -1
- package/dist/setup-relay.js +20 -235
- package/package.json +1 -1
- package/skills/clearly-agent/SKILL.md +14 -3
- package/skills/clearly-canvas/SKILL.md +9 -0
- package/skills/clearly-canvas/authoring.md +4 -4
- package/skills/index.json +2 -2
package/dist/commands/agent.js
CHANGED
|
@@ -25,7 +25,7 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
|
25
25
|
import { join, dirname } from 'node:path';
|
|
26
26
|
import { homedir } from 'node:os';
|
|
27
27
|
import { rpcOnce } from '../ipc-client.js';
|
|
28
|
-
import { saveAgent, loadAgent, listAgents, deleteAgent, findAgentByAvatar, isValidAgentName, resolveActiveAgent, activeAgentName, saveAgentSession, clearAgentSession, decodeAgentTokenClaims, } from '../agent-state.js';
|
|
28
|
+
import { saveAgent, loadAgent, listAgents, deleteAgent, findAgentByAvatar, isValidAgentName, resolveActiveAgent, activeAgentName, saveAgentSession, clearAgentSession, clearAgentSessionId, decodeAgentTokenClaims, } from '../agent-state.js';
|
|
29
29
|
const VALID_KINDS = ['claude-code', 'daemon-agent', 'clearly-ai'];
|
|
30
30
|
export const FREE_SKINS = [
|
|
31
31
|
'classic',
|
|
@@ -64,6 +64,16 @@ function normalizeCliAvatarSkin(raw) {
|
|
|
64
64
|
* socket alone cannot update the Durable Object row, so logout used to leave an `active` session
|
|
65
65
|
* behind until the server's idle sweep noticed it. Best-effort keeps offline logout usable; the
|
|
66
66
|
* 30-minute server reaper is the fallback when the relay is unavailable. */
|
|
67
|
+
/**
|
|
68
|
+
* End the agent's workspace work session.
|
|
69
|
+
*
|
|
70
|
+
* ⚠⚠ `summary` IS THE AGENT'S HANDOFF NOTE, NEVER A LIFECYCLE REASON (AWO-225). The next login's
|
|
71
|
+
* brief prints a session's summary as its payload — "what I did, what's next". Every lifecycle end
|
|
72
|
+
* used to pass its reason here ("Replaced by a new login session for this agent identity.",
|
|
73
|
+
* "Agent explicitly logged out."), so the payload slot only ever held boilerplate. Worse, the
|
|
74
|
+
* Worker accepts a summary on an ALREADY-ENDED session, so a login after `agent handoff` replaced
|
|
75
|
+
* the agent's real note with "Replaced by a new login session". Only `agent handoff` passes one.
|
|
76
|
+
*/
|
|
67
77
|
async function endWorkspaceSession(record, summary) {
|
|
68
78
|
const sessionId = String(record?.sessionId ?? '').trim();
|
|
69
79
|
if (!sessionId)
|
|
@@ -72,7 +82,7 @@ async function endWorkspaceSession(record, summary) {
|
|
|
72
82
|
const response = await rpcOnce('agent-session-end', {
|
|
73
83
|
id: sessionId,
|
|
74
84
|
...(record?.agentId ? { agentId: record.agentId } : {}),
|
|
75
|
-
summary,
|
|
85
|
+
...(summary?.trim() ? { summary: summary.trim() } : {}),
|
|
76
86
|
}, 5_000);
|
|
77
87
|
const result = response?.result ?? response;
|
|
78
88
|
return result?.ok !== false && !result?.error;
|
|
@@ -106,6 +116,8 @@ export async function cmdAgentSub(positionals, values) {
|
|
|
106
116
|
return agentStatus(rest, values);
|
|
107
117
|
case 'logout':
|
|
108
118
|
return agentLogout(rest, values);
|
|
119
|
+
case 'handoff':
|
|
120
|
+
return agentHandoff(rest, values);
|
|
109
121
|
case 'delete':
|
|
110
122
|
case 'remove':
|
|
111
123
|
return agentDelete(rest, values);
|
|
@@ -134,6 +146,7 @@ export function isAgentIdentitySub(sub) {
|
|
|
134
146
|
sub === 'whoami' ||
|
|
135
147
|
sub === 'status' ||
|
|
136
148
|
sub === 'logout' ||
|
|
149
|
+
sub === 'handoff' ||
|
|
137
150
|
sub === 'delete' ||
|
|
138
151
|
sub === 'remove' ||
|
|
139
152
|
sub === 'rebind' ||
|
|
@@ -161,6 +174,7 @@ function printAgentUsage() {
|
|
|
161
174
|
console.log(' list list stored identities');
|
|
162
175
|
console.log(' whoami show the active identity');
|
|
163
176
|
console.log(' status [<name>] --message <text> set the persistent desktop status label');
|
|
177
|
+
console.log(' handoff "<summary>" end the work session with a note the next login reads first');
|
|
164
178
|
console.log(' logout [<name>] end the session, KEEP the identity (default: active)');
|
|
165
179
|
console.log(' delete <name> archive this workspace projection + remove this device copy');
|
|
166
180
|
console.log(' delete <name> --account --yes delete the portable account identity + this device copy');
|
|
@@ -687,7 +701,7 @@ async function agentLogin(args, values) {
|
|
|
687
701
|
* actually began. `existingRecord` is read before the write for exactly this.
|
|
688
702
|
*/
|
|
689
703
|
const birthday = existingRecord?.birthday ?? new Date().toISOString();
|
|
690
|
-
const previousSessionEnded = await endWorkspaceSession(existingRecord
|
|
704
|
+
const previousSessionEnded = await endWorkspaceSession(existingRecord);
|
|
691
705
|
/**
|
|
692
706
|
* ⚠⚠ FILL THE LIFETIME FIELDS FROM THE TOKEN — the cloud mint path does not
|
|
693
707
|
* return them and a record without `expiresAt` can never be seen as expired.
|
|
@@ -880,104 +894,182 @@ async function agentLogin(args, values) {
|
|
|
880
894
|
console.log('');
|
|
881
895
|
}
|
|
882
896
|
/**
|
|
883
|
-
*
|
|
897
|
+
* Summaries older CLIs wrote when a session ended for a lifecycle reason. They are not handoff
|
|
898
|
+
* notes (AWO-225), so the brief does not print them as one; sessions that carry them still show
|
|
899
|
+
* their label and counts. Kept until no client in the field writes them.
|
|
900
|
+
*/
|
|
901
|
+
const LIFECYCLE_SUMMARIES = new Set([
|
|
902
|
+
'Replaced by a new login session for this agent identity.',
|
|
903
|
+
'Agent explicitly logged out.',
|
|
904
|
+
'Agent identity deleted.',
|
|
905
|
+
'Account agent identity deleted.',
|
|
906
|
+
]);
|
|
907
|
+
const briefAgo = (ts, now) => {
|
|
908
|
+
const d = now - ts;
|
|
909
|
+
if (d < 60_000)
|
|
910
|
+
return 'just now';
|
|
911
|
+
if (d < 3_600_000)
|
|
912
|
+
return `${Math.round(d / 60_000)}m ago`;
|
|
913
|
+
if (d < 86_400_000)
|
|
914
|
+
return `${Math.round(d / 3_600_000)}h ago`;
|
|
915
|
+
return `${Math.round(d / 86_400_000)}d ago`;
|
|
916
|
+
};
|
|
917
|
+
const oneLine = (v, max) => {
|
|
918
|
+
const t = String(v ?? '').replace(/\s+/g, ' ').trim();
|
|
919
|
+
return t.length > max ? `${t.slice(0, max - 1)}…` : t;
|
|
920
|
+
};
|
|
921
|
+
/**
|
|
922
|
+
* The login digest, as lines (AWO-225: pure, so it can be tested without a workspace).
|
|
884
923
|
*
|
|
885
924
|
* Everything here answers a question a returning colleague asks before touching anything, and
|
|
886
925
|
* NOTHING here is printed when it is empty — a brief padded with "0 sessions · 0 memories ·
|
|
887
926
|
* nothing assigned" is a wall an agent learns to skip, which costs the one thing the digest
|
|
888
927
|
* has to buy: being read.
|
|
928
|
+
*
|
|
929
|
+
* Order is the order of obligation: what someone asked you, what you left yourself, what is yours
|
|
930
|
+
* to do, what is waiting on a person, then where you were and what moved while you were away.
|
|
889
931
|
*/
|
|
890
|
-
function
|
|
932
|
+
export function formatBrief(brief, now = Date.now()) {
|
|
933
|
+
const out = [];
|
|
891
934
|
if (!brief)
|
|
892
|
-
return;
|
|
935
|
+
return out;
|
|
936
|
+
const section = (title, count) => {
|
|
937
|
+
out.push('');
|
|
938
|
+
out.push(` ${bold(title)}${count === undefined ? '' : ` ${dim(`(${count})`)}`}`);
|
|
939
|
+
};
|
|
940
|
+
const ws = brief.workspace ?? {};
|
|
893
941
|
const sessions = brief.sessions?.recent ?? [];
|
|
894
942
|
const assigned = brief.assignedToMe ?? [];
|
|
895
943
|
const pending = brief.awaitingApproval ?? [];
|
|
896
944
|
const memories = brief.memories ?? [];
|
|
897
945
|
const focus = brief.focus ?? [];
|
|
946
|
+
const recents = brief.recents ?? [];
|
|
947
|
+
const drafts = brief.drafts ?? [];
|
|
948
|
+
const changed = ws.changed ?? [];
|
|
949
|
+
const otherAgents = ws.otherAgents ?? [];
|
|
950
|
+
const openDecisions = ws.decisions?.open ?? [];
|
|
951
|
+
const pressure = brief.reviewPressure;
|
|
898
952
|
/**
|
|
899
953
|
* ⚠ SS-41 — ADDRESSED TO YOU, AND PRINTED FIRST. Voicemail and @mentions are somebody speaking
|
|
900
954
|
* TO this agent; everything else in the brief is ambient. Both stay until acknowledged.
|
|
901
955
|
*/
|
|
902
|
-
const mentions =
|
|
903
|
-
const voicemail =
|
|
956
|
+
const mentions = ws.mentions ?? [];
|
|
957
|
+
const voicemail = ws.voicemail ?? [];
|
|
904
958
|
/** WIB-1 — people and agents writing TO this agent. Oldest first; reading one clears it. */
|
|
905
|
-
const inbox =
|
|
959
|
+
const inbox = ws.inboxThreads ?? [];
|
|
906
960
|
if (mentions.length || voicemail.length || inbox.length) {
|
|
907
|
-
|
|
908
|
-
console.log(` ${bold('Addressed to you')} ${dim(`(${mentions.length + voicemail.length + inbox.length})`)}`);
|
|
961
|
+
section('Addressed to you', mentions.length + voicemail.length + inbox.length);
|
|
909
962
|
for (const t of inbox.slice(0, 5)) {
|
|
910
|
-
|
|
911
|
-
|
|
963
|
+
out.push(` ${dim('·')} ${cyan('message')} from ${t.from}: “${t.subject}” ${dim(`${t.unread} unread · ${t.threadId}`)}`);
|
|
964
|
+
out.push(` ${oneLine(t.preview, 120)}`);
|
|
912
965
|
}
|
|
913
966
|
if (inbox.length)
|
|
914
|
-
|
|
967
|
+
out.push(dim(` Read with \`beehaven inbox read <id>\`, answer with \`beehaven inbox reply <id> "…"\`, then \`beehaven inbox done <id>\`.`));
|
|
915
968
|
for (const v of voicemail.slice(0, 3)) {
|
|
916
|
-
|
|
969
|
+
out.push(` ${dim('·')} ${cyan('voicemail')} from ${v.from}: ${oneLine(v.text, 100)}`);
|
|
917
970
|
}
|
|
918
971
|
for (const m of mentions.slice(0, 5)) {
|
|
919
972
|
const on = m.on ?? {};
|
|
920
|
-
|
|
921
|
-
|
|
973
|
+
out.push(` ${dim('·')} ${cyan('@mention')} from ${m.from} on ${on.kind || 'item'} “${on.title || on.id || ''}” ${dim(String(m.id).slice(0, 8))}`);
|
|
974
|
+
out.push(` ${oneLine(m.text, 120)}`);
|
|
922
975
|
}
|
|
923
976
|
if (mentions.length)
|
|
924
|
-
|
|
977
|
+
out.push(dim(` Act, reply with \`beehaven comment <ref> "…"\`, then \`beehaven mention ack <id>\`.`));
|
|
925
978
|
}
|
|
926
|
-
|
|
927
|
-
if (!mentions.length && !voicemail.length && !inbox.length)
|
|
928
|
-
console.log(dim(' First visit — nothing to catch up on.'));
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
const ago = (ts) => {
|
|
932
|
-
const d = Date.now() - ts;
|
|
933
|
-
if (d < 60_000)
|
|
934
|
-
return 'just now';
|
|
935
|
-
if (d < 3_600_000)
|
|
936
|
-
return `${Math.round(d / 60_000)}m ago`;
|
|
937
|
-
if (d < 86_400_000)
|
|
938
|
-
return `${Math.round(d / 3_600_000)}h ago`;
|
|
939
|
-
return `${Math.round(d / 86_400_000)}d ago`;
|
|
940
|
-
};
|
|
979
|
+
// ── What you left yourself. The handoff note is the payload; counts are context. ──
|
|
941
980
|
if (sessions.length) {
|
|
942
|
-
|
|
943
|
-
console.log(` ${bold('Last time you were here')}`);
|
|
981
|
+
section('Last time you were here');
|
|
944
982
|
for (const s of sessions.slice(0, 3)) {
|
|
945
|
-
const when = s.startedAt ?
|
|
983
|
+
const when = s.startedAt ? briefAgo(s.startedAt, now) : '';
|
|
946
984
|
const counts = `${s.actions} action${s.actions === 1 ? '' : 's'}${s.errors ? `, ${s.errors} error${s.errors === 1 ? '' : 's'}` : ''}`;
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
if (
|
|
950
|
-
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
if (focus.length) {
|
|
954
|
-
console.log('');
|
|
955
|
-
console.log(` ${bold('You were working on')}`);
|
|
956
|
-
for (const f of focus.slice(0, 3)) {
|
|
957
|
-
console.log(` ${dim('·')} ${f.title || f.id} ${dim(`(${f.kind}, ${f.selected.length} selected)`)}`);
|
|
985
|
+
out.push(` ${dim('·')} ${s.label || '(unlabelled)'} ${dim(`— ${when}, ${counts}`)}`);
|
|
986
|
+
const note = String(s.summary ?? '').trim();
|
|
987
|
+
if (note && !LIFECYCLE_SUMMARIES.has(note))
|
|
988
|
+
out.push(` ${cyan('handoff:')} ${oneLine(note, 400)}`);
|
|
958
989
|
}
|
|
959
990
|
}
|
|
960
991
|
if (assigned.length) {
|
|
961
|
-
|
|
962
|
-
console.log(` ${bold('Assigned to you')} ${dim(`(${assigned.length})`)}`);
|
|
992
|
+
section('Assigned to you', assigned.length);
|
|
963
993
|
for (const t of assigned.slice(0, 5)) {
|
|
964
|
-
|
|
994
|
+
out.push(` ${dim('·')} ${t.key ? cyan(t.key) + ' ' : ''}${t.title} ${dim(`[${t.status}]`)}`);
|
|
965
995
|
}
|
|
966
996
|
}
|
|
967
997
|
if (pending.length) {
|
|
968
|
-
|
|
969
|
-
console.log(` ${bold('Waiting on a human')} ${dim(`(${pending.length})`)}`);
|
|
998
|
+
section('Waiting on a human', pending.length);
|
|
970
999
|
for (const c of pending.slice(0, 4)) {
|
|
971
|
-
|
|
1000
|
+
out.push(` ${dim('·')} ${c.title || c.id} ${dim(c.kind ? `(${c.kind})` : '')}`);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
/** WDP-9 — decisions still waiting on a person. Current decisions are context the agent can
|
|
1004
|
+
* re-read with `agent-brief`; open ones change what it should do next. */
|
|
1005
|
+
if (openDecisions.length) {
|
|
1006
|
+
section('Open decisions', openDecisions.length);
|
|
1007
|
+
for (const d of openDecisions.slice(0, 4)) {
|
|
1008
|
+
out.push(` ${dim('·')} ${d.key ? cyan(d.key) + ' ' : ''}${d.title} ${dim(`[${d.state}]`)}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
// ── Where you were. Focus is what you had selected; recents are the things themselves. ──
|
|
1012
|
+
if (focus.length) {
|
|
1013
|
+
section('You were working on');
|
|
1014
|
+
for (const f of focus.slice(0, 3)) {
|
|
1015
|
+
out.push(` ${dim('·')} ${f.title || f.id} ${dim(`(${f.kind}, ${f.selected.length} selected)`)}`);
|
|
972
1016
|
}
|
|
973
1017
|
}
|
|
1018
|
+
const shown = new Set([...focus, ...assigned].map((x) => String(x?.id ?? '')));
|
|
1019
|
+
const whereLeft = recents.filter((r) => r?.id && !shown.has(String(r.id)) && r.title).slice(0, 4);
|
|
1020
|
+
if (whereLeft.length) {
|
|
1021
|
+
section('Where you left off');
|
|
1022
|
+
for (const r of whereLeft) {
|
|
1023
|
+
const what = [r.kind, [r.reason || 'touched', r.at ? briefAgo(r.at, now) : ''].filter(Boolean).join(' ')].filter(Boolean).join(', ');
|
|
1024
|
+
out.push(` ${dim('·')} ${oneLine(r.title, 80)} ${dim(`(${what})`)}`);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
// ── What moved while you were away, and who moved it (`by` is the useful field). ──
|
|
1028
|
+
if (changed.length) {
|
|
1029
|
+
const waiting = ws.waiting ?? {};
|
|
1030
|
+
const total = Number(waiting.changedByPeople ?? 0) + Number(waiting.changedByAgents ?? 0) || changed.length;
|
|
1031
|
+
section('Changed since you were here', total);
|
|
1032
|
+
for (const c of changed.slice(0, 4)) {
|
|
1033
|
+
const who = c.actor ? `${c.actor}${c.by === 'agent' ? ' (agent)' : ''}` : c.by === 'agent' ? 'an agent' : 'a person';
|
|
1034
|
+
out.push(` ${dim('·')} ${oneLine(c.title || c.id, 70)} ${dim(`(${c.kind}) — ${c.op || 'changed'} by ${who}${c.at ? `, ${briefAgo(c.at, now)}` : ''}`)}`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (otherAgents.length) {
|
|
1038
|
+
const names = otherAgents.slice(0, 4).map((a) => `${a.agentId} ${dim(`(${a.actions} action${a.actions === 1 ? '' : 's'}${a.last ? `, ${briefAgo(a.last, now)}` : ''})`)}`);
|
|
1039
|
+
out.push('');
|
|
1040
|
+
out.push(` ${bold('Also working here')} ${names.join(', ')}`);
|
|
1041
|
+
}
|
|
1042
|
+
// ── What you have not shared yet. Filing is sharing; an unfiled draft is invisible to the team. ──
|
|
1043
|
+
if (drafts.length) {
|
|
1044
|
+
section('Your unfiled drafts', drafts.length);
|
|
1045
|
+
for (const d of drafts.slice(0, 3)) {
|
|
1046
|
+
out.push(` ${dim('·')} ${d.key ? cyan(d.key) + ' ' : ''}${oneLine(d.title, 80)}${d.updatedAt ? dim(` — ${briefAgo(d.updatedAt, now)}`) : ''}`);
|
|
1047
|
+
}
|
|
1048
|
+
out.push(dim(' Private to you until filed into a project.'));
|
|
1049
|
+
}
|
|
1050
|
+
if (pressure?.overBudget && pressure.instruction) {
|
|
1051
|
+
out.push('');
|
|
1052
|
+
out.push(` ${yellow('⚠')} ${pressure.instruction}`);
|
|
1053
|
+
}
|
|
974
1054
|
if (memories.length) {
|
|
975
|
-
|
|
976
|
-
console.log(` ${bold('You noted')}`);
|
|
1055
|
+
section('You noted');
|
|
977
1056
|
for (const m of memories.slice(0, 4)) {
|
|
978
|
-
|
|
1057
|
+
out.push(` ${dim('·')} ${cyan(m.key)}: ${oneLine(m.value, 90)}`);
|
|
979
1058
|
}
|
|
980
1059
|
}
|
|
1060
|
+
if (!out.length)
|
|
1061
|
+
out.push(dim(' First visit — nothing to catch up on.'));
|
|
1062
|
+
// The one way a session leaves the next one something to read. Printed every time: a handoff
|
|
1063
|
+
// you have to remember to write is a handoff most sessions will not leave.
|
|
1064
|
+
out.push('');
|
|
1065
|
+
out.push(dim(` Before you stop: beehaven agent handoff "what you did · what is next"`));
|
|
1066
|
+
return out;
|
|
1067
|
+
}
|
|
1068
|
+
function printBrief(brief) {
|
|
1069
|
+
if (!brief)
|
|
1070
|
+
return;
|
|
1071
|
+
for (const line of formatBrief(brief))
|
|
1072
|
+
console.log(line);
|
|
981
1073
|
}
|
|
982
1074
|
// ── use (prints the export line for eval) ────────────
|
|
983
1075
|
async function agentUse(args) {
|
|
@@ -1092,6 +1184,41 @@ async function agentStatus(args, values) {
|
|
|
1092
1184
|
}
|
|
1093
1185
|
console.log(` ${green('✓')} ${cyan(name)} status: ${String(result.message ?? message)}`);
|
|
1094
1186
|
}
|
|
1187
|
+
// ── handoff ──────────────────────────────────────────
|
|
1188
|
+
/**
|
|
1189
|
+
* End the current work session WITH a note — the one thing the next login (this agent's, or the
|
|
1190
|
+
* colleague it hands to) reads first, under "Last time you were here" (AWO-225).
|
|
1191
|
+
*
|
|
1192
|
+
* Unlike `logout`, the identity stays signed in and bound: an agent hands off a stretch of work,
|
|
1193
|
+
* not its seat. Only the ended session's id is forgotten (`clearAgentSessionId`), so the next
|
|
1194
|
+
* `agent login` starts a fresh session and this terminal still resolves to this agent.
|
|
1195
|
+
*/
|
|
1196
|
+
async function agentHandoff(args, values) {
|
|
1197
|
+
const name = activeAgentName(values);
|
|
1198
|
+
const summary = (args.join(' ') || String(values.summary ?? '')).trim();
|
|
1199
|
+
if (!name || !summary) {
|
|
1200
|
+
console.error(' Usage: beehaven agent handoff "<what you did · what is next>" [--agent <name>]');
|
|
1201
|
+
if (!name)
|
|
1202
|
+
console.error(' No active agent: log in first, or pass --agent <name>.');
|
|
1203
|
+
process.exit(1);
|
|
1204
|
+
}
|
|
1205
|
+
const record = loadAgent(name);
|
|
1206
|
+
if (!record?.sessionId) {
|
|
1207
|
+
console.error(` ${yellow('⚠')} ${cyan(name)} has no open work session on this device.`);
|
|
1208
|
+
console.error(dim(` Start one with: beehaven agent login ${name} --label "<work>" --client cli`));
|
|
1209
|
+
process.exitCode = 1;
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const ended = await endWorkspaceSession(record, summary);
|
|
1213
|
+
if (!ended) {
|
|
1214
|
+
console.error(` ${yellow('⚠')} The workspace did not accept the handoff; the session is still open.`);
|
|
1215
|
+
console.error(dim(' Check `beehaven status`, then run the same command again.'));
|
|
1216
|
+
process.exitCode = 1;
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
clearAgentSessionId(name);
|
|
1220
|
+
console.log(` ${green('✓')} Handed off session ${cyan(String(record.sessionId).slice(0, 8))} — the next login for ${cyan(name)} reads this first.`);
|
|
1221
|
+
}
|
|
1095
1222
|
// ── logout ───────────────────────────────────────────
|
|
1096
1223
|
async function agentLogout(args, values) {
|
|
1097
1224
|
const name = args[0] ?? activeAgentName(values);
|
|
@@ -1109,7 +1236,7 @@ async function agentLogout(args, values) {
|
|
|
1109
1236
|
// it stays in the workspace (source of truth) AND on this device, so you can
|
|
1110
1237
|
// log back in. To remove the current workspace projection use `agent delete <name>`;
|
|
1111
1238
|
// to remove the portable account identity use `agent delete <name> --account --yes`.
|
|
1112
|
-
await endWorkspaceSession(record
|
|
1239
|
+
await endWorkspaceSession(record);
|
|
1113
1240
|
try {
|
|
1114
1241
|
await rpcOnce('agent:disconnect', { agentId: record.agentId ?? name }, 5_000);
|
|
1115
1242
|
}
|
|
@@ -1134,7 +1261,7 @@ async function agentDelete(args, values) {
|
|
|
1134
1261
|
if (values.account === true)
|
|
1135
1262
|
return agentAccountDelete(args, values);
|
|
1136
1263
|
const record = loadAgent(name);
|
|
1137
|
-
await endWorkspaceSession(record
|
|
1264
|
+
await endWorkspaceSession(record);
|
|
1138
1265
|
// 1. Remove from the WORKSPACE (source of truth) — the `agent-delete` RPC
|
|
1139
1266
|
// soft-deletes (archived). Resolve by the record's agentId, else the name.
|
|
1140
1267
|
let cloud = false;
|
|
@@ -1193,7 +1320,7 @@ async function agentAccountDelete(args, values) {
|
|
|
1193
1320
|
console.error(` ${result.error || `Could not delete account agent "${name}".`}`);
|
|
1194
1321
|
process.exit(1);
|
|
1195
1322
|
}
|
|
1196
|
-
await endWorkspaceSession(record
|
|
1323
|
+
await endWorkspaceSession(record);
|
|
1197
1324
|
try {
|
|
1198
1325
|
await rpcOnce('agent:disconnect', { agentId }, 5_000);
|
|
1199
1326
|
}
|
package/dist/commands/canvas.js
CHANGED
|
@@ -243,7 +243,7 @@ async function cmdCanvasPublishLanding(positionals, values) {
|
|
|
243
243
|
}
|
|
244
244
|
const block = embed?.block ?? embed?.result?.block;
|
|
245
245
|
console.log(` ✓ Published: site comp ${siteCompositionId} embedded as block ${block?.id ?? '?'} on pasteboard ${targetCompositionId}`);
|
|
246
|
-
console.log(` ✓ Open it: https://clearly.sh/c/${targetCompositionId}`);
|
|
246
|
+
console.log(` ✓ Open it: https://www.clearly.sh/c/${targetCompositionId}`);
|
|
247
247
|
return print(embed, values);
|
|
248
248
|
}
|
|
249
249
|
async function cmdCanvasPasteText(positionals, values) {
|
package/dist/commands/compose.js
CHANGED
|
@@ -401,7 +401,7 @@ export async function cmdComposePush(_values) {
|
|
|
401
401
|
}
|
|
402
402
|
console.log('');
|
|
403
403
|
console.log(` Composition live at:`);
|
|
404
|
-
console.log(` https://clearly.sh/app/compositions/${link.compositionId}`);
|
|
404
|
+
console.log(` https://www.clearly.sh/app/compositions/${link.compositionId}`);
|
|
405
405
|
console.log('');
|
|
406
406
|
console.log(` Create a public share link with:`);
|
|
407
407
|
console.log(` beehaven compose share --password <pw>`);
|
|
@@ -466,7 +466,7 @@ export async function cmdComposeShare(values) {
|
|
|
466
466
|
process.exit(1);
|
|
467
467
|
}
|
|
468
468
|
console.log(` \u2713 Share link created`);
|
|
469
|
-
console.log(` https://clearly.sh/r/${res.id}`);
|
|
469
|
+
console.log(` https://www.clearly.sh/r/${res.id}`);
|
|
470
470
|
if (input.password)
|
|
471
471
|
console.log(` Password: ${input.password}`);
|
|
472
472
|
if (input.expiresAt)
|
package/dist/commands/help.js
CHANGED
|
@@ -1790,6 +1790,7 @@ const PAGES = {
|
|
|
1790
1790
|
agent use <name> — prints \`export BEEHAVEN_AGENT=<name>\`; eval it
|
|
1791
1791
|
agent list — list stored identities, mark the active one
|
|
1792
1792
|
agent whoami — show the active identity + TTL
|
|
1793
|
+
agent handoff "<summary>" — end the work session with a note; the next login prints it first
|
|
1793
1794
|
agent logout [<name>]— end its local session, keep the identity
|
|
1794
1795
|
agent delete <name> — archive the current workspace projection + remove this device copy
|
|
1795
1796
|
agent delete <name> --account --yes
|
|
@@ -1825,6 +1826,7 @@ const PAGES = {
|
|
|
1825
1826
|
{ cmd: 'beehaven say "hi from monet"', note: "speaks via monet's avatar" },
|
|
1826
1827
|
{ cmd: 'beehaven agent rebind monet --avatar studio-2' },
|
|
1827
1828
|
{ cmd: 'beehaven agent whoami' },
|
|
1829
|
+
{ cmd: 'beehaven agent handoff "Fixed AWO-224 login; next: release the CLI"', note: 'leave the next session a note' },
|
|
1828
1830
|
{ cmd: 'beehaven agent "what can you do?"', note: 'REPL one-shot' },
|
|
1829
1831
|
],
|
|
1830
1832
|
related: ['app', 'pipe', 'pwd'],
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `checkForUpdates` printed, unconditionally:
|
|
7
7
|
*
|
|
8
8
|
* Update available: 0.7.6 → 0.7.8
|
|
9
|
-
* Run curl -fsSL https://clearly.sh/install.sh | sh to update
|
|
9
|
+
* Run curl -fsSL https://www.clearly.sh/install.sh | sh to update
|
|
10
10
|
*
|
|
11
11
|
* For anyone whose `beehaven` comes from the Mac app — which is EVERY ordinary user, since the
|
|
12
12
|
* app bundles the CLI and installs the wrapper — following that instruction used to be actively
|
|
@@ -117,7 +117,7 @@ export function runSelfUpdate(kind = installKind()) {
|
|
|
117
117
|
return { ok: false, message: updateHint(kind) };
|
|
118
118
|
}
|
|
119
119
|
const feed = (process.env.BEEHAVEN_CLI_FEED || 'https://downloads.clearly.sh/cli/').replace(/\/+$/, '');
|
|
120
|
-
const installerUrl = process.env.BEEHAVEN_INSTALL_URL || 'https://clearly.sh/install.sh';
|
|
120
|
+
const installerUrl = process.env.BEEHAVEN_INSTALL_URL || 'https://www.clearly.sh/install.sh';
|
|
121
121
|
const temp = mkdtempSync(join(tmpdir(), 'beehaven-self-update-'));
|
|
122
122
|
try {
|
|
123
123
|
const manifestPath = join(temp, 'latest.json');
|
|
@@ -3065,15 +3065,20 @@ export async function cmdBroadcast(rawMsg, values) {
|
|
|
3065
3065
|
* calling all along (`Avatar.ts` `replyWithOmni`), which is why Omni was
|
|
3066
3066
|
* reachable from the desktop companion and not from the terminal.
|
|
3067
3067
|
*
|
|
3068
|
-
* The payload matters as much as the name: `agentId: '
|
|
3069
|
-
*
|
|
3068
|
+
* The payload matters as much as the name: `agentId: 'builtin:clearly'` selects the
|
|
3069
|
+
* workspace's built-in assistant, and `wait: true` is what makes this a one-shot rather than a
|
|
3070
3070
|
* fire-and-forget that prints an ack and exits before the reply exists.
|
|
3071
|
+
*
|
|
3072
|
+
* ⚠ AWO-226: it sent `'omni'`, the built-in's old name. Since PPO-291 an untyped row reads as an
|
|
3073
|
+
* external identity, and workspaces old enough to carry the legacy `omni` row refused every
|
|
3074
|
+
* `beehaven agent "…"` with "Omni is an external agent" (staging home, 2026-10-06).
|
|
3075
|
+
* `builtin:clearly` is the id the server always resolves to the built-in, on old and new Workers.
|
|
3071
3076
|
*/
|
|
3072
3077
|
export async function cmdAgent(prompt, values) {
|
|
3073
3078
|
try {
|
|
3074
3079
|
const result = await rpcOnce('agent-prompt', {
|
|
3075
3080
|
message: prompt,
|
|
3076
|
-
agentId: '
|
|
3081
|
+
agentId: 'builtin:clearly',
|
|
3077
3082
|
wait: true,
|
|
3078
3083
|
// ⚠ NOT `cwd`. The old call passed the shell's working directory, which
|
|
3079
3084
|
// the action does not accept — a local path is meaningless to a cloud
|
package/dist/daemon.js
CHANGED
|
@@ -171,14 +171,14 @@ async function simulateCloudAppRpc(params) {
|
|
|
171
171
|
* Curated "available to install" app list served by /api/marketplace.
|
|
172
172
|
*
|
|
173
173
|
* This is a stub catalog. Once the cloud marketplace is live, the daemon
|
|
174
|
-
* will fetch from https://clearly.sh/apps/catalog.json instead of this
|
|
174
|
+
* will fetch from https://www.clearly.sh/apps/catalog.json instead of this
|
|
175
175
|
* hardcoded array. The installed-app set is subtracted at serve time so
|
|
176
176
|
* users don't see things they already have.
|
|
177
177
|
*
|
|
178
178
|
* Shapes roughly mirror AgentHiveCatalogEntry minus the live "registered"
|
|
179
179
|
* state — a catalog entry is a promise of what installing would give you.
|
|
180
180
|
*/
|
|
181
|
-
const CATALOG_URL = 'https://clearly.sh/apps/catalog.json';
|
|
181
|
+
const CATALOG_URL = 'https://www.clearly.sh/apps/catalog.json';
|
|
182
182
|
const CATALOG_CACHE_MS = 5 * 60_000; // 5 min
|
|
183
183
|
let _catalogCache = null;
|
|
184
184
|
let _catalogFetchedAt = 0;
|
|
@@ -234,7 +234,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
234
234
|
tier: 'pro',
|
|
235
235
|
tags: ['commerce', 'cloud', 'api'],
|
|
236
236
|
kind: 'cloud',
|
|
237
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/shopify/shopify-0.1.0.hive' },
|
|
237
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/shopify/shopify-0.1.0.hive' },
|
|
238
238
|
frontend: { title: 'Shopify', icon: '🛍️' },
|
|
239
239
|
},
|
|
240
240
|
{
|
|
@@ -245,7 +245,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
245
245
|
tier: 'free',
|
|
246
246
|
tags: ['creative', '3d', 'local'],
|
|
247
247
|
kind: 'local',
|
|
248
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/blender/blender-0.1.0.hive' },
|
|
248
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/blender/blender-0.1.0.hive' },
|
|
249
249
|
frontend: { title: 'Blender', icon: '🧊' },
|
|
250
250
|
},
|
|
251
251
|
{
|
|
@@ -256,7 +256,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
256
256
|
tier: 'pro',
|
|
257
257
|
tags: ['design', 'cloud', 'api'],
|
|
258
258
|
kind: 'cloud',
|
|
259
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/figma/figma-0.1.0.hive' },
|
|
259
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/figma/figma-0.1.0.hive' },
|
|
260
260
|
frontend: { title: 'Figma', icon: '🎨' },
|
|
261
261
|
},
|
|
262
262
|
{
|
|
@@ -267,7 +267,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
267
267
|
tier: 'pro',
|
|
268
268
|
tags: ['creative', 'audio', 'local'],
|
|
269
269
|
kind: 'local',
|
|
270
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/logic-pro/logic-pro-0.1.0.hive' },
|
|
270
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/logic-pro/logic-pro-0.1.0.hive' },
|
|
271
271
|
frontend: { title: 'Logic Pro', icon: '🎚️' },
|
|
272
272
|
},
|
|
273
273
|
{
|
|
@@ -278,7 +278,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
278
278
|
tier: 'free',
|
|
279
279
|
tags: ['smart-home', 'local'],
|
|
280
280
|
kind: 'local',
|
|
281
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/hue/hue-0.1.0.hive' },
|
|
281
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/hue/hue-0.1.0.hive' },
|
|
282
282
|
frontend: { title: 'Philips Hue', icon: '💡' },
|
|
283
283
|
},
|
|
284
284
|
{
|
|
@@ -289,7 +289,7 @@ const STATIC_FALLBACK_APPS = [
|
|
|
289
289
|
tier: 'pro',
|
|
290
290
|
tags: ['productivity', 'cloud', 'api'],
|
|
291
291
|
kind: 'cloud',
|
|
292
|
-
install: { source: 'url', url: 'https://clearly.sh/apps/jira/jira-0.1.0.hive' },
|
|
292
|
+
install: { source: 'url', url: 'https://www.clearly.sh/apps/jira/jira-0.1.0.hive' },
|
|
293
293
|
frontend: { title: 'Jira', icon: '📋' },
|
|
294
294
|
},
|
|
295
295
|
{
|
|
@@ -707,7 +707,7 @@ let daemonAppManager = null;
|
|
|
707
707
|
// curl-only distribution — the CLI is NOT published to npm. Updates fetch the
|
|
708
708
|
// versioned tarball from downloads.clearly.sh via the public installer script.
|
|
709
709
|
let announcedManagedUpdates = false;
|
|
710
|
-
const INSTALL_URL = process.env.BEEHAVEN_INSTALL_URL || 'https://clearly.sh/install.sh';
|
|
710
|
+
const INSTALL_URL = process.env.BEEHAVEN_INSTALL_URL || 'https://www.clearly.sh/install.sh';
|
|
711
711
|
/**
|
|
712
712
|
* ⚠ THE SAME OVERRIDE ITS SIBLING ALREADY HAD. `min-version.ts` reads `BEEHAVEN_CLI_FEED` so the
|
|
713
713
|
* force-update gate can be exercised against a scratch channel; the auto-updater — the half that
|
package/dist/folder-sync.js
CHANGED
|
@@ -77,7 +77,7 @@ export async function linkProject(relay, folderPath, brandId) {
|
|
|
77
77
|
}
|
|
78
78
|
if (projects.length === 0) {
|
|
79
79
|
console.error(' No projects found in your Clearly account.');
|
|
80
|
-
console.error(' Create a project at https://clearly.sh first.');
|
|
80
|
+
console.error(' Create a project at https://www.clearly.sh first.');
|
|
81
81
|
process.exit(1);
|
|
82
82
|
}
|
|
83
83
|
console.log('\n Available projects:\n');
|
|
@@ -97,7 +97,7 @@ export class LocalAppManager extends EventEmitter {
|
|
|
97
97
|
*/
|
|
98
98
|
templateVars() {
|
|
99
99
|
// `webBase` — dev Mac app sets this via env; defaults to production.
|
|
100
|
-
const webBase = process.env.BEEHAVEN_WEB_BASE ?? process.env.CLEARLY_WEB_BASE ?? 'https://clearly.sh';
|
|
100
|
+
const webBase = process.env.BEEHAVEN_WEB_BASE ?? process.env.CLEARLY_WEB_BASE ?? 'https://www.clearly.sh';
|
|
101
101
|
// User identity — the daemon stores these in config on sign-in
|
|
102
102
|
// install(), so they're available without decoding the JWT here.
|
|
103
103
|
const cfg = readDaemonConfigSafe();
|