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
@@ -1,14 +1,14 @@
1
1
  import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
2
2
  import os, { homedir, userInfo, tmpdir } from 'node:os';
3
3
  import { randomUUID, randomInt, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
4
- import { H as notifyDaemonSessionEvent, q as getProjectPath, z as BUILTIN_TODO_DISCOVERY, I as initializeSandbox, J as wrapCommand, E as contentLogMetadata, K as InvalidateSync, R as RawJSONLinesSchema, G as delay, L as AsyncLock, S as STREAM_DELTA_MAX_CHARS, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, N as notifyDaemonClaudeAuthFailed, O as commandOnPath, u as checkIfDaemonRunningAndCleanupStaleState, P as deriveLocalCliUpdateSummary, Q as AGENT_VERSION_SOURCES, T as isClaudeSessionId, U as getLocalHappyAgentCredentialPath, V as readLocalHappyAgentCredentials, W as parseExactVersion, X as compareExactVersions, Y as extractCompleteLines, Z as decideBackfill, _ as parseTerminalHookPayload, $ as tmuxArgs, a0 as scrubTmuxClientEnv, a1 as decideMirrorBinding, a2 as MIRROR_BACKFILL_LINES_DEFAULT, a3 as mirrorLineKey, a4 as mirrorLocalId, a5 as listDaemonSessions, w as stopDaemonSession, a6 as detectCLIAvailability, a7 as detectResumeSupport, a8 as refreshAgentHomes, d as describeAgentHome, a9 as isDaemonRunningCurrentlyInstalledHappyVersion, aa as stopDaemon, ab as TurnActivityTracker, ac as summarizeSpawnSessionForLog, ad as locateClaudeConversation, ae as agentHomeSpawnEnv, af as shellescape, A as ApiClient, ag as fetchCliUpdateState, ah as resolveCliUpdateCheckInterval, ai as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, aj as cleanupDaemonState, i as isValidSessionId, v as spawnDaemonSession, ak as listVhTerminals, al as isValidTerminalId, am as readVhTerminal, an as sendToVhTerminal, ao as ContextUsageSchema, ap as TITLE_PROMPT_PREFIX, aq as BOARD_ANALYZER_PROMPT_PREFIX, x as connectionState, ar as startOfflineReconnection, y as notifyDaemonSessionStarted, c as createEnvelope, C as CodexAppServerClient } from './types-BN3eiipi.mjs';
5
- import { l as logger, F as ensurePrivateDirectory, G as hardenPrivateFile, c as configuration, p as packageJson, r as readSettings, H as readCredentials, x as credentialRelayProblem, o as readDaemonState, g as encodeBase64, I as readSettingsOutcome, u as updateSettings, J as encodeBase64Url, m as decodeBase64, K as writeCredentialsLegacy, L as writeCredentialsDataKey, e as readCredentialsForConfiguredRelay, j as encrypt, d as readPersistedSessions, w as writePrivateFileSync, M as decryptWithDataKey, N as decryptLegacy, O as persistSession, k as decrypt, h as ensurePrivateDirectorySync, P as acquireDaemonLock, Q as deletePersistedSessions, R as writeDaemonState, S as writeSettings, T as releaseDaemonLock, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, f as CLIPBOARD_MAX_BYTES, U as clearCredentials, V as clearMachineId, W as SandboxConfigSchema, X as getLatestDaemonLog } from './limits-DS-bnYYp.mjs';
4
+ import { v as notifyDaemonSessionEvent, g as getProjectPath, w as initializeSandbox, x as wrapCommand, u as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, c as delay, y as AsyncLock, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, z as notifyDaemonClaudeAuthFailed, B as commandOnPath, j as checkIfDaemonRunningAndCleanupStaleState, D as deriveLocalCliUpdateSummary, E as isClaudeSessionId, G as getLocalHappyAgentCredentialPath, H as readLocalHappyAgentCredentials, J as parseExactVersion, K as compareExactVersions, L as extractCompleteLines, N as decideBackfill, O as parseTerminalHookPayload, P as tmuxArgs, Q as scrubTmuxClientEnv, S as decideMirrorBinding, T as MIRROR_BACKFILL_LINES_DEFAULT, U as mirrorLineKey, V as mirrorLocalId, W as listDaemonSessions, k as stopDaemonSession, X as detectCLIAvailability, Y as detectResumeSupport, Z as refreshAgentHomes, d as describeAgentHome, _ as isDaemonRunningCurrentlyInstalledHappyVersion, $ as stopDaemon, a0 as TurnActivityTracker, a1 as summarizeSpawnSessionForLog, a2 as locateClaudeConversation, a3 as agentHomeSpawnEnv, a4 as shellescape, A as ApiClient, a5 as fetchCliUpdateState, a6 as resolveCliUpdateCheckInterval, a7 as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, a8 as cleanupDaemonState, i as isValidSessionId, s as spawnDaemonSession, a9 as listVhTerminals, aa as isValidTerminalId, ab as readVhTerminal, ac as sendToVhTerminal, ad as TITLE_PROMPT_PREFIX, ae as BOARD_ANALYZER_PROMPT_PREFIX, o as connectionState, af as startOfflineReconnection, q as notifyDaemonSessionStarted, C as CodexAppServerClient } from './types-xKrxbCRv.mjs';
5
+ import { l as logger, k as BUILTIN_TODO_DISCOVERY, U as STREAM_DELTA_MAX_CHARS, V as ensurePrivateDirectory, W as hardenPrivateFile, c as configuration, p as packageJson, r as readSettings, X as readCredentials, L as credentialRelayProblem, F as readDaemonState, Y as AGENT_VERSION_SOURCES, j as encodeBase64, Z as readSettingsOutcome, u as updateSettings, _ as encodeBase64Url, E as decodeBase64, $ as writeCredentialsLegacy, a0 as writeCredentialsDataKey, f as readCredentialsForConfiguredRelay, z as encrypt, d as readPersistedSessions, w as writePrivateFileSync, a1 as decryptWithDataKey, a2 as decryptLegacy, a3 as persistSession, D as decrypt, m as ensurePrivateDirectorySync, a4 as getAgentCodeDefaults, a5 as normalizeAgentKey, a6 as isClaudeOpus55Model, a7 as supportsClaudeOpus55, a8 as AUTOMATION_SUMMARY_MAX_CHARS, a9 as AUTOMATION_CLAIM_MAX_RUNS, aa as AUTOMATION_DEFAULT_LEASE_MS, i as isAutomationRunTerminal, ab as acquireDaemonLock, ac as deletePersistedSessions, ad as writeDaemonState, ae as writeSettings, af as releaseDaemonLock, ag as ContextUsageSchema, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, h as CLIPBOARD_MAX_BYTES, ah as clearCredentials, ai as clearMachineId, aj as SandboxConfigSchema, e as createEnvelope, ak as getLatestDaemonLog } from './limits-CKR_HJnO.mjs';
6
6
  import spawn$1, { spawn } from 'cross-spawn';
7
7
  import path, { join, resolve, basename, delimiter, isAbsolute, dirname, sep, normalize } from 'node:path';
8
8
  import { createInterface } from 'node:readline';
9
9
  import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, realpathSync, unlinkSync, renameSync, writeFileSync, accessSync, constants as constants$1, openSync, fsyncSync, closeSync, rmSync as rmSync$1, chmodSync as chmodSync$1 } from 'node:fs';
10
10
  import { p as projectPath } from './projectPath-CSpqWdxp.mjs';
11
- import { f as trimIdent, A as AGENT_GUIDANCE, t as teamScopePath, T as TeamConnectionPreflightError, e as createTeamsClient, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, g as TEAM_TOOL_NAMES, r as registerTeamsTools, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, h as REPORT_PROGRESS_TOOL_TITLE, i as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-Dvo3cZMW.mjs';
11
+ import { j as trimIdent, k as AGENT_GUIDANCE, l as createAutomationsClient, i as isAutomationsUnavailable, m as AutomationsRequestError, t as teamScopePath, T as TeamConnectionPreflightError, h as createTeamsClient, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, n as TEAM_TOOL_NAMES, o as AUTOMATION_TOOL_NAMES, r as registerTeamsTools, c as registerAutomationTools, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, p as REPORT_PROGRESS_TOOL_TITLE, q as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-Daxn5Nvj.mjs';
12
12
  import { readFile, writeFile, rename, unlink, mkdir, access as access$1, symlink, rm, readlink, lstat, constants, stat, open as open$1, appendFile } from 'node:fs/promises';
13
13
  import fs, { watch, access } from 'fs/promises';
14
14
  import { useStdout, useInput, Box, Text, render } from 'ink';
@@ -5380,8 +5380,8 @@ ${typeLabels[type] || type}:`));
5380
5380
  }
5381
5381
  console.log(chalk.gray(" An external claude command is only needed for native terminal/mirror use."));
5382
5382
  try {
5383
- const { resolveAgentHomesForCli } = await import('./agentHome-CeFDNzvq.mjs');
5384
- const { describeAgentHome } = await import('./types-BN3eiipi.mjs').then(function (n) { return n.as; });
5383
+ const { resolveAgentHomesForCli } = await import('./agentHome-CCYfs8Ec.mjs');
5384
+ const { describeAgentHome } = await import('./types-xKrxbCRv.mjs').then(function (n) { return n.ag; });
5385
5385
  const homes = await resolveAgentHomesForCli();
5386
5386
  console.log(chalk.green(`\u2713 Claude config dir: ${describeAgentHome(homes.claudeConfigDir)}`));
5387
5387
  console.log(chalk.green(`\u2713 Codex home: ${describeAgentHome(homes.codexHome)}`));
@@ -6208,7 +6208,7 @@ const DENIED_BASENAME_PATTERNS = [
6208
6208
  const DENIED_PATH_SUFFIXES = [
6209
6209
  `${sep}.git${sep}config`
6210
6210
  ];
6211
- function expandHome(input, home) {
6211
+ function expandHome$1(input, home) {
6212
6212
  if (input === "~") return home;
6213
6213
  if (input.startsWith(`~${sep}`) || input.startsWith("~/")) {
6214
6214
  return resolve(home, input.slice(2));
@@ -6231,7 +6231,7 @@ function checkPreviewPath(input, opts) {
6231
6231
  if (input.includes("\0")) {
6232
6232
  return { resolved: "", deniedReason: "path contains a NUL byte" };
6233
6233
  }
6234
- const expanded = expandHome(input.trim(), home);
6234
+ const expanded = expandHome$1(input.trim(), home);
6235
6235
  const resolved = isAbsolute(expanded) ? resolve(expanded) : resolve(cwd, expanded);
6236
6236
  for (const dir of DENIED_HOME_DIRS) {
6237
6237
  if (isUnder(resolved, resolve(home, dir))) {
@@ -9517,11 +9517,14 @@ function agentPiece(body) {
9517
9517
  }
9518
9518
  return null;
9519
9519
  }
9520
- function analyzeLatestTurn(entries) {
9520
+ function userPromptText(body) {
9521
+ return isUserPrompt(body) ? body.content.text : null;
9522
+ }
9523
+ function analyzeLatestTurn(entries, options = {}) {
9521
9524
  const lastSeq = entries.length > 0 ? entries[entries.length - 1].seq : 0;
9522
9525
  let userIndex = -1;
9523
9526
  for (let i = entries.length - 1; i >= 0; i--) {
9524
- if (isUserPrompt(entries[i].body)) {
9527
+ if (isUserPrompt(entries[i].body) && (!options.anchor || options.anchor(entries[i].body))) {
9525
9528
  userIndex = i;
9526
9529
  break;
9527
9530
  }
@@ -9535,6 +9538,10 @@ function analyzeLatestTurn(entries) {
9535
9538
  const emitsStarts = entries.some((entry) => sessionEnvelope(entry.body)?.ev?.t === "turn-start");
9536
9539
  for (let i = userIndex + 1; i < entries.length; i++) {
9537
9540
  const body = entries[i].body;
9541
+ if (options.anchor && isUserPrompt(body)) {
9542
+ if (ended) break;
9543
+ continue;
9544
+ }
9538
9545
  const env = sessionEnvelope(body);
9539
9546
  if (env?.ev?.t === "turn-start") {
9540
9547
  lastMarker = "start";
@@ -10027,7 +10034,7 @@ function readReceipt(home, id) {
10027
10034
  throw error;
10028
10035
  }
10029
10036
  }
10030
- function writeReceipt(home, receipt) {
10037
+ function writeReceipt$1(home, receipt) {
10031
10038
  ensurePrivateDirectorySync(join(home, "teams", "effects"));
10032
10039
  const target = receiptPath(home, receipt.operationId);
10033
10040
  const tmp = `${target}.${process.pid}.tmp`;
@@ -10104,7 +10111,7 @@ function createTeamWorker(deps) {
10104
10111
  const action = async (teamId, requestId, value) => (await http.post(`/v1/teams/${encodeURIComponent(teamId)}/actions`, { requestId, action: value })).data;
10105
10112
  async function complete(op, receipt) {
10106
10113
  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 } : {} });
10107
- writeReceipt(home, { ...receipt, phase: "completed" });
10114
+ writeReceipt$1(home, { ...receipt, phase: "completed" });
10108
10115
  }
10109
10116
  async function execute(op) {
10110
10117
  let receipt = readReceipt(home, op.id);
@@ -10121,7 +10128,7 @@ function createTeamWorker(deps) {
10121
10128
  op = claimed.operation;
10122
10129
  credential = claimed.credential;
10123
10130
  receipt = { operationId: op.id, teamId: op.teamId, claimId: op.claimId, phase: "prepared" };
10124
- writeReceipt(home, receipt);
10131
+ writeReceipt$1(home, receipt);
10125
10132
  }
10126
10133
  if (receipt.phase === "completed") return;
10127
10134
  try {
@@ -10134,7 +10141,7 @@ function createTeamWorker(deps) {
10134
10141
  if (!task) throw new Error("Task state unavailable; refusing to start");
10135
10142
  if (task.currentAttemptId !== op.attemptId || ["done", "cancelled"].includes(task.status)) {
10136
10143
  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" });
10137
- writeReceipt(home, { ...receipt, phase: "completed" });
10144
+ writeReceipt$1(home, { ...receipt, phase: "completed" });
10138
10145
  return;
10139
10146
  }
10140
10147
  if (!op.directory) throw new Error("No repository selected for worker");
@@ -10144,17 +10151,17 @@ function createTeamWorker(deps) {
10144
10151
  const scopeFile = join(scopes, `${op.id}.json`);
10145
10152
  writePrivateFileSync(scopeFile, JSON.stringify({ serverUrl: server, scopeToken: credential.token, teamId: op.teamId, botId: op.botId, taskId: op.taskId, attemptId: op.attemptId }));
10146
10153
  receipt = { ...receipt, ...resource, scopeFile, phase: "spawning" };
10147
- writeReceipt(home, receipt);
10154
+ writeReceipt$1(home, receipt);
10148
10155
  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 } });
10149
10156
  if (spawned.type !== "success") throw new Error(spawned.type === "error" ? spawned.errorMessage : "Directory approval required");
10150
10157
  receipt = { ...receipt, sessionId: spawned.sessionId, phase: "spawned" };
10151
- writeReceipt(home, receipt);
10158
+ writeReceipt$1(home, receipt);
10152
10159
  }
10153
10160
  if (receipt.phase === "spawning") {
10154
10161
  const matches = Object.entries(readPersistedSessions()).filter(([, entry]) => entry.metadata?.teamOperationId === op.id);
10155
10162
  if (matches.length !== 1) throw new Error("Spawn outcome unknown; reconcile the worker before retrying");
10156
10163
  receipt = { ...receipt, sessionId: matches[0][0], phase: "spawned" };
10157
- writeReceipt(home, receipt);
10164
+ writeReceipt$1(home, receipt);
10158
10165
  }
10159
10166
  if (receipt.phase === "spawned") {
10160
10167
  const current = (await http.get(`/v1/teams/${encodeURIComponent(op.teamId)}`)).data.team;
@@ -10169,14 +10176,14 @@ function createTeamWorker(deps) {
10169
10176
  const key = await waitForSessionKey(receipt.sessionId, 1e4);
10170
10177
  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 } : {} });
10171
10178
  receipt = { ...receipt, phase: "delivered" };
10172
- writeReceipt(home, receipt);
10179
+ writeReceipt$1(home, receipt);
10173
10180
  }
10174
10181
  await complete(op, receipt);
10175
10182
  } else {
10176
10183
  const team = (await http.get(`/v1/teams/${encodeURIComponent(op.teamId)}`)).data.team;
10177
10184
  const latest = team.operations.find((candidate) => candidate.id === op.id);
10178
10185
  if (latest?.status === "completed") {
10179
- writeReceipt(home, { ...receipt, phase: "completed" });
10186
+ writeReceipt$1(home, { ...receipt, phase: "completed" });
10180
10187
  return;
10181
10188
  }
10182
10189
  const bot = team.bots.find((candidate) => candidate.id === op.botId);
@@ -10306,6 +10313,643 @@ Use the teams tools to inspect current task state before acting. This message is
10306
10313
  } };
10307
10314
  }
10308
10315
 
10316
+ function resolveSpawnPermissionMode(agent, explicit) {
10317
+ return explicit ?? getAgentCodeDefaults(agent ?? "claude").permissionMode;
10318
+ }
10319
+ function outboundPermissionMode(agent, mode) {
10320
+ const key = normalizeAgentKey(agent ?? "claude");
10321
+ if ((key === "claude" || key === "pi") && mode === "yolo") return "bypassPermissions";
10322
+ return mode;
10323
+ }
10324
+ function resolveFirstMessageMeta(input) {
10325
+ const meta = {
10326
+ permissionMode: outboundPermissionMode(input.agent, input.permissionMode)
10327
+ };
10328
+ if (input.explicitModel !== void 0) {
10329
+ meta.model = input.explicitModel === "default" ? null : input.explicitModel;
10330
+ }
10331
+ if (normalizeAgentKey(input.agent ?? "claude") !== "claude") return meta;
10332
+ if (meta.model === void 0) {
10333
+ const model = getAgentCodeDefaults("claude").modelMode;
10334
+ const runnable = !isClaudeOpus55Model(model) || supportsClaudeOpus55({ capabilities: input.capabilities ?? null });
10335
+ meta.model = runnable && model !== "default" ? model : null;
10336
+ }
10337
+ meta.effort = null;
10338
+ return meta;
10339
+ }
10340
+
10341
+ function payloadJson(payload) {
10342
+ if (!payload) return void 0;
10343
+ try {
10344
+ return JSON.parse(payload);
10345
+ } catch {
10346
+ return void 0;
10347
+ }
10348
+ }
10349
+ function lookupPath(root, path) {
10350
+ let current = root;
10351
+ for (const segment of path) {
10352
+ if (current === null || typeof current !== "object") return void 0;
10353
+ current = current[segment];
10354
+ }
10355
+ return current;
10356
+ }
10357
+ function scalar(value) {
10358
+ if (value === void 0 || value === null) return "";
10359
+ if (typeof value === "string") return value;
10360
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
10361
+ try {
10362
+ return JSON.stringify(value);
10363
+ } catch {
10364
+ return "";
10365
+ }
10366
+ }
10367
+ function formatNow(now, tz) {
10368
+ if (!tz) return new Date(now).toISOString();
10369
+ try {
10370
+ 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));
10371
+ const get = (type) => parts.find((p) => p.type === type)?.value ?? "";
10372
+ const offset = get("timeZoneName").replace(/^GMT$/, "+00:00").replace(/^GMT/, "");
10373
+ return `${get("year")}-${get("month")}-${get("day")}T${get("hour")}:${get("minute")}:${get("second")}${offset || "Z"}`;
10374
+ } catch {
10375
+ return new Date(now).toISOString();
10376
+ }
10377
+ }
10378
+ function renderTemplate(text, context) {
10379
+ const json = payloadJson(context.payload);
10380
+ return text.replace(/\{\{\s*([a-zA-Z_][a-zA-Z0-9_.-]*)\s*\}\}/g, (whole, name) => {
10381
+ if (name === "payload") return context.payload ?? "";
10382
+ if (name.startsWith("payload.")) return scalar(lookupPath(json, name.slice("payload.".length).split(".")));
10383
+ if (name === "run.id") return context.runId;
10384
+ if (name === "automation.name") return context.automationName;
10385
+ if (name === "now") return formatNow(context.now ?? Date.now(), context.tz);
10386
+ return whole;
10387
+ });
10388
+ }
10389
+ function renderAction(action, context) {
10390
+ const r = (value) => renderTemplate(value, context);
10391
+ if (action.kind === "spawn") return { ...action, prompt: r(action.prompt) };
10392
+ if (action.kind === "send") return { ...action, prompt: r(action.prompt) };
10393
+ return { ...action, command: action.command.map(r), ...action.env ? { env: Object.fromEntries(Object.entries(action.env).map(([k, v]) => [k, r(v)])) } : {} };
10394
+ }
10395
+ function clipText(text, maxChars, keep = "head") {
10396
+ if (text.length <= maxChars) return text;
10397
+ return keep === "head" ? text.slice(0, maxChars) : text.slice(text.length - maxChars);
10398
+ }
10399
+ const DURATION_UNITS = { ms: 1, s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
10400
+ function parseDuration(raw) {
10401
+ const text = raw.trim();
10402
+ if (/^\d+$/.test(text)) return Number(text);
10403
+ const re = /(\d+(?:\.\d+)?)(ms|s|m|h|d)/gy;
10404
+ let total = 0;
10405
+ let matched = 0;
10406
+ let last = 0;
10407
+ let m;
10408
+ while ((m = re.exec(text)) !== null) {
10409
+ total += Number(m[1]) * DURATION_UNITS[m[2]];
10410
+ matched++;
10411
+ last = re.lastIndex;
10412
+ }
10413
+ if (matched === 0 || last !== text.length) throw new Error(`Invalid duration: ${raw} (use e.g. 90s, 5m, 1h30m, 2d)`);
10414
+ return Math.round(total);
10415
+ }
10416
+ function formatDuration(ms) {
10417
+ if (ms % 864e5 === 0) return `${ms / 864e5}d`;
10418
+ if (ms % 36e5 === 0) return `${ms / 36e5}h`;
10419
+ if (ms % 6e4 === 0) return `${ms / 6e4}m`;
10420
+ if (ms % 1e3 === 0) return `${ms / 1e3}s`;
10421
+ return `${ms}ms`;
10422
+ }
10423
+ function triggerFromFlags(flags, now = Date.now()) {
10424
+ const given = [flags.cron !== void 0, flags.every !== void 0, flags.at !== void 0, flags.manual === true].filter(Boolean).length;
10425
+ if (given === 0) {
10426
+ if (flags.tz !== void 0) throw new Error("--tz only applies with --cron");
10427
+ return void 0;
10428
+ }
10429
+ if (given > 1) throw new Error("Use exactly one of --cron, --every, --at, --manual");
10430
+ if (flags.cron !== void 0) {
10431
+ if (!flags.tz) throw new Error("--cron requires --tz <IANA zone> (e.g. Asia/Singapore)");
10432
+ return { kind: "cron", expr: flags.cron, tz: flags.tz };
10433
+ }
10434
+ if (flags.tz !== void 0) throw new Error("--tz only applies with --cron");
10435
+ if (flags.every !== void 0) return { kind: "interval", everyMs: parseDuration(flags.every), anchorAt: now };
10436
+ if (flags.at !== void 0) {
10437
+ const at = Date.parse(flags.at);
10438
+ if (!Number.isFinite(at)) throw new Error(`--at must be an ISO 8601 timestamp, got: ${flags.at}`);
10439
+ return { kind: "once", at };
10440
+ }
10441
+ return { kind: "manual" };
10442
+ }
10443
+ function describeTrigger(trigger) {
10444
+ switch (trigger.kind) {
10445
+ case "cron":
10446
+ return `cron ${trigger.expr} (${trigger.tz})`;
10447
+ case "interval":
10448
+ return `every ${formatDuration(trigger.everyMs)}`;
10449
+ case "once":
10450
+ return `once at ${new Date(trigger.at).toISOString()}`;
10451
+ default:
10452
+ return "manual";
10453
+ }
10454
+ }
10455
+ function describeAction(action) {
10456
+ if (action.kind === "spawn") return `spawn ${action.agent} in ${action.directory}${action.sticky ? ` (sticky ${action.sticky.key})` : ""}`;
10457
+ if (action.kind === "send") return `send to session ${action.sessionId}`;
10458
+ return `script ${action.command.join(" ")}`;
10459
+ }
10460
+
10461
+ const RECEIPT_RETENTION_MS = 7 * 864e5;
10462
+ const RECEIPT_MAX_ENTRIES = 2e3;
10463
+ function receiptsPath(home) {
10464
+ return join(home, "automation-receipts.json");
10465
+ }
10466
+ function readReceipts(home) {
10467
+ let raw;
10468
+ try {
10469
+ raw = readFileSync(receiptsPath(home), "utf8");
10470
+ } catch (error) {
10471
+ if (error.code === "ENOENT") return {};
10472
+ throw error;
10473
+ }
10474
+ const parsed = JSON.parse(raw);
10475
+ if (!parsed || typeof parsed !== "object" || typeof parsed.receipts !== "object") throw new Error("Invalid automation receipts file");
10476
+ return parsed.receipts;
10477
+ }
10478
+ function writeReceipt(home, receipt, now = Date.now()) {
10479
+ if (!/^[a-zA-Z0-9_-]{1,128}$/.test(receipt.runId)) throw new Error("Invalid automation run id");
10480
+ const all = readReceipts(home);
10481
+ const next = { ...receipt, updatedAt: now };
10482
+ all[receipt.runId] = next;
10483
+ const entries = Object.values(all).sort((a, b) => b.updatedAt - a.updatedAt);
10484
+ const kept = {};
10485
+ let finished = 0;
10486
+ for (const entry of entries) {
10487
+ if (entry.phase === "finished") {
10488
+ if (now - entry.updatedAt > RECEIPT_RETENTION_MS || finished >= RECEIPT_MAX_ENTRIES) continue;
10489
+ finished++;
10490
+ }
10491
+ kept[entry.runId] = entry;
10492
+ }
10493
+ const target = receiptsPath(home);
10494
+ ensurePrivateDirectorySync(dirname(target));
10495
+ const tmp = `${target}.${process.pid}.tmp`;
10496
+ writePrivateFileSync(tmp, JSON.stringify({ version: 1, receipts: kept }));
10497
+ const file = openSync(tmp, "r");
10498
+ try {
10499
+ fsyncSync(file);
10500
+ } finally {
10501
+ closeSync(file);
10502
+ }
10503
+ renameSync(tmp, target);
10504
+ if (process.platform !== "win32") {
10505
+ const directory = openSync(dirname(target), "r");
10506
+ try {
10507
+ fsyncSync(directory);
10508
+ } finally {
10509
+ closeSync(directory);
10510
+ }
10511
+ }
10512
+ return next;
10513
+ }
10514
+
10515
+ const SCRIPT_KILL_GRACE_MS = 1e4;
10516
+ const SCRIPT_DRAIN_MS = 2e3;
10517
+ class TailBuffer {
10518
+ constructor(maxChars) {
10519
+ this.maxChars = maxChars;
10520
+ }
10521
+ text = "";
10522
+ push(chunk) {
10523
+ this.text += chunk;
10524
+ if (this.text.length > this.maxChars * 2) this.text = this.text.slice(this.text.length - this.maxChars);
10525
+ }
10526
+ value() {
10527
+ return this.text.length > this.maxChars ? this.text.slice(this.text.length - this.maxChars) : this.text;
10528
+ }
10529
+ }
10530
+ function killScriptTree(child, signal) {
10531
+ if (child.exitCode !== null || child.signalCode !== null || !child.pid) return;
10532
+ try {
10533
+ if (process.platform !== "win32") process.kill(-child.pid, signal);
10534
+ else child.kill(signal);
10535
+ } catch {
10536
+ try {
10537
+ child.kill(signal);
10538
+ } catch {
10539
+ }
10540
+ }
10541
+ }
10542
+ function terminateScriptTree(child) {
10543
+ killScriptTree(child, "SIGTERM");
10544
+ const killer = setTimeout(() => killScriptTree(child, "SIGKILL"), SCRIPT_KILL_GRACE_MS);
10545
+ killer.unref();
10546
+ child.once("exit", () => clearTimeout(killer));
10547
+ }
10548
+ function runScript(options) {
10549
+ return new Promise((resolve) => {
10550
+ const tail = new TailBuffer(AUTOMATION_SUMMARY_MAX_CHARS);
10551
+ let timedOut = false;
10552
+ let settled = false;
10553
+ let child;
10554
+ const timers = [];
10555
+ const finish = (result) => {
10556
+ if (settled) return;
10557
+ settled = true;
10558
+ for (const t of timers) clearTimeout(t);
10559
+ resolve(result);
10560
+ };
10561
+ const timer = setTimeout(() => {
10562
+ timedOut = true;
10563
+ terminateScriptTree(child);
10564
+ }, options.timeoutMs);
10565
+ timer.unref();
10566
+ timers.push(timer);
10567
+ try {
10568
+ child = spawn$3(options.command[0], options.command.slice(1), {
10569
+ cwd: options.cwd,
10570
+ shell: false,
10571
+ windowsHide: true,
10572
+ stdio: ["ignore", "pipe", "pipe"],
10573
+ detached: process.platform !== "win32",
10574
+ env: { ...options.baseEnv ?? process.env, ...options.env ?? {} }
10575
+ });
10576
+ } catch (error) {
10577
+ finish({ exitCode: null, signal: null, timedOut: false, output: "", error: error instanceof Error ? error.message : String(error) });
10578
+ return;
10579
+ }
10580
+ options.onStart?.(child);
10581
+ child.stdout?.setEncoding("utf8").on("data", (chunk) => tail.push(chunk));
10582
+ child.stderr?.setEncoding("utf8").on("data", (chunk) => tail.push(chunk));
10583
+ child.on("error", (error) => finish({ exitCode: null, signal: null, timedOut, output: tail.value(), error: error.message }));
10584
+ child.on("close", (code, signal) => finish({ exitCode: code, signal, timedOut, output: tail.value() }));
10585
+ child.on("exit", (code, signal) => {
10586
+ const drain = setTimeout(() => {
10587
+ killScriptTree(child, "SIGKILL");
10588
+ finish({ exitCode: code, signal, timedOut, output: tail.value() });
10589
+ }, SCRIPT_DRAIN_MS);
10590
+ drain.unref();
10591
+ timers.push(drain);
10592
+ });
10593
+ });
10594
+ }
10595
+
10596
+ const AUTOMATION_TICK_MS = 5e3;
10597
+ const AUTOMATION_LEASE_RENEW_MS = 3e4;
10598
+ const AUTOMATION_LOG_READ_MS = 3e4;
10599
+ const AUTOMATION_UNAVAILABLE_LOG_MS = 10 * 6e4;
10600
+ const AUTOMATION_MAX_CONCURRENT = AUTOMATION_CLAIM_MAX_RUNS;
10601
+ const AUTOMATION_SPAWNED_BY = "automation";
10602
+ function automationPromptHeader(name, runId) {
10603
+ return `[Very Happy automation "${name}" run ${runId}; finish with automation_report (runId ${runId}) or \`very-happy auto report --run ${runId}\`]`;
10604
+ }
10605
+ function automationRunAnchor(runId) {
10606
+ const marker = ` run ${runId};`;
10607
+ return (body) => userPromptText(body)?.startsWith("[Very Happy automation ") === true && userPromptText(body).includes(marker);
10608
+ }
10609
+ async function defaultReadTurn(sessionId, runId) {
10610
+ const persisted = readPersistedSessions()[sessionId];
10611
+ if (!persisted) throw new Error(`No local key for session ${sessionId}`);
10612
+ return analyzeLatestTurn(await readSessionLog(sessionId, persisted, TURN_WINDOW), { anchor: automationRunAnchor(runId) });
10613
+ }
10614
+ async function defaultSend(sessionId, text, meta) {
10615
+ const persisted = await waitForSessionKey(sessionId, 15e3);
10616
+ await sendUserMessage(sessionId, persisted, text, "automation", { ...meta, sentFrom: "automation" });
10617
+ }
10618
+ function expandHome(path) {
10619
+ if (path === "~") return homedir();
10620
+ if (path.startsWith("~/")) return join(homedir(), path.slice(2));
10621
+ return path;
10622
+ }
10623
+ function isSettledByServer(error) {
10624
+ if (!(error instanceof AutomationsRequestError)) return false;
10625
+ if (error.status === 409 && (error.code === "run_finished" || error.code === "claim_mismatch")) return true;
10626
+ return error.status === 404 && error.code === "run_not_found";
10627
+ }
10628
+ function scriptAlive(item) {
10629
+ return !!item.child && item.child.exitCode === null && item.child.signalCode === null;
10630
+ }
10631
+ function createAutomationRunner(deps) {
10632
+ const home = deps.home ?? configuration.happyHomeDir;
10633
+ const client = deps.client ?? createAutomationsClient({ serverUrl: deps.serverUrl ?? configuration.serverUrl, token: deps.token, client: "daemon-auto" });
10634
+ const runScript$1 = deps.runScript ?? runScript;
10635
+ const readTurn = deps.readTurn ?? defaultReadTurn;
10636
+ const send = deps.send ?? defaultSend;
10637
+ const prepareWorktree = deps.prepareWorktree ?? prepareTeamWorktree;
10638
+ const now = deps.now ?? (() => Date.now());
10639
+ const tracked = /* @__PURE__ */ new Map();
10640
+ let running = false;
10641
+ let renewing = false;
10642
+ let closed = false;
10643
+ let disabledUntil = 0;
10644
+ let lastUnavailableLogAt = -Infinity;
10645
+ let recovered = false;
10646
+ function persist(receipt) {
10647
+ writeReceipt(home, receipt, now());
10648
+ }
10649
+ async function finish(item, report2) {
10650
+ try {
10651
+ await client.report(item.runId, report2);
10652
+ } catch (error) {
10653
+ if (!isSettledByServer(error)) {
10654
+ item.pendingFinal = report2;
10655
+ deps.log(`Automation run ${item.runId} final report deferred`);
10656
+ return;
10657
+ }
10658
+ }
10659
+ persist({ runId: item.runId, automationId: item.automationId, kind: item.kind, phase: "finished", claimId: item.claimId, sessionId: item.sessionId });
10660
+ tracked.delete(item.runId);
10661
+ }
10662
+ function track(claimed, kind, sessionId, child) {
10663
+ const at = now();
10664
+ 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 };
10665
+ tracked.set(item.runId, item);
10666
+ return item;
10667
+ }
10668
+ async function failNow(claimed, kind, error, attentionReason, sessionId) {
10669
+ const item = track(claimed, kind, sessionId);
10670
+ await finish(item, { claimId: item.claimId, status: "failed", error: clipText(error, AUTOMATION_SUMMARY_MAX_CHARS), ...sessionId ? { sessionId } : {}, ...attentionReason ? { needsAttention: true, attentionReason } : {} });
10671
+ }
10672
+ async function reportRunning(item, sessionId, stickyKey) {
10673
+ const body = { claimId: item.claimId, status: "running", ...sessionId ? { sessionId } : {}, ...stickyKey ? { stickyKey } : {} };
10674
+ try {
10675
+ await client.report(item.runId, body);
10676
+ } catch (error) {
10677
+ if (isSettledByServer(error)) {
10678
+ settleExternally(item);
10679
+ return;
10680
+ }
10681
+ if (sessionId) item.pendingRunning = { sessionId, ...stickyKey ? { stickyKey } : {} };
10682
+ deps.log(`Automation run ${item.runId} running report deferred`);
10683
+ }
10684
+ }
10685
+ async function execute(claimed) {
10686
+ const { run, automation } = claimed;
10687
+ const existing = readReceipts(home)[run.id];
10688
+ if (existing) {
10689
+ if (existing.phase === "finished" || tracked.has(run.id)) return;
10690
+ if (existing.phase === "starting") {
10691
+ await failNow(claimed, existing.kind, "Local execution outcome unknown after restart; verify before re-running", "unknown_outcome");
10692
+ return;
10693
+ }
10694
+ if (existing.kind === "script") {
10695
+ await failNow(claimed, "script", "Daemon restarted while the script was running", "daemon_restarted");
10696
+ return;
10697
+ }
10698
+ track(claimed, existing.kind, existing.sessionId).sawTurn = true;
10699
+ return;
10700
+ }
10701
+ const context = { payload: run.payload, runId: run.id, automationName: automation.name, ...automation.trigger.kind === "cron" ? { tz: automation.trigger.tz } : {}, now: now() };
10702
+ const action = renderAction(automation.action, context);
10703
+ const env = { VH_AUTOMATION_RUN_ID: run.id, VH_AUTOMATION_NAME: automation.name };
10704
+ const header = automationPromptHeader(automation.name, run.id);
10705
+ if (action.kind === "script") {
10706
+ const timeoutMs = action.timeoutMs ?? automation.maxRuntimeMs;
10707
+ persist({ runId: run.id, automationId: automation.id, kind: "script", phase: "starting", claimId: run.claimId });
10708
+ const item2 = track(claimed, "script");
10709
+ const finished = runScript$1({
10710
+ command: action.command,
10711
+ cwd: action.cwd ? expandHome(action.cwd) : void 0,
10712
+ timeoutMs,
10713
+ env: { ...action.env ?? {}, ...env, ...run.payload ? { VH_AUTOMATION_PAYLOAD: run.payload } : {} },
10714
+ onStart: (child) => {
10715
+ item2.child = child;
10716
+ }
10717
+ });
10718
+ persist({ runId: run.id, automationId: automation.id, kind: "script", phase: "running", claimId: run.claimId });
10719
+ await reportRunning(item2);
10720
+ void finished.then(async (result) => {
10721
+ if (!tracked.has(run.id)) return;
10722
+ const ok = result.exitCode === 0 && !result.timedOut && !result.error;
10723
+ 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;
10724
+ await finish(item2, { claimId: run.claimId, status: ok ? "done" : "failed", summary: clipText(result.output, AUTOMATION_SUMMARY_MAX_CHARS, "tail"), exitCode: result.exitCode, ...error ? { error } : {} });
10725
+ });
10726
+ return;
10727
+ }
10728
+ if (action.kind === "send") {
10729
+ if (!deps.live(action.sessionId)) {
10730
+ await failNow(claimed, "send", `Session ${action.sessionId} is not running on this machine`, "session_not_live", action.sessionId);
10731
+ return;
10732
+ }
10733
+ persist({ runId: run.id, automationId: automation.id, kind: "send", phase: "running", claimId: run.claimId, sessionId: action.sessionId });
10734
+ try {
10735
+ await send(action.sessionId, `${header}
10736
+ ${action.prompt}`, { localId: `automation-${run.id}` });
10737
+ } catch (error) {
10738
+ await failNow(claimed, "send", `Message delivery failed: ${error instanceof Error ? error.message : String(error)}`, "send_failed", action.sessionId);
10739
+ return;
10740
+ }
10741
+ await reportRunning(track(claimed, "send", action.sessionId), action.sessionId);
10742
+ return;
10743
+ }
10744
+ const agent = action.agent;
10745
+ if (!isSpawnAgent(agent)) {
10746
+ await failNow(claimed, "spawn", `Unknown agent "${agent}"`, "invalid_action");
10747
+ return;
10748
+ }
10749
+ if (action.permissionMode && sanitizeSpawnPermissionMode(action.permissionMode) === null) {
10750
+ await failNow(claimed, "spawn", `Invalid permission mode "${action.permissionMode}"`, "invalid_action");
10751
+ return;
10752
+ }
10753
+ const permissionMode = resolveSpawnPermissionMode(agent, action.permissionMode ?? void 0);
10754
+ const stickyKey = action.sticky ? renderTemplate(action.sticky.key, context) : void 0;
10755
+ if (stickyKey) {
10756
+ const bound = claimed.stickies.find((sticky) => sticky.key === stickyKey);
10757
+ if (bound && deps.live(bound.sessionId)) {
10758
+ persist({ runId: run.id, automationId: automation.id, kind: "send", phase: "running", claimId: run.claimId, sessionId: bound.sessionId, stickyKey });
10759
+ try {
10760
+ await send(bound.sessionId, `${header}
10761
+ ${action.prompt}`, { localId: `automation-${run.id}` });
10762
+ } catch (error) {
10763
+ await failNow(claimed, "send", `Sticky session delivery failed: ${error instanceof Error ? error.message : String(error)}`, "send_failed", bound.sessionId);
10764
+ return;
10765
+ }
10766
+ await reportRunning(track(claimed, "send", bound.sessionId), bound.sessionId, stickyKey);
10767
+ return;
10768
+ }
10769
+ if (bound) await client.deleteSticky(automation.id, stickyKey).catch(() => deps.log(`Automation ${automation.name}: stale sticky ${stickyKey} not cleared`));
10770
+ }
10771
+ let directory = expandHome(action.directory);
10772
+ if (!isAbsolute(directory)) {
10773
+ await failNow(claimed, "spawn", `Directory must be absolute: ${action.directory}`, "invalid_action");
10774
+ return;
10775
+ }
10776
+ if (action.worktree) {
10777
+ try {
10778
+ directory = (await prepareWorktree(home, directory, `auto-${run.id}`)).directory;
10779
+ } catch (error) {
10780
+ await failNow(claimed, "spawn", `Worktree preparation failed: ${error instanceof Error ? error.message : String(error)}`, "worktree_failed");
10781
+ return;
10782
+ }
10783
+ }
10784
+ persist({ runId: run.id, automationId: automation.id, kind: "spawn", phase: "starting", claimId: run.claimId, stickyKey, ...action.worktree ? { directory } : {} });
10785
+ const spawned = await deps.spawn({ directory, agent, permissionMode, spawnedBy: AUTOMATION_SPAWNED_BY, environmentVariables: env });
10786
+ if (spawned.type !== "success") {
10787
+ await failNow(claimed, "spawn", spawned.type === "error" ? spawned.errorMessage : "Directory approval required", "spawn_failed");
10788
+ return;
10789
+ }
10790
+ persist({ runId: run.id, automationId: automation.id, kind: "spawn", phase: "running", claimId: run.claimId, sessionId: spawned.sessionId, stickyKey, ...action.worktree ? { directory } : {} });
10791
+ const item = track(claimed, "spawn", spawned.sessionId);
10792
+ try {
10793
+ const capabilities = readPersistedSessions()[spawned.sessionId]?.metadata?.capabilities ?? null;
10794
+ const meta = resolveFirstMessageMeta({ agent, permissionMode, explicitModel: action.model ?? void 0, capabilities });
10795
+ await send(spawned.sessionId, `${header}
10796
+ ${action.prompt}`, { localId: `automation-${run.id}`, ...meta });
10797
+ } catch (error) {
10798
+ 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" });
10799
+ return;
10800
+ }
10801
+ await reportRunning(item, spawned.sessionId, stickyKey);
10802
+ }
10803
+ async function monitor() {
10804
+ for (const item of [...tracked.values()]) {
10805
+ if (closed) return;
10806
+ if (item.pendingFinal) {
10807
+ await finish(item, item.pendingFinal);
10808
+ continue;
10809
+ }
10810
+ if (item.kind === "script") continue;
10811
+ const sessionId = item.sessionId;
10812
+ const active = deps.turnActive(sessionId);
10813
+ if (active) item.sawTurn = true;
10814
+ const alive = deps.live(sessionId) && !item.exited;
10815
+ if (!alive) {
10816
+ const turn2 = await readTurn(sessionId, item.runId).catch(() => null);
10817
+ if (turn2?.ended && turn2.status !== "failed" && turn2.status !== "cancelled") await finish(item, { claimId: item.claimId, status: "done", sessionId, summary: clipText(turn2.answer, AUTOMATION_SUMMARY_MAX_CHARS) });
10818
+ else await finish(item, { claimId: item.claimId, status: "failed", sessionId, error: turn2?.error ?? "Session exited before the turn ended", needsAttention: true, attentionReason: "session_exited" });
10819
+ continue;
10820
+ }
10821
+ const edgeFell = item.sawTurn && !active;
10822
+ if (!edgeFell && now() < item.nextLogReadAt) continue;
10823
+ item.nextLogReadAt = now() + AUTOMATION_LOG_READ_MS;
10824
+ if (edgeFell) item.sawTurn = false;
10825
+ const turn = await readTurn(sessionId, item.runId).catch(() => null);
10826
+ if (!turn?.ended) continue;
10827
+ 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}` });
10828
+ else await finish(item, { claimId: item.claimId, status: "done", sessionId, summary: clipText(turn.answer, AUTOMATION_SUMMARY_MAX_CHARS) });
10829
+ }
10830
+ }
10831
+ async function renewAll() {
10832
+ if (renewing || closed) return;
10833
+ renewing = true;
10834
+ try {
10835
+ const at = now();
10836
+ for (const item of [...tracked.values()]) {
10837
+ if (closed) return;
10838
+ if (at - item.lastLeaseAt < AUTOMATION_LEASE_RENEW_MS) continue;
10839
+ item.lastLeaseAt = at;
10840
+ const body = { claimId: item.claimId, leaseMs: AUTOMATION_DEFAULT_LEASE_MS, ...item.pendingRunning ? { status: "running", ...item.pendingRunning } : {} };
10841
+ try {
10842
+ const run = await client.report(item.runId, body);
10843
+ item.pendingRunning = void 0;
10844
+ if (isAutomationRunTerminal(run.status)) settleExternally(item);
10845
+ } catch (error) {
10846
+ if (isSettledByServer(error)) settleExternally(item);
10847
+ else deps.log(`Automation run ${item.runId} lease renewal failed`);
10848
+ }
10849
+ }
10850
+ } finally {
10851
+ renewing = false;
10852
+ }
10853
+ }
10854
+ function settleExternally(item) {
10855
+ if (item.child && scriptAlive(item)) terminateScriptTree(item.child);
10856
+ persist({ runId: item.runId, automationId: item.automationId, kind: item.kind, phase: "finished", claimId: item.claimId, sessionId: item.sessionId });
10857
+ tracked.delete(item.runId);
10858
+ }
10859
+ async function recover() {
10860
+ recovered = true;
10861
+ let receipts;
10862
+ try {
10863
+ receipts = readReceipts(home);
10864
+ } catch {
10865
+ deps.log("Automation receipts unreadable; refusing to execute until repaired");
10866
+ closed = true;
10867
+ return;
10868
+ }
10869
+ for (const receipt of Object.values(receipts)) {
10870
+ if (receipt.phase === "finished") continue;
10871
+ const item = track({ run: { id: receipt.runId, claimId: receipt.claimId }, automation: { id: receipt.automationId } }, receipt.kind, receipt.sessionId);
10872
+ item.sawTurn = true;
10873
+ item.lastLeaseAt = 0;
10874
+ 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" });
10875
+ 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" });
10876
+ }
10877
+ }
10878
+ async function tick() {
10879
+ if (running || closed) return;
10880
+ running = true;
10881
+ try {
10882
+ if (!recovered) await recover();
10883
+ if (closed) return;
10884
+ await monitor();
10885
+ const capacity = AUTOMATION_MAX_CONCURRENT - tracked.size;
10886
+ if (now() >= disabledUntil && capacity > 0) {
10887
+ try {
10888
+ const claimed = await client.claim({ machineId: deps.machineId, limit: capacity, leaseMs: AUTOMATION_DEFAULT_LEASE_MS });
10889
+ 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)}`))));
10890
+ } catch (error) {
10891
+ if (isAutomationsUnavailable(error)) {
10892
+ disabledUntil = now() + 6e4;
10893
+ if (now() - lastUnavailableLogAt >= AUTOMATION_UNAVAILABLE_LOG_MS) {
10894
+ lastUnavailableLogAt = now();
10895
+ deps.log("Automations unavailable on this server (404); claim loop idle");
10896
+ }
10897
+ } else {
10898
+ disabledUntil = now() + 1e4;
10899
+ deps.log(`Automation claim failed${error instanceof AutomationsRequestError ? ` (${error.status})` : ""}`);
10900
+ }
10901
+ }
10902
+ }
10903
+ } finally {
10904
+ running = false;
10905
+ }
10906
+ }
10907
+ function report(sessionId, event) {
10908
+ for (const item of tracked.values()) {
10909
+ if (item.sessionId !== sessionId) continue;
10910
+ if (event === "exited") item.exited = true;
10911
+ if (event === "blocked" && !item.attentionReported) {
10912
+ item.attentionReported = true;
10913
+ void client.report(item.runId, { claimId: item.claimId, needsAttention: true, attentionReason: "needs_input" }).catch(() => deps.log(`Automation run ${item.runId} attention report failed`));
10914
+ }
10915
+ }
10916
+ }
10917
+ const tickMs = deps.tickMs ?? AUTOMATION_TICK_MS;
10918
+ const timer = setInterval(() => {
10919
+ void tick();
10920
+ }, tickMs);
10921
+ timer.unref();
10922
+ const leaseTimer = setInterval(() => {
10923
+ void renewAll();
10924
+ }, tickMs);
10925
+ leaseTimer.unref();
10926
+ void tick();
10927
+ return {
10928
+ tick,
10929
+ renewAll,
10930
+ report,
10931
+ hasSession: (id) => [...tracked.values()].some((item) => item.sessionId === id),
10932
+ trackedRunIds: () => [...tracked.keys()],
10933
+ /** Executing a claim, or a script child is still alive — handover and auto-update wait for this. */
10934
+ get busy() {
10935
+ return running || [...tracked.values()].some(scriptAlive);
10936
+ },
10937
+ /**
10938
+ * Stop the loops. `killScripts` (real shutdown) terminates live script
10939
+ * trees; the next daemon's recovery reports them failed + attention from
10940
+ * the receipt, never re-running them. A handover waits on `busy` instead.
10941
+ */
10942
+ stop: (options = {}) => {
10943
+ closed = true;
10944
+ clearInterval(timer);
10945
+ clearInterval(leaseTimer);
10946
+ if (options.killScripts) {
10947
+ for (const item of tracked.values()) if (item.child && scriptAlive(item)) terminateScriptTree(item.child);
10948
+ }
10949
+ }
10950
+ };
10951
+ }
10952
+
10309
10953
  function daemonExitCode(source) {
10310
10954
  return source === "exception" ? 1 : 0;
10311
10955
  }
@@ -10466,6 +11110,7 @@ async function startDaemon() {
10466
11110
  const turnActivity = new TurnActivityTracker();
10467
11111
  const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
10468
11112
  let teamWorker;
11113
+ let automationRunner;
10469
11114
  const persisted = readPersistedSessions();
10470
11115
  const liveHappyProcesses = await findAllHappyProcesses();
10471
11116
  for (const [id, s] of Object.entries(persisted)) {
@@ -11320,6 +11965,7 @@ async function startDaemon() {
11320
11965
  const onChildExited = (pid) => {
11321
11966
  const session = pidToTrackedSession.get(pid);
11322
11967
  if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
11968
+ if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
11323
11969
  if (session?.happySessionId) turnActivity.forget(session.happySessionId);
11324
11970
  if (session?.happySessionId && session.encryption) {
11325
11971
  sessionIdToFinishedSession.set(session.happySessionId, session);
@@ -11339,6 +11985,7 @@ async function startDaemon() {
11339
11985
  }
11340
11986
  };
11341
11987
  const onSessionStateEvent = (sessionId, event, spawnedByFromSession) => {
11988
+ if (automationRunner?.hasSession(sessionId)) automationRunner.report(sessionId, event === "completed" ? "idle" : "blocked");
11342
11989
  if (spawnedByFromSession === "teams" || teamWorker?.hasSession(sessionId) || [...pidToTrackedSession.values()].some((s) => s.happySessionId === sessionId && s.spawnedBy === "teams")) {
11343
11990
  teamWorker?.report(sessionId, event === "completed" ? "idle" : "blocked");
11344
11991
  return;
@@ -11577,7 +12224,7 @@ async function startDaemon() {
11577
12224
  } catch {
11578
12225
  }
11579
12226
  }
11580
- if (bundleReplaced && !teamWorker?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn()) {
12227
+ if (bundleReplaced && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn()) {
11581
12228
  const preflight = await preflightNewBundle(bundlePath);
11582
12229
  if (preflight.action === "hold") {
11583
12230
  if (lastHandoverHold !== preflight.reason) {
@@ -11587,10 +12234,11 @@ async function startDaemon() {
11587
12234
  updateController.holdHandover(preflight.reason);
11588
12235
  return;
11589
12236
  }
11590
- if (teamWorker?.busy || updateController.isRunning() || turnActivity.hasActiveTurn()) return;
12237
+ if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn()) return;
11591
12238
  lastHandoverHold = null;
11592
12239
  logger.debug("[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
11593
12240
  teamWorker?.stop();
12241
+ automationRunner?.stop();
11594
12242
  clearInterval(restartOnStaleVersionAndHeartbeat);
11595
12243
  clearInterval(cliUpdateInterval);
11596
12244
  stopAgentVersionChecks();
@@ -11642,6 +12290,14 @@ async function startDaemon() {
11642
12290
  live: (id) => [...pidToTrackedSession.values()].some((s) => s.happySessionId === id && isPidAlive(s.pid)),
11643
12291
  log: (message) => logger.debug(`[DAEMON RUN] ${message}`)
11644
12292
  });
12293
+ automationRunner = createAutomationRunner({
12294
+ machineId,
12295
+ token: credentials.token,
12296
+ spawn: spawnSession,
12297
+ live: (id) => [...pidToTrackedSession.values()].some((s) => s.happySessionId === id && isPidAlive(s.pid)),
12298
+ turnActive: (id) => turnActivity.activeSessions().includes(id),
12299
+ log: (message) => logger.debug(`[DAEMON RUN] ${message}`)
12300
+ });
11645
12301
  void apiMachine.updateMachineMetadata((current) => ({
11646
12302
  ...current ?? initialMachineMetadata,
11647
12303
  teamsVersion: 1,
@@ -11650,6 +12306,7 @@ async function startDaemon() {
11650
12306
  const cleanupAndShutdown = async (source, errorMessage) => {
11651
12307
  logger.debug(`[DAEMON RUN] Starting proper cleanup (source: ${source}, errorMessage: ${errorMessage})...`);
11652
12308
  teamWorker?.stop();
12309
+ automationRunner?.stop({ killScripts: true });
11653
12310
  if (restartOnStaleVersionAndHeartbeat) {
11654
12311
  clearInterval(restartOnStaleVersionAndHeartbeat);
11655
12312
  logger.debug("[DAEMON RUN] Health check interval cleared");
@@ -12154,6 +12811,7 @@ function createMcpServer(handlers, options, sessionId) {
12154
12811
  version: "1.0.0"
12155
12812
  });
12156
12813
  registerTeamsTools(mcp, sessionId);
12814
+ registerAutomationTools(mcp);
12157
12815
  mcp.registerTool("change_title", {
12158
12816
  description: "Change the title of the current chat session",
12159
12817
  title: "Change Chat Title",
@@ -12396,6 +13054,7 @@ async function startHappyServer(client, options) {
12396
13054
  PREVIEW_TOOL_NAME,
12397
13055
  REPORT_PROGRESS_TOOL_NAME,
12398
13056
  ...TEAM_TOOL_NAMES,
13057
+ ...AUTOMATION_TOOL_NAMES,
12399
13058
  ...options?.assistant && !process.env.VH_TEAM_SCOPE_FILE ? ASSISTANT_TOOL_NAMES : []
12400
13059
  ],
12401
13060
  stop: () => {
@@ -12732,7 +13391,7 @@ function registerSideQuestionHandler(rpc, deps) {
12732
13391
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
12733
13392
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
12734
13393
  const fork = deps.run ?? (async (input) => {
12735
- const { query } = await import('./index-DYQgcgL2.mjs');
13394
+ const { query } = await import('./index-CN7OF2cU.mjs');
12736
13395
  return runSideQuestion(query, input);
12737
13396
  });
12738
13397
  const run = async (input, live) => {
@@ -18020,7 +18679,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18020
18679
  return;
18021
18680
  } else if (subcommand === "install-terminal-hooks") {
18022
18681
  try {
18023
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-Ch_yTtEA.mjs');
18682
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-VvmlO6Ue.mjs');
18024
18683
  const command = parseTerminalHooksArgs(args.slice(1));
18025
18684
  if (command.action === "help") {
18026
18685
  console.log(TERMINAL_HOOKS_HELP);
@@ -18037,7 +18696,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18037
18696
  }
18038
18697
  } else if (subcommand === "agent-home") {
18039
18698
  try {
18040
- const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-CeFDNzvq.mjs');
18699
+ const { parseAgentHomeArgs, runAgentHomeCommand } = await import('./agentHome-CCYfs8Ec.mjs');
18041
18700
  await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
18042
18701
  process.exit(0);
18043
18702
  } catch (error) {
@@ -18046,7 +18705,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18046
18705
  }
18047
18706
  } else if (subcommand === "spawn") {
18048
18707
  try {
18049
- const { handleSpawnCommand } = await import('./spawn-DxHIIqhi.mjs');
18708
+ const { handleSpawnCommand } = await import('./spawn-BOlb5y1_.mjs');
18050
18709
  await handleSpawnCommand(args.slice(1));
18051
18710
  } catch (error) {
18052
18711
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18058,7 +18717,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18058
18717
  return;
18059
18718
  } else if (subcommand === "sessions") {
18060
18719
  try {
18061
- const { handleSessionsCommand } = await import('./sessions-DlU3foY_.mjs');
18720
+ const { handleSessionsCommand } = await import('./sessions-DXJEcUg6.mjs');
18062
18721
  await handleSessionsCommand(args.slice(1));
18063
18722
  } catch (error) {
18064
18723
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18070,7 +18729,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18070
18729
  return;
18071
18730
  } else if (subcommand === "send") {
18072
18731
  try {
18073
- const { handleSendCommand } = await import('./send-o6WM-1G8.mjs');
18732
+ const { handleSendCommand } = await import('./send-C44kcXz5.mjs');
18074
18733
  await handleSendCommand(args.slice(1));
18075
18734
  } catch (error) {
18076
18735
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18176,9 +18835,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18176
18835
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
18177
18836
  const projectId = args[3];
18178
18837
  try {
18179
- const { saveGoogleCloudProjectToConfig } = await import('./config-C-A1XF62.mjs');
18180
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-DS-bnYYp.mjs').then(function (n) { return n.Y; });
18181
- const { ApiClient: ApiClient2 } = await import('./types-BN3eiipi.mjs').then(function (n) { return n.at; });
18838
+ const { saveGoogleCloudProjectToConfig } = await import('./config-DbKpQpdL.mjs');
18839
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-CKR_HJnO.mjs').then(function (n) { return n.al; });
18840
+ const { ApiClient: ApiClient2 } = await import('./types-xKrxbCRv.mjs').then(function (n) { return n.ah; });
18182
18841
  let userEmail = void 0;
18183
18842
  try {
18184
18843
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -18209,7 +18868,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18209
18868
  }
18210
18869
  if (geminiSubcommand === "project" && args[2] === "get") {
18211
18870
  try {
18212
- const { readGeminiLocalConfig } = await import('./config-C-A1XF62.mjs');
18871
+ const { readGeminiLocalConfig } = await import('./config-DbKpQpdL.mjs');
18213
18872
  const config = readGeminiLocalConfig();
18214
18873
  if (config.googleCloudProject) {
18215
18874
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -18249,7 +18908,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18249
18908
  process.exit(0);
18250
18909
  }
18251
18910
  try {
18252
- const { runGemini } = await import('./runGemini-DvJwQVGp.mjs');
18911
+ const { runGemini } = await import('./runGemini-D5N11LxO.mjs');
18253
18912
  let startedBy = void 0;
18254
18913
  for (let i = 1; i < args.length; i++) {
18255
18914
  if (args[i] === "--started-by") {
@@ -18272,7 +18931,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18272
18931
  } else if (subcommand === "pi") {
18273
18932
  if (args[1] === "--terminal") {
18274
18933
  try {
18275
- const { runPiTerminal } = await import('./piTerminal-M677UP7y.mjs');
18934
+ const { runPiTerminal } = await import('./piTerminal-BLSXK1u2.mjs');
18276
18935
  process.exitCode = await runPiTerminal(args.slice(2));
18277
18936
  } catch (error) {
18278
18937
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18289,7 +18948,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18289
18948
  }
18290
18949
  };
18291
18950
  try {
18292
- const { runAcp, resolveAcpAgentConfig } = await import('./index-DEp6CmmS.mjs');
18951
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-DahaLbu4.mjs');
18293
18952
  const parsed = parsePiRunnerArgs(args.slice(1));
18294
18953
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
18295
18954
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -18317,7 +18976,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18317
18976
  return;
18318
18977
  } else if (subcommand === "acp") {
18319
18978
  try {
18320
- const { runAcp, resolveAcpAgentConfig } = await import('./index-DEp6CmmS.mjs');
18979
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-DahaLbu4.mjs');
18321
18980
  let startedBy = void 0;
18322
18981
  let verbose = false;
18323
18982
  const acpArgs = [];
@@ -18357,7 +19016,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18357
19016
  return;
18358
19017
  } else if (subcommand === "openclaw") {
18359
19018
  try {
18360
- const { runOpenClaw } = await import('./runOpenClaw-BG82bF18.mjs');
19019
+ const { runOpenClaw } = await import('./runOpenClaw-CCpyKbrn.mjs');
18361
19020
  let startedBy = void 0;
18362
19021
  let verbose = false;
18363
19022
  let gatewayUrl;
@@ -18408,7 +19067,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18408
19067
  return;
18409
19068
  } else if (subcommand === "todo") {
18410
19069
  try {
18411
- const { handleTodoCommand } = await import('./todo-BhJbG5eq.mjs');
19070
+ const { handleTodoCommand } = await import('./todo-nzsqGZqp.mjs');
18412
19071
  await handleTodoCommand(args.slice(1));
18413
19072
  } catch (error) {
18414
19073
  console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
@@ -18417,16 +19076,28 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18417
19076
  return;
18418
19077
  } else if (subcommand === "teams") {
18419
19078
  try {
18420
- const { handleTeamsCommand } = await import('./teams-DoAA_CcN.mjs');
19079
+ const { handleTeamsCommand } = await import('./teams-Bs8lI887.mjs');
18421
19080
  await handleTeamsCommand(args.slice(1));
18422
19081
  } catch (error) {
18423
19082
  console.error(error instanceof Error ? error.message : "Teams command failed");
18424
19083
  process.exitCode = 1;
18425
19084
  }
18426
19085
  return;
19086
+ } else if (subcommand === "auto") {
19087
+ try {
19088
+ const { handleAutoCommand } = await import('./auto-DV5emt1-.mjs');
19089
+ await handleAutoCommand(args.slice(1));
19090
+ } catch (error) {
19091
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
19092
+ if (process.env.DEBUG) {
19093
+ console.error(error);
19094
+ }
19095
+ process.exitCode = 1;
19096
+ }
19097
+ return;
18427
19098
  } else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
18428
19099
  try {
18429
- const { handleMcpCommand } = await import('./mcp-zu8n8TkR.mjs');
19100
+ const { handleMcpCommand } = await import('./mcp-D4dAo5BQ.mjs');
18430
19101
  await handleMcpCommand(args[1] === "--terminal-tools");
18431
19102
  } catch (error) {
18432
19103
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -18660,6 +19331,8 @@ ${chalk.bold("Usage:")}
18660
19331
  conversations on this machine (agent-home --help)
18661
19332
  very-happy connect Connect AI vendor API keys
18662
19333
  very-happy todo Built-in Todos and official agent skill (todo --help)
19334
+ very-happy auto Scheduled / triggered automations: spawn, sticky
19335
+ sessions or scripts with tracked runs (auto --help)
18663
19336
  very-happy sandbox Configure and manage OS-level sandboxing
18664
19337
  very-happy notify Send push notification
18665
19338
  very-happy mcp Stdio MCP server exposing copy_to_clipboard for a
@@ -18794,4 +19467,4 @@ ${chalk.bold("Examples:")}
18794
19467
  }
18795
19468
  }
18796
19469
 
18797
- export { ALLOWED_SPAWN_PERMISSION_MODES as A, TitleGenerator as B, registerKillSessionHandler as C, DEFAULT_RECENT_LIMIT as D, CLAUDE_ATTACHMENT_KINDS as E, stageClaudeAttachments as F, GEMINI_MODEL_ENV as G, appendStagedAttachmentsToPrompt as H, BasePermissionHandler as I, writeSessionModeFile as J, normalizeAcpPermissionMode as K, removeSessionModeFile as L, MAX_READ_LIMIT as M, TEAM_SKILL as N, GEMINI_API_KEY_ENV as O, PI_TEAMS_EXTENSION as P, GOOGLE_API_KEY_ENV as Q, BaseReasoningProcessor as R, SPAWN_AGENTS as S, TurnWaitTimeoutError as T, MessageBuffer as U, CHANGE_TITLE_INSTRUCTION as V, listSessions as a, resolvePermissionRequest as b, archiveSession as c, sendUserMessage as d, sessionWebUrl as e, DEFAULT_GEMINI_MODEL as f, checkPreviewPath as g, registerAssistantSessionTools as h, readSessionMetadata as i, isValidSpawnOrigin as j, sanitizeSpawnPermissionMode as k, listAccountSessions as l, waitForSessionKey as m, initialMachineMetadata as n, createSessionMetadata as o, setupOfflineReconnection as p, query as q, readSessionTranscript as r, stopSession as s, tmuxSupportsSessionEnv as t, StreamRelay as u, MessageQueue2 as v, waitForTurnEnd as w, hashObject as x, startHappyServer as y, registerAgentAttachmentDownloads as z };
19470
+ export { CHANGE_TITLE_INSTRUCTION as $, ALLOWED_SPAWN_PERMISSION_MODES as A, createSessionMetadata as B, setupOfflineReconnection as C, DEFAULT_RECENT_LIMIT as D, StreamRelay as E, MessageQueue2 as F, GEMINI_MODEL_ENV as G, hashObject as H, startHappyServer as I, registerAgentAttachmentDownloads as J, TitleGenerator as K, registerKillSessionHandler as L, MAX_READ_LIMIT as M, CLAUDE_ATTACHMENT_KINDS as N, stageClaudeAttachments as O, PI_TEAMS_EXTENSION as P, appendStagedAttachmentsToPrompt as Q, BasePermissionHandler as R, SPAWN_AGENTS as S, TurnWaitTimeoutError as T, writeSessionModeFile as U, normalizeAcpPermissionMode as V, removeSessionModeFile as W, GEMINI_API_KEY_ENV as X, GOOGLE_API_KEY_ENV as Y, BaseReasoningProcessor as Z, MessageBuffer as _, listSessions as a, resolvePermissionRequest as b, archiveSession as c, sendUserMessage as d, sessionWebUrl as e, DEFAULT_GEMINI_MODEL as f, describeTrigger as g, describeAction as h, sanitizeSpawnPermissionMode as i, tmuxSupportsSessionEnv as j, readSessionMetadata as k, listAccountSessions as l, resolveSpawnPermissionMode as m, resolveFirstMessageMeta as n, isValidSpawnOrigin as o, parseDuration as p, query as q, readSessionTranscript as r, stopSession as s, triggerFromFlags as t, waitForSessionKey as u, checkPreviewPath as v, waitForTurnEnd as w, registerAssistantSessionTools as x, TEAM_SKILL as y, initialMachineMetadata as z };