very-happy-cli 0.2.126 → 0.2.128

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 (47) hide show
  1. package/dist/{AcpBackend-C9eu6CDu.cjs → AcpBackend-BYU6J2n0.cjs} +2 -2
  2. package/dist/{AcpBackend-BzGpBAZ0.mjs → AcpBackend-D7987ody.mjs} +2 -2
  3. package/dist/{AcpSessionManager-B223yT78.mjs → AcpSessionManager-BwrjVZyn.mjs} +1 -1
  4. package/dist/{AcpSessionManager-DQ1e_0gc.cjs → AcpSessionManager-artXJgqb.cjs} +1 -1
  5. package/dist/{agentGuidance-Tm8Z5HYp.cjs → agentGuidance-B1xYgHx6.cjs} +63 -33
  6. package/dist/{agentGuidance-BR0OFumQ.mjs → agentGuidance-BPGPaNlo.mjs} +62 -35
  7. package/dist/codex/happyMcpStdioBridge.cjs +2 -2
  8. package/dist/codex/happyMcpStdioBridge.mjs +2 -2
  9. package/dist/{config-Bjd1nG0P.mjs → config-DSBE0Az0.mjs} +4 -4
  10. package/dist/{config-C0mofvCH.cjs → config-Dmkzs3o4.cjs} +4 -4
  11. package/dist/{index-BP56asuS.mjs → index-35FahgEU.mjs} +4 -4
  12. package/dist/{index-HW-KdG6W.cjs → index-CJmXLSQc.cjs} +4 -4
  13. package/dist/{index-D4eCRu9x.mjs → index-CfM6ewd8.mjs} +249 -27
  14. package/dist/{index-Cw9Zj1Zr.cjs → index-D_YVlpsW.cjs} +254 -30
  15. package/dist/{index-CV6sAa4a.mjs → index-m0Dnccc_.mjs} +13 -9
  16. package/dist/{index-CMbKpntk.cjs → index-vyTJsT_R.cjs} +13 -9
  17. package/dist/index.cjs +4 -4
  18. package/dist/index.mjs +4 -4
  19. package/dist/{installTerminalHooks-CNp1LExn.cjs → installTerminalHooks-C1zwiJJ2.cjs} +4 -4
  20. package/dist/{installTerminalHooks-D-CNSalI.mjs → installTerminalHooks-ClrFe2fA.mjs} +4 -4
  21. package/dist/lib.cjs +2 -2
  22. package/dist/lib.d.cts +1 -0
  23. package/dist/lib.d.mts +1 -0
  24. package/dist/lib.mjs +2 -2
  25. package/dist/{limits-DlbYYtOw.mjs → limits-BNTLh2lw.mjs} +9 -9
  26. package/dist/{limits-DG1OQnCb.cjs → limits-CrwtFbi8.cjs} +7 -7
  27. package/dist/{mcp-BtZoIIIM.cjs → mcp-Bc_mmJt8.cjs} +4 -4
  28. package/dist/{mcp-D3oV_dH-.mjs → mcp-BsohER5A.mjs} +4 -4
  29. package/dist/{runGemini-BfCSOPL1.cjs → runGemini-B2GesRWm.cjs} +6 -6
  30. package/dist/{runGemini-BJ8bU1pP.mjs → runGemini-B9S85Vms.mjs} +6 -6
  31. package/dist/{runOpenClaw-DTgOpJnO.mjs → runOpenClaw-Byhq-B1y.mjs} +5 -5
  32. package/dist/{runOpenClaw-CBlHXEAT.cjs → runOpenClaw-DCK-R8qe.cjs} +5 -5
  33. package/dist/{send-CDK1bZFj.cjs → send-CPdT1nOg.cjs} +4 -4
  34. package/dist/{send-9duZlpGX.mjs → send-DQS1zNWp.mjs} +4 -4
  35. package/dist/{sessions-CNkLCorF.cjs → sessions-DC0IKaD9.cjs} +4 -4
  36. package/dist/{sessions-jAFOkKvW.mjs → sessions-DIOdWnPe.mjs} +4 -4
  37. package/dist/{spawn-BG5oHi_f.mjs → spawn-DV4pIAHZ.mjs} +4 -4
  38. package/dist/{spawn-J-A5WGZ7.cjs → spawn-DrnCW2QK.cjs} +4 -4
  39. package/dist/{teams-MgWeyutG.cjs → teams-CwXEJs9I.cjs} +41 -6
  40. package/dist/{teams-Cuqsk7Ty.mjs → teams-DlTHOiCk.mjs} +38 -3
  41. package/dist/todo-BwXURAbQ.mjs +128 -0
  42. package/dist/todo-DrME15xq.cjs +132 -0
  43. package/dist/{types-n_VWu9qe.mjs → types-CFs8FmiY.mjs} +318 -6
  44. package/dist/{types-P5deH8Ls.cjs → types-DfatCoQL.cjs} +327 -12
  45. package/package.json +7 -7
  46. package/dist/resources-CVl7NdOB.cjs +0 -83
  47. package/dist/resources-D-mn7ptO.mjs +0 -80
@@ -1,13 +1,13 @@
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, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
4
- import { g as getProjectPath, p as projectPath, h as initializeSandbox, w as wrapCommand, b as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, d as delay, j as AsyncLock, S as STREAM_DELTA_MAX_CHARS, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, k as commandOnPath, l as deriveLocalCliUpdateSummary, m as isClaudeSessionId, n as getLocalHappyAgentCredentialPath, r as readLocalHappyAgentCredentials, o as parseExactVersion, q as compareExactVersions, t as extractCompleteLines, u as decideBackfill, v as parseTerminalHookPayload, x as tmuxArgs, y as scrubTmuxClientEnv, z as decideMirrorBinding, B as MIRROR_BACKFILL_LINES_DEFAULT, C as mirrorLineKey, D as mirrorLocalId, E as detectCLIAvailability, F as detectResumeSupport, G as summarizeSpawnSessionForLog, H as shellescape, A as ApiClient, J as fetchCliUpdateState, K as resolveCliUpdateCheckInterval, L as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, i as isValidSessionId, N as listVhTerminals, O as isValidTerminalId, P as readVhTerminal, Q as sendToVhTerminal, T as TITLE_PROMPT_PREFIX, U as BOARD_ANALYZER_PROMPT_PREFIX, f as connectionState, V as startOfflineReconnection, c as createEnvelope, W as CodexAppServerClient } from './types-n_VWu9qe.mjs';
5
- import { z as readDaemonState, l as logger, c as configuration, A as clearDaemonState, B as ensurePrivateDirectory, D as hardenPrivateFile, p as packageJson, f as readSettings, E as readCredentials, s as credentialRelayProblem, g as encodeBase64, F as readSettingsOutcome, G as updateSettings, H as encodeBase64Url, m as decodeBase64, I as writeCredentialsLegacy, J as writeCredentialsDataKey, i as readCredentialsForConfiguredRelay, j as encrypt, r as readPersistedSessions, w as writePrivateFileSync, K as decryptWithDataKey, L as decryptLegacy, M as persistSession, k as decrypt, e as ensurePrivateDirectorySync, N as acquireDaemonLock, O as deletePersistedSessions, P as writeDaemonState, Q as writeSettings, R as releaseDaemonLock, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, d as CLIPBOARD_MAX_BYTES, S as clearCredentials, T as clearMachineId, U as SandboxConfigSchema, V as getLatestDaemonLog } from './limits-DlbYYtOw.mjs';
4
+ import { j as getProjectPath, h as BUILTIN_TODO_DISCOVERY, p as projectPath, k as initializeSandbox, w as wrapCommand, d as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, f as delay, l as AsyncLock, S as STREAM_DELTA_MAX_CHARS, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, m as commandOnPath, n as deriveLocalCliUpdateSummary, o as isClaudeSessionId, q as getLocalHappyAgentCredentialPath, r as readLocalHappyAgentCredentials, t as parseExactVersion, u as compareExactVersions, v as extractCompleteLines, x as decideBackfill, y as parseTerminalHookPayload, z as tmuxArgs, C as scrubTmuxClientEnv, D as decideMirrorBinding, E as MIRROR_BACKFILL_LINES_DEFAULT, F as mirrorLineKey, G as mirrorLocalId, H as detectCLIAvailability, J as detectResumeSupport, K as summarizeSpawnSessionForLog, L as shellescape, A as ApiClient, N as fetchCliUpdateState, O as resolveCliUpdateCheckInterval, P as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, i as isValidSessionId, Q as listVhTerminals, T as isValidTerminalId, U as readVhTerminal, V as sendToVhTerminal, W as TITLE_PROMPT_PREFIX, X as BOARD_ANALYZER_PROMPT_PREFIX, g as connectionState, Y as startOfflineReconnection, c as createEnvelope, Z as CodexAppServerClient } from './types-CFs8FmiY.mjs';
5
+ import { z as readDaemonState, l as logger, c as configuration, A as clearDaemonState, B as ensurePrivateDirectory, D as hardenPrivateFile, p as packageJson, g as readSettings, E as readCredentials, s as credentialRelayProblem, i as encodeBase64, F as readSettingsOutcome, G as updateSettings, H as encodeBase64Url, m as decodeBase64, I as writeCredentialsLegacy, J as writeCredentialsDataKey, d as readCredentialsForConfiguredRelay, j as encrypt, r as readPersistedSessions, w as writePrivateFileSync, K as decryptWithDataKey, L as decryptLegacy, M as persistSession, k as decrypt, f as ensurePrivateDirectorySync, N as acquireDaemonLock, O as deletePersistedSessions, P as writeDaemonState, Q as writeSettings, R as releaseDaemonLock, C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, e as CLIPBOARD_MAX_BYTES, S as clearCredentials, T as clearMachineId, U as SandboxConfigSchema, V as getLatestDaemonLog } from './limits-BNTLh2lw.mjs';
6
6
  import spawn$1, { spawn } from 'cross-spawn';
7
7
  import path, { join, resolve, basename, isAbsolute, dirname, sep, delimiter, normalize } from 'node:path';
8
8
  import { createInterface } from 'node:readline';
9
9
  import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, unlinkSync, renameSync, writeFileSync, accessSync, constants, openSync, fsyncSync, closeSync, rmSync as rmSync$1, chmodSync as chmodSync$1 } from 'node:fs';
10
- import { d as trimIdent, A as AGENT_GUIDANCE, P as PREVIEW_TOOL_NAME, R as REPORT_PROGRESS_TOOL_NAME, T as TEAM_TOOL_NAMES, r as registerTeamsTools, a as PREVIEW_TOOL_TITLE, b as PREVIEW_TOOL_DESCRIPTION, e as REPORT_PROGRESS_TOOL_TITLE, f as REPORT_PROGRESS_TOOL_DESCRIPTION } from './agentGuidance-BR0OFumQ.mjs';
10
+ 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-BPGPaNlo.mjs';
11
11
  import { readFile, writeFile, rename, unlink, mkdir, symlink, rm, readlink, lstat, stat, open as open$1, appendFile } from 'node:fs/promises';
12
12
  import fs, { watch, access } from 'fs/promises';
13
13
  import { useStdout, useInput, Box, Text, render } from 'ink';
@@ -647,7 +647,7 @@ function shouldIncludeCoAuthoredBy() {
647
647
 
648
648
  const BASE_SYSTEM_PROMPT = (() => trimIdent(`
649
649
  ALWAYS when you start a new chat - you must call a tool "mcp__happy__change_title" to set a chat title. When you think chat title is not relevant anymore - call the tool again to change it. When chat name is too generic and you have a change to make it more specific - call the tool again to change it. This title is needed to easily find the chat in the future. Help human.
650
- `))() + "\n\n" + AGENT_GUIDANCE;
650
+ `))() + "\n\n" + AGENT_GUIDANCE + "\n\n" + BUILTIN_TODO_DISCOVERY;
651
651
  const CO_AUTHORED_CREDITS = (() => trimIdent(`
652
652
  When making commit messages, instead of just giving co-credit to Claude, also give credit to Happy like so:
653
653
 
@@ -4742,6 +4742,7 @@ function createSessionMetadata(opts) {
4742
4742
  version: packageJson.version,
4743
4743
  os: os.platform(),
4744
4744
  machineId: opts.machineId,
4745
+ ...opts.teamOperationId ? { teamOperationId: opts.teamOperationId } : {},
4745
4746
  homeDir: os.homedir(),
4746
4747
  happyHomeDir: configuration.happyHomeDir,
4747
4748
  happyLibDir: projectPath(),
@@ -5831,7 +5832,8 @@ async function sendUserMessage(sessionId, persisted, text, client, options = {})
5831
5832
  text
5832
5833
  },
5833
5834
  meta: {
5834
- sentFrom: options.sentFrom ?? "cli"
5835
+ sentFrom: options.sentFrom ?? "cli",
5836
+ ...options.model ? { model: options.model } : {}
5835
5837
  }
5836
5838
  };
5837
5839
  const encrypted = encodeBase64(encrypt(
@@ -8800,6 +8802,100 @@ async function claimSessionOrExit(happySessionId, options) {
8800
8802
  process.on("exit", () => releaseSessionLock(happySessionId));
8801
8803
  }
8802
8804
 
8805
+ const TEAM_SKILL = `---
8806
+ name: very-happy-teams
8807
+ description: Coordinate coding agents through Very Happy teams: delegate tasks, exchange messages, review results, and recover interrupted work. Use when asked to organize or participate in an agent team.
8808
+ ---
8809
+
8810
+ # Very Happy Agent Teams
8811
+
8812
+ Use the team_* tools in a managed Very Happy session. If absent, explain that this host needs Very Happy Teams setup; reading this skill alone does not establish a connection.
8813
+
8814
+ Start with team_inspect if already assigned. Otherwise team_create or team_join connects the current session. A scoped worker cannot create another root identity or use account-wide tools to bypass its scope.
8815
+
8816
+ Work yourself when delegation would add needless overhead. Delegate independent work with a clear goal and acceptance criteria. When delegating within your assignment, pass its parentTaskId. Members may delegate further within their existing authority. After delegating child work, wait for those results instead of continuing parallel edits yourself. Waiting parents yield their execution slot to their children; the team limit counts progressing leaf assignments, not OS processes.
8817
+
8818
+ Name exactly one integrator for each team. Workers edit only their assigned worktrees; they must not merge main, publish releases, change global daemon or npm installations, or allocate shared backlog IDs. The integrator serializes these operations. Across teams, the owner coordinates shared resources: a worktree is not a global lock.
8819
+
8820
+ Use a unique requestId for each action and reuse it with identical arguments when the outcome is unknown. Inspect state before retrying; a timeout does not prove no action occurred. A delivered message is not proof its recipient processed it.
8821
+
8822
+ Inspect the current attemptId and goalVersion before team_submit. Include artifact locations and verification evidence. The owner reviews and accepts or returns the result. All children completing does not automatically satisfy the parent goal.
8823
+
8824
+ Acceptance and cleanup are separate. Never delete dirty worktrees, stop user-owned sessions, or bypass permissions to finish cleanup. Use team_cancel or team_handoff and inspect the resulting status. Keep summaries concise; do not repeatedly forward full descendant transcripts.
8825
+
8826
+ A root member may create a persistent schedule for itself with the schedule tools; team_inspect shows schedules. Timely delivery requires an online daemon. A scheduled delivery does not prove the model completed the work. Pause or cancel schedules explicitly when they are no longer needed.
8827
+
8828
+ On restart inspect authoritative state and messages before taking new actions. Do not invent completed work from a previous context. Scope credentials are runtime-owned; never read, print, or copy them into prompts.
8829
+ `;
8830
+ const PI_TEAMS_EXTENSION = `// Managed by Very Happy Agent Teams.
8831
+ export default async function(pi) {
8832
+ const endpoint = process.env.HAPPY_MCP_URL;
8833
+ if (!endpoint) return;
8834
+ const url = new URL(endpoint);
8835
+ if (!['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname) || url.protocol !== 'http:') throw new Error('Very Happy MCP must be local');
8836
+ const {readFile} = await import('node:fs/promises');
8837
+ const {join} = await import('node:path');
8838
+ const {homedir} = await import('node:os');
8839
+ pi.on('tool_call', async (event, ctx) => {
8840
+ let mode = process.env.HAPPY_PERMISSION_MODE || 'default';
8841
+ const sid = process.env.HAPPY_SESSION_ID;
8842
+ if (sid && /^[a-zA-Z0-9_-]+$/.test(sid)) {
8843
+ try { mode = JSON.parse(await readFile(join(process.env.HAPPY_HOME_DIR || join(homedir(), '.happy'), 'session-modes', sid + '.json'), 'utf8')).permissionMode; }
8844
+ catch (error) { if (error.code !== 'ENOENT') mode = 'default'; }
8845
+ }
8846
+ if (mode === 'yolo') mode = 'bypassPermissions';
8847
+ if (!['default','plan','acceptEdits','bypassPermissions'].includes(mode)) mode = 'default';
8848
+ if (mode === 'bypassPermissions') return;
8849
+ if (['read','ls','find','grep','team_inspect','change_title','report_progress'].includes(event.toolName)) return;
8850
+ if (mode === 'plan') return {block:true,reason:'Plan mode allows read-only tools'};
8851
+ if (mode === 'acceptEdits' && ['write','edit'].includes(event.toolName)) return;
8852
+ try {
8853
+ const allowed = await ctx.ui.confirm('Allow ' + event.toolName + '?', JSON.stringify(event.input || {}).slice(0,4000));
8854
+ if (allowed === true) return;
8855
+ } catch {}
8856
+ return {block:true,reason:'Tool requires explicit approval'};
8857
+ });
8858
+ let nextId = 0;
8859
+ async function rpc(method, params) {
8860
+ const requestId = ++nextId;
8861
+ const response = await fetch(endpoint, { method: 'POST', redirect: 'error', headers: {'content-type':'application/json', accept:'application/json, text/event-stream'}, body: JSON.stringify({jsonrpc:'2.0',id:requestId,method,params}), signal: AbortSignal.timeout(30000) });
8862
+ if (!response.ok) throw new Error('Very Happy tools unavailable (HTTP ' + response.status + ')');
8863
+ const body = await response.text();
8864
+ let data;
8865
+ if ((response.headers.get('content-type') || '').includes('text/event-stream')) {
8866
+ for (const event of body.split(/\\r?\\n\\r?\\n/)) {
8867
+ const payload = event.split(/\\r?\\n/).filter(line => line.startsWith('data:')).map(line => line.slice(5).trimStart()).join('\\n');
8868
+ if (payload) { const candidate = JSON.parse(payload); if (candidate.id === requestId) data = candidate; }
8869
+ }
8870
+ } else data = JSON.parse(body);
8871
+ if (!data || data.error) throw new Error('Very Happy tool request failed');
8872
+ return data.result;
8873
+ }
8874
+ await rpc('initialize', {protocolVersion:'2024-11-05',capabilities:{},clientInfo:{name:'very-happy-pi-teams',version:'1'}});
8875
+ const available = await rpc('tools/list', {});
8876
+ for (const tool of available.tools || []) {
8877
+ pi.registerTool({name:tool.name,label:tool.name,description:tool.description || tool.name,parameters:tool.inputSchema,
8878
+ async execute(_id, args) { const result = await rpc('tools/call', {name:tool.name,arguments:args}); return {content:result.content,details:{isError:!!result.isError}}; }
8879
+ });
8880
+ }
8881
+ }
8882
+ `;
8883
+
8884
+ function teamInitialPrompt(goal, root, directory) {
8885
+ const role = root ? "You are the team lead. Inspect your assigned goal, arrange independent work when useful, review member results, integrate changes, and submit the final result for the user to accept. Delegate under your assigned goal using parentTaskId. You must not accept your own root goal." : "You are a team member. Inspect your assigned task and work in your assigned worktree. Delegate within your task when useful, then submit evidence for the lead to review.";
8886
+ return `${TEAM_SKILL}
8887
+
8888
+ ## Your role
8889
+ ${role}
8890
+
8891
+ ## Working directory
8892
+ ${directory ? JSON.stringify(directory) : "Use your current managed session working directory."}
8893
+ Delegate from this working directory unless a different repository was explicitly requested.
8894
+
8895
+ ## Assigned goal
8896
+ ${goal}`;
8897
+ }
8898
+
8803
8899
  const MAX_LINE_CHARS = 500;
8804
8900
  const MAX_ARGS_CHARS = 160;
8805
8901
  function truncateText(text, max = MAX_LINE_CHARS) {
@@ -9381,7 +9477,7 @@ function createTeamWorker(deps) {
9381
9477
  let knownSessionIds = /* @__PURE__ */ new Set();
9382
9478
  const action = async (teamId, requestId, value) => (await http.post(`/v1/teams/${encodeURIComponent(teamId)}/actions`, { requestId, action: value })).data;
9383
9479
  async function complete(op, receipt) {
9384
- await action(op.teamId, `complete-${op.id}`, { type: "complete-operation", operationId: op.id, machineId: deps.machineId, claimId: receipt.claimId, ...receipt.sessionId ? { sessionId: receipt.sessionId } : {} });
9480
+ 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 } : {} });
9385
9481
  writeReceipt(home, { ...receipt, phase: "completed" });
9386
9482
  }
9387
9483
  async function execute(op) {
@@ -9394,7 +9490,7 @@ function createTeamWorker(deps) {
9394
9490
  }
9395
9491
  return;
9396
9492
  }
9397
- const claimed = await action(op.teamId, `claim-${op.id}`, { type: "claim-operation", operationId: op.id, machineId: deps.machineId });
9493
+ const claimed = await action(op.teamId, `claim-${op.id}`, { type: "claim-operation", operationId: op.id, machineId: deps.machineId, ...op.teamLaunchVersion === 1 ? { teamLaunchVersion: 1 } : {} });
9398
9494
  if (!claimed.operation?.claimId) throw new Error("Operation claim did not return a receipt");
9399
9495
  op = claimed.operation;
9400
9496
  credential = claimed.credential;
@@ -9405,7 +9501,7 @@ function createTeamWorker(deps) {
9405
9501
  try {
9406
9502
  if (op.type === "spawn") {
9407
9503
  if (receipt.phase === "prepared") {
9408
- if (!credential) credential = (await action(op.teamId, `claim-${op.id}`, { type: "claim-operation", operationId: op.id, machineId: deps.machineId })).credential;
9504
+ if (!credential) credential = (await action(op.teamId, `claim-${op.id}`, { type: "claim-operation", operationId: op.id, machineId: deps.machineId, ...op.teamLaunchVersion === 1 ? { teamLaunchVersion: 1 } : {} })).credential;
9409
9505
  if (!credential) throw new Error("No scoped credential for worker");
9410
9506
  const current = (await http.get(`/v1/teams/${encodeURIComponent(op.teamId)}`)).data.team;
9411
9507
  const task = current.tasks.find((t) => t.id === op.taskId);
@@ -9445,7 +9541,7 @@ function createTeamWorker(deps) {
9445
9541
  return;
9446
9542
  }
9447
9543
  const key = await waitForSessionKey(receipt.sessionId, 1e4);
9448
- await sendUserMessage(receipt.sessionId, key, op.prompt, "teams", { localId: `teams-initial-${op.id}`, sentFrom: "team" });
9544
+ 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 } : {} });
9449
9545
  receipt = { ...receipt, phase: "delivered" };
9450
9546
  writeReceipt(home, receipt);
9451
9547
  }
@@ -9547,7 +9643,7 @@ Use the teams tools to inspect current task state before acting. This message is
9547
9643
  const status = axios.isAxiosError(error) ? error.response?.status : void 0;
9548
9644
  if (status !== 404) deps.log("Teams schedule advancement unavailable; regular reconciliation continues");
9549
9645
  }
9550
- const { data } = await http.get("/v1/teams/operations", { params: { machineId: deps.machineId, schedulesVersion: 1 } });
9646
+ const { data } = await http.get("/v1/teams/operations", { params: { machineId: deps.machineId, schedulesVersion: 1, teamLaunchVersion: 1 } });
9551
9647
  knownSessionIds = new Set((data.teams ?? []).flatMap((team) => team.bots.flatMap((bot) => bot.sessionId ? [bot.sessionId] : [])));
9552
9648
  for (const op of data.operations) {
9553
9649
  if (closed) break;
@@ -9591,6 +9687,7 @@ const hostSuffix = process.env.HAPPY_VARIANT === "dev" ? "-dev" : "";
9591
9687
  const startupCliAvailability = detectCLIAvailability();
9592
9688
  const initialMachineMetadata = {
9593
9689
  teamsVersion: 1,
9690
+ teamLaunchVersion: 1,
9594
9691
  host: os$1.hostname() + hostSuffix,
9595
9692
  platform: os$1.platform(),
9596
9693
  happyCliVersion: packageJson.version,
@@ -10861,7 +10958,8 @@ async function startDaemon() {
10861
10958
  });
10862
10959
  void apiMachine.updateMachineMetadata((current) => ({
10863
10960
  ...current ?? initialMachineMetadata,
10864
- teamsVersion: 1
10961
+ teamsVersion: 1,
10962
+ teamLaunchVersion: 1
10865
10963
  })).catch(() => logger.debug("[DAEMON RUN] Teams capability publication will retry on reconnect"));
10866
10964
  const cleanupAndShutdown = async (source, errorMessage) => {
10867
10965
  logger.debug(`[DAEMON RUN] Starting proper cleanup (source: ${source}, errorMessage: ${errorMessage})...`);
@@ -10900,6 +10998,113 @@ async function startDaemon() {
10900
10998
  }
10901
10999
  }
10902
11000
 
11001
+ const TEAMS_ADOPT_CAPABILITY = "teams-adopt-v1";
11002
+ const TeamAdoptSchema = z.object({
11003
+ requestId: z.string().min(1).max(128),
11004
+ name: z.string().trim().min(1).max(128),
11005
+ teamId: z.string().min(1).max(128).optional()
11006
+ }).strict();
11007
+ async function retainIntent(path, input) {
11008
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
11009
+ let file;
11010
+ try {
11011
+ file = await open$1(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
11012
+ } catch (error) {
11013
+ if (error.code !== "EEXIST") throw new Error("Cannot retain team connection request");
11014
+ }
11015
+ if (file) {
11016
+ try {
11017
+ await file.writeFile(JSON.stringify(input));
11018
+ await file.sync();
11019
+ } finally {
11020
+ await file.close();
11021
+ }
11022
+ return { fresh: true };
11023
+ }
11024
+ const existing = await open$1(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
11025
+ try {
11026
+ const stat = await existing.stat();
11027
+ if (!stat.isFile() || process.platform !== "win32" && (stat.mode & 63) !== 0 || typeof process.getuid === "function" && stat.uid !== process.getuid()) throw new Error("Unsafe team connection receipt");
11028
+ const stored = JSON.parse(await existing.readFile("utf8"));
11029
+ const { resolvedTeamId, guidanceDelivered, ...request } = stored;
11030
+ const previous = TeamAdoptSchema.parse(request);
11031
+ if (JSON.stringify(previous) !== JSON.stringify(input)) throw new Error("Another team connection request already exists; retry the original request");
11032
+ if (resolvedTeamId !== void 0 && (typeof resolvedTeamId !== "string" || !resolvedTeamId)) throw new Error("Invalid connection receipt");
11033
+ if (guidanceDelivered !== void 0 && typeof guidanceDelivered !== "boolean") throw new Error("Invalid guidance receipt");
11034
+ return { resolvedTeamId, guidanceDelivered };
11035
+ } finally {
11036
+ await existing.close();
11037
+ }
11038
+ }
11039
+ async function retainTeam(path, input, resolvedTeamId, guidanceDelivered = false) {
11040
+ const temporary = `${path}.${randomUUID()}.new`;
11041
+ const file = await open$1(temporary, "wx", 384);
11042
+ try {
11043
+ await file.writeFile(JSON.stringify({ ...input, resolvedTeamId, guidanceDelivered }));
11044
+ await file.sync();
11045
+ } finally {
11046
+ await file.close();
11047
+ }
11048
+ try {
11049
+ await rename(temporary, path);
11050
+ } finally {
11051
+ await unlink(temporary).catch(() => {
11052
+ });
11053
+ }
11054
+ }
11055
+ function createTeamAdoption(sessionId) {
11056
+ const client = createTeamsClient(sessionId);
11057
+ let current;
11058
+ return (raw) => {
11059
+ const parsed = TeamAdoptSchema.safeParse(raw);
11060
+ if (!parsed.success) return { error: "Invalid team connection request" };
11061
+ if (process.env.VH_TEAM_SCOPE_FILE) return { error: "Assigned team members cannot create or join another team" };
11062
+ let path;
11063
+ try {
11064
+ path = `${teamScopePath(sessionId)}.adopt.json`;
11065
+ } catch {
11066
+ return { error: "A managed session identity is required" };
11067
+ }
11068
+ const input = parsed.data;
11069
+ const signature = JSON.stringify(input);
11070
+ if (current) {
11071
+ if (current.signature !== signature && current.safeToChange) current = void 0;
11072
+ else if (current.signature !== signature) return { error: "Another team connection request already exists; retry the original request" };
11073
+ }
11074
+ if (current) {
11075
+ const result = current.result;
11076
+ if (result && "error" in result) current = void 0;
11077
+ return result ?? { status: "pending", requestId: input.requestId };
11078
+ }
11079
+ const operation = { signature };
11080
+ current = operation;
11081
+ void (async () => {
11082
+ let intent;
11083
+ try {
11084
+ intent = await retainIntent(path, input);
11085
+ const connected = await client.initialize(input, { resumeAdoption: true, expectedTeamId: intent.resolvedTeamId, onTeamResolved: (teamId) => retainTeam(path, input, teamId, intent?.guidanceDelivered) });
11086
+ const instructions = `${TEAM_SKILL}
11087
+
11088
+ This session is now connected as the team lead. Call team_inspect to read the authoritative state before continuing the user's goal. Do not call team_create or team_join again.`;
11089
+ if (!intent.guidanceDelivered) {
11090
+ const key = await waitForSessionKey(sessionId, 1e4);
11091
+ await sendUserMessage(sessionId, key, instructions, "teams", { localId: `teams-adopt-${input.requestId}`, sentFrom: "team" });
11092
+ await retainTeam(path, input, connected.team.id, true);
11093
+ }
11094
+ operation.result = { status: "connected", teamId: connected.team.id, botId: connected.botId, instructions };
11095
+ } catch (error) {
11096
+ if (error instanceof TeamConnectionPreflightError && intent?.fresh && !intent.resolvedTeamId) {
11097
+ await unlink(path).catch(() => {
11098
+ });
11099
+ operation.safeToChange = true;
11100
+ }
11101
+ operation.result = { error: error instanceof Error ? error.message : "Team connection failed; retry the same request", ...operation.safeToChange ? { safeToChange: true } : {} };
11102
+ }
11103
+ })();
11104
+ return { status: "pending", requestId: input.requestId };
11105
+ };
11106
+ }
11107
+
10903
11108
  const PERSONAL_MEMORY_SOFT_LIMIT_CHARS = 2e3;
10904
11109
  function headingTitle(line) {
10905
11110
  const m = /^##(?!#)\s*(.*?)\s*$/.exec(line);
@@ -11543,6 +11748,11 @@ async function startHappyServer(client, options) {
11543
11748
  resolve(new URL(`http://127.0.0.1:${addr.port}`));
11544
11749
  });
11545
11750
  });
11751
+ const adoptionEnabled = !process.env.VH_TEAM_SCOPE_FILE;
11752
+ if (adoptionEnabled) {
11753
+ client.rpcHandlerManager.registerHandler("teams-adopt", createTeamAdoption(client.sessionId));
11754
+ client.updateMetadata((metadata) => ({ ...metadata, capabilities: [.../* @__PURE__ */ new Set([...metadata.capabilities ?? [], TEAMS_ADOPT_CAPABILITY])] }));
11755
+ }
11546
11756
  logger.debug(`[happyMCP] server:ready sessionId=${client.sessionId} url=${baseUrl.toString()}`);
11547
11757
  return {
11548
11758
  url: baseUrl.toString(),
@@ -11556,6 +11766,7 @@ async function startHappyServer(client, options) {
11556
11766
  ],
11557
11767
  stop: () => {
11558
11768
  logger.debug(`[happyMCP] server:stop sessionId=${client.sessionId}`);
11769
+ if (adoptionEnabled) client.rpcHandlerManager.unregisterHandler("teams-adopt");
11559
11770
  server.close();
11560
11771
  }
11561
11772
  };
@@ -11812,7 +12023,7 @@ function registerSideQuestionHandler(rpc, deps) {
11812
12023
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
11813
12024
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
11814
12025
  const run = deps.run ?? (async (input) => {
11815
- const { query } = await import('./index-BP56asuS.mjs');
12026
+ const { query } = await import('./index-35FahgEU.mjs');
11816
12027
  return runSideQuestion(query, input);
11817
12028
  });
11818
12029
  const slots = /* @__PURE__ */ new Map();
@@ -15873,6 +16084,7 @@ function buildCodexTurnPrompt(opts) {
15873
16084
  parts.push(opts.message);
15874
16085
  if (opts.includeTitleInstruction) {
15875
16086
  parts.push(CHANGE_TITLE_INSTRUCTION);
16087
+ parts.push(BUILTIN_TODO_DISCOVERY);
15876
16088
  }
15877
16089
  return parts.join("\n\n");
15878
16090
  }
@@ -16765,7 +16977,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16765
16977
  process.exit(0);
16766
16978
  } else if (subcommand === "install-terminal-hooks") {
16767
16979
  try {
16768
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-D-CNSalI.mjs');
16980
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-ClrFe2fA.mjs');
16769
16981
  const command = parseTerminalHooksArgs(args.slice(1));
16770
16982
  if (command.action === "help") {
16771
16983
  console.log(TERMINAL_HOOKS_HELP);
@@ -16782,7 +16994,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16782
16994
  }
16783
16995
  } else if (subcommand === "spawn") {
16784
16996
  try {
16785
- const { handleSpawnCommand } = await import('./spawn-BG5oHi_f.mjs');
16997
+ const { handleSpawnCommand } = await import('./spawn-DV4pIAHZ.mjs');
16786
16998
  await handleSpawnCommand(args.slice(1));
16787
16999
  } catch (error) {
16788
17000
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -16794,7 +17006,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16794
17006
  return;
16795
17007
  } else if (subcommand === "sessions") {
16796
17008
  try {
16797
- const { handleSessionsCommand } = await import('./sessions-jAFOkKvW.mjs');
17009
+ const { handleSessionsCommand } = await import('./sessions-DIOdWnPe.mjs');
16798
17010
  await handleSessionsCommand(args.slice(1));
16799
17011
  } catch (error) {
16800
17012
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -16806,7 +17018,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16806
17018
  return;
16807
17019
  } else if (subcommand === "send") {
16808
17020
  try {
16809
- const { handleSendCommand } = await import('./send-9duZlpGX.mjs');
17021
+ const { handleSendCommand } = await import('./send-DQS1zNWp.mjs');
16810
17022
  await handleSendCommand(args.slice(1));
16811
17023
  } catch (error) {
16812
17024
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -16912,9 +17124,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16912
17124
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
16913
17125
  const projectId = args[3];
16914
17126
  try {
16915
- const { saveGoogleCloudProjectToConfig } = await import('./config-Bjd1nG0P.mjs');
16916
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-DlbYYtOw.mjs').then(function (n) { return n.W; });
16917
- const { ApiClient: ApiClient2 } = await import('./types-n_VWu9qe.mjs').then(function (n) { return n.X; });
17127
+ const { saveGoogleCloudProjectToConfig } = await import('./config-DSBE0Az0.mjs');
17128
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./limits-BNTLh2lw.mjs').then(function (n) { return n.W; });
17129
+ const { ApiClient: ApiClient2 } = await import('./types-CFs8FmiY.mjs').then(function (n) { return n._; });
16918
17130
  let userEmail = void 0;
16919
17131
  try {
16920
17132
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -16945,7 +17157,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16945
17157
  }
16946
17158
  if (geminiSubcommand === "project" && args[2] === "get") {
16947
17159
  try {
16948
- const { readGeminiLocalConfig } = await import('./config-Bjd1nG0P.mjs');
17160
+ const { readGeminiLocalConfig } = await import('./config-DSBE0Az0.mjs');
16949
17161
  const config = readGeminiLocalConfig();
16950
17162
  if (config.googleCloudProject) {
16951
17163
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -16985,7 +17197,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16985
17197
  process.exit(0);
16986
17198
  }
16987
17199
  try {
16988
- const { runGemini } = await import('./runGemini-BJ8bU1pP.mjs');
17200
+ const { runGemini } = await import('./runGemini-B9S85Vms.mjs');
16989
17201
  let startedBy = void 0;
16990
17202
  for (let i = 1; i < args.length; i++) {
16991
17203
  if (args[i] === "--started-by") {
@@ -17015,7 +17227,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17015
17227
  }
17016
17228
  };
17017
17229
  try {
17018
- const { runAcp, resolveAcpAgentConfig } = await import('./index-CV6sAa4a.mjs');
17230
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-m0Dnccc_.mjs');
17019
17231
  const parsed = parsePiRunnerArgs(args.slice(1));
17020
17232
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
17021
17233
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -17042,7 +17254,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17042
17254
  return;
17043
17255
  } else if (subcommand === "acp") {
17044
17256
  try {
17045
- const { runAcp, resolveAcpAgentConfig } = await import('./index-CV6sAa4a.mjs');
17257
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-m0Dnccc_.mjs');
17046
17258
  let startedBy = void 0;
17047
17259
  let verbose = false;
17048
17260
  const acpArgs = [];
@@ -17082,7 +17294,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17082
17294
  return;
17083
17295
  } else if (subcommand === "openclaw") {
17084
17296
  try {
17085
- const { runOpenClaw } = await import('./runOpenClaw-DTgOpJnO.mjs');
17297
+ const { runOpenClaw } = await import('./runOpenClaw-Byhq-B1y.mjs');
17086
17298
  let startedBy = void 0;
17087
17299
  let verbose = false;
17088
17300
  let gatewayUrl;
@@ -17131,9 +17343,18 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17131
17343
  process.exit(1);
17132
17344
  }
17133
17345
  return;
17346
+ } else if (subcommand === "todo") {
17347
+ try {
17348
+ const { handleTodoCommand } = await import('./todo-BwXURAbQ.mjs');
17349
+ await handleTodoCommand(args.slice(1));
17350
+ } catch (error) {
17351
+ console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
17352
+ process.exitCode = 1;
17353
+ }
17354
+ return;
17134
17355
  } else if (subcommand === "teams") {
17135
17356
  try {
17136
- const { handleTeamsCommand } = await import('./teams-Cuqsk7Ty.mjs');
17357
+ const { handleTeamsCommand } = await import('./teams-DlTHOiCk.mjs');
17137
17358
  await handleTeamsCommand(args.slice(1));
17138
17359
  } catch (error) {
17139
17360
  console.error(error instanceof Error ? error.message : "Teams command failed");
@@ -17142,7 +17363,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
17142
17363
  return;
17143
17364
  } else if (subcommand === "mcp" && args.length === 1) {
17144
17365
  try {
17145
- const { handleMcpCommand } = await import('./mcp-D3oV_dH-.mjs');
17366
+ const { handleMcpCommand } = await import('./mcp-BsohER5A.mjs');
17146
17367
  await handleMcpCommand();
17147
17368
  } catch (error) {
17148
17369
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -17369,6 +17590,7 @@ ${chalk.bold("Usage:")}
17369
17590
  Install optional Claude terminal mirror hooks
17370
17591
  (add --remove to uninstall them)
17371
17592
  very-happy connect Connect AI vendor API keys
17593
+ very-happy todo Built-in Todos and official agent skill (todo --help)
17372
17594
  very-happy sandbox Configure and manage OS-level sandboxing
17373
17595
  very-happy notify Send push notification
17374
17596
  very-happy mcp Stdio MCP server exposing copy_to_clipboard for a
@@ -17503,4 +17725,4 @@ ${chalk.bold("Examples:")}
17503
17725
  }
17504
17726
  }
17505
17727
 
17506
- export { ALLOWED_SPAWN_PERMISSION_MODES as A, hashObject as B, startHappyServer as C, DEFAULT_RECENT_LIMIT as D, BasePermissionHandler as E, writeSessionModeFile as F, GEMINI_MODEL_ENV as G, normalizeAcpPermissionMode as H, removeSessionModeFile as I, GEMINI_API_KEY_ENV as J, GOOGLE_API_KEY_ENV as K, BaseReasoningProcessor as L, MAX_READ_LIMIT as M, MessageBuffer as N, CHANGE_TITLE_INSTRUCTION as O, SPAWN_AGENTS as S, TitleGenerator as T, sessionWebUrl as a, sanitizeSpawnPermissionMode as b, checkIfDaemonRunningAndCleanupStaleState as c, sendUserMessage as d, listSessions as e, stopSession as f, resolvePermissionRequest as g, archiveSession as h, isValidSpawnOrigin as i, DEFAULT_GEMINI_MODEL as j, setTerminalTitleViaDaemon as k, listAccountSessions as l, registerAssistantSessionTools as m, initialMachineMetadata as n, createSessionMetadata as o, pushClipboardViaDaemon as p, query as q, readSessionTranscript as r, spawnDaemonSession as s, tmuxSupportsSessionEnv as t, setupOfflineReconnection as u, notifyDaemonSessionStarted as v, waitForSessionKey as w, MessageQueue2 as x, registerKillSessionHandler as y, StreamRelay as z };
17728
+ export { ALLOWED_SPAWN_PERMISSION_MODES as A, hashObject as B, startHappyServer as C, DEFAULT_RECENT_LIMIT as D, TitleGenerator as E, BasePermissionHandler as F, GEMINI_MODEL_ENV as G, writeSessionModeFile as H, normalizeAcpPermissionMode as I, removeSessionModeFile as J, GEMINI_API_KEY_ENV as K, GOOGLE_API_KEY_ENV as L, MAX_READ_LIMIT as M, BaseReasoningProcessor as N, MessageBuffer as O, PI_TEAMS_EXTENSION as P, CHANGE_TITLE_INSTRUCTION as Q, SPAWN_AGENTS as S, TEAM_SKILL as T, sessionWebUrl as a, sanitizeSpawnPermissionMode as b, checkIfDaemonRunningAndCleanupStaleState as c, sendUserMessage as d, listSessions as e, stopSession as f, resolvePermissionRequest as g, archiveSession as h, isValidSpawnOrigin as i, DEFAULT_GEMINI_MODEL as j, setTerminalTitleViaDaemon as k, listAccountSessions as l, registerAssistantSessionTools as m, initialMachineMetadata as n, createSessionMetadata as o, pushClipboardViaDaemon as p, query as q, readSessionTranscript as r, spawnDaemonSession as s, tmuxSupportsSessionEnv as t, setupOfflineReconnection as u, notifyDaemonSessionStarted as v, waitForSessionKey as w, MessageQueue2 as x, registerKillSessionHandler as y, StreamRelay as z };