very-happy-cli 0.2.150 → 0.2.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AcpBackend-C-dhGJgd.mjs → AcpBackend-BjTA5gUB.mjs} +2 -2
- package/dist/{AcpBackend-CXawaWWx.cjs → AcpBackend-blE9IEod.cjs} +2 -2
- package/dist/{AcpSessionManager-DKOtSqJc.mjs → AcpSessionManager-D-9mrp40.mjs} +1 -1
- package/dist/{AcpSessionManager-TrwEZR7y.cjs → AcpSessionManager-bF8PYu_B.cjs} +1 -1
- package/dist/{agentGuidance-Dla-BXO8.cjs → agentGuidance-BXa9ikrZ.cjs} +1 -1
- package/dist/{agentGuidance-DrfhtVP1.mjs → agentGuidance-Dvo3cZMW.mjs} +1 -1
- package/dist/{agentHome-Dm-Wjs38.mjs → agentHome-CeFDNzvq.mjs} +2 -2
- package/dist/{agentHome-rylNIkix.cjs → agentHome-D6MSfwnp.cjs} +2 -2
- package/dist/codex/happyMcpStdioBridge.cjs +2 -2
- package/dist/codex/happyMcpStdioBridge.mjs +2 -2
- package/dist/{config-Dhu6LU0h.mjs → config-C-A1XF62.mjs} +4 -4
- package/dist/{config-CgPu2wtS.cjs → config-Cadw1sAk.cjs} +4 -4
- package/dist/{index-DEVkDRas.cjs → index-BKGspSWk.cjs} +7 -7
- package/dist/{index-CpNYvMTJ.mjs → index-CIyjJy8D.mjs} +214 -59
- package/dist/{index-K6mUS0jF.cjs → index-CqtfRzug.cjs} +220 -62
- package/dist/{index-CzpooENj.mjs → index-DEp6CmmS.mjs} +8 -8
- package/dist/{index-BsYH1sjV.mjs → index-DYQgcgL2.mjs} +4 -4
- package/dist/{index-DvfCUuDK.cjs → index-vyRqLjDv.cjs} +4 -4
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{installTerminalHooks-NIbsQ1wi.cjs → installTerminalHooks-Bf_adbl3.cjs} +4 -4
- package/dist/{installTerminalHooks-BF_tmw9V.mjs → installTerminalHooks-Ch_yTtEA.mjs} +4 -4
- package/dist/lib.cjs +2 -2
- package/dist/lib.mjs +2 -2
- package/dist/{limits-FThmm7WR.mjs → limits-DS-bnYYp.mjs} +7 -7
- package/dist/{limits-Dyma1REp.cjs → limits-DwcY3kyi.cjs} +7 -7
- package/dist/{mcp-_TVUSg8W.cjs → mcp-CMxEZ6-0.cjs} +4 -4
- package/dist/{mcp-DzydgXpf.mjs → mcp-zu8n8TkR.mjs} +4 -4
- package/dist/{piRuntime-ACOhil6t.mjs → piRuntime-Cu6cXZ6N.mjs} +2 -2
- package/dist/{piRuntime-CSAiJk0X.cjs → piRuntime-D3ny0mVg.cjs} +2 -2
- package/dist/{piTerminal-BlE4vbvZ.mjs → piTerminal-M677UP7y.mjs} +6 -6
- package/dist/{piTerminal-DHxGB3Ic.cjs → piTerminal-kDSd_r9X.cjs} +6 -6
- package/dist/{runGemini-BMYCU16c.cjs → runGemini-DuwdVV1O.cjs} +6 -6
- package/dist/{runGemini-D5IV1MoF.mjs → runGemini-DvJwQVGp.mjs} +6 -6
- package/dist/{runOpenClaw-CWPXrKMJ.mjs → runOpenClaw-BG82bF18.mjs} +5 -5
- package/dist/{runOpenClaw-Qs4eFtcS.cjs → runOpenClaw-BsvtCE_H.cjs} +5 -5
- package/dist/{send-qXWGilm8.cjs → send-aUkjhey7.cjs} +20 -6
- package/dist/{send-Dv0km8UI.mjs → send-o6WM-1G8.mjs} +20 -6
- package/dist/{sessions-CpIdDhH7.cjs → sessions-B3vP18kz.cjs} +56 -10
- package/dist/{sessions-DoULfKui.mjs → sessions-DlU3foY_.mjs} +56 -10
- package/dist/{spawn-DqzG1bTH.cjs → spawn-DH6_p4-f.cjs} +166 -27
- package/dist/spawn-DxHIIqhi.mjs +397 -0
- package/dist/{teams-ByEgUzsH.cjs → teams-BW8D1n3o.cjs} +4 -4
- package/dist/{teams-4mhNh5cj.mjs → teams-DoAA_CcN.mjs} +4 -4
- package/dist/{todo-D7SDXL0c.cjs → todo-Bcc2XxGh.cjs} +3 -3
- package/dist/{todo-C9IAUf0K.mjs → todo-BhJbG5eq.mjs} +3 -3
- package/dist/{types-BD542hb1.mjs → types-BN3eiipi.mjs} +46 -3
- package/dist/{types-D2Gkpe0N.cjs → types-CPubg05Z.cjs} +52 -2
- package/package.json +7 -7
- package/dist/spawn-DQuOmUP7.mjs +0 -258
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
|
-
var index = require('./index-
|
|
5
|
-
var index$1 = require('./types-
|
|
4
|
+
var index = require('./index-CqtfRzug.cjs');
|
|
5
|
+
var index$1 = require('./types-CPubg05Z.cjs');
|
|
6
6
|
require('node:os');
|
|
7
7
|
require('node:crypto');
|
|
8
|
-
require('./limits-
|
|
8
|
+
require('./limits-DwcY3kyi.cjs');
|
|
9
9
|
require('node:fs/promises');
|
|
10
10
|
require('node:fs');
|
|
11
11
|
require('node:path');
|
|
@@ -17,7 +17,7 @@ require('node:readline');
|
|
|
17
17
|
require('./projectPath-ChLhdOyt.cjs');
|
|
18
18
|
require('path');
|
|
19
19
|
require('url');
|
|
20
|
-
require('./agentGuidance-
|
|
20
|
+
require('./agentGuidance-BXa9ikrZ.cjs');
|
|
21
21
|
require('fs/promises');
|
|
22
22
|
require('ink');
|
|
23
23
|
require('react');
|
|
@@ -54,7 +54,7 @@ const ACTIONS_NEEDING_ID = /* @__PURE__ */ new Set(["read", "stop", "archive", "
|
|
|
54
54
|
const ACTIONS_NEEDING_REQUEST_ID = /* @__PURE__ */ new Set(["approve", "deny"]);
|
|
55
55
|
const REQUEST_ID_RE = /^[a-zA-Z0-9_.:-]{1,128}$/;
|
|
56
56
|
function defaultOptions() {
|
|
57
|
-
return { action: "help", all: false, includeArchived: false, forSession: false, json: false };
|
|
57
|
+
return { action: "help", all: false, includeArchived: false, forSession: false, full: false, answer: false, wait: false, json: false };
|
|
58
58
|
}
|
|
59
59
|
function parseSessionsArgs(args) {
|
|
60
60
|
const options = defaultOptions();
|
|
@@ -72,6 +72,18 @@ function parseSessionsArgs(args) {
|
|
|
72
72
|
options.includeArchived = true;
|
|
73
73
|
} else if (arg === "--for-session") {
|
|
74
74
|
options.forSession = true;
|
|
75
|
+
} else if (arg === "--full") {
|
|
76
|
+
options.full = true;
|
|
77
|
+
} else if (arg === "--answer") {
|
|
78
|
+
options.answer = true;
|
|
79
|
+
} else if (arg === "--wait") {
|
|
80
|
+
options.wait = true;
|
|
81
|
+
} else if (arg === "--timeout") {
|
|
82
|
+
const value = args[++i];
|
|
83
|
+
if (value === void 0) throw new Error("--timeout requires a value");
|
|
84
|
+
const parsed = Number(value);
|
|
85
|
+
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error("--timeout must be a positive number of seconds");
|
|
86
|
+
options.timeoutSec = parsed;
|
|
75
87
|
} else if (arg === "--reason") {
|
|
76
88
|
const value = args[++i];
|
|
77
89
|
if (value === void 0) throw new Error("--reason requires a value");
|
|
@@ -124,6 +136,12 @@ function parseSessionsArgs(args) {
|
|
|
124
136
|
}
|
|
125
137
|
if (options.forSession && options.action !== "approve") throw new Error("--for-session only applies to `sessions approve`");
|
|
126
138
|
if (options.reason !== void 0 && options.action !== "deny") throw new Error("--reason only applies to `sessions deny`");
|
|
139
|
+
if (options.action !== "read") {
|
|
140
|
+
if (options.full) throw new Error("--full only applies to `sessions read`");
|
|
141
|
+
if (options.answer) throw new Error("--answer only applies to `sessions read`");
|
|
142
|
+
if (options.wait) throw new Error("--wait only applies to `sessions read`");
|
|
143
|
+
}
|
|
144
|
+
if (options.timeoutSec !== void 0 && !options.wait) throw new Error("--timeout only applies to `sessions read --wait`");
|
|
127
145
|
return options;
|
|
128
146
|
}
|
|
129
147
|
const SESSIONS_HELP = `
|
|
@@ -131,7 +149,8 @@ ${chalk.bold("very-happy sessions")} - Inspect and control sessions on this mach
|
|
|
131
149
|
|
|
132
150
|
${chalk.bold("Usage:")}
|
|
133
151
|
very-happy sessions list [--all [--include-archived]] [--tag <name>] [--limit <n>] [--json]
|
|
134
|
-
very-happy sessions read <id> [--limit <n>] [--
|
|
152
|
+
very-happy sessions read <id> [--limit <n>] [--full] [--answer]
|
|
153
|
+
[--wait [--timeout <s>]] [--json]
|
|
135
154
|
very-happy sessions stop <id> [--json]
|
|
136
155
|
very-happy sessions archive <id> [--json]
|
|
137
156
|
very-happy sessions approve <id> <requestId> [--for-session] [--json]
|
|
@@ -143,7 +162,9 @@ ${chalk.bold("Actions:")}
|
|
|
143
162
|
REST), attention first. Each row says whether this machine could
|
|
144
163
|
decrypt it (\`decryptable\`); rows from other machines show only
|
|
145
164
|
id / active / archived / timestamps / url.
|
|
146
|
-
read The tail of a session as a role-tagged transcript
|
|
165
|
+
read The tail of a session as a role-tagged transcript, plus where
|
|
166
|
+
the latest turn stands (\`turn\` in --json: ended, status and
|
|
167
|
+
\`answer\` = the agent's reply to the latest prompt).
|
|
147
168
|
stop SIGTERM the session's process via the local daemon.
|
|
148
169
|
archive Mark the session inactive server-side (it stays resumable).
|
|
149
170
|
approve Answer a pending permission request \`requestId\` with approve \u2014
|
|
@@ -161,6 +182,14 @@ ${chalk.bold("Options:")}
|
|
|
161
182
|
--limit <n> list: how many NOT-running / not-attention sessions to
|
|
162
183
|
include (default ${index.DEFAULT_RECENT_LIMIT}; running and attention rows are
|
|
163
184
|
never cut). read: how many messages (default 20, max ${index.MAX_READ_LIMIT}).
|
|
185
|
+
--full read only: do not truncate entries (default caps each
|
|
186
|
+
line at 500 chars).
|
|
187
|
+
--answer read only: print just the agent's reply to the latest
|
|
188
|
+
prompt (the text after its last tool call), untruncated.
|
|
189
|
+
--wait read only: block until the latest turn has ended
|
|
190
|
+
(claude / codex / pi), then read. Pairs with
|
|
191
|
+
\`spawn --prompt\` / \`send\` for "ask and collect".
|
|
192
|
+
--timeout <s> read --wait only: give up after <s> seconds (default 600).
|
|
164
193
|
--for-session approve only: approved_for_session instead of approved.
|
|
165
194
|
--reason <text> deny only: reason forwarded to the wrapper.
|
|
166
195
|
--json Machine-readable output.
|
|
@@ -176,6 +205,8 @@ ${chalk.bold("Scope:")}
|
|
|
176
205
|
the account content key \u2014 a credentials change, not a flag.
|
|
177
206
|
|
|
178
207
|
${chalk.bold("Exit codes:")}
|
|
208
|
+
2 read --wait: the turn had not ended when --timeout ran out (the
|
|
209
|
+
partial state is still printed)
|
|
179
210
|
0 success (approve/deny: the wrapper acknowledged the verdict; check
|
|
180
211
|
\`settled\` in --json to see whether the request has left the pending set)
|
|
181
212
|
1 bad arguments, unknown session, no local key, request not pending,
|
|
@@ -263,15 +294,30 @@ async function handleSessionsCommand(args) {
|
|
|
263
294
|
}
|
|
264
295
|
const sessionId = options.sessionId;
|
|
265
296
|
if (options.action === "read") {
|
|
266
|
-
|
|
297
|
+
let timedOut = false;
|
|
298
|
+
if (options.wait) {
|
|
299
|
+
try {
|
|
300
|
+
await index.waitForTurnEnd(sessionId, { timeoutMs: (options.timeoutSec ?? 600) * 1e3 });
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (!(error instanceof index.TurnWaitTimeoutError)) throw error;
|
|
303
|
+
timedOut = true;
|
|
304
|
+
console.error(chalk.yellow("Timeout:"), error.message);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const result = await index.readSessionTranscript(sessionId, options.limit ?? 20, { full: options.full });
|
|
267
308
|
if (options.json) {
|
|
268
|
-
console.log(JSON.stringify(result));
|
|
309
|
+
console.log(JSON.stringify(options.answer ? { sessionId, turn: result.turn } : result));
|
|
310
|
+
} else if (options.answer) {
|
|
311
|
+
if (result.turn.answer.length > 0) console.log(result.turn.answer);
|
|
312
|
+
else console.error(chalk.dim("(the agent has not replied to the latest prompt yet)"));
|
|
269
313
|
} else {
|
|
270
314
|
console.log(formatSummaryLine(result.summary));
|
|
271
315
|
console.log(`--- last ${result.messageCount} message(s) ---`);
|
|
272
316
|
console.log(result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)");
|
|
317
|
+
const turn = result.turn;
|
|
318
|
+
console.log(chalk.dim(`--- latest turn: ${turn.ended ? `ended (${turn.status ?? "unknown"})` : "running"}${turn.error ? ` \u2014 ${turn.error}` : ""} ---`));
|
|
273
319
|
}
|
|
274
|
-
process.exit(0);
|
|
320
|
+
process.exit(timedOut ? 2 : 0);
|
|
275
321
|
}
|
|
276
322
|
if (options.action === "stop") {
|
|
277
323
|
const stopped = await index.stopSession(sessionId);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { D as DEFAULT_RECENT_LIMIT, M as MAX_READ_LIMIT, l as listAccountSessions,
|
|
3
|
-
import { i as isValidSessionId } from './types-
|
|
2
|
+
import { D as DEFAULT_RECENT_LIMIT, M as MAX_READ_LIMIT, l as listAccountSessions, a as listSessions, w as waitForTurnEnd, T as TurnWaitTimeoutError, r as readSessionTranscript, s as stopSession, b as resolvePermissionRequest, c as archiveSession } from './index-CIyjJy8D.mjs';
|
|
3
|
+
import { i as isValidSessionId } from './types-BN3eiipi.mjs';
|
|
4
4
|
import 'node:os';
|
|
5
5
|
import 'node:crypto';
|
|
6
|
-
import './limits-
|
|
6
|
+
import './limits-DS-bnYYp.mjs';
|
|
7
7
|
import 'node:fs/promises';
|
|
8
8
|
import 'node:fs';
|
|
9
9
|
import 'node:path';
|
|
@@ -15,7 +15,7 @@ import 'node:readline';
|
|
|
15
15
|
import './projectPath-CSpqWdxp.mjs';
|
|
16
16
|
import 'path';
|
|
17
17
|
import 'url';
|
|
18
|
-
import './agentGuidance-
|
|
18
|
+
import './agentGuidance-Dvo3cZMW.mjs';
|
|
19
19
|
import 'fs/promises';
|
|
20
20
|
import 'ink';
|
|
21
21
|
import 'react';
|
|
@@ -52,7 +52,7 @@ const ACTIONS_NEEDING_ID = /* @__PURE__ */ new Set(["read", "stop", "archive", "
|
|
|
52
52
|
const ACTIONS_NEEDING_REQUEST_ID = /* @__PURE__ */ new Set(["approve", "deny"]);
|
|
53
53
|
const REQUEST_ID_RE = /^[a-zA-Z0-9_.:-]{1,128}$/;
|
|
54
54
|
function defaultOptions() {
|
|
55
|
-
return { action: "help", all: false, includeArchived: false, forSession: false, json: false };
|
|
55
|
+
return { action: "help", all: false, includeArchived: false, forSession: false, full: false, answer: false, wait: false, json: false };
|
|
56
56
|
}
|
|
57
57
|
function parseSessionsArgs(args) {
|
|
58
58
|
const options = defaultOptions();
|
|
@@ -70,6 +70,18 @@ function parseSessionsArgs(args) {
|
|
|
70
70
|
options.includeArchived = true;
|
|
71
71
|
} else if (arg === "--for-session") {
|
|
72
72
|
options.forSession = true;
|
|
73
|
+
} else if (arg === "--full") {
|
|
74
|
+
options.full = true;
|
|
75
|
+
} else if (arg === "--answer") {
|
|
76
|
+
options.answer = true;
|
|
77
|
+
} else if (arg === "--wait") {
|
|
78
|
+
options.wait = true;
|
|
79
|
+
} else if (arg === "--timeout") {
|
|
80
|
+
const value = args[++i];
|
|
81
|
+
if (value === void 0) throw new Error("--timeout requires a value");
|
|
82
|
+
const parsed = Number(value);
|
|
83
|
+
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error("--timeout must be a positive number of seconds");
|
|
84
|
+
options.timeoutSec = parsed;
|
|
73
85
|
} else if (arg === "--reason") {
|
|
74
86
|
const value = args[++i];
|
|
75
87
|
if (value === void 0) throw new Error("--reason requires a value");
|
|
@@ -122,6 +134,12 @@ function parseSessionsArgs(args) {
|
|
|
122
134
|
}
|
|
123
135
|
if (options.forSession && options.action !== "approve") throw new Error("--for-session only applies to `sessions approve`");
|
|
124
136
|
if (options.reason !== void 0 && options.action !== "deny") throw new Error("--reason only applies to `sessions deny`");
|
|
137
|
+
if (options.action !== "read") {
|
|
138
|
+
if (options.full) throw new Error("--full only applies to `sessions read`");
|
|
139
|
+
if (options.answer) throw new Error("--answer only applies to `sessions read`");
|
|
140
|
+
if (options.wait) throw new Error("--wait only applies to `sessions read`");
|
|
141
|
+
}
|
|
142
|
+
if (options.timeoutSec !== void 0 && !options.wait) throw new Error("--timeout only applies to `sessions read --wait`");
|
|
125
143
|
return options;
|
|
126
144
|
}
|
|
127
145
|
const SESSIONS_HELP = `
|
|
@@ -129,7 +147,8 @@ ${chalk.bold("very-happy sessions")} - Inspect and control sessions on this mach
|
|
|
129
147
|
|
|
130
148
|
${chalk.bold("Usage:")}
|
|
131
149
|
very-happy sessions list [--all [--include-archived]] [--tag <name>] [--limit <n>] [--json]
|
|
132
|
-
very-happy sessions read <id> [--limit <n>] [--
|
|
150
|
+
very-happy sessions read <id> [--limit <n>] [--full] [--answer]
|
|
151
|
+
[--wait [--timeout <s>]] [--json]
|
|
133
152
|
very-happy sessions stop <id> [--json]
|
|
134
153
|
very-happy sessions archive <id> [--json]
|
|
135
154
|
very-happy sessions approve <id> <requestId> [--for-session] [--json]
|
|
@@ -141,7 +160,9 @@ ${chalk.bold("Actions:")}
|
|
|
141
160
|
REST), attention first. Each row says whether this machine could
|
|
142
161
|
decrypt it (\`decryptable\`); rows from other machines show only
|
|
143
162
|
id / active / archived / timestamps / url.
|
|
144
|
-
read The tail of a session as a role-tagged transcript
|
|
163
|
+
read The tail of a session as a role-tagged transcript, plus where
|
|
164
|
+
the latest turn stands (\`turn\` in --json: ended, status and
|
|
165
|
+
\`answer\` = the agent's reply to the latest prompt).
|
|
145
166
|
stop SIGTERM the session's process via the local daemon.
|
|
146
167
|
archive Mark the session inactive server-side (it stays resumable).
|
|
147
168
|
approve Answer a pending permission request \`requestId\` with approve \u2014
|
|
@@ -159,6 +180,14 @@ ${chalk.bold("Options:")}
|
|
|
159
180
|
--limit <n> list: how many NOT-running / not-attention sessions to
|
|
160
181
|
include (default ${DEFAULT_RECENT_LIMIT}; running and attention rows are
|
|
161
182
|
never cut). read: how many messages (default 20, max ${MAX_READ_LIMIT}).
|
|
183
|
+
--full read only: do not truncate entries (default caps each
|
|
184
|
+
line at 500 chars).
|
|
185
|
+
--answer read only: print just the agent's reply to the latest
|
|
186
|
+
prompt (the text after its last tool call), untruncated.
|
|
187
|
+
--wait read only: block until the latest turn has ended
|
|
188
|
+
(claude / codex / pi), then read. Pairs with
|
|
189
|
+
\`spawn --prompt\` / \`send\` for "ask and collect".
|
|
190
|
+
--timeout <s> read --wait only: give up after <s> seconds (default 600).
|
|
162
191
|
--for-session approve only: approved_for_session instead of approved.
|
|
163
192
|
--reason <text> deny only: reason forwarded to the wrapper.
|
|
164
193
|
--json Machine-readable output.
|
|
@@ -174,6 +203,8 @@ ${chalk.bold("Scope:")}
|
|
|
174
203
|
the account content key \u2014 a credentials change, not a flag.
|
|
175
204
|
|
|
176
205
|
${chalk.bold("Exit codes:")}
|
|
206
|
+
2 read --wait: the turn had not ended when --timeout ran out (the
|
|
207
|
+
partial state is still printed)
|
|
177
208
|
0 success (approve/deny: the wrapper acknowledged the verdict; check
|
|
178
209
|
\`settled\` in --json to see whether the request has left the pending set)
|
|
179
210
|
1 bad arguments, unknown session, no local key, request not pending,
|
|
@@ -261,15 +292,30 @@ async function handleSessionsCommand(args) {
|
|
|
261
292
|
}
|
|
262
293
|
const sessionId = options.sessionId;
|
|
263
294
|
if (options.action === "read") {
|
|
264
|
-
|
|
295
|
+
let timedOut = false;
|
|
296
|
+
if (options.wait) {
|
|
297
|
+
try {
|
|
298
|
+
await waitForTurnEnd(sessionId, { timeoutMs: (options.timeoutSec ?? 600) * 1e3 });
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (!(error instanceof TurnWaitTimeoutError)) throw error;
|
|
301
|
+
timedOut = true;
|
|
302
|
+
console.error(chalk.yellow("Timeout:"), error.message);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const result = await readSessionTranscript(sessionId, options.limit ?? 20, { full: options.full });
|
|
265
306
|
if (options.json) {
|
|
266
|
-
console.log(JSON.stringify(result));
|
|
307
|
+
console.log(JSON.stringify(options.answer ? { sessionId, turn: result.turn } : result));
|
|
308
|
+
} else if (options.answer) {
|
|
309
|
+
if (result.turn.answer.length > 0) console.log(result.turn.answer);
|
|
310
|
+
else console.error(chalk.dim("(the agent has not replied to the latest prompt yet)"));
|
|
267
311
|
} else {
|
|
268
312
|
console.log(formatSummaryLine(result.summary));
|
|
269
313
|
console.log(`--- last ${result.messageCount} message(s) ---`);
|
|
270
314
|
console.log(result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)");
|
|
315
|
+
const turn = result.turn;
|
|
316
|
+
console.log(chalk.dim(`--- latest turn: ${turn.ended ? `ended (${turn.status ?? "unknown"})` : "running"}${turn.error ? ` \u2014 ${turn.error}` : ""} ---`));
|
|
271
317
|
}
|
|
272
|
-
process.exit(0);
|
|
318
|
+
process.exit(timedOut ? 2 : 0);
|
|
273
319
|
}
|
|
274
320
|
if (options.action === "stop") {
|
|
275
321
|
const stopped = await stopSession(sessionId);
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
4
|
var node_fs = require('node:fs');
|
|
5
5
|
var path = require('node:path');
|
|
6
|
-
var index$1 = require('./types-
|
|
7
|
-
var
|
|
8
|
-
var
|
|
6
|
+
var index$1 = require('./types-CPubg05Z.cjs');
|
|
7
|
+
var persistence = require('./limits-DwcY3kyi.cjs');
|
|
8
|
+
var index = require('./index-CqtfRzug.cjs');
|
|
9
9
|
require('axios');
|
|
10
10
|
require('node:events');
|
|
11
11
|
require('socket.io-client');
|
|
@@ -30,7 +30,8 @@ require('tweetnacl');
|
|
|
30
30
|
require('node:readline');
|
|
31
31
|
require('@anthropic-ai/sandbox-runtime');
|
|
32
32
|
require('expo-server-sdk');
|
|
33
|
-
require('
|
|
33
|
+
require('node:util');
|
|
34
|
+
require('./agentGuidance-BXa9ikrZ.cjs');
|
|
34
35
|
require('ink');
|
|
35
36
|
require('react');
|
|
36
37
|
require('@anthropic-ai/claude-agent-sdk');
|
|
@@ -40,7 +41,6 @@ require('open');
|
|
|
40
41
|
require('fastify');
|
|
41
42
|
require('fastify-type-provider-zod');
|
|
42
43
|
require('node:module');
|
|
43
|
-
require('node:util');
|
|
44
44
|
require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
45
45
|
require('node:http');
|
|
46
46
|
require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
@@ -49,6 +49,71 @@ require('inquirer');
|
|
|
49
49
|
require('node:url');
|
|
50
50
|
require('node:readline/promises');
|
|
51
51
|
|
|
52
|
+
function resolveSpawnPermissionMode(agent, explicit) {
|
|
53
|
+
return index$1.getAgentCodeDefaults(agent ?? "claude").permissionMode;
|
|
54
|
+
}
|
|
55
|
+
function outboundPermissionMode(agent, mode) {
|
|
56
|
+
const key = index$1.normalizeAgentKey(agent ?? "claude");
|
|
57
|
+
if ((key === "claude" || key === "pi") && mode === "yolo") return "bypassPermissions";
|
|
58
|
+
return mode;
|
|
59
|
+
}
|
|
60
|
+
function resolveFirstMessageMeta(input) {
|
|
61
|
+
const meta = {
|
|
62
|
+
permissionMode: outboundPermissionMode(input.agent, input.permissionMode)
|
|
63
|
+
};
|
|
64
|
+
if (input.explicitModel !== void 0) {
|
|
65
|
+
meta.model = input.explicitModel === "default" ? null : input.explicitModel;
|
|
66
|
+
}
|
|
67
|
+
if (index$1.normalizeAgentKey(input.agent ?? "claude") !== "claude") return meta;
|
|
68
|
+
if (meta.model === void 0) {
|
|
69
|
+
const model = index$1.getAgentCodeDefaults("claude").modelMode;
|
|
70
|
+
const runnable = !index$1.isClaudeOpus55Model(model) || index$1.supportsClaudeOpus55({ capabilities: input.capabilities ?? null });
|
|
71
|
+
meta.model = runnable && model !== "default" ? model : null;
|
|
72
|
+
}
|
|
73
|
+
meta.effort = null;
|
|
74
|
+
return meta;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function nonEmpty(value) {
|
|
78
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
79
|
+
}
|
|
80
|
+
function resolveForkSource(sessionId, persisted) {
|
|
81
|
+
if (!persisted) {
|
|
82
|
+
throw new Error(`Session ${sessionId} has no local record \u2014 only sessions spawned by this machine's daemon (in the last 14 days) can be forked from here.`);
|
|
83
|
+
}
|
|
84
|
+
const metadata = persisted.metadata;
|
|
85
|
+
const directory = metadata?.path;
|
|
86
|
+
if (!nonEmpty(directory)) throw new Error(`Session ${sessionId} has no working directory in its metadata.`);
|
|
87
|
+
const permissionMode = nonEmpty(metadata.permissionMode) ? metadata.permissionMode : void 0;
|
|
88
|
+
if (metadata.flavor === "codex") {
|
|
89
|
+
if (!nonEmpty(metadata.codexThreadId)) throw new Error(`Codex session ${sessionId} has no thread id yet (it never ran a turn) \u2014 nothing to fork.`);
|
|
90
|
+
return { agent: "codex", directory, codexThreadId: metadata.codexThreadId, permissionMode, parentSessionId: sessionId };
|
|
91
|
+
}
|
|
92
|
+
if (metadata.flavor && metadata.flavor !== "claude") {
|
|
93
|
+
throw new Error(`Session ${sessionId} runs ${metadata.flavor}; only claude and codex sessions can be forked.`);
|
|
94
|
+
}
|
|
95
|
+
if (!nonEmpty(metadata.claudeSessionId)) throw new Error(`Session ${sessionId} has no Claude conversation id yet (it never ran a turn) \u2014 nothing to fork.`);
|
|
96
|
+
return { agent: "claude", directory, claudeSessionId: metadata.claudeSessionId, permissionMode, parentSessionId: sessionId };
|
|
97
|
+
}
|
|
98
|
+
async function forkProviderConversation(source) {
|
|
99
|
+
if (source.agent === "claude") {
|
|
100
|
+
try {
|
|
101
|
+
return { resumeClaudeSessionId: await index$1.forkSession(index$1.getProjectPath(source.directory), source.claudeSessionId) };
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof index$1.ForkSourceMissingError) throw new Error("Claude session file not found on this machine");
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const client = new index$1.CodexAppServerClient();
|
|
108
|
+
await client.connect();
|
|
109
|
+
try {
|
|
110
|
+
const result = await index$1.forkCodexThread(client, { threadId: source.codexThreadId, cwd: source.directory });
|
|
111
|
+
return { resumeCodexThreadId: result.newCodexThreadId };
|
|
112
|
+
} finally {
|
|
113
|
+
await client.disconnect();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
52
117
|
function parseEnvAssignment(raw) {
|
|
53
118
|
const eq = raw.indexOf("=");
|
|
54
119
|
if (eq <= 0) throw new Error(`--env expects KEY=VALUE, got: ${raw}`);
|
|
@@ -98,6 +163,15 @@ function parseSpawnArgs(args) {
|
|
|
98
163
|
if (value === void 0) throw new Error("--env requires a value");
|
|
99
164
|
const [key, val] = parseEnvAssignment(value);
|
|
100
165
|
options.env = { ...options.env ?? {}, [key]: val };
|
|
166
|
+
} else if (arg === "--model" || arg === "-m") {
|
|
167
|
+
const value = args[++i];
|
|
168
|
+
if (value === void 0 || value.trim().length === 0) throw new Error("--model requires a value");
|
|
169
|
+
options.model = value;
|
|
170
|
+
} else if (arg === "--fork") {
|
|
171
|
+
const value = args[++i];
|
|
172
|
+
if (value === void 0) throw new Error("--fork requires a session id");
|
|
173
|
+
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(value)) throw new Error(`Invalid session id for --fork: ${value}`);
|
|
174
|
+
options.fork = value;
|
|
101
175
|
} else if (arg === "--json") {
|
|
102
176
|
options.json = true;
|
|
103
177
|
} else if (arg === "--help" || arg === "-h") {
|
|
@@ -118,7 +192,9 @@ ${chalk.bold("very-happy spawn")} - Spawn a remote session via the local daemon
|
|
|
118
192
|
${chalk.bold("Usage:")}
|
|
119
193
|
very-happy spawn --dir <path> [--prompt <text> | --prompt-file <file>]
|
|
120
194
|
[--spawned-by <name>] [--permission-mode <mode>]
|
|
121
|
-
[--agent <name>] [--env KEY=VALUE]... [--json]
|
|
195
|
+
[--agent <name>] [--model <id>] [--env KEY=VALUE]... [--json]
|
|
196
|
+
very-happy spawn --fork <sessionId> [--prompt \u2026] [--permission-mode <mode>]
|
|
197
|
+
[--model <id>] [--spawned-by <name>] [--json]
|
|
122
198
|
|
|
123
199
|
${chalk.bold("Options:")}
|
|
124
200
|
--dir, -d <path> Working directory for the new session (required)
|
|
@@ -129,22 +205,34 @@ ${chalk.bold("Options:")}
|
|
|
129
205
|
the web list and is searchable as #<name>.
|
|
130
206
|
--permission-mode <m> Permission mode for the new session: default,
|
|
131
207
|
acceptEdits, plan, yolo, bypassPermissions.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
208
|
+
Default = the web launcher's: yolo for claude /
|
|
209
|
+
codex / pi, 'default' for gemini / openclaw. A fork
|
|
210
|
+
keeps its source's mode. 'default' stops at the first
|
|
211
|
+
un-allowlisted tool until someone approves it (see
|
|
212
|
+
\`sessions approve\`).
|
|
136
213
|
--agent <name> Backend to run: claude (default), codex, gemini,
|
|
137
214
|
openclaw, pi (needs pi-acp on the daemon's PATH).
|
|
215
|
+
--model, -m <id> Model for the session, sent with the first message
|
|
216
|
+
(needs --prompt). Default = the web launcher's:
|
|
217
|
+
claude-opus-5-5 for claude (the machine default when
|
|
218
|
+
its Claude Code is too old for it); other agents use
|
|
219
|
+
their own default. 'default' = the machine default.
|
|
220
|
+
--fork <sessionId> Fork an existing session of THIS machine (same as the
|
|
221
|
+
web's fork): copies its conversation and continues
|
|
222
|
+
it in a new session, in the same directory and with
|
|
223
|
+
the same agent (so --dir / --agent are not needed).
|
|
138
224
|
--env KEY=VALUE Extra environment for the session process. Repeatable.
|
|
139
225
|
\${VAR} is expanded against the daemon's environment;
|
|
140
226
|
an unresolved reference fails the spawn.
|
|
141
|
-
--json Machine-readable output: {"sessionId", "url"
|
|
227
|
+
--json Machine-readable output: {"sessionId", "url",
|
|
228
|
+
"permissionMode", \u2026}
|
|
142
229
|
-h, --help Show this help
|
|
143
230
|
|
|
144
231
|
${chalk.bold("Behavior:")}
|
|
145
232
|
Requires the Very Happy daemon to be running on this machine (same semantics
|
|
146
233
|
as spawning from the web: an offline machine cannot spawn). Without
|
|
147
|
-
--prompt / --prompt-file the session is spawned idle.
|
|
234
|
+
--prompt / --prompt-file the session is spawned idle. To collect the reply:
|
|
235
|
+
\`very-happy sessions read <id> --wait --answer\`.
|
|
148
236
|
|
|
149
237
|
${chalk.bold("Exit codes:")}
|
|
150
238
|
0 success
|
|
@@ -152,9 +240,11 @@ ${chalk.bold("Exit codes:")}
|
|
|
152
240
|
2 session spawned but first message failed (session URL still printed)
|
|
153
241
|
`);
|
|
154
242
|
}
|
|
155
|
-
async function sendFirstMessage(sessionId, text) {
|
|
243
|
+
async function sendFirstMessage(sessionId, text, metaFor) {
|
|
156
244
|
const persisted = await index.waitForSessionKey(sessionId, 15e3);
|
|
157
|
-
|
|
245
|
+
const meta = metaFor(persisted.metadata?.capabilities ?? null);
|
|
246
|
+
await index.sendUserMessage(sessionId, persisted, text, "cli-spawn", meta);
|
|
247
|
+
return meta;
|
|
158
248
|
}
|
|
159
249
|
async function handleSpawnCommand(args) {
|
|
160
250
|
let options;
|
|
@@ -169,12 +259,32 @@ async function handleSpawnCommand(args) {
|
|
|
169
259
|
printHelp();
|
|
170
260
|
process.exit(0);
|
|
171
261
|
}
|
|
172
|
-
|
|
173
|
-
|
|
262
|
+
let forkSource = null;
|
|
263
|
+
if (options.fork !== void 0) {
|
|
264
|
+
try {
|
|
265
|
+
const persisted = persistence.readPersistedSessions()[options.fork];
|
|
266
|
+
const current = persisted ? await index.readSessionMetadata(options.fork, persisted) : null;
|
|
267
|
+
forkSource = resolveForkSource(options.fork, persisted && current ? { ...persisted, metadata: { ...persisted.metadata, ...current } } : persisted);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
if (options.dir !== void 0 && path.resolve(options.dir) !== forkSource.directory) {
|
|
273
|
+
console.error(chalk.red("Error:"), `--dir ${path.resolve(options.dir)} differs from the forked session's directory ${forkSource.directory}; a fork always continues in its source directory.`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
if (options.agent !== void 0 && options.agent !== forkSource.agent) {
|
|
277
|
+
console.error(chalk.red("Error:"), `--agent ${options.agent} differs from the forked session's agent ${forkSource.agent}.`);
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
} else if (!options.dir) {
|
|
281
|
+
console.error(chalk.red("Error:"), "--dir is required (or --fork <sessionId>)");
|
|
174
282
|
console.error(`Run ${chalk.cyan("very-happy spawn --help")} for usage.`);
|
|
175
283
|
process.exit(1);
|
|
176
284
|
}
|
|
177
|
-
const directory = path.resolve(options.dir);
|
|
285
|
+
const directory = forkSource ? forkSource.directory : path.resolve(options.dir);
|
|
286
|
+
const agent = forkSource ? forkSource.agent : options.agent;
|
|
287
|
+
const permissionMode = options.permissionMode ?? forkSource?.permissionMode ?? resolveSpawnPermissionMode(agent);
|
|
178
288
|
let prompt = options.prompt;
|
|
179
289
|
if (options.promptFile !== void 0) {
|
|
180
290
|
try {
|
|
@@ -188,6 +298,10 @@ async function handleSpawnCommand(args) {
|
|
|
188
298
|
console.error(chalk.red("Error:"), "Prompt is empty");
|
|
189
299
|
process.exit(1);
|
|
190
300
|
}
|
|
301
|
+
if (options.model !== void 0 && prompt === void 0) {
|
|
302
|
+
console.error(chalk.red("Error:"), "--model needs --prompt / --prompt-file (the model is sent with the first message); for an idle session pass it later with `very-happy send --model`.");
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
191
305
|
try {
|
|
192
306
|
if (!node_fs.statSync(directory).isDirectory()) {
|
|
193
307
|
console.error(chalk.red("Error:"), `Not a directory: ${directory}`);
|
|
@@ -202,12 +316,23 @@ async function handleSpawnCommand(args) {
|
|
|
202
316
|
console.error(`Start it with ${chalk.cyan("very-happy daemon start")} and retry.`);
|
|
203
317
|
process.exit(1);
|
|
204
318
|
}
|
|
205
|
-
|
|
319
|
+
let resumeIds = {};
|
|
320
|
+
if (forkSource) {
|
|
321
|
+
try {
|
|
322
|
+
resumeIds = await forkProviderConversation(forkSource);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error(chalk.red("Error:"), `Failed to fork session ${forkSource.parentSessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
persistence.logger.debug(`[SPAWN CMD] Spawning session in ${directory}${forkSource ? ` (fork of ${forkSource.parentSessionId})` : ""}`);
|
|
206
329
|
const result = await index$1.spawnDaemonSession(directory, void 0, {
|
|
207
330
|
spawnedBy: options.spawnedBy,
|
|
208
|
-
permissionMode
|
|
209
|
-
agent
|
|
210
|
-
environmentVariables: options.env
|
|
331
|
+
permissionMode,
|
|
332
|
+
agent,
|
|
333
|
+
environmentVariables: options.env,
|
|
334
|
+
...resumeIds,
|
|
335
|
+
...forkSource ? { parentSessionId: forkSource.parentSessionId } : {}
|
|
211
336
|
});
|
|
212
337
|
if (result?.error || !result?.success || !result?.sessionId) {
|
|
213
338
|
const message = result?.error || "Daemon returned no session ID";
|
|
@@ -216,24 +341,38 @@ async function handleSpawnCommand(args) {
|
|
|
216
341
|
}
|
|
217
342
|
const sessionId = result.sessionId;
|
|
218
343
|
const url = index.sessionWebUrl(sessionId);
|
|
344
|
+
if (forkSource && result.resumed !== true) {
|
|
345
|
+
await index$1.stopDaemonSession(sessionId).catch(() => false);
|
|
346
|
+
console.error(chalk.red("Error:"), "The running daemon is too old to fork from the CLI (it spawned a fresh session, now stopped). Restart the daemon on this CLI version and retry.");
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
219
349
|
let promptError = null;
|
|
350
|
+
let sentMeta = null;
|
|
220
351
|
if (prompt !== void 0) {
|
|
221
352
|
try {
|
|
222
|
-
await sendFirstMessage(sessionId, prompt)
|
|
353
|
+
sentMeta = await sendFirstMessage(sessionId, prompt, (capabilities) => resolveFirstMessageMeta({
|
|
354
|
+
agent,
|
|
355
|
+
permissionMode,
|
|
356
|
+
explicitModel: options.model,
|
|
357
|
+
capabilities
|
|
358
|
+
}));
|
|
223
359
|
} catch (error) {
|
|
224
360
|
promptError = error instanceof Error ? error.message : String(error);
|
|
225
361
|
}
|
|
226
362
|
}
|
|
227
363
|
if (options.json) {
|
|
228
|
-
const payload = { sessionId, url };
|
|
364
|
+
const payload = { sessionId, url, permissionMode };
|
|
229
365
|
if (options.spawnedBy !== void 0) {
|
|
230
366
|
payload.spawnedBy = options.spawnedBy;
|
|
231
367
|
}
|
|
232
|
-
if (
|
|
233
|
-
payload.
|
|
368
|
+
if (agent !== void 0) {
|
|
369
|
+
payload.agent = agent;
|
|
370
|
+
}
|
|
371
|
+
if (forkSource) {
|
|
372
|
+
payload.forkedFrom = forkSource.parentSessionId;
|
|
234
373
|
}
|
|
235
|
-
if (
|
|
236
|
-
payload.
|
|
374
|
+
if (sentMeta && sentMeta.model !== void 0) {
|
|
375
|
+
payload.model = sentMeta.model;
|
|
237
376
|
}
|
|
238
377
|
if (prompt !== void 0) {
|
|
239
378
|
payload.promptDelivered = promptError === null;
|