very-happy-cli 0.2.154 → 0.2.156

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/{AcpBackend-B14xNj5L.cjs → AcpBackend-BHe1_F1h.cjs} +3 -3
  2. package/dist/{AcpBackend-Ckyadhf4.mjs → AcpBackend-C3nc1u-s.mjs} +2 -2
  3. package/dist/{AcpSessionManager-CCHmPdrR.cjs → AcpSessionManager-BVxkQnNf.cjs} +1 -1
  4. package/dist/{AcpSessionManager-H8t4P-IW.mjs → AcpSessionManager-dN3k1LbI.mjs} +1 -1
  5. package/dist/agentGuidance-C-CmV03f.cjs +1665 -0
  6. package/dist/agentGuidance-C604n0Zq.mjs +1613 -0
  7. package/dist/{agentHome-BUsLTZnz.cjs → agentHome-CmIJNV7f.cjs} +5 -5
  8. package/dist/{agentHome-B20RWdgE.mjs → agentHome-D2XGsoqs.mjs} +5 -5
  9. package/dist/{auto-BQnncDcs.mjs → auto-B1I6G_Nf.mjs} +21 -21
  10. package/dist/{auto-IeldARBS.cjs → auto-CMDq4oIu.cjs} +24 -24
  11. package/dist/codex/happyMcpStdioBridge.cjs +6 -3
  12. package/dist/codex/happyMcpStdioBridge.mjs +6 -3
  13. package/dist/{config-DM_xI9M6.cjs → config-BYHSRY2E.cjs} +8 -8
  14. package/dist/{config-B-ocQh5V.mjs → config-BkTu25bH.mjs} +8 -8
  15. package/dist/{index-ZKuBjvHi.mjs → index-B5QbzEga.mjs} +21 -13
  16. package/dist/{index-BUxBxSxJ.cjs → index-B_-7NpGZ.cjs} +21 -13
  17. package/dist/{index-eCjkm8Y2.cjs → index-BbtR-Qto.cjs} +606 -982
  18. package/dist/{index-okLyUzeD.cjs → index-CB8QnjvS.cjs} +7 -7
  19. package/dist/{index-BELV05ko.mjs → index-DAAreiRF.mjs} +548 -911
  20. package/dist/{index-CIx3248o.mjs → index-PYO8Mdz4.mjs} +7 -7
  21. package/dist/index.cjs +6 -6
  22. package/dist/index.mjs +6 -6
  23. package/dist/{install-CoLN3whR.mjs → install-CNtXzwT_.mjs} +1 -1
  24. package/dist/{install-BZOdxfPd.cjs → install-CO1ps6Wt.cjs} +1 -1
  25. package/dist/{installTerminalHooks-CA4OcAS7.cjs → installTerminalHooks-BOAnQ1Qe.cjs} +5 -5
  26. package/dist/{installTerminalHooks-Dde_8t36.mjs → installTerminalHooks-CMpOV3RU.mjs} +5 -5
  27. package/dist/lib.cjs +2 -2
  28. package/dist/lib.d.cts +24 -8
  29. package/dist/lib.d.mts +24 -8
  30. package/dist/lib.mjs +2 -2
  31. package/dist/{limits-Dt7_unH4.mjs → limits-DmMdchTz.mjs} +309 -13
  32. package/dist/{limits-CmzISka2.cjs → limits-o29KXBgj.cjs} +329 -12
  33. package/dist/{mcp-9Ui4nQZL.mjs → mcp-C_uejotl.mjs} +17 -17
  34. package/dist/{mcp-7iDHlyCI.cjs → mcp-CwK1nI7Y.cjs} +23 -23
  35. package/dist/{piRuntime-C4E4pgye.mjs → piRuntime-Cu2-8nOT.mjs} +2 -2
  36. package/dist/{piRuntime-BesGGPME.cjs → piRuntime-Dj5WnqzJ.cjs} +2 -2
  37. package/dist/{piTerminal-fUogwxYr.cjs → piTerminal-D-BIbov9.cjs} +6 -6
  38. package/dist/{piTerminal-CCEGCQOY.mjs → piTerminal-DanZeBrO.mjs} +6 -6
  39. package/dist/{runGemini-CNPGSsSK.cjs → runGemini--QqK-Ap5.cjs} +7 -7
  40. package/dist/{runGemini-BW-pEklU.mjs → runGemini-CZ_lhMWY.mjs} +6 -6
  41. package/dist/{runOpenClaw-Bsodkg_s.cjs → runOpenClaw-C0xLhTvk.cjs} +6 -6
  42. package/dist/{runOpenClaw-NRB6-_jU.mjs → runOpenClaw-CxgLpm38.mjs} +5 -5
  43. package/dist/{send-DYeSFg8s.mjs → send-BaOkDgH0.mjs} +5 -39
  44. package/dist/{send-Bd00QcHC.cjs → send-CTcGMVuG.cjs} +8 -42
  45. package/dist/{sessions-DlPOYKLU.mjs → sessions-Bn304xF1.mjs} +88 -45
  46. package/dist/{sessions-RIFUoy_g.cjs → sessions-OmvyJb0n.cjs} +99 -56
  47. package/dist/{spawn-D1bhyHbH.mjs → spawn-BKNabutV.mjs} +22 -22
  48. package/dist/{spawn-DxrfHTF4.cjs → spawn-CC6V2KiA.cjs} +30 -30
  49. package/dist/{teams-pfKHpkgL.mjs → teams-CLbS2_4Q.mjs} +8 -8
  50. package/dist/{teams-Cfy0VMCo.cjs → teams-XtrH3_h3.cjs} +8 -8
  51. package/dist/{todo-B2CjDg_V.mjs → todo-BcPbNKt2.mjs} +8 -6
  52. package/dist/{todo-B4cBFzHB.cjs → todo-CEDvKNXK.cjs} +8 -6
  53. package/dist/{types-D5RWyBE5.mjs → types-BEVzYSoV.mjs} +52 -291
  54. package/dist/{types-Bd_TGr0F.cjs → types-CphWjqHc.cjs} +64 -320
  55. package/package.json +7 -7
  56. package/dist/agentGuidance-DUBXppv-.cjs +0 -554
  57. package/dist/agentGuidance-TLYQxsm2.mjs +0 -530
@@ -1,530 +0,0 @@
1
- import { z } from 'zod';
2
- import { randomUUID } from 'node:crypto';
3
- import { o as normalizeHttpEndpoint, c as configuration, r as readSettings, d as readPersistedSessions, f as readCredentialsForConfiguredRelay, q as AutomationNameSchema, t as AutomationConcurrencySchema, v as AutomationActionSchema, x as AutomationTriggerSchema } from './limits-Dt7_unH4.mjs';
4
- import { open, mkdir, rename, unlink } from 'node:fs/promises';
5
- import { constants } from 'node:fs';
6
- import { isAbsolute, join, dirname } from 'node:path';
7
-
8
- const TeamScopeSchema = z.object({
9
- serverUrl: z.string(),
10
- scopeToken: z.string().min(1),
11
- teamId: z.string().min(1),
12
- botId: z.string().min(1),
13
- taskId: z.string().optional(),
14
- attemptId: z.string().optional()
15
- });
16
- function teamScopePath(sessionId) {
17
- const supplied = process.env.VH_TEAM_SCOPE_FILE;
18
- if (supplied) {
19
- if (!isAbsolute(supplied)) throw new Error("VH_TEAM_SCOPE_FILE must be absolute");
20
- return supplied;
21
- }
22
- if (!sessionId || !/^[a-zA-Z0-9_-]+$/.test(sessionId)) throw new Error("Teams requires a managed session or VH_TEAM_SCOPE_FILE");
23
- return join(configuration.happyHomeDir, "teams", "scopes", `${sessionId}.json`);
24
- }
25
- async function readTeamScope(path) {
26
- let file;
27
- try {
28
- file = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
29
- } catch (error) {
30
- if (error.code === "ENOENT") return null;
31
- throw new Error("Cannot open team scope");
32
- }
33
- try {
34
- const stat = await file.stat();
35
- if (typeof process.getuid === "function" && stat.uid !== process.getuid()) throw new Error("Team scope owner mismatch");
36
- if (!stat.isFile() || process.platform !== "win32" && (stat.mode & 63) !== 0) throw new Error("Team scope must be a private 0600 file");
37
- const scope = TeamScopeSchema.parse(JSON.parse(await file.readFile("utf8")));
38
- scope.serverUrl = normalizeHttpEndpoint(scope.serverUrl, "team scope");
39
- if (scope.serverUrl !== configuration.serverUrl) throw new Error("Team scope belongs to another server");
40
- return scope;
41
- } catch {
42
- throw new Error("Invalid or unsafe team scope file");
43
- } finally {
44
- await file.close();
45
- }
46
- }
47
- async function writeTeamScope(path, scope) {
48
- await mkdir(dirname(path), { recursive: true, mode: 448 });
49
- const temporary = `${path}.${randomUUID()}.new`;
50
- const file = await open(temporary, "wx", 384);
51
- try {
52
- await file.writeFile(JSON.stringify(TeamScopeSchema.parse(scope)));
53
- await file.sync();
54
- } finally {
55
- await file.close();
56
- }
57
- try {
58
- await rename(temporary, path);
59
- } finally {
60
- await unlink(temporary).catch(() => {
61
- });
62
- }
63
- }
64
-
65
- async function teamRequest(path, method, token, scoped, body) {
66
- const response = await fetch(`${configuration.serverUrl}${path}`, {
67
- method,
68
- redirect: "error",
69
- headers: { "content-type": "application/json", ...scoped ? { "x-happy-team-token": token } : { authorization: `Bearer ${token}` } },
70
- body: body === void 0 ? void 0 : JSON.stringify(body),
71
- signal: AbortSignal.timeout(25e3)
72
- }).catch(() => {
73
- throw new Error("Teams connection failed; outcome may be unknown. Reuse the request ID when retrying.");
74
- });
75
- if (!response.ok) throw new Error(`Teams request failed (HTTP ${response.status})`);
76
- try {
77
- return await response.json();
78
- } catch {
79
- throw new Error("Teams returned an invalid response");
80
- }
81
- }
82
- class TeamConnectionPreflightError extends Error {
83
- }
84
- const initializationLocks = /* @__PURE__ */ new Map();
85
- async function serializeInitialization(path, work) {
86
- const previous = initializationLocks.get(path) ?? Promise.resolve();
87
- const next = previous.catch(() => {
88
- }).then(work);
89
- initializationLocks.set(path, next);
90
- try {
91
- return await next;
92
- } finally {
93
- if (initializationLocks.get(path) === next) initializationLocks.delete(path);
94
- }
95
- }
96
- function createTeamsClient(sessionId) {
97
- const scopePath = () => teamScopePath(sessionId ?? process.env.HAPPY_SESSION_ID);
98
- return {
99
- async initialize(input, options) {
100
- const path = scopePath();
101
- return serializeInitialization(path, async () => {
102
- const priorScope = await readTeamScope(path);
103
- if (priorScope && !options?.resumeAdoption) throw new Error("This session already has a team scope; use scoped team tools");
104
- if (process.env.VH_TEAM_SCOPE_FILE) throw new Error("Assigned team scope is missing or already managed; ask the owner to restore it");
105
- const sid = sessionId ?? process.env.HAPPY_SESSION_ID;
106
- if (!sid) throw new Error("A managed session identity is required");
107
- const machineId = (await readSettings()).machineId;
108
- if (!machineId) throw new TeamConnectionPreflightError("Connect this machine to Very Happy before joining a team");
109
- if (input.machineId && input.machineId !== machineId) throw new TeamConnectionPreflightError("Teams must use this session\u2019s local machine");
110
- const persisted = readPersistedSessions()[sid];
111
- if (!persisted || persisted.metadata.machineId !== machineId) throw new TeamConnectionPreflightError("Team identity must be a persisted session on this machine");
112
- const credentials = await readCredentialsForConfiguredRelay();
113
- if (!credentials) throw new TeamConnectionPreflightError("Sign in to Very Happy before creating or joining a team");
114
- if (priorScope && !options?.expectedTeamId) throw new Error("This session already belongs to another team scope");
115
- let teamId = input.teamId ?? options?.expectedTeamId;
116
- if (!teamId) {
117
- const created = await teamRequest("/v1/teams", "POST", credentials.token, false, { name: input.name, machineId, requestId: input.requestId });
118
- teamId = created.team.id;
119
- } else {
120
- const existing = await teamRequest(`/v1/teams/${encodeURIComponent(teamId)}`, "GET", credentials.token, false);
121
- if (existing.team?.machineId !== machineId) throw new TeamConnectionPreflightError("Team belongs to another machine");
122
- }
123
- if (options?.onTeamResolved) await options.onTeamResolved(teamId);
124
- if (priorScope) {
125
- if (priorScope.teamId !== teamId || priorScope.taskId) throw new Error("This session already belongs to another team scope");
126
- const inspected = await teamRequest(`/v1/teams/${encodeURIComponent(teamId)}/agent`, "GET", priorScope.scopeToken, true);
127
- const bot = inspected.team?.bots?.find((candidate) => candidate.id === priorScope.botId);
128
- if (!bot?.root || bot.managed || bot.sessionId !== sid) throw new Error("Existing scope is not this session\u2019s root identity");
129
- return { team: inspected.team, botId: priorScope.botId };
130
- }
131
- const result = await teamRequest(
132
- `/v1/teams/${encodeURIComponent(teamId)}/actions`,
133
- "POST",
134
- credentials.token,
135
- false,
136
- { requestId: input.requestId, action: { type: "join", name: input.name, sessionId: sid, ...input.botId ? { botId: input.botId } : {} } }
137
- );
138
- if (!result.credential?.token || !result.credential?.botId) throw new Error("Team join did not issue a credential");
139
- await writeTeamScope(path, { serverUrl: configuration.serverUrl, teamId, botId: result.credential.botId, scopeToken: result.credential.token });
140
- return { team: result.team, botId: result.credential.botId };
141
- });
142
- },
143
- async inspect() {
144
- const scope = await readTeamScope(scopePath());
145
- if (!scope) throw new Error("Join or create a team first");
146
- const { credential: _credential, ...safe } = await teamRequest(`/v1/teams/${encodeURIComponent(scope.teamId)}/agent`, "GET", scope.scopeToken, true);
147
- return safe;
148
- },
149
- async action(action, requestId = randomUUID()) {
150
- const scope = await readTeamScope(scopePath());
151
- if (!scope) throw new Error("Join or create a team first");
152
- const result = await teamRequest(`/v1/teams/${encodeURIComponent(scope.teamId)}/agent`, "POST", scope.scopeToken, true, { requestId, action });
153
- const { credential: _credential, ...safe } = result;
154
- return safe;
155
- }
156
- };
157
- }
158
-
159
- const TEAM_TOOL_NAMES = ["team_create", "team_join", "team_inspect", "team_delegate", "team_message", "team_submit", "team_accept", "team_return", "team_cancel", "team_handoff", "team_schedule_create", "team_schedule_pause", "team_schedule_resume", "team_schedule_cancel"];
160
- const id$1 = z.string().min(1).max(128);
161
- const text = z.string().min(1).max(32e3);
162
- const requestId = id$1.describe("Unique operation ID. Reuse the same ID and arguments after an uncertain response.");
163
- function registerTeamsTools(server, sessionId, client = createTeamsClient(sessionId)) {
164
- const register = (name, description, inputSchema, execute) => {
165
- server.registerTool(name, { description, inputSchema, annotations: { readOnlyHint: name === "team_inspect", idempotentHint: true, openWorldHint: false, destructiveHint: ["team_cancel", "team_handoff", "team_return", "team_schedule_cancel"].includes(name) } }, async (args) => {
166
- try {
167
- return { content: [{ type: "text", text: JSON.stringify(await execute(args)) }], isError: false };
168
- } catch (error) {
169
- return { content: [{ type: "text", text: error instanceof Error ? error.message : "Teams operation failed" }], isError: true };
170
- }
171
- });
172
- };
173
- register(
174
- "team_create",
175
- "Create an official Very Happy team and join this session as its root bot. Does not grant access outside this account.",
176
- { name: id$1, machineId: id$1.optional(), requestId },
177
- (args) => client.initialize(args)
178
- );
179
- register(
180
- "team_join",
181
- "Join an existing team as a root bot using this session owner\u2019s account. Scoped workers cannot elevate their role.",
182
- { teamId: id$1, name: id$1, botId: id$1.optional().describe("Existing root bot to reconnect; preserves its identity"), requestId },
183
- (args) => client.initialize(args)
184
- );
185
- register("team_inspect", "Read your visible team tasks, attempts and messages. Inspect before decisions; submission is not acceptance.", {}, () => client.inspect());
186
- const action = (name, description, fields) => register(
187
- `team_${name}`,
188
- description,
189
- { requestId, ...fields },
190
- ({ requestId: key, ...args }) => client.action({ type: name, ...args, ...name === "delegate" && args.directory === void 0 ? { directory: process.cwd() } : {} }, key)
191
- );
192
- action("delegate", "Delegate a goal with acceptance criteria. Use parentTaskId for subdelegation. Existing assigneeBotId or a managed worker may execute it.", {
193
- goal: text,
194
- acceptance: z.array(text).min(1).max(32),
195
- parentTaskId: id$1.optional(),
196
- assigneeBotId: id$1.optional(),
197
- botName: id$1.optional(),
198
- directory: z.string().min(1).optional(),
199
- assistant: z.enum(["claude", "codex", "pi-acp"]).optional(),
200
- model: z.string().trim().min(1).max(256).optional()
201
- });
202
- action("message", "Send a persistent task-related message. Delivery does not mean the model has read or acted on it.", { taskId: id$1, body: text, recipientBotId: id$1.optional() });
203
- action("submit", "Submit evidence for your current attempt and goal version. The owner still has to accept it.", { taskId: id$1, attemptId: id$1, goalVersion: z.number().int().positive(), result: text });
204
- const current = { attemptId: id$1, goalVersion: z.number().int().positive() };
205
- action("accept", "Accept a task you own after checking its evidence. Resource cleanup is tracked separately.", { taskId: id$1, ...current });
206
- action("return", "Return your task for further work with a concrete reason.", { taskId: id$1, reason: text, ...current });
207
- action("cancel", "Cancel an owned task and its dependent work. Cleanup may remain pending.", { taskId: id$1, reason: text, ...current });
208
- action("handoff", "Move an owned task to another bot; prior execution authority is superseded.", { taskId: id$1, assigneeBotId: id$1, ...current });
209
- register("team_schedule_create", "Create a persistent one-time or fixed-interval reminder for your own root bot. Delivery wakes the bound session; it does not prove task completion. Use epoch milliseconds, and inspect before retrying.", { requestId, name: id$1, botId: id$1, body: text, runAt: z.number().int().nonnegative(), intervalMs: z.number().int().min(6e4).optional() }, ({ requestId: key, ...args }) => client.action({ type: "schedule-create", ...args }, key));
210
- for (const operation of ["pause", "resume", "cancel"]) {
211
- register(`team_schedule_${operation}`, `${operation} your root bot\u2019s schedule using its current version from team_inspect. An already in-flight message may still arrive.`, { requestId, scheduleId: id$1, version: z.number().int().positive() }, ({ requestId: key, ...args }) => client.action({ type: `schedule-${operation}`, ...args }, key));
212
- }
213
- }
214
-
215
- class AutomationsRequestError extends Error {
216
- constructor(status, code, runStatus) {
217
- super(describeAutomationError(status, code, runStatus));
218
- this.status = status;
219
- this.code = code;
220
- this.runStatus = runStatus;
221
- }
222
- }
223
- function isAutomationsUnavailable(error) {
224
- return error instanceof AutomationsRequestError && error.status === 404 && !error.code.endsWith("_not_found");
225
- }
226
- const AUTOMATIONS_UNAVAILABLE_MESSAGE = "Automations are not enabled on this server (set VH_AUTOMATIONS_ENABLED=true on the server, or upgrade it).";
227
- function describeAutomationError(status, code, runStatus) {
228
- if (status === 404 && !code.endsWith("_not_found")) return AUTOMATIONS_UNAVAILABLE_MESSAGE;
229
- const known = {
230
- automation_not_found: "No automation with that name or id.",
231
- run_not_found: "No automation run with that id.",
232
- machine_not_found: "That machine is not registered on this account.",
233
- session_not_found: "That session does not belong to this account.",
234
- automation_name_taken: "An automation with that name already exists.",
235
- automation_count_quota_exceeded: "This account already holds the maximum number of automations (active + paused). Delete one you no longer need (`very-happy auto delete <name>`) before creating another.",
236
- automation_paused: "That automation is paused; resume it before firing.",
237
- stale_automation: "The automation changed since you read it; read it again and retry with the new version.",
238
- run_finished: `The run already reached a terminal state${runStatus ? ` (${runStatus})` : ""}. In a continued (sticky) session, pass the runId named in the latest prompt header \u2014 the environment default is the run that started the session.`,
239
- claim_mismatch: "Another daemon holds the claim for that run.",
240
- run_not_claimed: "The run has not been claimed by its machine yet; cancel it instead of reporting.",
241
- status_required: "An explicit report needs status done or failed.",
242
- claim_required: "Lease, session and sticky fields are daemon-only; report just the outcome.",
243
- sticky_requires_session: "A sticky key needs a session id.",
244
- transaction_conflict: "The server was busy; retry the same request.",
245
- invalid_cron: "The cron expression is invalid.",
246
- invalid_timezone: "The time zone is not a valid IANA zone."
247
- };
248
- if (known[code]) return known[code];
249
- if (status === 400) return "The server rejected the request (HTTP 400): check the arguments, or the server may expect a newer request shape.";
250
- return `Automations request failed (HTTP ${status}${code ? `, ${code}` : ""}).`;
251
- }
252
- function createAutomationsClient(options) {
253
- const doFetch = options.fetch ?? fetch;
254
- const base = options.serverUrl.replace(/\/+$/, "");
255
- async function request(method, path, body, query) {
256
- const url = new URL(`${base}${path}`);
257
- for (const [key, value] of Object.entries(query ?? {})) if (value !== void 0) url.searchParams.set(key, value);
258
- let response;
259
- try {
260
- response = await doFetch(url.toString(), {
261
- method,
262
- redirect: "error",
263
- headers: { "content-type": "application/json", authorization: `Bearer ${options.token}`, "x-happy-client": `${options.client ?? "cli-auto"}/${configuration.currentCliVersion}` },
264
- body: body === void 0 ? void 0 : JSON.stringify(body),
265
- signal: AbortSignal.timeout(options.timeoutMs ?? 25e3)
266
- });
267
- } catch {
268
- throw new Error("Automations connection failed; the outcome may be unknown. Read the current state before retrying.");
269
- }
270
- let parsed = null;
271
- try {
272
- parsed = await response.json();
273
- } catch {
274
- parsed = null;
275
- }
276
- if (!response.ok) {
277
- const code = typeof parsed?.error === "string" ? parsed.error : "";
278
- throw new AutomationsRequestError(response.status, code, typeof parsed?.status === "string" ? parsed.status : void 0);
279
- }
280
- return parsed;
281
- }
282
- const enc = encodeURIComponent;
283
- return {
284
- list: (machineId) => request("GET", "/v1/automations", void 0, { machineId }).then((r) => r.automations),
285
- get: (id) => request("GET", `/v1/automations/${enc(id)}`).then((r) => r.automation),
286
- getByName: (name) => request("GET", `/v1/automations/by-name/${enc(name)}`).then((r) => r.automation),
287
- create: (input) => request("POST", "/v1/automations", input).then((r) => r.automation),
288
- update: (id, input) => request("PATCH", `/v1/automations/${enc(id)}`, input).then((r) => r.automation),
289
- remove: (id) => request("DELETE", `/v1/automations/${enc(id)}`),
290
- pause: (id) => request("POST", `/v1/automations/${enc(id)}/pause`).then((r) => r.automation),
291
- resume: (id) => request("POST", `/v1/automations/${enc(id)}/resume`).then((r) => r.automation),
292
- run: (id, input = {}) => request("POST", `/v1/automations/${enc(id)}/run`, input).then((r) => r.run),
293
- fire: (name, input = {}) => request("POST", `/v1/automations/by-name/${enc(name)}/fire`, input),
294
- runs: (query = {}) => request("GET", "/v1/automations/runs", void 0, {
295
- automationId: query.automationId,
296
- name: query.name,
297
- status: query.status,
298
- attention: query.attention ? "1" : void 0,
299
- limit: query.limit === void 0 ? void 0 : String(query.limit)
300
- }).then((r) => r.runs),
301
- getRun: (id) => request("GET", `/v1/automations/runs/${enc(id)}`).then((r) => r.run),
302
- cancel: (id) => request("POST", `/v1/automations/runs/${enc(id)}/cancel`).then((r) => r.run),
303
- ack: (id) => request("POST", `/v1/automations/runs/${enc(id)}/ack`).then((r) => r.run),
304
- report: (id, input) => request("POST", `/v1/automations/runs/${enc(id)}/report`, input).then((r) => r.run),
305
- claim: (input) => request("POST", "/v1/automations/claim", input),
306
- stickies: (id, key) => request("GET", `/v1/automations/${enc(id)}/stickies`, void 0, { key }).then((r) => r.stickies),
307
- putSticky: (id, key, sessionId) => request("PUT", `/v1/automations/${enc(id)}/stickies`, { key, sessionId }).then((r) => r.sticky),
308
- deleteSticky: (id, key) => request("DELETE", `/v1/automations/${enc(id)}/stickies`, { key }).then((r) => r.removed)
309
- };
310
- }
311
- async function authenticatedAutomationsClient(client = "cli-auto") {
312
- const credentials = await readCredentialsForConfiguredRelay();
313
- if (!credentials) throw new Error("Not authenticated. Run `very-happy auth login` first.");
314
- const settings = await readSettings();
315
- return { client: createAutomationsClient({ serverUrl: configuration.serverUrl, token: credentials.token, client }), machineId: settings.machineId };
316
- }
317
- function automationRunIdFromEnv(env = process.env) {
318
- const value = env.VH_AUTOMATION_RUN_ID;
319
- return value && /^[a-zA-Z0-9_-]{1,128}$/.test(value) ? value : void 0;
320
- }
321
-
322
- const AUTOMATION_TOOL_NAMES = [
323
- "automation_list",
324
- "automation_get",
325
- "automation_create",
326
- "automation_update",
327
- "automation_pause",
328
- "automation_resume",
329
- "automation_delete",
330
- "automation_run",
331
- "automation_fire",
332
- "automation_runs",
333
- "automation_report",
334
- "automation_ack"
335
- ];
336
- const id = z.string().min(1).max(128);
337
- const ref = { name: AutomationNameSchema.optional().describe("Automation name"), id: id.optional().describe("Automation id (alternative to name)") };
338
- const payload = z.string().max(65536).optional().describe("Payload text; JSON lets prompts use {{payload.field}}");
339
- const maxRuntimeMs = z.number().int().min(6e4).max(7 * 864e5).optional();
340
- const AUTOMATION_TOOL_SCHEMAS = {
341
- automation_list: { description: "List this account\u2019s automations (name, trigger, action, machine, status, next run). Defaults to every machine.", readOnly: true, inputSchema: { machineId: id.optional().describe("Only this machine id, or 'this' for the current machine") } },
342
- automation_get: { description: "Read one automation by name or id, including its version (needed for automation_update).", readOnly: true, inputSchema: ref },
343
- automation_create: {
344
- description: "Create an automation: a trigger (cron+tz / interval / once / manual) and an action (spawn a session, send into a session, or run a script argv) on one machine. Defaults to the current machine. Prompts and argv may use {{payload}}, {{payload.path}}, {{run.id}}, {{automation.name}}, {{now}}.",
345
- inputSchema: {
346
- name: AutomationNameSchema,
347
- description: z.string().max(4e3).optional(),
348
- machineId: id.optional().describe("Target machine id; omit or 'this' for the current machine"),
349
- trigger: AutomationTriggerSchema,
350
- action: AutomationActionSchema,
351
- concurrency: AutomationConcurrencySchema.optional().describe("skip (default): a run while another is active is recorded as skipped; queue: it waits"),
352
- maxRuntimeMs,
353
- paused: z.boolean().optional().describe("Create paused")
354
- }
355
- },
356
- automation_update: {
357
- description: "Change an automation (name / description / machine / trigger / action / concurrency / maxRuntimeMs). Requires the current version from automation_get; a stale version is rejected.",
358
- inputSchema: {
359
- ...ref,
360
- version: z.number().int().positive(),
361
- newName: AutomationNameSchema.optional(),
362
- description: z.string().max(4e3).nullable().optional(),
363
- machineId: id.optional(),
364
- trigger: AutomationTriggerSchema.optional(),
365
- action: AutomationActionSchema.optional(),
366
- concurrency: AutomationConcurrencySchema.optional(),
367
- maxRuntimeMs
368
- }
369
- },
370
- automation_pause: { description: "Pause an automation: no new scheduled runs and fire is refused until resumed.", inputSchema: ref },
371
- automation_resume: { description: "Resume a paused automation; the next run is computed from now.", inputSchema: ref },
372
- automation_delete: { description: "Delete an automation and its run history. Irreversible.", destructive: true, inputSchema: ref },
373
- automation_run: { description: "Queue one run of an automation now (source: manual), optionally with a payload.", inputSchema: { ...ref, payload } },
374
- automation_fire: { description: "Trigger an automation by name as an external event, with an optional payload and dedupe key (the same key within 24h returns the original run).", inputSchema: { name: AutomationNameSchema, payload, dedupeKey: z.string().min(1).max(256).optional() } },
375
- automation_runs: { description: "List runs, newest first: status, session, summary, error, attention. Filter by automation, status or attention.", readOnly: true, inputSchema: { ...ref, status: z.string().min(1).max(64).optional(), attention: z.boolean().optional().describe("Only runs flagged for a human"), limit: z.number().int().min(1).max(200).optional() } },
376
- automation_report: {
377
- description: 'Report the result of an automation run. Without runId it targets the run that STARTED this session (VH_AUTOMATION_RUN_ID); in a continued sticky session always pass the runId named in the latest "[Very Happy automation \u2026 run <id>; \u2026]" prompt header. Use status done with a short summary, or failed with the error; set needsAttention when a human must look.',
378
- inputSchema: { runId: id.optional(), status: z.enum(["done", "failed"]), summary: z.string().max(4096).optional(), error: z.string().max(4096).optional(), needsAttention: z.boolean().optional(), attentionReason: z.string().max(512).optional() }
379
- },
380
- automation_ack: { description: "Clear the attention flag of a run after a human handled it.", inputSchema: { runId: id } }
381
- };
382
- async function resolve(context, args) {
383
- if (args.id) return context.client.get(args.id);
384
- if (args.name) return context.client.getByName(args.name);
385
- throw new Error("Give the automation name or id");
386
- }
387
- function machineFor(context, requested) {
388
- if (requested && requested !== "this") return requested;
389
- return context.machineId;
390
- }
391
- function requireMachine(context, requested) {
392
- const machine = machineFor(context, requested);
393
- if (!machine) throw new Error("This machine is not registered with Very Happy; pass machineId explicitly");
394
- return machine;
395
- }
396
- async function executeAutomationTool(name, args, context) {
397
- const { client } = context;
398
- switch (name) {
399
- case "automation_list":
400
- return { automations: await client.list(machineFor(context, args.machineId)) };
401
- case "automation_get":
402
- return { automation: await resolve(context, args) };
403
- case "automation_create": {
404
- const input = {
405
- name: args.name,
406
- machineId: requireMachine(context, args.machineId),
407
- trigger: args.trigger,
408
- action: args.action,
409
- ...args.description !== void 0 ? { description: args.description } : {},
410
- ...args.concurrency !== void 0 ? { concurrency: args.concurrency } : {},
411
- ...args.maxRuntimeMs !== void 0 ? { maxRuntimeMs: args.maxRuntimeMs } : {},
412
- ...args.paused ? { status: "paused" } : {}
413
- };
414
- return { automation: await client.create(input) };
415
- }
416
- case "automation_update": {
417
- const current = await resolve(context, args);
418
- const input = {
419
- version: args.version,
420
- ...args.newName !== void 0 ? { name: args.newName } : {},
421
- ...args.description !== void 0 ? { description: args.description } : {},
422
- ...args.machineId !== void 0 ? { machineId: machineFor(context, args.machineId) } : {},
423
- ...args.trigger !== void 0 ? { trigger: args.trigger } : {},
424
- ...args.action !== void 0 ? { action: args.action } : {},
425
- ...args.concurrency !== void 0 ? { concurrency: args.concurrency } : {},
426
- ...args.maxRuntimeMs !== void 0 ? { maxRuntimeMs: args.maxRuntimeMs } : {}
427
- };
428
- return { automation: await client.update(current.id, input) };
429
- }
430
- case "automation_pause":
431
- return { automation: await client.pause((await resolve(context, args)).id) };
432
- case "automation_resume":
433
- return { automation: await client.resume((await resolve(context, args)).id) };
434
- case "automation_delete": {
435
- const target = await resolve(context, args);
436
- await client.remove(target.id);
437
- return { deleted: true, id: target.id, name: target.name };
438
- }
439
- case "automation_run":
440
- return { run: await client.run((await resolve(context, args)).id, args.payload !== void 0 ? { payload: args.payload } : {}) };
441
- case "automation_fire":
442
- return client.fire(args.name, { ...args.payload !== void 0 ? { payload: args.payload } : {}, ...args.dedupeKey !== void 0 ? { dedupeKey: args.dedupeKey } : {} });
443
- case "automation_runs": {
444
- const automationId = args.id ?? (args.name ? (await client.getByName(args.name)).id : void 0);
445
- return { runs: await client.runs({ automationId, status: args.status, attention: args.attention === true, limit: args.limit }) };
446
- }
447
- case "automation_report": {
448
- const runId = args.runId ?? automationRunIdFromEnv(context.env ?? process.env);
449
- if (!runId) throw new Error("No run id: pass runId (this session was not started by an automation)");
450
- const report = {
451
- status: args.status,
452
- ...args.summary !== void 0 ? { summary: args.summary } : {},
453
- ...args.error !== void 0 ? { error: args.error } : {},
454
- ...args.needsAttention !== void 0 ? { needsAttention: args.needsAttention } : {},
455
- ...args.attentionReason !== void 0 ? { attentionReason: args.attentionReason } : {}
456
- };
457
- return { run: await client.report(runId, report) };
458
- }
459
- case "automation_ack":
460
- return { run: await client.ack(args.runId) };
461
- }
462
- }
463
- function createAutomationToolExecutor() {
464
- let context = null;
465
- return async (name, args) => {
466
- context ??= authenticatedAutomationsClient("mcp-auto").then(({ client, machineId }) => ({ client, machineId }));
467
- let resolved;
468
- try {
469
- resolved = await context;
470
- } catch (error) {
471
- context = null;
472
- throw error;
473
- }
474
- return executeAutomationTool(name, args, resolved);
475
- };
476
- }
477
- function registerAutomationTools(server, execute = createAutomationToolExecutor()) {
478
- for (const name of AUTOMATION_TOOL_NAMES) {
479
- const spec = AUTOMATION_TOOL_SCHEMAS[name];
480
- server.registerTool(name, {
481
- description: spec.description,
482
- inputSchema: spec.inputSchema,
483
- annotations: { readOnlyHint: spec.readOnly === true, idempotentHint: name !== "automation_run" && name !== "automation_create", openWorldHint: false, destructiveHint: spec.destructive === true }
484
- }, async (args) => {
485
- try {
486
- return { content: [{ type: "text", text: JSON.stringify(await execute(name, args ?? {})) }], isError: false };
487
- } catch (error) {
488
- return { content: [{ type: "text", text: error instanceof Error ? error.message : "Automation operation failed" }], isError: true };
489
- }
490
- });
491
- }
492
- }
493
-
494
- function trimIdent(text) {
495
- const lines = text.split("\n");
496
- while (lines.length > 0 && lines[0].trim() === "") {
497
- lines.shift();
498
- }
499
- while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
500
- lines.pop();
501
- }
502
- const minSpaces = lines.reduce((min, line) => {
503
- if (line.trim() === "") {
504
- return min;
505
- }
506
- const leadingSpaces = line.match(/^\s*/)[0].length;
507
- return Math.min(min, leadingSpaces);
508
- }, Infinity);
509
- const trimmedLines = lines.map((line) => line.slice(minSpaces));
510
- return trimmedLines.join("\n");
511
- }
512
-
513
- const AGENT_GUIDANCE = (() => trimIdent(`
514
- You are running inside Happy: the user is reading this session in a web client (often a phone), NOT in the terminal where you run. Two consequences:
515
-
516
- - Terminal output is not a good delivery channel. When the user asks for a piece of text ("copy that", "give me X", "\u590D\u5236\u7ED9\u6211", "\u53D1\u6211"), hand it over with mcp__happy__copy_to_clipboard instead of printing it for them to select by hand.
517
- - When you produce or want to show a FILE (a document you wrote, a report, an image, a PDF, a diff), call mcp__happy__open_preview with its path instead of cat-ing it. Their client opens the rendered file.
518
-
519
- Judgement, not reflex: use these when the user's goal is to RECEIVE something. If they are asking a question you can just answer, answer it. Do not preview a file you only read for your own reasoning, and do not preview the same file repeatedly within a turn.
520
-
521
- If you are working on a task the user is tracking, call mcp__happy__report_progress at meaningful milestones (started / blocked / needs review / done) so their board reflects reality. Do not report every tool call.
522
- `))();
523
- const PREVIEW_TOOL_NAME = "open_preview";
524
- const PREVIEW_TOOL_TITLE = "Open File Preview for the User";
525
- const PREVIEW_TOOL_DESCRIPTION = 'Open a file in the preview panel of every web client the user currently has open. Use this whenever you produce or want to show the user a file: a document or report you just wrote, a generated image or diagram, a PDF, a data file, or a source file you want them to look at. Trigger phrases include: "write me a doc", "show me that file", "let me see it", "\u5199\u4E2A\u6587\u6863", "\u7ED9\u6211\u770B\u770B\u8FD9\u4E2A\u6587\u4EF6", "\u6253\u5F00\u770B\u4E0B". Pass an absolute path (or one starting with ~). The file is read on this machine and rendered in their browser \u2014 markdown is rendered, images and PDFs are displayed, other text is syntax-highlighted. Prefer this over printing file contents to the terminal, which the user is probably not looking at. NOTE: this only requests that their client open the file; it cannot confirm the user actually looked at it, so do not assume they have seen it.';
526
- const REPORT_PROGRESS_TOOL_NAME = "report_progress";
527
- const REPORT_PROGRESS_TOOL_TITLE = "Report Progress to the Task Board";
528
- const REPORT_PROGRESS_TOOL_DESCRIPTION = `Report what you are doing to the user's task board, so they can see the state of this session at a glance without reading the transcript. Call it at meaningful milestones only: when you start substantive work, when you get blocked and need the user, when something is ready for their review, and when you are done. Do NOT call it after every tool call or every file edit \u2014 it is a status line, not a log. Set attention to "blocked" when you cannot proceed without the user, "review" when you want them to look at something, and "none" for ordinary progress. Keep progress to one short line describing the current state (the user reads it on a card).`;
529
-
530
- export { AUTOMATIONS_UNAVAILABLE_MESSAGE as A, PREVIEW_TOOL_NAME as P, REPORT_PROGRESS_TOOL_NAME as R, TeamConnectionPreflightError as T, PREVIEW_TOOL_TITLE as a, PREVIEW_TOOL_DESCRIPTION as b, registerAutomationTools as c, authenticatedAutomationsClient as d, automationRunIdFromEnv as e, readTeamScope as f, teamRequest as g, createTeamsClient as h, isAutomationsUnavailable as i, trimIdent as j, AGENT_GUIDANCE as k, createAutomationsClient as l, AutomationsRequestError as m, TEAM_TOOL_NAMES as n, AUTOMATION_TOOL_NAMES as o, REPORT_PROGRESS_TOOL_TITLE as p, REPORT_PROGRESS_TOOL_DESCRIPTION as q, registerTeamsTools as r, teamScopePath as t };