very-happy-cli 0.2.151 → 0.2.152

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-BjTA5gUB.mjs → AcpBackend-Bs3wwYbt.mjs} +2 -2
  2. package/dist/{AcpBackend-blE9IEod.cjs → AcpBackend-CogiKCng.cjs} +2 -2
  3. package/dist/{AcpSessionManager-D-9mrp40.mjs → AcpSessionManager-BT8PJMEm.mjs} +1 -1
  4. package/dist/{AcpSessionManager-bF8PYu_B.cjs → AcpSessionManager-BazkBuFo.cjs} +9 -9
  5. package/dist/agentGuidance-CqBn77AI.cjs +553 -0
  6. package/dist/agentGuidance-Daxn5Nvj.mjs +529 -0
  7. package/dist/{agentHome-CeFDNzvq.mjs → agentHome-CCYfs8Ec.mjs} +3 -3
  8. package/dist/{agentHome-D6MSfwnp.cjs → agentHome-CpJjrQZc.cjs} +3 -3
  9. package/dist/auto-DV5emt1-.mjs +466 -0
  10. package/dist/auto-DncmTDvw.cjs +472 -0
  11. package/dist/codex/happyMcpStdioBridge.cjs +4 -2
  12. package/dist/codex/happyMcpStdioBridge.mjs +4 -2
  13. package/dist/{config-C-A1XF62.mjs → config-DbKpQpdL.mjs} +5 -5
  14. package/dist/{config-Cadw1sAk.cjs → config-DeJgSMh-.cjs} +5 -5
  15. package/dist/{index-CIyjJy8D.mjs → index-BauTv2jB.mjs} +712 -39
  16. package/dist/{index-DYQgcgL2.mjs → index-CN7OF2cU.mjs} +5 -5
  17. package/dist/{index-vyRqLjDv.cjs → index-CRaJ9k2p.cjs} +5 -5
  18. package/dist/{index-DEp6CmmS.mjs → index-DahaLbu4.mjs} +19 -19
  19. package/dist/{index-CqtfRzug.cjs → index-ZIW1jrYm.cjs} +750 -71
  20. package/dist/{index-BKGspSWk.cjs → index-imt4mC41.cjs} +19 -19
  21. package/dist/index.cjs +4 -4
  22. package/dist/index.mjs +4 -4
  23. package/dist/install-C1K3nrxF.mjs +114 -0
  24. package/dist/install-CDR3qBhs.cjs +118 -0
  25. package/dist/{installTerminalHooks-Bf_adbl3.cjs → installTerminalHooks-BoDxpaBO.cjs} +5 -5
  26. package/dist/{installTerminalHooks-Ch_yTtEA.mjs → installTerminalHooks-VvmlO6Ue.mjs} +5 -5
  27. package/dist/lib.cjs +3 -3
  28. package/dist/lib.mjs +3 -3
  29. package/dist/limits-BNTWyUaZ.cjs +2175 -0
  30. package/dist/limits-CKR_HJnO.mjs +2080 -0
  31. package/dist/{mcp-CMxEZ6-0.cjs → mcp-CCKCKL6l.cjs} +5 -5
  32. package/dist/{mcp-zu8n8TkR.mjs → mcp-D4dAo5BQ.mjs} +5 -5
  33. package/dist/{piRuntime-D3ny0mVg.cjs → piRuntime-Bn5ypbjH.cjs} +2 -2
  34. package/dist/{piRuntime-Cu6cXZ6N.mjs → piRuntime-CW-XA4rz.mjs} +2 -2
  35. package/dist/{piTerminal-kDSd_r9X.cjs → piTerminal-B7K75aXB.cjs} +6 -6
  36. package/dist/{piTerminal-M677UP7y.mjs → piTerminal-BLSXK1u2.mjs} +6 -6
  37. package/dist/{runGemini-DuwdVV1O.cjs → runGemini-Bgg1BzDK.cjs} +7 -7
  38. package/dist/{runGemini-DvJwQVGp.mjs → runGemini-D5N11LxO.mjs} +7 -7
  39. package/dist/{runOpenClaw-BsvtCE_H.cjs → runOpenClaw-B9qWHybc.cjs} +6 -6
  40. package/dist/{runOpenClaw-BG82bF18.mjs → runOpenClaw-CCpyKbrn.mjs} +6 -6
  41. package/dist/{send-o6WM-1G8.mjs → send-C44kcXz5.mjs} +5 -5
  42. package/dist/{send-aUkjhey7.cjs → send-wl9ei8yG.cjs} +5 -5
  43. package/dist/{sessions-DlU3foY_.mjs → sessions-DXJEcUg6.mjs} +5 -5
  44. package/dist/{sessions-B3vP18kz.cjs → sessions-Tad8elmy.cjs} +5 -5
  45. package/dist/{spawn-DxHIIqhi.mjs → spawn-BOlb5y1_.mjs} +6 -31
  46. package/dist/{spawn-DH6_p4-f.cjs → spawn-DA8WAFmm.cjs} +7 -32
  47. package/dist/{teams-DoAA_CcN.mjs → teams-Bs8lI887.mjs} +15 -72
  48. package/dist/{teams-BW8D1n3o.cjs → teams-qVLrFcdd.cjs} +13 -70
  49. package/dist/{todo-Bcc2XxGh.cjs → todo-mCMoGjJk.cjs} +4 -23
  50. package/dist/{todo-BhJbG5eq.mjs → todo-nzsqGZqp.mjs} +2 -21
  51. package/dist/{types-CPubg05Z.cjs → types-CN28qJl7.cjs} +31 -856
  52. package/dist/{types-BN3eiipi.mjs → types-xKrxbCRv.mjs} +204 -1017
  53. package/package.json +7 -7
  54. package/dist/agentGuidance-BXa9ikrZ.cjs +0 -267
  55. package/dist/agentGuidance-Dvo3cZMW.mjs +0 -251
  56. package/dist/limits-DS-bnYYp.mjs +0 -1165
  57. package/dist/limits-DwcY3kyi.cjs +0 -1236
@@ -3,14 +3,14 @@
3
3
  var chalk = require('chalk');
4
4
  var os = require('node:os');
5
5
  var node_crypto = require('node:crypto');
6
- var index = require('./types-CPubg05Z.cjs');
7
- var persistence = require('./limits-DwcY3kyi.cjs');
6
+ var index = require('./types-CN28qJl7.cjs');
7
+ var persistence = require('./limits-BNTWyUaZ.cjs');
8
8
  var spawn = require('cross-spawn');
9
9
  var path = require('node:path');
10
10
  var node_readline = require('node:readline');
11
11
  var node_fs = require('node:fs');
12
12
  var projectPath = require('./projectPath-ChLhdOyt.cjs');
13
- var agentGuidance = require('./agentGuidance-BXa9ikrZ.cjs');
13
+ var agentGuidance = require('./agentGuidance-CqBn77AI.cjs');
14
14
  var promises = require('node:fs/promises');
15
15
  var fs = require('fs/promises');
16
16
  var ink = require('ink');
@@ -492,7 +492,7 @@ function shouldIncludeCoAuthoredBy() {
492
492
 
493
493
  const BASE_SYSTEM_PROMPT = (() => agentGuidance.trimIdent(`
494
494
  This chat is titled automatically from the user's first message. Call "mcp__happy__change_title" only when the topic has clearly moved on or the current title is too generic to find this chat later. Do not call it as a ritual at the start of a chat, and not for a quick question you can just answer.
495
- `))() + "\n\n" + agentGuidance.AGENT_GUIDANCE + "\n\n" + index.BUILTIN_TODO_DISCOVERY;
495
+ `))() + "\n\n" + agentGuidance.AGENT_GUIDANCE + "\n\n" + persistence.BUILTIN_TODO_DISCOVERY;
496
496
  const CO_AUTHORED_CREDITS = (() => agentGuidance.trimIdent(`
497
497
  When you yourself create a git commit, instead of just giving co-credit to Claude, also give credit to Happy like so:
498
498
 
@@ -3507,7 +3507,7 @@ class StreamRelay {
3507
3507
  }
3508
3508
  this.pending.text += text;
3509
3509
  this.pending.bytes += Buffer.byteLength(text, "utf8");
3510
- if (this.pending.bytes >= STREAM_FLUSH_MAX_BYTES || this.pending.text.length >= index.STREAM_DELTA_MAX_CHARS) {
3510
+ if (this.pending.bytes >= STREAM_FLUSH_MAX_BYTES || this.pending.text.length >= persistence.STREAM_DELTA_MAX_CHARS) {
3511
3511
  this.flushPending();
3512
3512
  return;
3513
3513
  }
@@ -5402,8 +5402,8 @@ ${typeLabels[type] || type}:`));
5402
5402
  }
5403
5403
  console.log(chalk.gray(" An external claude command is only needed for native terminal/mirror use."));
5404
5404
  try {
5405
- const { resolveAgentHomesForCli } = await Promise.resolve().then(function () { return require('./agentHome-D6MSfwnp.cjs'); });
5406
- const { describeAgentHome } = await Promise.resolve().then(function () { return require('./types-CPubg05Z.cjs'); }).then(function (n) { return n.index; });
5405
+ const { resolveAgentHomesForCli } = await Promise.resolve().then(function () { return require('./agentHome-CpJjrQZc.cjs'); });
5406
+ const { describeAgentHome } = await Promise.resolve().then(function () { return require('./types-CN28qJl7.cjs'); }).then(function (n) { return n.index; });
5407
5407
  const homes = await resolveAgentHomesForCli();
5408
5408
  console.log(chalk.green(`\u2713 Claude config dir: ${describeAgentHome(homes.claudeConfigDir)}`));
5409
5409
  console.log(chalk.green(`\u2713 Codex home: ${describeAgentHome(homes.codexHome)}`));
@@ -5514,7 +5514,7 @@ async function registryVersion(name) {
5514
5514
  return typeof data.version === "string" ? stableVersion(data.version) : null;
5515
5515
  }
5516
5516
  async function detectAgentVersions(deps = { probe: probeAgentVersion, registryVersion }, now = Date.now()) {
5517
- const agents = await Promise.all(index.AGENT_VERSION_SOURCES.map(async (source) => {
5517
+ const agents = await Promise.all(persistence.AGENT_VERSION_SOURCES.map(async (source) => {
5518
5518
  const local = source.command ? await deps.probe(source.command).catch(() => ({ version: null, missing: false })) : { version: stableVersion(persistence.packageJson.dependencies["@anthropic-ai/claude-agent-sdk"]), missing: false };
5519
5519
  if (local.missing) return { id: source.id, installed: null, latest: null, status: "not-installed" };
5520
5520
  const latest = await deps.registryVersion(source.package).catch(() => null);
@@ -6230,7 +6230,7 @@ const DENIED_BASENAME_PATTERNS = [
6230
6230
  const DENIED_PATH_SUFFIXES = [
6231
6231
  `${path.sep}.git${path.sep}config`
6232
6232
  ];
6233
- function expandHome(input, home) {
6233
+ function expandHome$1(input, home) {
6234
6234
  if (input === "~") return home;
6235
6235
  if (input.startsWith(`~${path.sep}`) || input.startsWith("~/")) {
6236
6236
  return path.resolve(home, input.slice(2));
@@ -6253,7 +6253,7 @@ function checkPreviewPath(input, opts) {
6253
6253
  if (input.includes("\0")) {
6254
6254
  return { resolved: "", deniedReason: "path contains a NUL byte" };
6255
6255
  }
6256
- const expanded = expandHome(input.trim(), home);
6256
+ const expanded = expandHome$1(input.trim(), home);
6257
6257
  const resolved = path.isAbsolute(expanded) ? path.resolve(expanded) : path.resolve(cwd, expanded);
6258
6258
  for (const dir of DENIED_HOME_DIRS) {
6259
6259
  if (isUnder(resolved, path.resolve(home, dir))) {
@@ -8144,7 +8144,7 @@ function parseJsonObject(text) {
8144
8144
  return null;
8145
8145
  }
8146
8146
  }
8147
- function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-CqtfRzug.cjs', document.baseURI).href))).resolve(s)) {
8147
+ function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-ZIW1jrYm.cjs', document.baseURI).href))).resolve(s)) {
8148
8148
  const platformArch = `${platform}-${arch}`;
8149
8149
  const candidates = [
8150
8150
  `@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
@@ -9539,11 +9539,14 @@ function agentPiece(body) {
9539
9539
  }
9540
9540
  return null;
9541
9541
  }
9542
- function analyzeLatestTurn(entries) {
9542
+ function userPromptText(body) {
9543
+ return isUserPrompt(body) ? body.content.text : null;
9544
+ }
9545
+ function analyzeLatestTurn(entries, options = {}) {
9543
9546
  const lastSeq = entries.length > 0 ? entries[entries.length - 1].seq : 0;
9544
9547
  let userIndex = -1;
9545
9548
  for (let i = entries.length - 1; i >= 0; i--) {
9546
- if (isUserPrompt(entries[i].body)) {
9549
+ if (isUserPrompt(entries[i].body) && (!options.anchor || options.anchor(entries[i].body))) {
9547
9550
  userIndex = i;
9548
9551
  break;
9549
9552
  }
@@ -9557,6 +9560,10 @@ function analyzeLatestTurn(entries) {
9557
9560
  const emitsStarts = entries.some((entry) => sessionEnvelope(entry.body)?.ev?.t === "turn-start");
9558
9561
  for (let i = userIndex + 1; i < entries.length; i++) {
9559
9562
  const body = entries[i].body;
9563
+ if (options.anchor && isUserPrompt(body)) {
9564
+ if (ended) break;
9565
+ continue;
9566
+ }
9560
9567
  const env = sessionEnvelope(body);
9561
9568
  if (env?.ev?.t === "turn-start") {
9562
9569
  lastMarker = "start";
@@ -10049,7 +10056,7 @@ function readReceipt(home, id) {
10049
10056
  throw error;
10050
10057
  }
10051
10058
  }
10052
- function writeReceipt(home, receipt) {
10059
+ function writeReceipt$1(home, receipt) {
10053
10060
  persistence.ensurePrivateDirectorySync(path.join(home, "teams", "effects"));
10054
10061
  const target = receiptPath(home, receipt.operationId);
10055
10062
  const tmp = `${target}.${process.pid}.tmp`;
@@ -10126,7 +10133,7 @@ function createTeamWorker(deps) {
10126
10133
  const action = async (teamId, requestId, value) => (await http.post(`/v1/teams/${encodeURIComponent(teamId)}/actions`, { requestId, action: value })).data;
10127
10134
  async function complete(op, receipt) {
10128
10135
  await action(op.teamId, `complete-${op.id}`, { type: "complete-operation", operationId: op.id, machineId: deps.machineId, claimId: receipt.claimId, ...receipt.sessionId ? { sessionId: receipt.sessionId } : {}, ...op.type === "spawn" && receipt.directory ? { workingDirectory: receipt.directory } : {} });
10129
- writeReceipt(home, { ...receipt, phase: "completed" });
10136
+ writeReceipt$1(home, { ...receipt, phase: "completed" });
10130
10137
  }
10131
10138
  async function execute(op) {
10132
10139
  let receipt = readReceipt(home, op.id);
@@ -10143,7 +10150,7 @@ function createTeamWorker(deps) {
10143
10150
  op = claimed.operation;
10144
10151
  credential = claimed.credential;
10145
10152
  receipt = { operationId: op.id, teamId: op.teamId, claimId: op.claimId, phase: "prepared" };
10146
- writeReceipt(home, receipt);
10153
+ writeReceipt$1(home, receipt);
10147
10154
  }
10148
10155
  if (receipt.phase === "completed") return;
10149
10156
  try {
@@ -10156,7 +10163,7 @@ function createTeamWorker(deps) {
10156
10163
  if (!task) throw new Error("Task state unavailable; refusing to start");
10157
10164
  if (task.currentAttemptId !== op.attemptId || ["done", "cancelled"].includes(task.status)) {
10158
10165
  await action(op.teamId, `cancelled-before-spawn-${op.id}`, { type: "fail-operation", operationId: op.id, machineId: deps.machineId, claimId: receipt.claimId, error: "task_closed_before_spawn" });
10159
- writeReceipt(home, { ...receipt, phase: "completed" });
10166
+ writeReceipt$1(home, { ...receipt, phase: "completed" });
10160
10167
  return;
10161
10168
  }
10162
10169
  if (!op.directory) throw new Error("No repository selected for worker");
@@ -10166,17 +10173,17 @@ function createTeamWorker(deps) {
10166
10173
  const scopeFile = path.join(scopes, `${op.id}.json`);
10167
10174
  persistence.writePrivateFileSync(scopeFile, JSON.stringify({ serverUrl: server, scopeToken: credential.token, teamId: op.teamId, botId: op.botId, taskId: op.taskId, attemptId: op.attemptId }));
10168
10175
  receipt = { ...receipt, ...resource, scopeFile, phase: "spawning" };
10169
- writeReceipt(home, receipt);
10176
+ writeReceipt$1(home, receipt);
10170
10177
  const spawned = await deps.spawn({ directory: resource.directory, agent: op.assistant === "pi-acp" ? "pi" : op.assistant, permissionMode: op.permissionMode === "bypassPermissions" ? "bypassPermissions" : "default", spawnedBy: "teams", environmentVariables: { VH_TEAM_SCOPE_FILE: scopeFile, VH_TEAM_OPERATION_ID: op.id } });
10171
10178
  if (spawned.type !== "success") throw new Error(spawned.type === "error" ? spawned.errorMessage : "Directory approval required");
10172
10179
  receipt = { ...receipt, sessionId: spawned.sessionId, phase: "spawned" };
10173
- writeReceipt(home, receipt);
10180
+ writeReceipt$1(home, receipt);
10174
10181
  }
10175
10182
  if (receipt.phase === "spawning") {
10176
10183
  const matches = Object.entries(persistence.readPersistedSessions()).filter(([, entry]) => entry.metadata?.teamOperationId === op.id);
10177
10184
  if (matches.length !== 1) throw new Error("Spawn outcome unknown; reconcile the worker before retrying");
10178
10185
  receipt = { ...receipt, sessionId: matches[0][0], phase: "spawned" };
10179
- writeReceipt(home, receipt);
10186
+ writeReceipt$1(home, receipt);
10180
10187
  }
10181
10188
  if (receipt.phase === "spawned") {
10182
10189
  const current = (await http.get(`/v1/teams/${encodeURIComponent(op.teamId)}`)).data.team;
@@ -10191,14 +10198,14 @@ function createTeamWorker(deps) {
10191
10198
  const key = await waitForSessionKey(receipt.sessionId, 1e4);
10192
10199
  await sendUserMessage(receipt.sessionId, key, teamInitialPrompt(op.prompt, bot?.root === true, receipt.directory), "teams", { localId: `teams-initial-${op.id}`, sentFrom: "team", ...op.model ? { model: op.model } : {} });
10193
10200
  receipt = { ...receipt, phase: "delivered" };
10194
- writeReceipt(home, receipt);
10201
+ writeReceipt$1(home, receipt);
10195
10202
  }
10196
10203
  await complete(op, receipt);
10197
10204
  } else {
10198
10205
  const team = (await http.get(`/v1/teams/${encodeURIComponent(op.teamId)}`)).data.team;
10199
10206
  const latest = team.operations.find((candidate) => candidate.id === op.id);
10200
10207
  if (latest?.status === "completed") {
10201
- writeReceipt(home, { ...receipt, phase: "completed" });
10208
+ writeReceipt$1(home, { ...receipt, phase: "completed" });
10202
10209
  return;
10203
10210
  }
10204
10211
  const bot = team.bots.find((candidate) => candidate.id === op.botId);
@@ -10328,6 +10335,643 @@ Use the teams tools to inspect current task state before acting. This message is
10328
10335
  } };
10329
10336
  }
10330
10337
 
10338
+ function resolveSpawnPermissionMode(agent, explicit) {
10339
+ return explicit ?? persistence.getAgentCodeDefaults(agent ?? "claude").permissionMode;
10340
+ }
10341
+ function outboundPermissionMode(agent, mode) {
10342
+ const key = persistence.normalizeAgentKey(agent ?? "claude");
10343
+ if ((key === "claude" || key === "pi") && mode === "yolo") return "bypassPermissions";
10344
+ return mode;
10345
+ }
10346
+ function resolveFirstMessageMeta(input) {
10347
+ const meta = {
10348
+ permissionMode: outboundPermissionMode(input.agent, input.permissionMode)
10349
+ };
10350
+ if (input.explicitModel !== void 0) {
10351
+ meta.model = input.explicitModel === "default" ? null : input.explicitModel;
10352
+ }
10353
+ if (persistence.normalizeAgentKey(input.agent ?? "claude") !== "claude") return meta;
10354
+ if (meta.model === void 0) {
10355
+ const model = persistence.getAgentCodeDefaults("claude").modelMode;
10356
+ const runnable = !persistence.isClaudeOpus55Model(model) || persistence.supportsClaudeOpus55({ capabilities: input.capabilities ?? null });
10357
+ meta.model = runnable && model !== "default" ? model : null;
10358
+ }
10359
+ meta.effort = null;
10360
+ return meta;
10361
+ }
10362
+
10363
+ function payloadJson(payload) {
10364
+ if (!payload) return void 0;
10365
+ try {
10366
+ return JSON.parse(payload);
10367
+ } catch {
10368
+ return void 0;
10369
+ }
10370
+ }
10371
+ function lookupPath(root, path) {
10372
+ let current = root;
10373
+ for (const segment of path) {
10374
+ if (current === null || typeof current !== "object") return void 0;
10375
+ current = current[segment];
10376
+ }
10377
+ return current;
10378
+ }
10379
+ function scalar(value) {
10380
+ if (value === void 0 || value === null) return "";
10381
+ if (typeof value === "string") return value;
10382
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
10383
+ try {
10384
+ return JSON.stringify(value);
10385
+ } catch {
10386
+ return "";
10387
+ }
10388
+ }
10389
+ function formatNow(now, tz) {
10390
+ if (!tz) return new Date(now).toISOString();
10391
+ try {
10392
+ const parts = new Intl.DateTimeFormat("en-CA", { timeZone: tz, hourCycle: "h23", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", timeZoneName: "longOffset" }).formatToParts(new Date(now));
10393
+ const get = (type) => parts.find((p) => p.type === type)?.value ?? "";
10394
+ const offset = get("timeZoneName").replace(/^GMT$/, "+00:00").replace(/^GMT/, "");
10395
+ return `${get("year")}-${get("month")}-${get("day")}T${get("hour")}:${get("minute")}:${get("second")}${offset || "Z"}`;
10396
+ } catch {
10397
+ return new Date(now).toISOString();
10398
+ }
10399
+ }
10400
+ function renderTemplate(text, context) {
10401
+ const json = payloadJson(context.payload);
10402
+ return text.replace(/\{\{\s*([a-zA-Z_][a-zA-Z0-9_.-]*)\s*\}\}/g, (whole, name) => {
10403
+ if (name === "payload") return context.payload ?? "";
10404
+ if (name.startsWith("payload.")) return scalar(lookupPath(json, name.slice("payload.".length).split(".")));
10405
+ if (name === "run.id") return context.runId;
10406
+ if (name === "automation.name") return context.automationName;
10407
+ if (name === "now") return formatNow(context.now ?? Date.now(), context.tz);
10408
+ return whole;
10409
+ });
10410
+ }
10411
+ function renderAction(action, context) {
10412
+ const r = (value) => renderTemplate(value, context);
10413
+ if (action.kind === "spawn") return { ...action, prompt: r(action.prompt) };
10414
+ if (action.kind === "send") return { ...action, prompt: r(action.prompt) };
10415
+ return { ...action, command: action.command.map(r), ...action.env ? { env: Object.fromEntries(Object.entries(action.env).map(([k, v]) => [k, r(v)])) } : {} };
10416
+ }
10417
+ function clipText(text, maxChars, keep = "head") {
10418
+ if (text.length <= maxChars) return text;
10419
+ return keep === "head" ? text.slice(0, maxChars) : text.slice(text.length - maxChars);
10420
+ }
10421
+ const DURATION_UNITS = { ms: 1, s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
10422
+ function parseDuration(raw) {
10423
+ const text = raw.trim();
10424
+ if (/^\d+$/.test(text)) return Number(text);
10425
+ const re = /(\d+(?:\.\d+)?)(ms|s|m|h|d)/gy;
10426
+ let total = 0;
10427
+ let matched = 0;
10428
+ let last = 0;
10429
+ let m;
10430
+ while ((m = re.exec(text)) !== null) {
10431
+ total += Number(m[1]) * DURATION_UNITS[m[2]];
10432
+ matched++;
10433
+ last = re.lastIndex;
10434
+ }
10435
+ if (matched === 0 || last !== text.length) throw new Error(`Invalid duration: ${raw} (use e.g. 90s, 5m, 1h30m, 2d)`);
10436
+ return Math.round(total);
10437
+ }
10438
+ function formatDuration(ms) {
10439
+ if (ms % 864e5 === 0) return `${ms / 864e5}d`;
10440
+ if (ms % 36e5 === 0) return `${ms / 36e5}h`;
10441
+ if (ms % 6e4 === 0) return `${ms / 6e4}m`;
10442
+ if (ms % 1e3 === 0) return `${ms / 1e3}s`;
10443
+ return `${ms}ms`;
10444
+ }
10445
+ function triggerFromFlags(flags, now = Date.now()) {
10446
+ const given = [flags.cron !== void 0, flags.every !== void 0, flags.at !== void 0, flags.manual === true].filter(Boolean).length;
10447
+ if (given === 0) {
10448
+ if (flags.tz !== void 0) throw new Error("--tz only applies with --cron");
10449
+ return void 0;
10450
+ }
10451
+ if (given > 1) throw new Error("Use exactly one of --cron, --every, --at, --manual");
10452
+ if (flags.cron !== void 0) {
10453
+ if (!flags.tz) throw new Error("--cron requires --tz <IANA zone> (e.g. Asia/Singapore)");
10454
+ return { kind: "cron", expr: flags.cron, tz: flags.tz };
10455
+ }
10456
+ if (flags.tz !== void 0) throw new Error("--tz only applies with --cron");
10457
+ if (flags.every !== void 0) return { kind: "interval", everyMs: parseDuration(flags.every), anchorAt: now };
10458
+ if (flags.at !== void 0) {
10459
+ const at = Date.parse(flags.at);
10460
+ if (!Number.isFinite(at)) throw new Error(`--at must be an ISO 8601 timestamp, got: ${flags.at}`);
10461
+ return { kind: "once", at };
10462
+ }
10463
+ return { kind: "manual" };
10464
+ }
10465
+ function describeTrigger(trigger) {
10466
+ switch (trigger.kind) {
10467
+ case "cron":
10468
+ return `cron ${trigger.expr} (${trigger.tz})`;
10469
+ case "interval":
10470
+ return `every ${formatDuration(trigger.everyMs)}`;
10471
+ case "once":
10472
+ return `once at ${new Date(trigger.at).toISOString()}`;
10473
+ default:
10474
+ return "manual";
10475
+ }
10476
+ }
10477
+ function describeAction(action) {
10478
+ if (action.kind === "spawn") return `spawn ${action.agent} in ${action.directory}${action.sticky ? ` (sticky ${action.sticky.key})` : ""}`;
10479
+ if (action.kind === "send") return `send to session ${action.sessionId}`;
10480
+ return `script ${action.command.join(" ")}`;
10481
+ }
10482
+
10483
+ const RECEIPT_RETENTION_MS = 7 * 864e5;
10484
+ const RECEIPT_MAX_ENTRIES = 2e3;
10485
+ function receiptsPath(home) {
10486
+ return path.join(home, "automation-receipts.json");
10487
+ }
10488
+ function readReceipts(home) {
10489
+ let raw;
10490
+ try {
10491
+ raw = node_fs.readFileSync(receiptsPath(home), "utf8");
10492
+ } catch (error) {
10493
+ if (error.code === "ENOENT") return {};
10494
+ throw error;
10495
+ }
10496
+ const parsed = JSON.parse(raw);
10497
+ if (!parsed || typeof parsed !== "object" || typeof parsed.receipts !== "object") throw new Error("Invalid automation receipts file");
10498
+ return parsed.receipts;
10499
+ }
10500
+ function writeReceipt(home, receipt, now = Date.now()) {
10501
+ if (!/^[a-zA-Z0-9_-]{1,128}$/.test(receipt.runId)) throw new Error("Invalid automation run id");
10502
+ const all = readReceipts(home);
10503
+ const next = { ...receipt, updatedAt: now };
10504
+ all[receipt.runId] = next;
10505
+ const entries = Object.values(all).sort((a, b) => b.updatedAt - a.updatedAt);
10506
+ const kept = {};
10507
+ let finished = 0;
10508
+ for (const entry of entries) {
10509
+ if (entry.phase === "finished") {
10510
+ if (now - entry.updatedAt > RECEIPT_RETENTION_MS || finished >= RECEIPT_MAX_ENTRIES) continue;
10511
+ finished++;
10512
+ }
10513
+ kept[entry.runId] = entry;
10514
+ }
10515
+ const target = receiptsPath(home);
10516
+ persistence.ensurePrivateDirectorySync(path.dirname(target));
10517
+ const tmp = `${target}.${process.pid}.tmp`;
10518
+ persistence.writePrivateFileSync(tmp, JSON.stringify({ version: 1, receipts: kept }));
10519
+ const file = node_fs.openSync(tmp, "r");
10520
+ try {
10521
+ node_fs.fsyncSync(file);
10522
+ } finally {
10523
+ node_fs.closeSync(file);
10524
+ }
10525
+ node_fs.renameSync(tmp, target);
10526
+ if (process.platform !== "win32") {
10527
+ const directory = node_fs.openSync(path.dirname(target), "r");
10528
+ try {
10529
+ node_fs.fsyncSync(directory);
10530
+ } finally {
10531
+ node_fs.closeSync(directory);
10532
+ }
10533
+ }
10534
+ return next;
10535
+ }
10536
+
10537
+ const SCRIPT_KILL_GRACE_MS = 1e4;
10538
+ const SCRIPT_DRAIN_MS = 2e3;
10539
+ class TailBuffer {
10540
+ constructor(maxChars) {
10541
+ this.maxChars = maxChars;
10542
+ }
10543
+ text = "";
10544
+ push(chunk) {
10545
+ this.text += chunk;
10546
+ if (this.text.length > this.maxChars * 2) this.text = this.text.slice(this.text.length - this.maxChars);
10547
+ }
10548
+ value() {
10549
+ return this.text.length > this.maxChars ? this.text.slice(this.text.length - this.maxChars) : this.text;
10550
+ }
10551
+ }
10552
+ function killScriptTree(child, signal) {
10553
+ if (child.exitCode !== null || child.signalCode !== null || !child.pid) return;
10554
+ try {
10555
+ if (process.platform !== "win32") process.kill(-child.pid, signal);
10556
+ else child.kill(signal);
10557
+ } catch {
10558
+ try {
10559
+ child.kill(signal);
10560
+ } catch {
10561
+ }
10562
+ }
10563
+ }
10564
+ function terminateScriptTree(child) {
10565
+ killScriptTree(child, "SIGTERM");
10566
+ const killer = setTimeout(() => killScriptTree(child, "SIGKILL"), SCRIPT_KILL_GRACE_MS);
10567
+ killer.unref();
10568
+ child.once("exit", () => clearTimeout(killer));
10569
+ }
10570
+ function runScript(options) {
10571
+ return new Promise((resolve) => {
10572
+ const tail = new TailBuffer(persistence.AUTOMATION_SUMMARY_MAX_CHARS);
10573
+ let timedOut = false;
10574
+ let settled = false;
10575
+ let child;
10576
+ const timers = [];
10577
+ const finish = (result) => {
10578
+ if (settled) return;
10579
+ settled = true;
10580
+ for (const t of timers) clearTimeout(t);
10581
+ resolve(result);
10582
+ };
10583
+ const timer = setTimeout(() => {
10584
+ timedOut = true;
10585
+ terminateScriptTree(child);
10586
+ }, options.timeoutMs);
10587
+ timer.unref();
10588
+ timers.push(timer);
10589
+ try {
10590
+ child = node_child_process.spawn(options.command[0], options.command.slice(1), {
10591
+ cwd: options.cwd,
10592
+ shell: false,
10593
+ windowsHide: true,
10594
+ stdio: ["ignore", "pipe", "pipe"],
10595
+ detached: process.platform !== "win32",
10596
+ env: { ...options.baseEnv ?? process.env, ...options.env ?? {} }
10597
+ });
10598
+ } catch (error) {
10599
+ finish({ exitCode: null, signal: null, timedOut: false, output: "", error: error instanceof Error ? error.message : String(error) });
10600
+ return;
10601
+ }
10602
+ options.onStart?.(child);
10603
+ child.stdout?.setEncoding("utf8").on("data", (chunk) => tail.push(chunk));
10604
+ child.stderr?.setEncoding("utf8").on("data", (chunk) => tail.push(chunk));
10605
+ child.on("error", (error) => finish({ exitCode: null, signal: null, timedOut, output: tail.value(), error: error.message }));
10606
+ child.on("close", (code, signal) => finish({ exitCode: code, signal, timedOut, output: tail.value() }));
10607
+ child.on("exit", (code, signal) => {
10608
+ const drain = setTimeout(() => {
10609
+ killScriptTree(child, "SIGKILL");
10610
+ finish({ exitCode: code, signal, timedOut, output: tail.value() });
10611
+ }, SCRIPT_DRAIN_MS);
10612
+ drain.unref();
10613
+ timers.push(drain);
10614
+ });
10615
+ });
10616
+ }
10617
+
10618
+ const AUTOMATION_TICK_MS = 5e3;
10619
+ const AUTOMATION_LEASE_RENEW_MS = 3e4;
10620
+ const AUTOMATION_LOG_READ_MS = 3e4;
10621
+ const AUTOMATION_UNAVAILABLE_LOG_MS = 10 * 6e4;
10622
+ const AUTOMATION_MAX_CONCURRENT = persistence.AUTOMATION_CLAIM_MAX_RUNS;
10623
+ const AUTOMATION_SPAWNED_BY = "automation";
10624
+ function automationPromptHeader(name, runId) {
10625
+ return `[Very Happy automation "${name}" run ${runId}; finish with automation_report (runId ${runId}) or \`very-happy auto report --run ${runId}\`]`;
10626
+ }
10627
+ function automationRunAnchor(runId) {
10628
+ const marker = ` run ${runId};`;
10629
+ return (body) => userPromptText(body)?.startsWith("[Very Happy automation ") === true && userPromptText(body).includes(marker);
10630
+ }
10631
+ async function defaultReadTurn(sessionId, runId) {
10632
+ const persisted = persistence.readPersistedSessions()[sessionId];
10633
+ if (!persisted) throw new Error(`No local key for session ${sessionId}`);
10634
+ return analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW), { anchor: automationRunAnchor(runId) });
10635
+ }
10636
+ async function defaultSend(sessionId, text, meta) {
10637
+ const persisted = await waitForSessionKey(sessionId, 15e3);
10638
+ await sendUserMessage(sessionId, persisted, text, "automation", { ...meta, sentFrom: "automation" });
10639
+ }
10640
+ function expandHome(path$1) {
10641
+ if (path$1 === "~") return os.homedir();
10642
+ if (path$1.startsWith("~/")) return path.join(os.homedir(), path$1.slice(2));
10643
+ return path$1;
10644
+ }
10645
+ function isSettledByServer(error) {
10646
+ if (!(error instanceof agentGuidance.AutomationsRequestError)) return false;
10647
+ if (error.status === 409 && (error.code === "run_finished" || error.code === "claim_mismatch")) return true;
10648
+ return error.status === 404 && error.code === "run_not_found";
10649
+ }
10650
+ function scriptAlive(item) {
10651
+ return !!item.child && item.child.exitCode === null && item.child.signalCode === null;
10652
+ }
10653
+ function createAutomationRunner(deps) {
10654
+ const home = deps.home ?? persistence.configuration.happyHomeDir;
10655
+ const client = deps.client ?? agentGuidance.createAutomationsClient({ serverUrl: deps.serverUrl ?? persistence.configuration.serverUrl, token: deps.token, client: "daemon-auto" });
10656
+ const runScript$1 = deps.runScript ?? runScript;
10657
+ const readTurn = deps.readTurn ?? defaultReadTurn;
10658
+ const send = deps.send ?? defaultSend;
10659
+ const prepareWorktree = deps.prepareWorktree ?? prepareTeamWorktree;
10660
+ const now = deps.now ?? (() => Date.now());
10661
+ const tracked = /* @__PURE__ */ new Map();
10662
+ let running = false;
10663
+ let renewing = false;
10664
+ let closed = false;
10665
+ let disabledUntil = 0;
10666
+ let lastUnavailableLogAt = -Infinity;
10667
+ let recovered = false;
10668
+ function persist(receipt) {
10669
+ writeReceipt(home, receipt, now());
10670
+ }
10671
+ async function finish(item, report2) {
10672
+ try {
10673
+ await client.report(item.runId, report2);
10674
+ } catch (error) {
10675
+ if (!isSettledByServer(error)) {
10676
+ item.pendingFinal = report2;
10677
+ deps.log(`Automation run ${item.runId} final report deferred`);
10678
+ return;
10679
+ }
10680
+ }
10681
+ persist({ runId: item.runId, automationId: item.automationId, kind: item.kind, phase: "finished", claimId: item.claimId, sessionId: item.sessionId });
10682
+ tracked.delete(item.runId);
10683
+ }
10684
+ function track(claimed, kind, sessionId, child) {
10685
+ const at = now();
10686
+ const item = { runId: claimed.run.id, automationId: claimed.automation.id, claimId: claimed.run.claimId, kind, sessionId, child, sawTurn: false, startedAt: at, lastLeaseAt: at, nextLogReadAt: at + AUTOMATION_LOG_READ_MS, attentionReported: false, exited: false };
10687
+ tracked.set(item.runId, item);
10688
+ return item;
10689
+ }
10690
+ async function failNow(claimed, kind, error, attentionReason, sessionId) {
10691
+ const item = track(claimed, kind, sessionId);
10692
+ await finish(item, { claimId: item.claimId, status: "failed", error: clipText(error, persistence.AUTOMATION_SUMMARY_MAX_CHARS), ...sessionId ? { sessionId } : {}, ...attentionReason ? { needsAttention: true, attentionReason } : {} });
10693
+ }
10694
+ async function reportRunning(item, sessionId, stickyKey) {
10695
+ const body = { claimId: item.claimId, status: "running", ...sessionId ? { sessionId } : {}, ...stickyKey ? { stickyKey } : {} };
10696
+ try {
10697
+ await client.report(item.runId, body);
10698
+ } catch (error) {
10699
+ if (isSettledByServer(error)) {
10700
+ settleExternally(item);
10701
+ return;
10702
+ }
10703
+ if (sessionId) item.pendingRunning = { sessionId, ...stickyKey ? { stickyKey } : {} };
10704
+ deps.log(`Automation run ${item.runId} running report deferred`);
10705
+ }
10706
+ }
10707
+ async function execute(claimed) {
10708
+ const { run, automation } = claimed;
10709
+ const existing = readReceipts(home)[run.id];
10710
+ if (existing) {
10711
+ if (existing.phase === "finished" || tracked.has(run.id)) return;
10712
+ if (existing.phase === "starting") {
10713
+ await failNow(claimed, existing.kind, "Local execution outcome unknown after restart; verify before re-running", "unknown_outcome");
10714
+ return;
10715
+ }
10716
+ if (existing.kind === "script") {
10717
+ await failNow(claimed, "script", "Daemon restarted while the script was running", "daemon_restarted");
10718
+ return;
10719
+ }
10720
+ track(claimed, existing.kind, existing.sessionId).sawTurn = true;
10721
+ return;
10722
+ }
10723
+ const context = { payload: run.payload, runId: run.id, automationName: automation.name, ...automation.trigger.kind === "cron" ? { tz: automation.trigger.tz } : {}, now: now() };
10724
+ const action = renderAction(automation.action, context);
10725
+ const env = { VH_AUTOMATION_RUN_ID: run.id, VH_AUTOMATION_NAME: automation.name };
10726
+ const header = automationPromptHeader(automation.name, run.id);
10727
+ if (action.kind === "script") {
10728
+ const timeoutMs = action.timeoutMs ?? automation.maxRuntimeMs;
10729
+ persist({ runId: run.id, automationId: automation.id, kind: "script", phase: "starting", claimId: run.claimId });
10730
+ const item2 = track(claimed, "script");
10731
+ const finished = runScript$1({
10732
+ command: action.command,
10733
+ cwd: action.cwd ? expandHome(action.cwd) : void 0,
10734
+ timeoutMs,
10735
+ env: { ...action.env ?? {}, ...env, ...run.payload ? { VH_AUTOMATION_PAYLOAD: run.payload } : {} },
10736
+ onStart: (child) => {
10737
+ item2.child = child;
10738
+ }
10739
+ });
10740
+ persist({ runId: run.id, automationId: automation.id, kind: "script", phase: "running", claimId: run.claimId });
10741
+ await reportRunning(item2);
10742
+ void finished.then(async (result) => {
10743
+ if (!tracked.has(run.id)) return;
10744
+ const ok = result.exitCode === 0 && !result.timedOut && !result.error;
10745
+ const error = result.error ? `Launch failed: ${result.error}` : result.timedOut ? `Timed out after ${timeoutMs}ms` : result.exitCode !== 0 ? `Exit ${result.exitCode ?? `signal ${result.signal}`}` : void 0;
10746
+ await finish(item2, { claimId: run.claimId, status: ok ? "done" : "failed", summary: clipText(result.output, persistence.AUTOMATION_SUMMARY_MAX_CHARS, "tail"), exitCode: result.exitCode, ...error ? { error } : {} });
10747
+ });
10748
+ return;
10749
+ }
10750
+ if (action.kind === "send") {
10751
+ if (!deps.live(action.sessionId)) {
10752
+ await failNow(claimed, "send", `Session ${action.sessionId} is not running on this machine`, "session_not_live", action.sessionId);
10753
+ return;
10754
+ }
10755
+ persist({ runId: run.id, automationId: automation.id, kind: "send", phase: "running", claimId: run.claimId, sessionId: action.sessionId });
10756
+ try {
10757
+ await send(action.sessionId, `${header}
10758
+ ${action.prompt}`, { localId: `automation-${run.id}` });
10759
+ } catch (error) {
10760
+ await failNow(claimed, "send", `Message delivery failed: ${error instanceof Error ? error.message : String(error)}`, "send_failed", action.sessionId);
10761
+ return;
10762
+ }
10763
+ await reportRunning(track(claimed, "send", action.sessionId), action.sessionId);
10764
+ return;
10765
+ }
10766
+ const agent = action.agent;
10767
+ if (!isSpawnAgent(agent)) {
10768
+ await failNow(claimed, "spawn", `Unknown agent "${agent}"`, "invalid_action");
10769
+ return;
10770
+ }
10771
+ if (action.permissionMode && sanitizeSpawnPermissionMode(action.permissionMode) === null) {
10772
+ await failNow(claimed, "spawn", `Invalid permission mode "${action.permissionMode}"`, "invalid_action");
10773
+ return;
10774
+ }
10775
+ const permissionMode = resolveSpawnPermissionMode(agent, action.permissionMode ?? void 0);
10776
+ const stickyKey = action.sticky ? renderTemplate(action.sticky.key, context) : void 0;
10777
+ if (stickyKey) {
10778
+ const bound = claimed.stickies.find((sticky) => sticky.key === stickyKey);
10779
+ if (bound && deps.live(bound.sessionId)) {
10780
+ persist({ runId: run.id, automationId: automation.id, kind: "send", phase: "running", claimId: run.claimId, sessionId: bound.sessionId, stickyKey });
10781
+ try {
10782
+ await send(bound.sessionId, `${header}
10783
+ ${action.prompt}`, { localId: `automation-${run.id}` });
10784
+ } catch (error) {
10785
+ await failNow(claimed, "send", `Sticky session delivery failed: ${error instanceof Error ? error.message : String(error)}`, "send_failed", bound.sessionId);
10786
+ return;
10787
+ }
10788
+ await reportRunning(track(claimed, "send", bound.sessionId), bound.sessionId, stickyKey);
10789
+ return;
10790
+ }
10791
+ if (bound) await client.deleteSticky(automation.id, stickyKey).catch(() => deps.log(`Automation ${automation.name}: stale sticky ${stickyKey} not cleared`));
10792
+ }
10793
+ let directory = expandHome(action.directory);
10794
+ if (!path.isAbsolute(directory)) {
10795
+ await failNow(claimed, "spawn", `Directory must be absolute: ${action.directory}`, "invalid_action");
10796
+ return;
10797
+ }
10798
+ if (action.worktree) {
10799
+ try {
10800
+ directory = (await prepareWorktree(home, directory, `auto-${run.id}`)).directory;
10801
+ } catch (error) {
10802
+ await failNow(claimed, "spawn", `Worktree preparation failed: ${error instanceof Error ? error.message : String(error)}`, "worktree_failed");
10803
+ return;
10804
+ }
10805
+ }
10806
+ persist({ runId: run.id, automationId: automation.id, kind: "spawn", phase: "starting", claimId: run.claimId, stickyKey, ...action.worktree ? { directory } : {} });
10807
+ const spawned = await deps.spawn({ directory, agent, permissionMode, spawnedBy: AUTOMATION_SPAWNED_BY, environmentVariables: env });
10808
+ if (spawned.type !== "success") {
10809
+ await failNow(claimed, "spawn", spawned.type === "error" ? spawned.errorMessage : "Directory approval required", "spawn_failed");
10810
+ return;
10811
+ }
10812
+ persist({ runId: run.id, automationId: automation.id, kind: "spawn", phase: "running", claimId: run.claimId, sessionId: spawned.sessionId, stickyKey, ...action.worktree ? { directory } : {} });
10813
+ const item = track(claimed, "spawn", spawned.sessionId);
10814
+ try {
10815
+ const capabilities = persistence.readPersistedSessions()[spawned.sessionId]?.metadata?.capabilities ?? null;
10816
+ const meta = resolveFirstMessageMeta({ agent, permissionMode, explicitModel: action.model ?? void 0, capabilities });
10817
+ await send(spawned.sessionId, `${header}
10818
+ ${action.prompt}`, { localId: `automation-${run.id}`, ...meta });
10819
+ } catch (error) {
10820
+ await finish(item, { claimId: run.claimId, status: "failed", sessionId: spawned.sessionId, error: `First message failed: ${error instanceof Error ? error.message : String(error)}`, needsAttention: true, attentionReason: "first_message_failed" });
10821
+ return;
10822
+ }
10823
+ await reportRunning(item, spawned.sessionId, stickyKey);
10824
+ }
10825
+ async function monitor() {
10826
+ for (const item of [...tracked.values()]) {
10827
+ if (closed) return;
10828
+ if (item.pendingFinal) {
10829
+ await finish(item, item.pendingFinal);
10830
+ continue;
10831
+ }
10832
+ if (item.kind === "script") continue;
10833
+ const sessionId = item.sessionId;
10834
+ const active = deps.turnActive(sessionId);
10835
+ if (active) item.sawTurn = true;
10836
+ const alive = deps.live(sessionId) && !item.exited;
10837
+ if (!alive) {
10838
+ const turn2 = await readTurn(sessionId, item.runId).catch(() => null);
10839
+ if (turn2?.ended && turn2.status !== "failed" && turn2.status !== "cancelled") await finish(item, { claimId: item.claimId, status: "done", sessionId, summary: clipText(turn2.answer, persistence.AUTOMATION_SUMMARY_MAX_CHARS) });
10840
+ else await finish(item, { claimId: item.claimId, status: "failed", sessionId, error: turn2?.error ?? "Session exited before the turn ended", needsAttention: true, attentionReason: "session_exited" });
10841
+ continue;
10842
+ }
10843
+ const edgeFell = item.sawTurn && !active;
10844
+ if (!edgeFell && now() < item.nextLogReadAt) continue;
10845
+ item.nextLogReadAt = now() + AUTOMATION_LOG_READ_MS;
10846
+ if (edgeFell) item.sawTurn = false;
10847
+ const turn = await readTurn(sessionId, item.runId).catch(() => null);
10848
+ if (!turn?.ended) continue;
10849
+ if (turn.status === "failed" || turn.status === "cancelled") await finish(item, { claimId: item.claimId, status: "failed", sessionId, error: turn.error ?? `Turn ${turn.status}`, needsAttention: true, attentionReason: `turn_${turn.status}` });
10850
+ else await finish(item, { claimId: item.claimId, status: "done", sessionId, summary: clipText(turn.answer, persistence.AUTOMATION_SUMMARY_MAX_CHARS) });
10851
+ }
10852
+ }
10853
+ async function renewAll() {
10854
+ if (renewing || closed) return;
10855
+ renewing = true;
10856
+ try {
10857
+ const at = now();
10858
+ for (const item of [...tracked.values()]) {
10859
+ if (closed) return;
10860
+ if (at - item.lastLeaseAt < AUTOMATION_LEASE_RENEW_MS) continue;
10861
+ item.lastLeaseAt = at;
10862
+ const body = { claimId: item.claimId, leaseMs: persistence.AUTOMATION_DEFAULT_LEASE_MS, ...item.pendingRunning ? { status: "running", ...item.pendingRunning } : {} };
10863
+ try {
10864
+ const run = await client.report(item.runId, body);
10865
+ item.pendingRunning = void 0;
10866
+ if (persistence.isAutomationRunTerminal(run.status)) settleExternally(item);
10867
+ } catch (error) {
10868
+ if (isSettledByServer(error)) settleExternally(item);
10869
+ else deps.log(`Automation run ${item.runId} lease renewal failed`);
10870
+ }
10871
+ }
10872
+ } finally {
10873
+ renewing = false;
10874
+ }
10875
+ }
10876
+ function settleExternally(item) {
10877
+ if (item.child && scriptAlive(item)) terminateScriptTree(item.child);
10878
+ persist({ runId: item.runId, automationId: item.automationId, kind: item.kind, phase: "finished", claimId: item.claimId, sessionId: item.sessionId });
10879
+ tracked.delete(item.runId);
10880
+ }
10881
+ async function recover() {
10882
+ recovered = true;
10883
+ let receipts;
10884
+ try {
10885
+ receipts = readReceipts(home);
10886
+ } catch {
10887
+ deps.log("Automation receipts unreadable; refusing to execute until repaired");
10888
+ closed = true;
10889
+ return;
10890
+ }
10891
+ for (const receipt of Object.values(receipts)) {
10892
+ if (receipt.phase === "finished") continue;
10893
+ const item = track({ run: { id: receipt.runId, claimId: receipt.claimId }, automation: { id: receipt.automationId } }, receipt.kind, receipt.sessionId);
10894
+ item.sawTurn = true;
10895
+ item.lastLeaseAt = 0;
10896
+ if (receipt.phase === "starting") await finish(item, { claimId: receipt.claimId, status: "failed", error: "Daemon restarted before the launch outcome was known", needsAttention: true, attentionReason: "unknown_outcome" });
10897
+ else if (receipt.kind === "script") await finish(item, { claimId: receipt.claimId, status: "failed", error: "Daemon restarted while the script was running", needsAttention: true, attentionReason: "daemon_restarted" });
10898
+ }
10899
+ }
10900
+ async function tick() {
10901
+ if (running || closed) return;
10902
+ running = true;
10903
+ try {
10904
+ if (!recovered) await recover();
10905
+ if (closed) return;
10906
+ await monitor();
10907
+ const capacity = AUTOMATION_MAX_CONCURRENT - tracked.size;
10908
+ if (now() >= disabledUntil && capacity > 0) {
10909
+ try {
10910
+ const claimed = await client.claim({ machineId: deps.machineId, limit: capacity, leaseMs: persistence.AUTOMATION_DEFAULT_LEASE_MS });
10911
+ await Promise.all(claimed.runs.map((entry) => closed ? Promise.resolve() : execute(entry).catch((error) => deps.log(`Automation run ${entry.run.id} failed to start: ${error instanceof Error ? error.message : String(error)}`))));
10912
+ } catch (error) {
10913
+ if (agentGuidance.isAutomationsUnavailable(error)) {
10914
+ disabledUntil = now() + 6e4;
10915
+ if (now() - lastUnavailableLogAt >= AUTOMATION_UNAVAILABLE_LOG_MS) {
10916
+ lastUnavailableLogAt = now();
10917
+ deps.log("Automations unavailable on this server (404); claim loop idle");
10918
+ }
10919
+ } else {
10920
+ disabledUntil = now() + 1e4;
10921
+ deps.log(`Automation claim failed${error instanceof agentGuidance.AutomationsRequestError ? ` (${error.status})` : ""}`);
10922
+ }
10923
+ }
10924
+ }
10925
+ } finally {
10926
+ running = false;
10927
+ }
10928
+ }
10929
+ function report(sessionId, event) {
10930
+ for (const item of tracked.values()) {
10931
+ if (item.sessionId !== sessionId) continue;
10932
+ if (event === "exited") item.exited = true;
10933
+ if (event === "blocked" && !item.attentionReported) {
10934
+ item.attentionReported = true;
10935
+ void client.report(item.runId, { claimId: item.claimId, needsAttention: true, attentionReason: "needs_input" }).catch(() => deps.log(`Automation run ${item.runId} attention report failed`));
10936
+ }
10937
+ }
10938
+ }
10939
+ const tickMs = deps.tickMs ?? AUTOMATION_TICK_MS;
10940
+ const timer = setInterval(() => {
10941
+ void tick();
10942
+ }, tickMs);
10943
+ timer.unref();
10944
+ const leaseTimer = setInterval(() => {
10945
+ void renewAll();
10946
+ }, tickMs);
10947
+ leaseTimer.unref();
10948
+ void tick();
10949
+ return {
10950
+ tick,
10951
+ renewAll,
10952
+ report,
10953
+ hasSession: (id) => [...tracked.values()].some((item) => item.sessionId === id),
10954
+ trackedRunIds: () => [...tracked.keys()],
10955
+ /** Executing a claim, or a script child is still alive — handover and auto-update wait for this. */
10956
+ get busy() {
10957
+ return running || [...tracked.values()].some(scriptAlive);
10958
+ },
10959
+ /**
10960
+ * Stop the loops. `killScripts` (real shutdown) terminates live script
10961
+ * trees; the next daemon's recovery reports them failed + attention from
10962
+ * the receipt, never re-running them. A handover waits on `busy` instead.
10963
+ */
10964
+ stop: (options = {}) => {
10965
+ closed = true;
10966
+ clearInterval(timer);
10967
+ clearInterval(leaseTimer);
10968
+ if (options.killScripts) {
10969
+ for (const item of tracked.values()) if (item.child && scriptAlive(item)) terminateScriptTree(item.child);
10970
+ }
10971
+ }
10972
+ };
10973
+ }
10974
+
10331
10975
  function daemonExitCode(source) {
10332
10976
  return source === "exception" ? 1 : 0;
10333
10977
  }
@@ -10488,6 +11132,7 @@ async function startDaemon() {
10488
11132
  const turnActivity = new index.TurnActivityTracker();
10489
11133
  const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
10490
11134
  let teamWorker;
11135
+ let automationRunner;
10491
11136
  const persisted = persistence.readPersistedSessions();
10492
11137
  const liveHappyProcesses = await findAllHappyProcesses();
10493
11138
  for (const [id, s] of Object.entries(persisted)) {
@@ -11342,6 +11987,7 @@ async function startDaemon() {
11342
11987
  const onChildExited = (pid) => {
11343
11988
  const session = pidToTrackedSession.get(pid);
11344
11989
  if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
11990
+ if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
11345
11991
  if (session?.happySessionId) turnActivity.forget(session.happySessionId);
11346
11992
  if (session?.happySessionId && session.encryption) {
11347
11993
  sessionIdToFinishedSession.set(session.happySessionId, session);
@@ -11361,6 +12007,7 @@ async function startDaemon() {
11361
12007
  }
11362
12008
  };
11363
12009
  const onSessionStateEvent = (sessionId, event, spawnedByFromSession) => {
12010
+ if (automationRunner?.hasSession(sessionId)) automationRunner.report(sessionId, event === "completed" ? "idle" : "blocked");
11364
12011
  if (spawnedByFromSession === "teams" || teamWorker?.hasSession(sessionId) || [...pidToTrackedSession.values()].some((s) => s.happySessionId === sessionId && s.spawnedBy === "teams")) {
11365
12012
  teamWorker?.report(sessionId, event === "completed" ? "idle" : "blocked");
11366
12013
  return;
@@ -11599,7 +12246,7 @@ async function startDaemon() {
11599
12246
  } catch {
11600
12247
  }
11601
12248
  }
11602
- if (bundleReplaced && !teamWorker?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn()) {
12249
+ if (bundleReplaced && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn()) {
11603
12250
  const preflight = await preflightNewBundle(bundlePath);
11604
12251
  if (preflight.action === "hold") {
11605
12252
  if (lastHandoverHold !== preflight.reason) {
@@ -11609,10 +12256,11 @@ async function startDaemon() {
11609
12256
  updateController.holdHandover(preflight.reason);
11610
12257
  return;
11611
12258
  }
11612
- if (teamWorker?.busy || updateController.isRunning() || turnActivity.hasActiveTurn()) return;
12259
+ if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn()) return;
11613
12260
  lastHandoverHold = null;
11614
12261
  persistence.logger.debug("[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
11615
12262
  teamWorker?.stop();
12263
+ automationRunner?.stop();
11616
12264
  clearInterval(restartOnStaleVersionAndHeartbeat);
11617
12265
  clearInterval(cliUpdateInterval);
11618
12266
  stopAgentVersionChecks();
@@ -11664,6 +12312,14 @@ async function startDaemon() {
11664
12312
  live: (id) => [...pidToTrackedSession.values()].some((s) => s.happySessionId === id && isPidAlive(s.pid)),
11665
12313
  log: (message) => persistence.logger.debug(`[DAEMON RUN] ${message}`)
11666
12314
  });
12315
+ automationRunner = createAutomationRunner({
12316
+ machineId,
12317
+ token: credentials.token,
12318
+ spawn: spawnSession,
12319
+ live: (id) => [...pidToTrackedSession.values()].some((s) => s.happySessionId === id && isPidAlive(s.pid)),
12320
+ turnActive: (id) => turnActivity.activeSessions().includes(id),
12321
+ log: (message) => persistence.logger.debug(`[DAEMON RUN] ${message}`)
12322
+ });
11667
12323
  void apiMachine.updateMachineMetadata((current) => ({
11668
12324
  ...current ?? initialMachineMetadata,
11669
12325
  teamsVersion: 1,
@@ -11672,6 +12328,7 @@ async function startDaemon() {
11672
12328
  const cleanupAndShutdown = async (source, errorMessage) => {
11673
12329
  persistence.logger.debug(`[DAEMON RUN] Starting proper cleanup (source: ${source}, errorMessage: ${errorMessage})...`);
11674
12330
  teamWorker?.stop();
12331
+ automationRunner?.stop({ killScripts: true });
11675
12332
  if (restartOnStaleVersionAndHeartbeat) {
11676
12333
  clearInterval(restartOnStaleVersionAndHeartbeat);
11677
12334
  persistence.logger.debug("[DAEMON RUN] Health check interval cleared");
@@ -12176,6 +12833,7 @@ function createMcpServer(handlers, options, sessionId) {
12176
12833
  version: "1.0.0"
12177
12834
  });
12178
12835
  agentGuidance.registerTeamsTools(mcp, sessionId);
12836
+ agentGuidance.registerAutomationTools(mcp);
12179
12837
  mcp.registerTool("change_title", {
12180
12838
  description: "Change the title of the current chat session",
12181
12839
  title: "Change Chat Title",
@@ -12370,7 +13028,7 @@ async function startHappyServer(client, options) {
12370
13028
  return;
12371
13029
  }
12372
13030
  }
12373
- const usage = index.ContextUsageSchema.parse({ ...JSON.parse(body), updatedAt: Date.now() });
13031
+ const usage = persistence.ContextUsageSchema.parse({ ...JSON.parse(body), updatedAt: Date.now() });
12374
13032
  options.onContextUsage(usage);
12375
13033
  res.writeHead(204).end();
12376
13034
  } catch {
@@ -12418,6 +13076,7 @@ async function startHappyServer(client, options) {
12418
13076
  agentGuidance.PREVIEW_TOOL_NAME,
12419
13077
  agentGuidance.REPORT_PROGRESS_TOOL_NAME,
12420
13078
  ...agentGuidance.TEAM_TOOL_NAMES,
13079
+ ...agentGuidance.AUTOMATION_TOOL_NAMES,
12421
13080
  ...options?.assistant && !process.env.VH_TEAM_SCOPE_FILE ? ASSISTANT_TOOL_NAMES : []
12422
13081
  ],
12423
13082
  stop: () => {
@@ -12754,7 +13413,7 @@ function registerSideQuestionHandler(rpc, deps) {
12754
13413
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
12755
13414
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
12756
13415
  const fork = deps.run ?? (async (input) => {
12757
- const { query } = await Promise.resolve().then(function () { return require('./index-vyRqLjDv.cjs'); });
13416
+ const { query } = await Promise.resolve().then(function () { return require('./index-CRaJ9k2p.cjs'); });
12758
13417
  return runSideQuestion(query, input);
12759
13418
  });
12760
13419
  const run = async (input, live) => {
@@ -12873,7 +13532,7 @@ const MAX_TITLE_CHARS = 60;
12873
13532
  const GENERATION_TIMEOUT_MS = 3e4;
12874
13533
  function resolveClaudeBinary() {
12875
13534
  try {
12876
- const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-CqtfRzug.cjs', document.baseURI).href)));
13535
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-ZIW1jrYm.cjs', document.baseURI).href)));
12877
13536
  const utilsPath = path.resolve(path.join(projectPath.projectPath(), "scripts", "claude_version_utils.cjs"));
12878
13537
  const { getClaudeCliPath } = require$1(utilsPath);
12879
13538
  const path$1 = getClaudeCliPath();
@@ -15102,9 +15761,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
15102
15761
  };
15103
15762
  }
15104
15763
 
15105
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-CqtfRzug.cjs', document.baseURI).href)));
15764
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-ZIW1jrYm.cjs', document.baseURI).href)));
15106
15765
  const __dirname$1 = path.dirname(__filename$1);
15107
- const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-CqtfRzug.cjs', document.baseURI).href)));
15766
+ const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-ZIW1jrYm.cjs', document.baseURI).href)));
15108
15767
  const PRISMA_QUERY_ENGINE_FILES = {
15109
15768
  "arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
15110
15769
  "x64-darwin": "libquery_engine-darwin.dylib.node",
@@ -15539,14 +16198,14 @@ function maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagent
15539
16198
  if (!subagent || startedSubagents.has(subagent)) {
15540
16199
  return;
15541
16200
  }
15542
- envelopes.push(index.createEnvelope("agent", { t: "start" }, { ...opts, subagent }));
16201
+ envelopes.push(persistence.createEnvelope("agent", { t: "start" }, { ...opts, subagent }));
15543
16202
  startedSubagents.add(subagent);
15544
16203
  activeSubagents.add(subagent);
15545
16204
  }
15546
16205
  function emitSubagentStops(opts, startedSubagents, activeSubagents) {
15547
16206
  const envelopes = [];
15548
16207
  for (const subagent of activeSubagents) {
15549
- envelopes.push(index.createEnvelope("agent", { t: "stop" }, { ...opts, subagent }));
16208
+ envelopes.push(persistence.createEnvelope("agent", { t: "stop" }, { ...opts, subagent }));
15550
16209
  }
15551
16210
  activeSubagents.clear();
15552
16211
  startedSubagents.clear();
@@ -15642,7 +16301,7 @@ function turnStatus(turn) {
15642
16301
  function emitHistoricalToolCall(envelopes, turn, item, name, title, args, output) {
15643
16302
  const time = turnTimestampMs(turn);
15644
16303
  const opts = { turn: turn.id, time, codexItemId: item.id };
15645
- envelopes.push(index.createEnvelope("agent", {
16304
+ envelopes.push(persistence.createEnvelope("agent", {
15646
16305
  t: "tool-call-start",
15647
16306
  call: item.id,
15648
16307
  name,
@@ -15654,7 +16313,7 @@ function emitHistoricalToolCall(envelopes, turn, item, name, title, args, output
15654
16313
  id: `${item.id}:start`
15655
16314
  }));
15656
16315
  if (output && output.trim().length > 0) {
15657
- envelopes.push(index.createEnvelope("agent", {
16316
+ envelopes.push(persistence.createEnvelope("agent", {
15658
16317
  t: "text",
15659
16318
  text: output,
15660
16319
  thinking: true
@@ -15664,7 +16323,7 @@ function emitHistoricalToolCall(envelopes, turn, item, name, title, args, output
15664
16323
  }));
15665
16324
  }
15666
16325
  if ("status" in item && (item.status === "inProgress" || item.status === "running")) return;
15667
- envelopes.push(index.createEnvelope("agent", {
16326
+ envelopes.push(persistence.createEnvelope("agent", {
15668
16327
  t: "tool-call-end",
15669
16328
  call: item.id
15670
16329
  }, {
@@ -15686,7 +16345,7 @@ function mapCodexCollaborationItem(item, opts = {}, source = "history") {
15686
16345
  agentsStates: item.agentsStates ?? {}
15687
16346
  };
15688
16347
  const startId = source === "live" ? `${item.id}:${item.status ?? "unknown"}:start` : `${item.id}:start`;
15689
- const envelopes = [index.createEnvelope("agent", {
16348
+ const envelopes = [persistence.createEnvelope("agent", {
15690
16349
  t: "tool-call-start",
15691
16350
  call: item.id,
15692
16351
  name: "CodexCollaboration",
@@ -15695,7 +16354,7 @@ function mapCodexCollaborationItem(item, opts = {}, source = "history") {
15695
16354
  args
15696
16355
  }, { ...opts, id: startId })];
15697
16356
  if (item.status === "completed" || item.status === "failed" || item.status === "interrupted") {
15698
- envelopes.push(index.createEnvelope("agent", {
16357
+ envelopes.push(persistence.createEnvelope("agent", {
15699
16358
  t: "tool-call-end",
15700
16359
  call: item.id,
15701
16360
  result: { text: JSON.stringify(args, null, 2), isError: item.status !== "completed" }
@@ -15708,7 +16367,7 @@ function mapCodexThreadToSessionEnvelopes(thread) {
15708
16367
  for (const turn of thread.turns ?? []) {
15709
16368
  const startedAt = turnTimestampMs(turn);
15710
16369
  const completedAt = completedTimestampMs(turn);
15711
- envelopes.push(index.createEnvelope("agent", { t: "turn-start" }, {
16370
+ envelopes.push(persistence.createEnvelope("agent", { t: "turn-start" }, {
15712
16371
  id: `${turn.id}:start`,
15713
16372
  turn: turn.id,
15714
16373
  time: startedAt
@@ -15723,7 +16382,7 @@ function mapCodexThreadToSessionEnvelopes(thread) {
15723
16382
  case "userMessage": {
15724
16383
  const text = textFromInputItems(item.content);
15725
16384
  if (text) {
15726
- envelopes.push(index.createEnvelope("user", { t: "text", text }, {
16385
+ envelopes.push(persistence.createEnvelope("user", { t: "text", text }, {
15727
16386
  id: item.id,
15728
16387
  time: startedAt,
15729
16388
  codexItemId: item.id
@@ -15734,7 +16393,7 @@ function mapCodexThreadToSessionEnvelopes(thread) {
15734
16393
  case "agentMessage": {
15735
16394
  const text = typeof item.text === "string" ? item.text.trim() : "";
15736
16395
  if (text.length > 0) {
15737
- envelopes.push(index.createEnvelope("agent", { t: "text", text }, {
16396
+ envelopes.push(persistence.createEnvelope("agent", { t: "text", text }, {
15738
16397
  id: item.id,
15739
16398
  turn: turn.id,
15740
16399
  time: completedAt,
@@ -15746,7 +16405,7 @@ function mapCodexThreadToSessionEnvelopes(thread) {
15746
16405
  case "reasoning": {
15747
16406
  const text = reasoningText(item);
15748
16407
  if (text) {
15749
- envelopes.push(index.createEnvelope("agent", { t: "text", text, thinking: true }, {
16408
+ envelopes.push(persistence.createEnvelope("agent", { t: "text", text, thinking: true }, {
15750
16409
  id: item.id,
15751
16410
  turn: turn.id,
15752
16411
  time: startedAt,
@@ -15801,7 +16460,7 @@ function mapCodexThreadToSessionEnvelopes(thread) {
15801
16460
  }
15802
16461
  }
15803
16462
  }
15804
- envelopes.push(index.createEnvelope("agent", { t: "turn-end", status: turnStatus(turn) }, {
16463
+ envelopes.push(persistence.createEnvelope("agent", { t: "turn-end", status: turnStatus(turn) }, {
15805
16464
  id: `${turn.id}:end`,
15806
16465
  turn: turn.id,
15807
16466
  time: completedAt
@@ -15856,7 +16515,7 @@ function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15856
16515
  }
15857
16516
  if (type === "task_started") {
15858
16517
  const turnId = cuid2.createId();
15859
- const turnStart = index.createEnvelope("agent", { t: "turn-start" }, { turn: turnId });
16518
+ const turnStart = persistence.createEnvelope("agent", { t: "turn-start" }, { turn: turnId });
15860
16519
  startedSubagents.clear();
15861
16520
  activeSubagents.clear();
15862
16521
  providerSubagentToSessionSubagent.clear();
@@ -15887,7 +16546,7 @@ function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15887
16546
  providerSubagentToSessionSubagent,
15888
16547
  envelopes: [
15889
16548
  ...emitSubagentStops(lifecycleOpts, startedSubagents, activeSubagents),
15890
- index.createEnvelope("agent", {
16549
+ persistence.createEnvelope("agent", {
15891
16550
  t: "turn-end",
15892
16551
  status: pickTurnEndStatus(message, type)
15893
16552
  }, lifecycleOpts)
@@ -15917,7 +16576,7 @@ function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15917
16576
  }
15918
16577
  const envelopes = [];
15919
16578
  maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15920
- envelopes.push(index.createEnvelope("agent", { t: "text", text: message.message }, opts));
16579
+ envelopes.push(persistence.createEnvelope("agent", { t: "text", text: message.message }, opts));
15921
16580
  return {
15922
16581
  currentTurnId: state.currentTurnId,
15923
16582
  startedSubagents,
@@ -15939,7 +16598,7 @@ function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15939
16598
  }
15940
16599
  const envelopes = [];
15941
16600
  maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15942
- envelopes.push(index.createEnvelope("agent", { t: "text", text, thinking: true }, opts));
16601
+ envelopes.push(persistence.createEnvelope("agent", { t: "text", text, thinking: true }, opts));
15943
16602
  return {
15944
16603
  currentTurnId: state.currentTurnId,
15945
16604
  startedSubagents,
@@ -15956,7 +16615,7 @@ function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15956
16615
  const envelopes = [];
15957
16616
  maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15958
16617
  envelopes.push(
15959
- index.createEnvelope("agent", {
16618
+ persistence.createEnvelope("agent", {
15960
16619
  t: "tool-call-start",
15961
16620
  call,
15962
16621
  name: "CodexBash",
@@ -15977,7 +16636,7 @@ function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15977
16636
  const call = pickCallId(message);
15978
16637
  const envelopes = [];
15979
16638
  maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15980
- envelopes.push(index.createEnvelope("agent", { t: "tool-call-end", call }, opts));
16639
+ envelopes.push(persistence.createEnvelope("agent", { t: "tool-call-end", call }, opts));
15981
16640
  return {
15982
16641
  currentTurnId: state.currentTurnId,
15983
16642
  startedSubagents,
@@ -15993,7 +16652,7 @@ function mapCodexMcpMessageToSessionEnvelopes(message, state) {
15993
16652
  const envelopes = [];
15994
16653
  maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
15995
16654
  envelopes.push(
15996
- index.createEnvelope("agent", {
16655
+ persistence.createEnvelope("agent", {
15997
16656
  t: "tool-call-start",
15998
16657
  call,
15999
16658
  name: "CodexPatch",
@@ -16017,7 +16676,7 @@ function mapCodexMcpMessageToSessionEnvelopes(message, state) {
16017
16676
  const call = pickCallId(message);
16018
16677
  const envelopes = [];
16019
16678
  maybeEmitSubagentStart(subagent, opts, startedSubagents, activeSubagents, envelopes);
16020
- envelopes.push(index.createEnvelope("agent", { t: "tool-call-end", call }, opts));
16679
+ envelopes.push(persistence.createEnvelope("agent", { t: "tool-call-end", call }, opts));
16021
16680
  return {
16022
16681
  currentTurnId: state.currentTurnId,
16023
16682
  startedSubagents,
@@ -16038,7 +16697,7 @@ function mapCodexProcessorMessageToSessionEnvelopes(message, state) {
16038
16697
  const toolLikeMessage = message;
16039
16698
  const opts = buildEnvelopeOptions(state.currentTurnId);
16040
16699
  if (message.type === "reasoning") {
16041
- return [index.createEnvelope("agent", {
16700
+ return [persistence.createEnvelope("agent", {
16042
16701
  t: "text",
16043
16702
  text: message.message,
16044
16703
  thinking: true
@@ -16046,7 +16705,7 @@ function mapCodexProcessorMessageToSessionEnvelopes(message, state) {
16046
16705
  }
16047
16706
  if (message.type === "tool-call") {
16048
16707
  const title = typeof toolLikeMessage.input?.title === "string" ? toolLikeMessage.input.title : `${toolLikeMessage.name || "Tool"} call`;
16049
- return [index.createEnvelope("agent", {
16708
+ return [persistence.createEnvelope("agent", {
16050
16709
  t: "tool-call-start",
16051
16710
  call: toolLikeMessage.callId,
16052
16711
  name: toolLikeMessage.name || "unknown",
@@ -16059,13 +16718,13 @@ function mapCodexProcessorMessageToSessionEnvelopes(message, state) {
16059
16718
  const envelopes = [];
16060
16719
  const content = toolLikeMessage.output?.content;
16061
16720
  if (typeof content === "string" && content.trim().length > 0) {
16062
- envelopes.push(index.createEnvelope("agent", {
16721
+ envelopes.push(persistence.createEnvelope("agent", {
16063
16722
  t: "text",
16064
16723
  text: content,
16065
16724
  thinking: true
16066
16725
  }, opts));
16067
16726
  }
16068
- envelopes.push(index.createEnvelope("agent", {
16727
+ envelopes.push(persistence.createEnvelope("agent", {
16069
16728
  t: "tool-call-end",
16070
16729
  call: toolLikeMessage.callId
16071
16730
  }, opts));
@@ -17061,7 +17720,7 @@ function buildCodexTurnPrompt(opts) {
17061
17720
  parts.push(opts.message);
17062
17721
  if (opts.includeTitleInstruction) {
17063
17722
  parts.push(CHANGE_TITLE_INSTRUCTION);
17064
- parts.push(index.BUILTIN_TODO_DISCOVERY);
17723
+ parts.push(persistence.BUILTIN_TODO_DISCOVERY);
17065
17724
  }
17066
17725
  return parts.join("\n\n");
17067
17726
  }
@@ -18042,7 +18701,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18042
18701
  return;
18043
18702
  } else if (subcommand === "install-terminal-hooks") {
18044
18703
  try {
18045
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-Bf_adbl3.cjs'); });
18704
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-BoDxpaBO.cjs'); });
18046
18705
  const command = parseTerminalHooksArgs(args.slice(1));
18047
18706
  if (command.action === "help") {
18048
18707
  console.log(TERMINAL_HOOKS_HELP);
@@ -18059,7 +18718,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18059
18718
  }
18060
18719
  } else if (subcommand === "agent-home") {
18061
18720
  try {
18062
- const { parseAgentHomeArgs, runAgentHomeCommand } = await Promise.resolve().then(function () { return require('./agentHome-D6MSfwnp.cjs'); });
18721
+ const { parseAgentHomeArgs, runAgentHomeCommand } = await Promise.resolve().then(function () { return require('./agentHome-CpJjrQZc.cjs'); });
18063
18722
  await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
18064
18723
  process.exit(0);
18065
18724
  } catch (error) {
@@ -18068,7 +18727,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18068
18727
  }
18069
18728
  } else if (subcommand === "spawn") {
18070
18729
  try {
18071
- const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-DH6_p4-f.cjs'); });
18730
+ const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-DA8WAFmm.cjs'); });
18072
18731
  await handleSpawnCommand(args.slice(1));
18073
18732
  } catch (error) {
18074
18733
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18080,7 +18739,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18080
18739
  return;
18081
18740
  } else if (subcommand === "sessions") {
18082
18741
  try {
18083
- const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-B3vP18kz.cjs'); });
18742
+ const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-Tad8elmy.cjs'); });
18084
18743
  await handleSessionsCommand(args.slice(1));
18085
18744
  } catch (error) {
18086
18745
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18092,7 +18751,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18092
18751
  return;
18093
18752
  } else if (subcommand === "send") {
18094
18753
  try {
18095
- const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-aUkjhey7.cjs'); });
18754
+ const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-wl9ei8yG.cjs'); });
18096
18755
  await handleSendCommand(args.slice(1));
18097
18756
  } catch (error) {
18098
18757
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18198,9 +18857,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18198
18857
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
18199
18858
  const projectId = args[3];
18200
18859
  try {
18201
- const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-Cadw1sAk.cjs'); });
18202
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-DwcY3kyi.cjs'); }).then(function (n) { return n.persistence; });
18203
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-CPubg05Z.cjs'); }).then(function (n) { return n.api; });
18860
+ const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-DeJgSMh-.cjs'); });
18861
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-BNTWyUaZ.cjs'); }).then(function (n) { return n.persistence; });
18862
+ const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-CN28qJl7.cjs'); }).then(function (n) { return n.api; });
18204
18863
  let userEmail = void 0;
18205
18864
  try {
18206
18865
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -18231,7 +18890,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18231
18890
  }
18232
18891
  if (geminiSubcommand === "project" && args[2] === "get") {
18233
18892
  try {
18234
- const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-Cadw1sAk.cjs'); });
18893
+ const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-DeJgSMh-.cjs'); });
18235
18894
  const config = readGeminiLocalConfig();
18236
18895
  if (config.googleCloudProject) {
18237
18896
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -18271,7 +18930,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18271
18930
  process.exit(0);
18272
18931
  }
18273
18932
  try {
18274
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-DuwdVV1O.cjs'); });
18933
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-Bgg1BzDK.cjs'); });
18275
18934
  let startedBy = void 0;
18276
18935
  for (let i = 1; i < args.length; i++) {
18277
18936
  if (args[i] === "--started-by") {
@@ -18294,7 +18953,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18294
18953
  } else if (subcommand === "pi") {
18295
18954
  if (args[1] === "--terminal") {
18296
18955
  try {
18297
- const { runPiTerminal } = await Promise.resolve().then(function () { return require('./piTerminal-kDSd_r9X.cjs'); });
18956
+ const { runPiTerminal } = await Promise.resolve().then(function () { return require('./piTerminal-B7K75aXB.cjs'); });
18298
18957
  process.exitCode = await runPiTerminal(args.slice(2));
18299
18958
  } catch (error) {
18300
18959
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18311,7 +18970,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18311
18970
  }
18312
18971
  };
18313
18972
  try {
18314
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-BKGspSWk.cjs'); });
18973
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-imt4mC41.cjs'); });
18315
18974
  const parsed = parsePiRunnerArgs(args.slice(1));
18316
18975
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
18317
18976
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -18339,7 +18998,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18339
18998
  return;
18340
18999
  } else if (subcommand === "acp") {
18341
19000
  try {
18342
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-BKGspSWk.cjs'); });
19001
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-imt4mC41.cjs'); });
18343
19002
  let startedBy = void 0;
18344
19003
  let verbose = false;
18345
19004
  const acpArgs = [];
@@ -18379,7 +19038,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18379
19038
  return;
18380
19039
  } else if (subcommand === "openclaw") {
18381
19040
  try {
18382
- const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-BsvtCE_H.cjs'); });
19041
+ const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-B9qWHybc.cjs'); });
18383
19042
  let startedBy = void 0;
18384
19043
  let verbose = false;
18385
19044
  let gatewayUrl;
@@ -18430,7 +19089,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18430
19089
  return;
18431
19090
  } else if (subcommand === "todo") {
18432
19091
  try {
18433
- const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-Bcc2XxGh.cjs'); });
19092
+ const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-mCMoGjJk.cjs'); });
18434
19093
  await handleTodoCommand(args.slice(1));
18435
19094
  } catch (error) {
18436
19095
  console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
@@ -18439,16 +19098,28 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18439
19098
  return;
18440
19099
  } else if (subcommand === "teams") {
18441
19100
  try {
18442
- const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-BW8D1n3o.cjs'); });
19101
+ const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-qVLrFcdd.cjs'); });
18443
19102
  await handleTeamsCommand(args.slice(1));
18444
19103
  } catch (error) {
18445
19104
  console.error(error instanceof Error ? error.message : "Teams command failed");
18446
19105
  process.exitCode = 1;
18447
19106
  }
18448
19107
  return;
19108
+ } else if (subcommand === "auto") {
19109
+ try {
19110
+ const { handleAutoCommand } = await Promise.resolve().then(function () { return require('./auto-DncmTDvw.cjs'); });
19111
+ await handleAutoCommand(args.slice(1));
19112
+ } catch (error) {
19113
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
19114
+ if (process.env.DEBUG) {
19115
+ console.error(error);
19116
+ }
19117
+ process.exitCode = 1;
19118
+ }
19119
+ return;
18449
19120
  } else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
18450
19121
  try {
18451
- const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-CMxEZ6-0.cjs'); });
19122
+ const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-CCKCKL6l.cjs'); });
18452
19123
  await handleMcpCommand(args[1] === "--terminal-tools");
18453
19124
  } catch (error) {
18454
19125
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -18682,6 +19353,8 @@ ${chalk.bold("Usage:")}
18682
19353
  conversations on this machine (agent-home --help)
18683
19354
  very-happy connect Connect AI vendor API keys
18684
19355
  very-happy todo Built-in Todos and official agent skill (todo --help)
19356
+ very-happy auto Scheduled / triggered automations: spawn, sticky
19357
+ sessions or scripts with tracked runs (auto --help)
18685
19358
  very-happy sandbox Configure and manage OS-level sandboxing
18686
19359
  very-happy notify Send push notification
18687
19360
  very-happy mcp Stdio MCP server exposing copy_to_clipboard for a
@@ -18839,12 +19512,15 @@ exports.appendStagedAttachmentsToPrompt = appendStagedAttachmentsToPrompt;
18839
19512
  exports.archiveSession = archiveSession;
18840
19513
  exports.checkPreviewPath = checkPreviewPath;
18841
19514
  exports.createSessionMetadata = createSessionMetadata;
19515
+ exports.describeAction = describeAction;
19516
+ exports.describeTrigger = describeTrigger;
18842
19517
  exports.hashObject = hashObject;
18843
19518
  exports.initialMachineMetadata = initialMachineMetadata;
18844
19519
  exports.isValidSpawnOrigin = isValidSpawnOrigin;
18845
19520
  exports.listAccountSessions = listAccountSessions;
18846
19521
  exports.listSessions = listSessions;
18847
19522
  exports.normalizeAcpPermissionMode = normalizeAcpPermissionMode;
19523
+ exports.parseDuration = parseDuration;
18848
19524
  exports.query = query;
18849
19525
  exports.readSessionMetadata = readSessionMetadata;
18850
19526
  exports.readSessionTranscript = readSessionTranscript;
@@ -18852,7 +19528,9 @@ exports.registerAgentAttachmentDownloads = registerAgentAttachmentDownloads;
18852
19528
  exports.registerAssistantSessionTools = registerAssistantSessionTools;
18853
19529
  exports.registerKillSessionHandler = registerKillSessionHandler;
18854
19530
  exports.removeSessionModeFile = removeSessionModeFile;
19531
+ exports.resolveFirstMessageMeta = resolveFirstMessageMeta;
18855
19532
  exports.resolvePermissionRequest = resolvePermissionRequest;
19533
+ exports.resolveSpawnPermissionMode = resolveSpawnPermissionMode;
18856
19534
  exports.sanitizeSpawnPermissionMode = sanitizeSpawnPermissionMode;
18857
19535
  exports.sendUserMessage = sendUserMessage;
18858
19536
  exports.sessionWebUrl = sessionWebUrl;
@@ -18861,6 +19539,7 @@ exports.stageClaudeAttachments = stageClaudeAttachments;
18861
19539
  exports.startHappyServer = startHappyServer;
18862
19540
  exports.stopSession = stopSession;
18863
19541
  exports.tmuxSupportsSessionEnv = tmuxSupportsSessionEnv;
19542
+ exports.triggerFromFlags = triggerFromFlags;
18864
19543
  exports.waitForSessionKey = waitForSessionKey;
18865
19544
  exports.waitForTurnEnd = waitForTurnEnd;
18866
19545
  exports.writeSessionModeFile = writeSessionModeFile;