@phnx-labs/agents-cli 1.20.72 → 1.20.73
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/CHANGELOG.md +299 -0
- package/README.md +40 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +12 -0
- package/dist/commands/activity.js +68 -0
- package/dist/commands/apply.d.ts +13 -0
- package/dist/commands/apply.js +28 -3
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cloud.js +21 -11
- package/dist/commands/doctor.js +8 -4
- package/dist/commands/events.d.ts +11 -10
- package/dist/commands/events.js +30 -21
- package/dist/commands/exec.d.ts +34 -0
- package/dist/commands/exec.js +337 -39
- package/dist/commands/feed.d.ts +13 -0
- package/dist/commands/feed.js +130 -29
- package/dist/commands/hq.d.ts +2 -0
- package/dist/commands/hq.js +58 -0
- package/dist/commands/lease.d.ts +19 -0
- package/dist/commands/lease.js +138 -11
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +123 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mine.d.ts +27 -0
- package/dist/commands/mine.js +207 -0
- package/dist/commands/monitors.js +79 -49
- package/dist/commands/plugins.js +1 -0
- package/dist/commands/profiles.js +158 -0
- package/dist/commands/repo.js +2 -60
- package/dist/commands/resources.d.ts +5 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/routines.d.ts +2 -0
- package/dist/commands/routines.js +139 -37
- package/dist/commands/secrets.js +234 -47
- package/dist/commands/send.d.ts +15 -0
- package/dist/commands/send.js +62 -0
- package/dist/commands/sessions-tail.d.ts +1 -0
- package/dist/commands/sessions-tail.js +20 -9
- package/dist/commands/sessions.js +18 -3
- package/dist/commands/setup-mine.d.ts +18 -0
- package/dist/commands/setup-mine.js +106 -0
- package/dist/commands/setup-share.js +2 -2
- package/dist/commands/setup.js +3 -1
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/index.js +79 -10
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.js +16 -5
- package/dist/lib/artifact-actions.d.ts +1 -1
- package/dist/lib/artifact-actions.js +1 -1
- package/dist/lib/ask-classifier.d.ts +2 -1
- package/dist/lib/ask-classifier.js +3 -3
- package/dist/lib/auth-health.d.ts +32 -4
- package/dist/lib/auth-health.js +40 -1
- package/dist/lib/auto-pull.js +8 -1
- package/dist/lib/brand.d.ts +40 -0
- package/dist/lib/brand.js +122 -0
- package/dist/lib/browser/drivers/ssh.js +4 -7
- package/dist/lib/browser/service.js +6 -8
- package/dist/lib/channels/providers/index.d.ts +2 -0
- package/dist/lib/channels/providers/index.js +20 -0
- package/dist/lib/channels/providers/mailbox.d.ts +2 -0
- package/dist/lib/channels/providers/mailbox.js +26 -0
- package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
- package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
- package/dist/lib/channels/providers/rush.d.ts +6 -0
- package/dist/lib/channels/providers/rush.js +55 -0
- package/dist/lib/channels/registry.d.ts +42 -0
- package/dist/lib/channels/registry.js +20 -0
- package/dist/lib/channels/resolve.d.ts +11 -0
- package/dist/lib/channels/resolve.js +12 -0
- package/dist/lib/cli-resources.d.ts +18 -0
- package/dist/lib/cli-resources.js +53 -4
- package/dist/lib/cloud/codex.js +6 -3
- package/dist/lib/cloud/factory.d.ts +1 -0
- package/dist/lib/cloud/factory.js +10 -4
- package/dist/lib/cloud/rush.d.ts +3 -1
- package/dist/lib/cloud/rush.js +6 -1
- package/dist/lib/cloud/types.d.ts +2 -0
- package/dist/lib/codex-home.d.ts +35 -0
- package/dist/lib/codex-home.js +136 -0
- package/dist/lib/crabbox/cli.d.ts +44 -0
- package/dist/lib/crabbox/cli.js +120 -20
- package/dist/lib/crabbox/lease.d.ts +52 -1
- package/dist/lib/crabbox/lease.js +117 -16
- package/dist/lib/crabbox/progress.d.ts +31 -0
- package/dist/lib/crabbox/progress.js +76 -0
- package/dist/lib/crabbox/runtimes.d.ts +4 -0
- package/dist/lib/crabbox/runtimes.js +33 -8
- package/dist/lib/crabbox/setup-copy.d.ts +87 -0
- package/dist/lib/crabbox/setup-copy.js +127 -0
- package/dist/lib/daemon.d.ts +5 -5
- package/dist/lib/daemon.js +9 -9
- package/dist/lib/drive-sync.js +6 -3
- package/dist/lib/event-stream.d.ts +36 -0
- package/dist/lib/event-stream.js +68 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +11 -0
- package/dist/lib/exec.js +23 -2
- package/dist/lib/feed-policy.d.ts +1 -1
- package/dist/lib/feed-policy.js +14 -9
- package/dist/lib/feed-ranking.d.ts +32 -0
- package/dist/lib/feed-ranking.js +224 -0
- package/dist/lib/feed.d.ts +35 -2
- package/dist/lib/feed.js +66 -3
- package/dist/lib/fleet/auth-sync.d.ts +53 -0
- package/dist/lib/fleet/auth-sync.js +92 -0
- package/dist/lib/fleet/remote-login.d.ts +187 -0
- package/dist/lib/fleet/remote-login.js +556 -0
- package/dist/lib/format.d.ts +30 -3
- package/dist/lib/format.js +34 -5
- package/dist/lib/fs-atomic.d.ts +7 -2
- package/dist/lib/fs-atomic.js +8 -12
- package/dist/lib/git.d.ts +16 -0
- package/dist/lib/git.js +79 -2
- package/dist/lib/heal.js +11 -3
- package/dist/lib/hosts/progress.d.ts +28 -10
- package/dist/lib/hosts/progress.js +79 -22
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/hq/floor.d.ts +87 -0
- package/dist/lib/hq/floor.js +226 -0
- package/dist/lib/menubar/install-menubar.js +14 -20
- package/dist/lib/notify.d.ts +1 -0
- package/dist/lib/notify.js +3 -3
- package/dist/lib/open-url.d.ts +2 -0
- package/dist/lib/open-url.js +19 -0
- package/dist/lib/openclaw-keychain.d.ts +56 -0
- package/dist/lib/openclaw-keychain.js +236 -0
- package/dist/lib/overdue.js +10 -0
- package/dist/lib/permissions.d.ts +44 -1
- package/dist/lib/permissions.js +284 -7
- package/dist/lib/project-launch.d.ts +6 -12
- package/dist/lib/project-launch.js +13 -228
- package/dist/lib/project-resources.d.ts +7 -0
- package/dist/lib/project-resources.js +291 -0
- package/dist/lib/pty-client.d.ts +27 -0
- package/dist/lib/pty-client.js +136 -10
- package/dist/lib/refresh.js +14 -10
- package/dist/lib/resource-profiles.d.ts +26 -0
- package/dist/lib/resource-profiles.js +157 -0
- package/dist/lib/resources/permissions.js +7 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/resources.js +32 -3
- package/dist/lib/routines.d.ts +11 -0
- package/dist/lib/routines.js +56 -15
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +66 -5
- package/dist/lib/secrets/bundles.d.ts +16 -3
- package/dist/lib/secrets/bundles.js +206 -37
- package/dist/lib/secrets/icloud-import.d.ts +2 -2
- package/dist/lib/secrets/icloud-import.js +9 -6
- package/dist/lib/secrets/mcp.js +1 -1
- package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
- package/dist/lib/secrets/vault-age-helper.js +34 -0
- package/dist/lib/secrets/vault.d.ts +49 -0
- package/dist/lib/secrets/vault.js +397 -0
- package/dist/lib/self-heal/checks/path.js +3 -1
- package/dist/lib/self-heal/checks/shadowing.js +10 -2
- package/dist/lib/self-heal/checks/shims.js +19 -11
- package/dist/lib/session/cloud.d.ts +2 -2
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +44 -7
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +114 -3
- package/dist/lib/session/stream-render.d.ts +3 -0
- package/dist/lib/session/stream-render.js +130 -0
- package/dist/lib/session/types.d.ts +6 -0
- package/dist/lib/share/analytics.d.ts +13 -0
- package/dist/lib/share/analytics.js +45 -0
- package/dist/lib/share/config.d.ts +19 -5
- package/dist/lib/share/config.js +44 -8
- package/dist/lib/share/provision.d.ts +58 -6
- package/dist/lib/share/provision.js +97 -22
- package/dist/lib/share/publish.d.ts +36 -13
- package/dist/lib/share/publish.js +55 -8
- package/dist/lib/share/worker-template.js +83 -17
- package/dist/lib/shims.d.ts +9 -0
- package/dist/lib/shims.js +91 -13
- package/dist/lib/ssh-exec.d.ts +14 -0
- package/dist/lib/ssh-exec.js +57 -0
- package/dist/lib/staleness/detectors/hooks.js +25 -1
- package/dist/lib/staleness/detectors/permissions.js +66 -0
- package/dist/lib/staleness/detectors/workflows.js +20 -0
- package/dist/lib/staleness/writers/hooks.js +58 -4
- package/dist/lib/staleness/writers/permissions.js +2 -2
- package/dist/lib/staleness/writers/skills.js +1 -1
- package/dist/lib/staleness/writers/sources.d.ts +10 -1
- package/dist/lib/staleness/writers/sources.js +68 -1
- package/dist/lib/star-nudge.d.ts +45 -0
- package/dist/lib/star-nudge.js +91 -0
- package/dist/lib/startup/command-registry.d.ts +6 -1
- package/dist/lib/startup/command-registry.js +17 -3
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +3 -0
- package/dist/lib/subagents-registry.d.ts +2 -0
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/teams/parsers.js +214 -6
- package/dist/lib/teams/supervisor.d.ts +7 -0
- package/dist/lib/teams/supervisor.js +2 -1
- package/dist/lib/template.d.ts +1 -1
- package/dist/lib/template.js +1 -1
- package/dist/lib/triggers/webhook.js +36 -3
- package/dist/lib/types.d.ts +73 -0
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +11 -0
- package/dist/lib/versions.js +208 -76
- package/dist/lib/workflows.d.ts +2 -0
- package/dist/lib/workflows.js +88 -0
- package/package.json +2 -1
- package/dist/commands/daemon.d.ts +0 -10
- package/dist/commands/daemon.js +0 -121
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { mailboxIdForActiveSession } from '../mailbox-target.js';
|
|
2
|
+
function slug(s) {
|
|
3
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'unknown';
|
|
4
|
+
}
|
|
5
|
+
function roomIdForTeam(team) {
|
|
6
|
+
return `team:${slug(team)}`;
|
|
7
|
+
}
|
|
8
|
+
function roomIdForMachine(machine) {
|
|
9
|
+
return `machine:${slug(machine)}`;
|
|
10
|
+
}
|
|
11
|
+
function agentDisplayName(session) {
|
|
12
|
+
return session.name || session.label || session.agentId || session.sessionId?.slice(0, 8) || session.kind;
|
|
13
|
+
}
|
|
14
|
+
function sessionMachine(session) {
|
|
15
|
+
return session.machine || session.provenance?.host || session.host || 'local';
|
|
16
|
+
}
|
|
17
|
+
function teamForSession(session, teammatesById) {
|
|
18
|
+
if (session.teamName)
|
|
19
|
+
return session.teamName;
|
|
20
|
+
if (session.agentId && teammatesById.has(session.agentId))
|
|
21
|
+
return teammatesById.get(session.agentId).team;
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
function teammateName(session, teammatesById) {
|
|
25
|
+
if (session.agentId && teammatesById.has(session.agentId)) {
|
|
26
|
+
return teammatesById.get(session.agentId).name || session.agentId.slice(0, 8);
|
|
27
|
+
}
|
|
28
|
+
return session.agentId;
|
|
29
|
+
}
|
|
30
|
+
function moodForSession(session, hasOpenBlock) {
|
|
31
|
+
if (session.status === 'input_required' || session.activity === 'waiting_input' || hasOpenBlock)
|
|
32
|
+
return 'waiting';
|
|
33
|
+
if (session.rateLimited)
|
|
34
|
+
return 'blocked';
|
|
35
|
+
if (session.pr)
|
|
36
|
+
return 'celebrating';
|
|
37
|
+
if (session.status === 'running' || session.activity === 'working')
|
|
38
|
+
return 'working';
|
|
39
|
+
return 'idle';
|
|
40
|
+
}
|
|
41
|
+
function action(id, label, command, target, extra = {}) {
|
|
42
|
+
return { id, label, command, target, ...extra };
|
|
43
|
+
}
|
|
44
|
+
function agentActions(agentId, session, mailboxId, team, teammate) {
|
|
45
|
+
const actions = [];
|
|
46
|
+
if (mailboxId) {
|
|
47
|
+
actions.push(action(`agent:${agentId}:message`, 'Message', ['message', mailboxId, '{message}', '--surface', 'hq'], { kind: 'agent', id: agentId }, { input: [{ name: 'message', label: 'Message', placeholder: 'What should this agent do next?', required: true }] }));
|
|
48
|
+
actions.push(action(`agent:${agentId}:stop`, 'Stop', ['feed', '--kill', mailboxId], { kind: 'agent', id: agentId }, { destructive: true }));
|
|
49
|
+
}
|
|
50
|
+
if (team && teammate) {
|
|
51
|
+
actions.push(action(`agent:${agentId}:team-stop`, 'Stop teammate', ['teams', 'stop', team, teammate], { kind: 'agent', id: agentId }, { destructive: true }));
|
|
52
|
+
}
|
|
53
|
+
if (session.sessionId) {
|
|
54
|
+
actions.push(action(`agent:${agentId}:transcript`, 'Transcript', ['sessions', session.sessionId, '--markdown'], { kind: 'agent', id: agentId }));
|
|
55
|
+
}
|
|
56
|
+
return actions;
|
|
57
|
+
}
|
|
58
|
+
function spawnActionForRoom(roomId, team) {
|
|
59
|
+
if (team) {
|
|
60
|
+
return action(`room:${roomId}:spawn-teammate`, 'Spawn teammate', ['teams', 'add', team, '{agent}', '{task}', '--name', '{name}', '--mode', 'edit'], { kind: 'room', id: roomId }, {
|
|
61
|
+
input: [
|
|
62
|
+
{ name: 'agent', label: 'Agent', placeholder: 'claude', required: true },
|
|
63
|
+
{ name: 'name', label: 'Name', placeholder: 'frontend', required: true },
|
|
64
|
+
{ name: 'task', label: 'Task', placeholder: 'Implement the next scoped task', required: true },
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return action(`room:${roomId}:spawn-run`, 'Run agent', ['run', '{agent}', '{task}', '--name', '{name}'], { kind: 'room', id: roomId }, {
|
|
69
|
+
input: [
|
|
70
|
+
{ name: 'agent', label: 'Agent', placeholder: 'claude', required: true },
|
|
71
|
+
{ name: 'name', label: 'Name', placeholder: 'investigate', required: true },
|
|
72
|
+
{ name: 'task', label: 'Task', placeholder: 'Investigate this room', required: true },
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export function buildHqFloor(input) {
|
|
77
|
+
const generatedAt = (input.generatedAt ?? new Date()).toISOString();
|
|
78
|
+
const teammatesById = new Map();
|
|
79
|
+
for (const [team, teammates] of input.teammatesByTeam) {
|
|
80
|
+
for (const teammate of teammates)
|
|
81
|
+
teammatesById.set(teammate.agent_id, { ...teammate, team });
|
|
82
|
+
}
|
|
83
|
+
const openBlocksByMailbox = new Map();
|
|
84
|
+
for (const block of input.blocks) {
|
|
85
|
+
const blocks = openBlocksByMailbox.get(block.mailboxId) || [];
|
|
86
|
+
blocks.push(block);
|
|
87
|
+
openBlocksByMailbox.set(block.mailboxId, blocks);
|
|
88
|
+
}
|
|
89
|
+
const rooms = new Map();
|
|
90
|
+
const ensureRoom = (room) => {
|
|
91
|
+
const existing = rooms.get(room.id);
|
|
92
|
+
if (existing)
|
|
93
|
+
return existing;
|
|
94
|
+
const created = {
|
|
95
|
+
...room,
|
|
96
|
+
counts: { agents: 0, needsInput: 0, running: 0, failed: 0 },
|
|
97
|
+
actions: [spawnActionForRoom(room.id, room.team)],
|
|
98
|
+
};
|
|
99
|
+
rooms.set(room.id, created);
|
|
100
|
+
return created;
|
|
101
|
+
};
|
|
102
|
+
for (const team of input.teams) {
|
|
103
|
+
ensureRoom({ id: roomIdForTeam(team.task_name), kind: 'team', name: team.task_name, team: team.task_name });
|
|
104
|
+
}
|
|
105
|
+
const agents = [];
|
|
106
|
+
for (const session of input.sessions) {
|
|
107
|
+
const team = teamForSession(session, teammatesById);
|
|
108
|
+
const machine = sessionMachine(session);
|
|
109
|
+
const room = team
|
|
110
|
+
? ensureRoom({ id: roomIdForTeam(team), kind: 'team', name: team, team })
|
|
111
|
+
: ensureRoom({ id: roomIdForMachine(machine), kind: 'machine', name: machine, machine });
|
|
112
|
+
const mailboxId = mailboxIdForActiveSession(session);
|
|
113
|
+
const blocks = mailboxId ? openBlocksByMailbox.get(mailboxId) || [] : [];
|
|
114
|
+
const id = session.agentId || session.sessionId || `${session.kind}:${agents.length + 1}`;
|
|
115
|
+
const mood = moodForSession(session, blocks.length > 0);
|
|
116
|
+
const teammate = teammateName(session, teammatesById);
|
|
117
|
+
room.counts.agents++;
|
|
118
|
+
if (mood === 'waiting')
|
|
119
|
+
room.counts.needsInput++;
|
|
120
|
+
if (session.status === 'running')
|
|
121
|
+
room.counts.running++;
|
|
122
|
+
if (mood === 'blocked')
|
|
123
|
+
room.counts.failed++;
|
|
124
|
+
agents.push({
|
|
125
|
+
id,
|
|
126
|
+
label: agentDisplayName(session),
|
|
127
|
+
agent: session.kind,
|
|
128
|
+
roomId: room.id,
|
|
129
|
+
mood,
|
|
130
|
+
status: session.status,
|
|
131
|
+
activity: session.activity,
|
|
132
|
+
preview: session.preview || session.question?.text,
|
|
133
|
+
machine,
|
|
134
|
+
team,
|
|
135
|
+
teammate,
|
|
136
|
+
sessionId: session.sessionId,
|
|
137
|
+
mailboxId,
|
|
138
|
+
pid: session.pid,
|
|
139
|
+
prUrl: session.pr?.url,
|
|
140
|
+
ticketId: session.ticket?.id,
|
|
141
|
+
actions: agentActions(id, session, mailboxId, team, teammate),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const ambientEvents = [];
|
|
145
|
+
for (const agent of agents) {
|
|
146
|
+
if (agent.mood === 'waiting') {
|
|
147
|
+
ambientEvents.push({
|
|
148
|
+
id: `needs-input:${agent.id}`,
|
|
149
|
+
kind: 'needs_input',
|
|
150
|
+
roomId: agent.roomId,
|
|
151
|
+
agentId: agent.id,
|
|
152
|
+
label: `${agent.label} needs input`,
|
|
153
|
+
intensity: 'high',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else if (agent.mood === 'blocked') {
|
|
157
|
+
ambientEvents.push({
|
|
158
|
+
id: `blocked:${agent.id}`,
|
|
159
|
+
kind: 'error',
|
|
160
|
+
roomId: agent.roomId,
|
|
161
|
+
agentId: agent.id,
|
|
162
|
+
label: `${agent.label} is blocked`,
|
|
163
|
+
intensity: 'high',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else if (agent.mood === 'celebrating' && agent.prUrl) {
|
|
167
|
+
ambientEvents.push({
|
|
168
|
+
id: `pr:${agent.id}`,
|
|
169
|
+
kind: 'pr',
|
|
170
|
+
roomId: agent.roomId,
|
|
171
|
+
agentId: agent.id,
|
|
172
|
+
label: `${agent.label} opened a PR`,
|
|
173
|
+
intensity: 'medium',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const room of rooms.values()) {
|
|
178
|
+
if (room.kind === 'team' && room.counts.running > 0) {
|
|
179
|
+
ambientEvents.push({
|
|
180
|
+
id: `team-active:${room.id}`,
|
|
181
|
+
kind: 'team_active',
|
|
182
|
+
roomId: room.id,
|
|
183
|
+
label: `${room.name} has ${room.counts.running} active teammate${room.counts.running === 1 ? '' : 's'}`,
|
|
184
|
+
intensity: 'low',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (room.counts.agents > 0 && room.counts.running === 0 && room.counts.needsInput === 0) {
|
|
188
|
+
ambientEvents.push({
|
|
189
|
+
id: `idle:${room.id}`,
|
|
190
|
+
kind: 'idle_scene',
|
|
191
|
+
roomId: room.id,
|
|
192
|
+
label: `${room.name} is calm`,
|
|
193
|
+
intensity: 'low',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const floorActions = [
|
|
198
|
+
action('floor:create-team', 'Create team room', ['teams', 'create', '{team}', '--description', '{description}'], { kind: 'floor', id: 'floor' }, {
|
|
199
|
+
input: [
|
|
200
|
+
{ name: 'team', label: 'Team', placeholder: 'release-squad', required: true },
|
|
201
|
+
{ name: 'description', label: 'Description', placeholder: 'What this room owns' },
|
|
202
|
+
],
|
|
203
|
+
}),
|
|
204
|
+
];
|
|
205
|
+
const roomList = [...rooms.values()].sort((a, b) => {
|
|
206
|
+
if (a.kind !== b.kind)
|
|
207
|
+
return a.kind === 'team' ? -1 : 1;
|
|
208
|
+
return a.name.localeCompare(b.name);
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
version: 1,
|
|
212
|
+
generatedAt,
|
|
213
|
+
counters: {
|
|
214
|
+
rooms: roomList.length,
|
|
215
|
+
agents: agents.length,
|
|
216
|
+
teams: input.teams.length,
|
|
217
|
+
needsInput: agents.filter((agent) => agent.mood === 'waiting').length,
|
|
218
|
+
failed: agents.filter((agent) => agent.mood === 'blocked').length,
|
|
219
|
+
prs: agents.filter((agent) => agent.prUrl).length,
|
|
220
|
+
},
|
|
221
|
+
rooms: roomList,
|
|
222
|
+
agents,
|
|
223
|
+
ambientEvents,
|
|
224
|
+
actions: floorActions,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -20,7 +20,7 @@ import * as fs from 'fs';
|
|
|
20
20
|
import * as os from 'os';
|
|
21
21
|
import * as path from 'path';
|
|
22
22
|
import { getRuntimeStateDir, getHelpersDir } from '../state.js';
|
|
23
|
-
import { getCliVersion } from '../version.js';
|
|
23
|
+
import { getCliVersion, resolveAgentsBin, resolveInstalledLayout } from '../version.js';
|
|
24
24
|
const APP_BUNDLE_NAME = 'MenubarHelper.app';
|
|
25
25
|
const INSTALL_DIR_NAME = 'agents-cli';
|
|
26
26
|
const SERVICE_LABEL = 'com.phnx-labs.agents-menubar';
|
|
@@ -78,6 +78,10 @@ export function menubarServiceInstalled() {
|
|
|
78
78
|
* 1. dist/lib/menubar/MenubarHelper.app — npm install layout (sibling of this file)
|
|
79
79
|
* 2. <repo>/bin/MenubarHelper.app — raw working tree (tsx/dev)
|
|
80
80
|
* 3. apps/cli/menubar/dist/MenubarHelper.app — fresh local build
|
|
81
|
+
* 4. <on-disk install>/dist/lib/menubar/MenubarHelper.app — Bun single-file
|
|
82
|
+
* binary: `import.meta.url` is a virtual `/$bunfs/` path, so the sibling
|
|
83
|
+
* candidates above can't see the on-disk bundle; recover it via the
|
|
84
|
+
* `agents` launcher symlink.
|
|
81
85
|
*/
|
|
82
86
|
function sourceAppPath() {
|
|
83
87
|
const candidates = [];
|
|
@@ -94,25 +98,15 @@ function sourceAppPath() {
|
|
|
94
98
|
if (fs.existsSync(c))
|
|
95
99
|
return c;
|
|
96
100
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
path.join(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
path.join(home, '.npm-global', 'bin', 'agents'),
|
|
107
|
-
];
|
|
108
|
-
for (const c of candidates) {
|
|
109
|
-
try {
|
|
110
|
-
fs.accessSync(c, fs.constants.X_OK);
|
|
111
|
-
return c;
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
/* try next */
|
|
115
|
-
}
|
|
101
|
+
// Candidate 4 — only reached when the sibling candidates miss (the Bun
|
|
102
|
+
// single-file binary, whose `import.meta.url` is a virtual `/$bunfs/` path).
|
|
103
|
+
// Resolve the launcher symlink lazily so the common Node path pays no extra
|
|
104
|
+
// filesystem probe.
|
|
105
|
+
const layout = resolveInstalledLayout();
|
|
106
|
+
if (layout) {
|
|
107
|
+
const p = path.join(layout.distDir, 'lib', 'menubar', APP_BUNDLE_NAME);
|
|
108
|
+
if (fs.existsSync(p))
|
|
109
|
+
return p;
|
|
116
110
|
}
|
|
117
111
|
return null;
|
|
118
112
|
}
|
package/dist/lib/notify.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface NotifyResult {
|
|
|
11
11
|
skipped?: boolean;
|
|
12
12
|
error?: string;
|
|
13
13
|
}
|
|
14
|
+
export declare function formatUrgentBlockMessage(block: OpenBlock): string;
|
|
14
15
|
/** Build openclaw argv for urgent notify (exported for tests). */
|
|
15
16
|
export declare function buildOpenClawNotifyArgs(text: string, options?: Pick<NotifyOptions, 'channel' | 'account' | 'target'>): string[];
|
|
16
17
|
export declare function notifyUrgentBlock(block: OpenBlock, options?: NotifyOptions): Promise<NotifyResult>;
|
package/dist/lib/notify.js
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
import { execFile } from 'child_process';
|
|
10
10
|
import { promisify } from 'util';
|
|
11
11
|
const execFileAsync = promisify(execFile);
|
|
12
|
-
function
|
|
12
|
+
export function formatUrgentBlockMessage(block) {
|
|
13
13
|
const q = block.questions[0];
|
|
14
14
|
const header = q?.header ? `[${q.header}] ` : '';
|
|
15
15
|
const text = q?.text ?? 'Agent needs input';
|
|
16
16
|
const host = block.host ? ` on ${block.host}` : '';
|
|
17
17
|
const cls = block.blockClass ?? 'approval';
|
|
18
18
|
const cost = block.costOfDelay ?? 'low';
|
|
19
|
-
return
|
|
19
|
+
return `URGENT ${cls.toUpperCase()}${host}: ${header}${text} (cost: ${cost}, id: ${block.blockId})`;
|
|
20
20
|
}
|
|
21
21
|
/** Build openclaw argv for urgent notify (exported for tests). */
|
|
22
22
|
export function buildOpenClawNotifyArgs(text, options = {}) {
|
|
@@ -50,7 +50,7 @@ export async function notifyUrgentBlock(block, options = {}) {
|
|
|
50
50
|
catch {
|
|
51
51
|
return { ok: false, error: 'openclaw CLI not found on PATH' };
|
|
52
52
|
}
|
|
53
|
-
const text =
|
|
53
|
+
const text = formatUrgentBlockMessage(block);
|
|
54
54
|
try {
|
|
55
55
|
await execFileAsync('openclaw', buildOpenClawNotifyArgs(text, options));
|
|
56
56
|
return { ok: true };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open a URL in the user's default browser, cross-platform. Best-effort and
|
|
3
|
+
* detached — never throws and never blocks the caller. Shared by `agents lease`
|
|
4
|
+
* (Hetzner console) and `agents fleet login` (the local login dashboard).
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
/** Best-effort: open a URL in the user's default browser. Never throws. */
|
|
8
|
+
export function openUrl(url) {
|
|
9
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
10
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
11
|
+
try {
|
|
12
|
+
const p = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
13
|
+
p.on('error', () => { });
|
|
14
|
+
p.unref();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
/* best-effort */
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type SpawnSyncOptions } from 'child_process';
|
|
2
|
+
export declare const OPENCLAW_KEYCHAIN_ACCOUNT = "openclaw";
|
|
3
|
+
export declare const OPENCLAW_KEYCHAIN_PROVIDER = "agents_keychain";
|
|
4
|
+
export declare const OPENCLAW_KEYCHAIN_ENV_SERVICES: Record<string, string>;
|
|
5
|
+
export interface OpenClawSecretRef {
|
|
6
|
+
source: 'exec';
|
|
7
|
+
provider: string;
|
|
8
|
+
id: string;
|
|
9
|
+
}
|
|
10
|
+
export interface OpenClawKeychainMigrationOptions {
|
|
11
|
+
account?: string;
|
|
12
|
+
provider?: string;
|
|
13
|
+
agentsBin?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface OpenClawKeychainMigrationResult {
|
|
16
|
+
services: Array<{
|
|
17
|
+
envKey: string;
|
|
18
|
+
service: string;
|
|
19
|
+
value: string;
|
|
20
|
+
}>;
|
|
21
|
+
replacedPaths: string[];
|
|
22
|
+
removedEnvPaths: string[];
|
|
23
|
+
unsupportedEnvKeys: string[];
|
|
24
|
+
changed: boolean;
|
|
25
|
+
}
|
|
26
|
+
interface JsonObject {
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
export declare function migrateOpenClawConfigToKeychainRefs(config: JsonObject, opts?: OpenClawKeychainMigrationOptions): OpenClawKeychainMigrationResult;
|
|
30
|
+
export declare function buildOpenClawKeychainStoreInvocation(service: string, value: string, account?: string): {
|
|
31
|
+
command: string;
|
|
32
|
+
args: string[];
|
|
33
|
+
options: SpawnSyncOptions & {
|
|
34
|
+
input: string;
|
|
35
|
+
detached: boolean;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export declare function storeOpenClawKeychainServices(services: Array<{
|
|
39
|
+
service: string;
|
|
40
|
+
value: string;
|
|
41
|
+
}>, account?: string): void;
|
|
42
|
+
export declare function readOpenClawKeychainService(service: string, account?: string): string;
|
|
43
|
+
export interface OpenClawExecResolverRequest {
|
|
44
|
+
protocolVersion?: number;
|
|
45
|
+
provider?: string;
|
|
46
|
+
ids?: unknown;
|
|
47
|
+
}
|
|
48
|
+
export interface OpenClawExecResolverResponse {
|
|
49
|
+
protocolVersion: 1;
|
|
50
|
+
values: Record<string, string>;
|
|
51
|
+
errors?: Record<string, {
|
|
52
|
+
code: string;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
export declare function resolveOpenClawKeychainRequest(request: OpenClawExecResolverRequest, lookup: (service: string) => string): OpenClawExecResolverResponse;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from 'child_process';
|
|
2
|
+
import { getCliLaunch } from './cli-entry.js';
|
|
3
|
+
import { buildAddGenericPasswordArgs, buildAddGenericPasswordSpawnOptions, } from './secrets/index.js';
|
|
4
|
+
export const OPENCLAW_KEYCHAIN_ACCOUNT = 'openclaw';
|
|
5
|
+
export const OPENCLAW_KEYCHAIN_PROVIDER = 'agents_keychain';
|
|
6
|
+
export const OPENCLAW_KEYCHAIN_ENV_SERVICES = {
|
|
7
|
+
OPENROUTER_API_KEY: 'openrouter-api-key',
|
|
8
|
+
LINEAR_API_KEY: 'linear-api-key',
|
|
9
|
+
GRAFANA_API_KEY: 'grafana-api-key',
|
|
10
|
+
POSTHOG_API_KEY: 'posthog-api-key',
|
|
11
|
+
};
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function secretRef(provider, service) {
|
|
16
|
+
return { source: 'exec', provider, id: service };
|
|
17
|
+
}
|
|
18
|
+
function openClawPath(parts) {
|
|
19
|
+
return parts.join('.');
|
|
20
|
+
}
|
|
21
|
+
function collectOpenClawEnv(config) {
|
|
22
|
+
const env = isRecord(config.env) ? config.env : null;
|
|
23
|
+
const out = new Map();
|
|
24
|
+
if (!env)
|
|
25
|
+
return out;
|
|
26
|
+
for (const [key, service] of Object.entries(OPENCLAW_KEYCHAIN_ENV_SERVICES)) {
|
|
27
|
+
void service;
|
|
28
|
+
const top = env[key];
|
|
29
|
+
if (typeof top === 'string' && top.trim()) {
|
|
30
|
+
out.set(key, { path: ['env', key], value: top });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const vars = isRecord(env.vars) ? env.vars : null;
|
|
34
|
+
const nested = vars?.[key];
|
|
35
|
+
if (typeof nested === 'string' && nested.trim()) {
|
|
36
|
+
out.set(key, { path: ['env', 'vars', key], value: nested });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
function deletePath(config, parts) {
|
|
42
|
+
let current = config;
|
|
43
|
+
for (const part of parts.slice(0, -1)) {
|
|
44
|
+
if (!isRecord(current))
|
|
45
|
+
return false;
|
|
46
|
+
current = current[part];
|
|
47
|
+
}
|
|
48
|
+
if (!isRecord(current))
|
|
49
|
+
return false;
|
|
50
|
+
const key = parts[parts.length - 1];
|
|
51
|
+
if (!(key in current))
|
|
52
|
+
return false;
|
|
53
|
+
delete current[key];
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
function normalizeObject(value) {
|
|
57
|
+
return isRecord(value) ? value : {};
|
|
58
|
+
}
|
|
59
|
+
function ensureOpenClawKeychainProvider(config, opts, agentsBin) {
|
|
60
|
+
const launch = getCliLaunch(['secrets', 'openclaw-keychain', 'resolve', '--account', opts.account], agentsBin);
|
|
61
|
+
const secrets = normalizeObject(config.secrets);
|
|
62
|
+
const providers = normalizeObject(secrets.providers);
|
|
63
|
+
providers[opts.provider] = {
|
|
64
|
+
source: 'exec',
|
|
65
|
+
command: launch.command,
|
|
66
|
+
args: launch.args,
|
|
67
|
+
jsonOnly: true,
|
|
68
|
+
};
|
|
69
|
+
secrets.providers = providers;
|
|
70
|
+
config.secrets = secrets;
|
|
71
|
+
}
|
|
72
|
+
function providerEnvKeyForPath(parts) {
|
|
73
|
+
if (parts.length === 8 &&
|
|
74
|
+
parts[0] === 'plugins' &&
|
|
75
|
+
parts[1] === 'entries' &&
|
|
76
|
+
parts[3] === 'config' &&
|
|
77
|
+
parts[4] === 'mcpServers' &&
|
|
78
|
+
parts[6] === 'env') {
|
|
79
|
+
return parts[7];
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function serviceForSupportedPath(parts) {
|
|
84
|
+
const envKey = providerEnvKeyForPath(parts);
|
|
85
|
+
if (envKey)
|
|
86
|
+
return OPENCLAW_KEYCHAIN_ENV_SERVICES[envKey] ?? null;
|
|
87
|
+
if (parts.length === 4 && parts[0] === 'models' && parts[1] === 'providers' && parts[3] === 'apiKey') {
|
|
88
|
+
if (parts[2] === 'openrouter')
|
|
89
|
+
return OPENCLAW_KEYCHAIN_ENV_SERVICES.OPENROUTER_API_KEY;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function envKeyForService(service) {
|
|
94
|
+
return Object.entries(OPENCLAW_KEYCHAIN_ENV_SERVICES).find(([, known]) => known === service)?.[0] ?? service;
|
|
95
|
+
}
|
|
96
|
+
function assertSameOpenClawSecretValue(envKey, service, existingPath, existingValue, nextPath, nextValue) {
|
|
97
|
+
if (existingValue === nextValue)
|
|
98
|
+
return;
|
|
99
|
+
throw new Error(`OpenClaw credential value mismatch for ${envKey} (${service}): ${existingPath} and ${nextPath} differ. ` +
|
|
100
|
+
`Resolve the plaintext values before migrating to Keychain.`);
|
|
101
|
+
}
|
|
102
|
+
function canReplaceByValue(parts) {
|
|
103
|
+
if (parts.length === 4 && parts[0] === 'models' && parts[1] === 'providers' && parts[3] === 'apiKey')
|
|
104
|
+
return true;
|
|
105
|
+
if (parts.length === 6 && parts[0] === 'plugins' && parts[1] === 'entries' && parts[3] === 'config' && parts[4] === 'webSearch' && parts[5] === 'apiKey')
|
|
106
|
+
return true;
|
|
107
|
+
if (parts.length === 5 && parts[0] === 'tools' && parts[1] === 'web' && parts[2] === 'search' && parts[4] === 'apiKey')
|
|
108
|
+
return true;
|
|
109
|
+
if (parts.length === 4 && parts[0] === 'tools' && parts[1] === 'web' && parts[2] === 'search' && parts[3] === 'apiKey')
|
|
110
|
+
return true;
|
|
111
|
+
return providerEnvKeyForPath(parts) !== null;
|
|
112
|
+
}
|
|
113
|
+
function replaceSupportedSecrets(current, parts, provider, envByValue, env, services, servicePaths, replacedPaths) {
|
|
114
|
+
if (!isRecord(current) && !Array.isArray(current))
|
|
115
|
+
return;
|
|
116
|
+
const entries = Array.isArray(current)
|
|
117
|
+
? current.map((value, index) => [String(index), value])
|
|
118
|
+
: Object.entries(current);
|
|
119
|
+
for (const [key, value] of entries) {
|
|
120
|
+
const childParts = [...parts, key];
|
|
121
|
+
if (typeof value === 'string' && value.trim() && canReplaceByValue(childParts)) {
|
|
122
|
+
const directService = serviceForSupportedPath(childParts);
|
|
123
|
+
const matchedService = directService ?? envByValue.get(value);
|
|
124
|
+
if (matchedService) {
|
|
125
|
+
const envKey = envKeyForService(matchedService);
|
|
126
|
+
const childPath = openClawPath(childParts);
|
|
127
|
+
const existingService = services.get(matchedService);
|
|
128
|
+
if (existingService) {
|
|
129
|
+
assertSameOpenClawSecretValue(envKey, matchedService, servicePaths.get(matchedService) ?? matchedService, existingService.value, childPath, value);
|
|
130
|
+
}
|
|
131
|
+
if (directService) {
|
|
132
|
+
const envEntry = env.get(envKey);
|
|
133
|
+
if (envEntry) {
|
|
134
|
+
assertSameOpenClawSecretValue(envKey, matchedService, openClawPath(envEntry.path), envEntry.value, childPath, value);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
services.set(matchedService, { envKey, service: matchedService, value });
|
|
138
|
+
servicePaths.set(matchedService, childPath);
|
|
139
|
+
current[key] = secretRef(provider, matchedService);
|
|
140
|
+
replacedPaths.push(childPath);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
replaceSupportedSecrets(value, childParts, provider, envByValue, env, services, servicePaths, replacedPaths);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export function migrateOpenClawConfigToKeychainRefs(config, opts = {}) {
|
|
148
|
+
const account = opts.account ?? OPENCLAW_KEYCHAIN_ACCOUNT;
|
|
149
|
+
const provider = opts.provider ?? OPENCLAW_KEYCHAIN_PROVIDER;
|
|
150
|
+
const env = collectOpenClawEnv(config);
|
|
151
|
+
const envByValue = new Map();
|
|
152
|
+
for (const [envKey, entry] of env) {
|
|
153
|
+
envByValue.set(entry.value, OPENCLAW_KEYCHAIN_ENV_SERVICES[envKey]);
|
|
154
|
+
}
|
|
155
|
+
const services = new Map();
|
|
156
|
+
const servicePaths = new Map();
|
|
157
|
+
const replacedPaths = [];
|
|
158
|
+
replaceSupportedSecrets(config, [], provider, envByValue, env, services, servicePaths, replacedPaths);
|
|
159
|
+
const removedEnvPaths = [];
|
|
160
|
+
const unsupportedEnvKeys = [];
|
|
161
|
+
for (const [envKey, entry] of env) {
|
|
162
|
+
const service = OPENCLAW_KEYCHAIN_ENV_SERVICES[envKey];
|
|
163
|
+
const mappedService = services.get(service);
|
|
164
|
+
if (!mappedService) {
|
|
165
|
+
unsupportedEnvKeys.push(envKey);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
assertSameOpenClawSecretValue(envKey, service, servicePaths.get(service) ?? service, mappedService.value, openClawPath(entry.path), entry.value);
|
|
169
|
+
if (deletePath(config, entry.path))
|
|
170
|
+
removedEnvPaths.push(openClawPath(entry.path));
|
|
171
|
+
}
|
|
172
|
+
if (services.size > 0) {
|
|
173
|
+
ensureOpenClawKeychainProvider(config, { account, provider }, opts.agentsBin);
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
services: [...services.values()],
|
|
177
|
+
replacedPaths,
|
|
178
|
+
removedEnvPaths,
|
|
179
|
+
unsupportedEnvKeys,
|
|
180
|
+
changed: services.size > 0 || replacedPaths.length > 0 || removedEnvPaths.length > 0,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export function buildOpenClawKeychainStoreInvocation(service, value, account = OPENCLAW_KEYCHAIN_ACCOUNT) {
|
|
184
|
+
return {
|
|
185
|
+
command: '/usr/bin/security',
|
|
186
|
+
args: buildAddGenericPasswordArgs(account, service),
|
|
187
|
+
options: buildAddGenericPasswordSpawnOptions(value),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function storeOpenClawKeychainServices(services, account = OPENCLAW_KEYCHAIN_ACCOUNT) {
|
|
191
|
+
if (process.platform !== 'darwin') {
|
|
192
|
+
throw new Error('OpenClaw Keychain migration must run on macOS.');
|
|
193
|
+
}
|
|
194
|
+
for (const { service, value } of services) {
|
|
195
|
+
const write = buildOpenClawKeychainStoreInvocation(service, value, account);
|
|
196
|
+
const result = spawnSync(write.command, write.args, write.options);
|
|
197
|
+
if (result.status !== 0) {
|
|
198
|
+
const msg = result.stderr?.toString().trim();
|
|
199
|
+
throw new Error(msg || `Failed to write OpenClaw Keychain service '${service}'.`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function stripSecurityTrailingNewline(value) {
|
|
204
|
+
return value.replace(/\r?\n$/, '');
|
|
205
|
+
}
|
|
206
|
+
export function readOpenClawKeychainService(service, account = OPENCLAW_KEYCHAIN_ACCOUNT) {
|
|
207
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:/#-]{0,255}$/.test(service)) {
|
|
208
|
+
throw new Error(`Invalid OpenClaw Keychain service id '${service}'.`);
|
|
209
|
+
}
|
|
210
|
+
if (process.platform !== 'darwin') {
|
|
211
|
+
throw new Error('OpenClaw Keychain resolver must run on macOS.');
|
|
212
|
+
}
|
|
213
|
+
const value = execFileSync('/usr/bin/security', ['find-generic-password', '-s', service, '-a', account, '-w'], {
|
|
214
|
+
encoding: 'utf-8',
|
|
215
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
216
|
+
});
|
|
217
|
+
return stripSecurityTrailingNewline(value);
|
|
218
|
+
}
|
|
219
|
+
export function resolveOpenClawKeychainRequest(request, lookup) {
|
|
220
|
+
const ids = Array.isArray(request.ids) ? request.ids.filter((id) => typeof id === 'string') : [];
|
|
221
|
+
const values = {};
|
|
222
|
+
const errors = {};
|
|
223
|
+
for (const id of ids) {
|
|
224
|
+
try {
|
|
225
|
+
values[id] = lookup(id);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
errors[id] = { code: 'NOT_FOUND' };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
protocolVersion: 1,
|
|
233
|
+
values,
|
|
234
|
+
...(Object.keys(errors).length > 0 ? { errors } : {}),
|
|
235
|
+
};
|
|
236
|
+
}
|
package/dist/lib/overdue.js
CHANGED
|
@@ -93,6 +93,11 @@ export function notifyOverdue(jobs) {
|
|
|
93
93
|
const safeTitle = title.replace(/"/g, '\\"');
|
|
94
94
|
const safeBody = body.replace(/"/g, '\\"');
|
|
95
95
|
const child = spawn('osascript', ['-e', `display notification "${safeBody}" with title "${safeTitle}"`], { detached: true, stdio: 'ignore' });
|
|
96
|
+
// A missing binary surfaces as an async 'error' event, NOT a synchronous
|
|
97
|
+
// throw the try/catch would catch. Without a listener Node re-throws it as
|
|
98
|
+
// an uncaught exception and takes the whole daemon down. Swallow it — the
|
|
99
|
+
// notification is best-effort.
|
|
100
|
+
child.on('error', () => { });
|
|
96
101
|
child.unref();
|
|
97
102
|
}
|
|
98
103
|
else if (platform === 'linux') {
|
|
@@ -100,6 +105,11 @@ export function notifyOverdue(jobs) {
|
|
|
100
105
|
detached: true,
|
|
101
106
|
stdio: 'ignore',
|
|
102
107
|
});
|
|
108
|
+
// Headless Linux boxes have no `notify-send` (libnotify-bin); its ENOENT
|
|
109
|
+
// arrives as an async 'error' event, not a throw. Without this listener
|
|
110
|
+
// the daemon crashes on every overdue routine and systemd restart-loops it
|
|
111
|
+
// (which also tears down the browser IPC socket). Swallow — best-effort.
|
|
112
|
+
child.on('error', () => { });
|
|
103
113
|
child.unref();
|
|
104
114
|
}
|
|
105
115
|
}
|