very-happy-cli 0.2.124 → 0.2.126
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-CT9TOBEM.mjs → AcpBackend-BzGpBAZ0.mjs} +2 -1
- package/dist/{AcpBackend-au0veuIF.cjs → AcpBackend-C9eu6CDu.cjs} +30 -29
- package/dist/{AcpSessionManager-p1tj7FxF.mjs → AcpSessionManager-B223yT78.mjs} +1 -1
- package/dist/{AcpSessionManager-BCWeBKDd.cjs → AcpSessionManager-DQ1e_0gc.cjs} +9 -9
- package/dist/agentGuidance-BR0OFumQ.mjs +224 -0
- package/dist/agentGuidance-Tm8Z5HYp.cjs +237 -0
- package/dist/codex/happyMcpStdioBridge.cjs +29 -6
- package/dist/codex/happyMcpStdioBridge.mjs +25 -2
- package/dist/{config-DwMic6AC.mjs → config-Bjd1nG0P.mjs} +11 -11
- package/dist/{config-GzzqE5Ev.cjs → config-C0mofvCH.cjs} +11 -11
- package/dist/{index-B3-daYaX.mjs → index-BP56asuS.mjs} +8 -8
- package/dist/{index-DgN9P02i.cjs → index-CMbKpntk.cjs} +53 -19
- package/dist/{index-Ba6Fu6MJ.mjs → index-CV6sAa4a.mjs} +49 -15
- package/dist/{index-DI_VC3vJ.cjs → index-Cw9Zj1Zr.cjs} +2191 -1864
- package/dist/{index-SQKnWyz-.mjs → index-D4eCRu9x.mjs} +2046 -1719
- package/dist/{index-C69uF6Up.cjs → index-HW-KdG6W.cjs} +8 -8
- package/dist/index.cjs +5 -5
- package/dist/index.mjs +5 -5
- package/dist/{installTerminalHooks-CvMt8cBI.cjs → installTerminalHooks-CNp1LExn.cjs} +9 -9
- package/dist/{installTerminalHooks-Df4hzzdM.mjs → installTerminalHooks-D-CNSalI.mjs} +8 -8
- package/dist/lib.cjs +12 -12
- package/dist/lib.d.cts +3 -0
- package/dist/lib.d.mts +3 -0
- package/dist/lib.mjs +9 -9
- package/dist/limits-DG1OQnCb.cjs +1198 -0
- package/dist/limits-DlbYYtOw.mjs +1129 -0
- package/dist/{mcp-DnyLwSPq.cjs → mcp-BtZoIIIM.cjs} +11 -10
- package/dist/{mcp-C0oW5u-b.mjs → mcp-D3oV_dH-.mjs} +7 -6
- package/dist/resources-CVl7NdOB.cjs +83 -0
- package/dist/resources-D-mn7ptO.mjs +80 -0
- package/dist/{runGemini-mqLjz-M7.mjs → runGemini-BJ8bU1pP.mjs} +13 -13
- package/dist/{runGemini-_FHrxuHN.cjs → runGemini-BfCSOPL1.cjs} +30 -30
- package/dist/{runOpenClaw-C5VZQ6Na.cjs → runOpenClaw-CBlHXEAT.cjs} +24 -24
- package/dist/{runOpenClaw-Bs03yQHe.mjs → runOpenClaw-DTgOpJnO.mjs} +9 -9
- package/dist/{send-BaLjQPZQ.mjs → send-9duZlpGX.mjs} +10 -10
- package/dist/{send-BD2gTVoP.cjs → send-CDK1bZFj.cjs} +10 -10
- package/dist/{sessions-B108j_Xj.cjs → sessions-CNkLCorF.cjs} +11 -11
- package/dist/{sessions-ggBePfSY.mjs → sessions-jAFOkKvW.mjs} +10 -10
- package/dist/{spawn-CORFRb9D.mjs → spawn-BG5oHi_f.mjs} +21 -21
- package/dist/{spawn-iI8w8sD7.cjs → spawn-J-A5WGZ7.cjs} +21 -21
- package/dist/teams-Cuqsk7Ty.mjs +189 -0
- package/dist/teams-MgWeyutG.cjs +191 -0
- package/dist/{types-CZIwVSN2.cjs → types-P5deH8Ls.cjs} +464 -1571
- package/dist/{types-gDOmwOKw.mjs → types-n_VWu9qe.mjs} +78 -1148
- package/package.json +7 -7
- package/dist/agentGuidance-BifkGXdE.cjs +0 -46
- package/dist/agentGuidance-C2_pkgRI.mjs +0 -37
- package/dist/limits-CwFb5S2b.mjs +0 -22
- package/dist/limits-KdWKmwtH.cjs +0 -28
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { l as logger,
|
|
4
|
+
import { l as logger, p as packageJson } from './limits-DlbYYtOw.mjs';
|
|
5
|
+
import { b as contentLogMetadata, e as errorLogMetadata, d as delay } from './types-n_VWu9qe.mjs';
|
|
5
6
|
|
|
6
7
|
const DEFAULT_TIMEOUTS = {
|
|
7
8
|
/** Default initialization timeout: 60 seconds */
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
var node_child_process = require('node:child_process');
|
|
4
4
|
var sdk = require('@agentclientprotocol/sdk');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
|
-
var persistence = require('./
|
|
6
|
+
var persistence = require('./limits-DG1OQnCb.cjs');
|
|
7
|
+
var api = require('./types-P5deH8Ls.cjs');
|
|
7
8
|
|
|
8
9
|
const DEFAULT_TIMEOUTS = {
|
|
9
10
|
/** Default initialization timeout: 60 seconds */
|
|
@@ -238,7 +239,7 @@ function handleAgentMessageChunk(update, ctx) {
|
|
|
238
239
|
payload: { text, streaming: true }
|
|
239
240
|
});
|
|
240
241
|
} else {
|
|
241
|
-
persistence.logger.debug("[AcpBackend] Received message chunk:",
|
|
242
|
+
persistence.logger.debug("[AcpBackend] Received message chunk:", api.contentLogMetadata(text));
|
|
242
243
|
ctx.emit({
|
|
243
244
|
type: "model-output",
|
|
244
245
|
textDelta: text
|
|
@@ -267,7 +268,7 @@ function handleAgentThoughtChunk(update, ctx) {
|
|
|
267
268
|
}
|
|
268
269
|
if (ctx.activeToolCalls.size > 0) {
|
|
269
270
|
persistence.logger.debug("[AcpBackend] Thinking chunk received during active tool calls:", {
|
|
270
|
-
...
|
|
271
|
+
...api.contentLogMetadata(text),
|
|
271
272
|
activeToolCallCount: ctx.activeToolCalls.size
|
|
272
273
|
});
|
|
273
274
|
}
|
|
@@ -369,7 +370,7 @@ function failToolCall(toolCallId, status, toolKind, content, ctx) {
|
|
|
369
370
|
persistence.logger.debug(`[AcpBackend] \u{1F50D} \u26A0\uFE0F Investigation tool failed at ~3 minutes - likely Gemini CLI timeout, not our timeout`);
|
|
370
371
|
}
|
|
371
372
|
}
|
|
372
|
-
persistence.logger.debug("[AcpBackend] Investigation tool failed with content metadata:",
|
|
373
|
+
persistence.logger.debug("[AcpBackend] Investigation tool failed with content metadata:", api.contentLogMetadata(content));
|
|
373
374
|
persistence.logger.debug(`[AcpBackend] \u{1F50D} Investigation tool timeout status BEFORE cleanup: ${hadTimeout ? "timeout was set" : "no timeout was set"}`);
|
|
374
375
|
persistence.logger.debug(`[AcpBackend] \u{1F50D} Investigation tool startTime status BEFORE cleanup: ${startTime ? `set at ${new Date(startTime).toISOString()}` : "not set"}`);
|
|
375
376
|
}
|
|
@@ -388,7 +389,7 @@ function failToolCall(toolCallId, status, toolKind, content, ctx) {
|
|
|
388
389
|
persistence.logger.debug(`[AcpBackend] Tool call ${status.toUpperCase()}: ${toolCallId} - Duration: ${durationStr}. Active tool calls: ${ctx.activeToolCalls.size}`);
|
|
389
390
|
const errorDetail = extractErrorDetail(content);
|
|
390
391
|
if (errorDetail) {
|
|
391
|
-
persistence.logger.debug("[AcpBackend] Tool call error detail received:",
|
|
392
|
+
persistence.logger.debug("[AcpBackend] Tool call error detail received:", api.contentLogMetadata(errorDetail));
|
|
392
393
|
} else {
|
|
393
394
|
persistence.logger.debug(`[AcpBackend] \u274C Tool call ${status} but no error details in content`);
|
|
394
395
|
}
|
|
@@ -408,7 +409,7 @@ function handleToolCallUpdate(update, ctx) {
|
|
|
408
409
|
const status = update.status;
|
|
409
410
|
const toolCallId = update.toolCallId;
|
|
410
411
|
if (!toolCallId) {
|
|
411
|
-
persistence.logger.debug("[AcpBackend] Tool call update without toolCallId:",
|
|
412
|
+
persistence.logger.debug("[AcpBackend] Tool call update without toolCallId:", api.contentLogMetadata(update));
|
|
412
413
|
return { handled: false };
|
|
413
414
|
}
|
|
414
415
|
const toolKind = update.kind || "unknown";
|
|
@@ -532,7 +533,7 @@ function nodeToWebStreams(stdin, stdout) {
|
|
|
532
533
|
return new Promise((resolve, reject) => {
|
|
533
534
|
const ok = stdin.write(chunk, (err) => {
|
|
534
535
|
if (err) {
|
|
535
|
-
persistence.logger.debug("[AcpBackend] Error writing to stdin:",
|
|
536
|
+
persistence.logger.debug("[AcpBackend] Error writing to stdin:", api.errorLogMetadata(err));
|
|
536
537
|
reject(err);
|
|
537
538
|
}
|
|
538
539
|
});
|
|
@@ -561,7 +562,7 @@ function nodeToWebStreams(stdin, stdout) {
|
|
|
561
562
|
controller.close();
|
|
562
563
|
});
|
|
563
564
|
stdout.on("error", (err) => {
|
|
564
|
-
persistence.logger.debug("[AcpBackend] Stdout error:",
|
|
565
|
+
persistence.logger.debug("[AcpBackend] Stdout error:", api.errorLogMetadata(err));
|
|
565
566
|
controller.error(err);
|
|
566
567
|
});
|
|
567
568
|
},
|
|
@@ -584,9 +585,9 @@ async function withRetry(operation, options) {
|
|
|
584
585
|
options.baseDelayMs * Math.pow(2, attempt - 1),
|
|
585
586
|
options.maxDelayMs
|
|
586
587
|
);
|
|
587
|
-
persistence.logger.debug(`[AcpBackend] ${options.operationName} failed (attempt ${attempt}/${options.maxAttempts}). Retrying in ${delayMs}ms...`,
|
|
588
|
+
persistence.logger.debug(`[AcpBackend] ${options.operationName} failed (attempt ${attempt}/${options.maxAttempts}). Retrying in ${delayMs}ms...`, api.errorLogMetadata(lastError));
|
|
588
589
|
options.onRetry?.(attempt, lastError);
|
|
589
|
-
await
|
|
590
|
+
await api.delay(delayMs);
|
|
590
591
|
} else {
|
|
591
592
|
break;
|
|
592
593
|
}
|
|
@@ -640,7 +641,7 @@ class AcpBackend {
|
|
|
640
641
|
try {
|
|
641
642
|
listener(msg);
|
|
642
643
|
} catch (error) {
|
|
643
|
-
persistence.logger.warn("[AcpBackend] Error in message handler:",
|
|
644
|
+
persistence.logger.warn("[AcpBackend] Error in message handler:", api.errorLogMetadata(error));
|
|
644
645
|
}
|
|
645
646
|
}
|
|
646
647
|
}
|
|
@@ -702,7 +703,7 @@ class AcpBackend {
|
|
|
702
703
|
};
|
|
703
704
|
persistence.logger.debug("[AcpBackend] Agent stderr received:", {
|
|
704
705
|
investigationActive: hasActiveInvestigation,
|
|
705
|
-
...
|
|
706
|
+
...api.contentLogMetadata(text)
|
|
706
707
|
});
|
|
707
708
|
if (this.transport.handleStderr) {
|
|
708
709
|
const result = this.transport.handleStderr(text, context);
|
|
@@ -713,7 +714,7 @@ class AcpBackend {
|
|
|
713
714
|
});
|
|
714
715
|
this.process.on("error", (err) => {
|
|
715
716
|
signalStartupFailure(err);
|
|
716
|
-
persistence.logger.debug("[AcpBackend] Process error:",
|
|
717
|
+
persistence.logger.debug("[AcpBackend] Process error:", api.errorLogMetadata(err));
|
|
717
718
|
startupStatusErrorEmitted = true;
|
|
718
719
|
this.emit({ type: "status", status: "error", detail: err.message });
|
|
719
720
|
});
|
|
@@ -774,7 +775,7 @@ class AcpBackend {
|
|
|
774
775
|
}
|
|
775
776
|
}
|
|
776
777
|
} catch (error) {
|
|
777
|
-
persistence.logger.debug("[AcpBackend] Error filtering stdout stream:",
|
|
778
|
+
persistence.logger.debug("[AcpBackend] Error filtering stdout stream:", api.errorLogMetadata(error));
|
|
778
779
|
controller.error(error);
|
|
779
780
|
} finally {
|
|
780
781
|
reader.releaseLock();
|
|
@@ -808,7 +809,7 @@ class AcpBackend {
|
|
|
808
809
|
hasToolCall: !!toolCall,
|
|
809
810
|
toolCallId,
|
|
810
811
|
optionCount: options.length,
|
|
811
|
-
input:
|
|
812
|
+
input: api.contentLogMetadata(input)
|
|
812
813
|
});
|
|
813
814
|
this.emit({
|
|
814
815
|
type: "permission-request",
|
|
@@ -871,7 +872,7 @@ class AcpBackend {
|
|
|
871
872
|
}
|
|
872
873
|
return { outcome: { outcome: "selected", optionId } };
|
|
873
874
|
} catch (error) {
|
|
874
|
-
persistence.logger.debug("[AcpBackend] Error in permission handler:",
|
|
875
|
+
persistence.logger.debug("[AcpBackend] Error in permission handler:", api.errorLogMetadata(error));
|
|
875
876
|
return { outcome: { outcome: "selected", optionId: "cancel" } };
|
|
876
877
|
}
|
|
877
878
|
}
|
|
@@ -1004,13 +1005,13 @@ class AcpBackend {
|
|
|
1004
1005
|
this.emitIdleStatus();
|
|
1005
1006
|
if (initialPrompt) {
|
|
1006
1007
|
this.sendPrompt(sessionId, initialPrompt).catch((error) => {
|
|
1007
|
-
persistence.logger.debug("[AcpBackend] Error sending initial prompt:",
|
|
1008
|
+
persistence.logger.debug("[AcpBackend] Error sending initial prompt:", api.errorLogMetadata(error));
|
|
1008
1009
|
this.emit({ type: "status", status: "error", detail: String(error) });
|
|
1009
1010
|
});
|
|
1010
1011
|
}
|
|
1011
1012
|
return { sessionId };
|
|
1012
1013
|
} catch (error) {
|
|
1013
|
-
persistence.logger.debug("[AcpBackend] Error starting session:",
|
|
1014
|
+
persistence.logger.debug("[AcpBackend] Error starting session:", api.errorLogMetadata(error));
|
|
1014
1015
|
if (!startupStatusErrorEmitted) {
|
|
1015
1016
|
this.emit({
|
|
1016
1017
|
type: "status",
|
|
@@ -1082,12 +1083,12 @@ class AcpBackend {
|
|
|
1082
1083
|
const notification = params;
|
|
1083
1084
|
const update = notification.update;
|
|
1084
1085
|
if (!update) {
|
|
1085
|
-
persistence.logger.debug("[AcpBackend] Received session update without update field:",
|
|
1086
|
+
persistence.logger.debug("[AcpBackend] Received session update without update field:", api.contentLogMetadata(params));
|
|
1086
1087
|
return;
|
|
1087
1088
|
}
|
|
1088
1089
|
const sessionUpdateType = update.sessionUpdate;
|
|
1089
1090
|
const updateType = sessionUpdateType;
|
|
1090
|
-
const updateMetadata =
|
|
1091
|
+
const updateMetadata = api.contentLogMetadata(update);
|
|
1091
1092
|
persistence.logger.debug(`[AcpBackend] Received session update type: ${sessionUpdateType ?? "unknown"}`, updateMetadata);
|
|
1092
1093
|
if (this.options.verbose) {
|
|
1093
1094
|
logAcpBackendMuted(
|
|
@@ -1161,7 +1162,7 @@ class AcpBackend {
|
|
|
1161
1162
|
"current_mode_update"
|
|
1162
1163
|
];
|
|
1163
1164
|
if (updateType && !handledTypes.includes(updateType) && !update.messageChunk && !update.plan && !update.thinking) {
|
|
1164
|
-
persistence.logger.debug(`[AcpBackend] Unhandled session update type: ${updateType}`,
|
|
1165
|
+
persistence.logger.debug(`[AcpBackend] Unhandled session update type: ${updateType}`, api.contentLogMetadata(update));
|
|
1165
1166
|
}
|
|
1166
1167
|
}
|
|
1167
1168
|
// Promise resolver for waitForIdle - set when waiting for response to complete
|
|
@@ -1185,7 +1186,7 @@ class AcpBackend {
|
|
|
1185
1186
|
try {
|
|
1186
1187
|
persistence.logger.debug("[AcpBackend] Sending prompt:", {
|
|
1187
1188
|
sessionId,
|
|
1188
|
-
...
|
|
1189
|
+
...api.contentLogMetadata(prompt)
|
|
1189
1190
|
});
|
|
1190
1191
|
const contentBlock = {
|
|
1191
1192
|
type: "text",
|
|
@@ -1205,7 +1206,7 @@ class AcpBackend {
|
|
|
1205
1206
|
}
|
|
1206
1207
|
persistence.logger.debug("[AcpBackend] Prompt request sent to ACP connection");
|
|
1207
1208
|
} catch (error) {
|
|
1208
|
-
persistence.logger.debug("[AcpBackend] Error sending prompt:",
|
|
1209
|
+
persistence.logger.debug("[AcpBackend] Error sending prompt:", api.errorLogMetadata(error));
|
|
1209
1210
|
this.waitingForResponse = false;
|
|
1210
1211
|
let errorDetail;
|
|
1211
1212
|
if (error instanceof Error) {
|
|
@@ -1256,8 +1257,8 @@ class AcpBackend {
|
|
|
1256
1257
|
} catch (error) {
|
|
1257
1258
|
persistence.logger.debug("[AcpBackend] Failed to set session config option:", {
|
|
1258
1259
|
configId,
|
|
1259
|
-
value:
|
|
1260
|
-
error:
|
|
1260
|
+
value: api.contentLogMetadata(value),
|
|
1261
|
+
error: api.errorLogMetadata(error)
|
|
1261
1262
|
});
|
|
1262
1263
|
return false;
|
|
1263
1264
|
}
|
|
@@ -1282,7 +1283,7 @@ class AcpBackend {
|
|
|
1282
1283
|
});
|
|
1283
1284
|
return true;
|
|
1284
1285
|
} catch (error) {
|
|
1285
|
-
persistence.logger.debug("[AcpBackend] Failed to set session mode:", { modeId, error:
|
|
1286
|
+
persistence.logger.debug("[AcpBackend] Failed to set session mode:", { modeId, error: api.errorLogMetadata(error) });
|
|
1286
1287
|
return false;
|
|
1287
1288
|
}
|
|
1288
1289
|
}
|
|
@@ -1304,7 +1305,7 @@ class AcpBackend {
|
|
|
1304
1305
|
});
|
|
1305
1306
|
return true;
|
|
1306
1307
|
} catch (error) {
|
|
1307
|
-
persistence.logger.debug("[AcpBackend] Failed to set session model:", { modelId, error:
|
|
1308
|
+
persistence.logger.debug("[AcpBackend] Failed to set session model:", { modelId, error: api.errorLogMetadata(error) });
|
|
1308
1309
|
return false;
|
|
1309
1310
|
}
|
|
1310
1311
|
}
|
|
@@ -1348,7 +1349,7 @@ class AcpBackend {
|
|
|
1348
1349
|
await this.connection.cancel({ sessionId: this.acpSessionId });
|
|
1349
1350
|
this.emit({ type: "status", status: "stopped", detail: "Cancelled by user" });
|
|
1350
1351
|
} catch (error) {
|
|
1351
|
-
persistence.logger.debug("[AcpBackend] Error cancelling:",
|
|
1352
|
+
persistence.logger.debug("[AcpBackend] Error cancelling:", api.errorLogMetadata(error));
|
|
1352
1353
|
}
|
|
1353
1354
|
}
|
|
1354
1355
|
/**
|
|
@@ -1382,7 +1383,7 @@ class AcpBackend {
|
|
|
1382
1383
|
// 2s timeout for graceful shutdown
|
|
1383
1384
|
]);
|
|
1384
1385
|
} catch (error) {
|
|
1385
|
-
persistence.logger.debug("[AcpBackend] Error during graceful shutdown:",
|
|
1386
|
+
persistence.logger.debug("[AcpBackend] Error during graceful shutdown:", api.errorLogMetadata(error));
|
|
1386
1387
|
}
|
|
1387
1388
|
}
|
|
1388
1389
|
if (this.process) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createId } from '@paralleldrive/cuid2';
|
|
2
|
-
import { s as streamKeyOf,
|
|
2
|
+
import { s as streamKeyOf, c as createEnvelope } from './types-n_VWu9qe.mjs';
|
|
3
3
|
|
|
4
4
|
function turnOptions(turnId, time) {
|
|
5
5
|
return turnId ? { turn: turnId, time } : { time };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var cuid2 = require('@paralleldrive/cuid2');
|
|
4
|
-
var
|
|
4
|
+
var api = require('./types-P5deH8Ls.cjs');
|
|
5
5
|
|
|
6
6
|
function turnOptions(turnId, time) {
|
|
7
7
|
return turnId ? { turn: turnId, time } : { time };
|
|
@@ -108,12 +108,12 @@ class AcpSessionManager {
|
|
|
108
108
|
}
|
|
109
109
|
const options = {
|
|
110
110
|
...turnOptions(this.currentTurnId, this.nextTime()),
|
|
111
|
-
...key ? { streamKey:
|
|
111
|
+
...key ? { streamKey: api.streamKeyOf(key.mid, key.idx) } : {}
|
|
112
112
|
};
|
|
113
113
|
if (type === "thinking") {
|
|
114
|
-
return [
|
|
114
|
+
return [api.createEnvelope("agent", { t: "text", text, thinking: true }, options)];
|
|
115
115
|
}
|
|
116
|
-
return [
|
|
116
|
+
return [api.createEnvelope("agent", { t: "text", text }, options)];
|
|
117
117
|
}
|
|
118
118
|
startTurn() {
|
|
119
119
|
if (this.currentTurnId) {
|
|
@@ -125,7 +125,7 @@ class AcpSessionManager {
|
|
|
125
125
|
this.blockCursor = 0;
|
|
126
126
|
return [
|
|
127
127
|
...flushed,
|
|
128
|
-
|
|
128
|
+
api.createEnvelope("agent", { t: "turn-start" }, { turn: this.currentTurnId, time: this.nextTime() })
|
|
129
129
|
];
|
|
130
130
|
}
|
|
131
131
|
endTurn(status) {
|
|
@@ -138,7 +138,7 @@ class AcpSessionManager {
|
|
|
138
138
|
this.acpCallToSessionCall.clear();
|
|
139
139
|
return [
|
|
140
140
|
...flushed,
|
|
141
|
-
|
|
141
|
+
api.createEnvelope("agent", { t: "turn-end", status }, { turn: turnId, time: this.nextTime() })
|
|
142
142
|
];
|
|
143
143
|
}
|
|
144
144
|
mapMessage(msg) {
|
|
@@ -156,7 +156,7 @@ class AcpSessionManager {
|
|
|
156
156
|
}
|
|
157
157
|
return [
|
|
158
158
|
...this.flush(),
|
|
159
|
-
|
|
159
|
+
api.createEnvelope("agent", { t: "text", text: trimmed, thinking: true }, turnOptions(this.currentTurnId, this.nextTime()))
|
|
160
160
|
];
|
|
161
161
|
}
|
|
162
162
|
if (msg.type === "status") {
|
|
@@ -174,7 +174,7 @@ class AcpSessionManager {
|
|
|
174
174
|
const call = this.ensureSessionCallId(msg.callId);
|
|
175
175
|
return [
|
|
176
176
|
...flushed,
|
|
177
|
-
|
|
177
|
+
api.createEnvelope("agent", {
|
|
178
178
|
t: "tool-call-start",
|
|
179
179
|
call,
|
|
180
180
|
name: msg.toolName,
|
|
@@ -190,7 +190,7 @@ class AcpSessionManager {
|
|
|
190
190
|
const result = toolCallEndResult(msg.result);
|
|
191
191
|
return [
|
|
192
192
|
...flushed,
|
|
193
|
-
|
|
193
|
+
api.createEnvelope("agent", { t: "tool-call-end", call, ...result ? { result } : {} }, turnOptions(this.currentTurnId, this.nextTime()))
|
|
194
194
|
];
|
|
195
195
|
}
|
|
196
196
|
return [];
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { n as normalizeHttpEndpoint, c as configuration, f as readSettings, r as readPersistedSessions, i as readCredentialsForConfiguredRelay } from './limits-DlbYYtOw.mjs';
|
|
4
|
+
import { open, mkdir, rename, unlink } from 'node:fs/promises';
|
|
5
|
+
import { constants } from 'node:fs';
|
|
6
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
const TeamScopeSchema = z.object({
|
|
9
|
+
serverUrl: z.string(),
|
|
10
|
+
scopeToken: z.string().min(1),
|
|
11
|
+
teamId: z.string().min(1),
|
|
12
|
+
botId: z.string().min(1),
|
|
13
|
+
taskId: z.string().optional(),
|
|
14
|
+
attemptId: z.string().optional()
|
|
15
|
+
});
|
|
16
|
+
function teamScopePath(sessionId) {
|
|
17
|
+
const supplied = process.env.VH_TEAM_SCOPE_FILE;
|
|
18
|
+
if (supplied) {
|
|
19
|
+
if (!isAbsolute(supplied)) throw new Error("VH_TEAM_SCOPE_FILE must be absolute");
|
|
20
|
+
return supplied;
|
|
21
|
+
}
|
|
22
|
+
if (!sessionId || !/^[a-zA-Z0-9_-]+$/.test(sessionId)) throw new Error("Teams requires a managed session or VH_TEAM_SCOPE_FILE");
|
|
23
|
+
return join(configuration.happyHomeDir, "teams", "scopes", `${sessionId}.json`);
|
|
24
|
+
}
|
|
25
|
+
async function readTeamScope(path) {
|
|
26
|
+
let file;
|
|
27
|
+
try {
|
|
28
|
+
file = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error.code === "ENOENT") return null;
|
|
31
|
+
throw new Error("Cannot open team scope");
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const stat = await file.stat();
|
|
35
|
+
if (typeof process.getuid === "function" && stat.uid !== process.getuid()) throw new Error("Team scope owner mismatch");
|
|
36
|
+
if (!stat.isFile() || process.platform !== "win32" && (stat.mode & 63) !== 0) throw new Error("Team scope must be a private 0600 file");
|
|
37
|
+
const scope = TeamScopeSchema.parse(JSON.parse(await file.readFile("utf8")));
|
|
38
|
+
scope.serverUrl = normalizeHttpEndpoint(scope.serverUrl, "team scope");
|
|
39
|
+
if (scope.serverUrl !== configuration.serverUrl) throw new Error("Team scope belongs to another server");
|
|
40
|
+
return scope;
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error("Invalid or unsafe team scope file");
|
|
43
|
+
} finally {
|
|
44
|
+
await file.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function writeTeamScope(path, scope) {
|
|
48
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
49
|
+
const temporary = `${path}.${randomUUID()}.new`;
|
|
50
|
+
const file = await open(temporary, "wx", 384);
|
|
51
|
+
try {
|
|
52
|
+
await file.writeFile(JSON.stringify(TeamScopeSchema.parse(scope)));
|
|
53
|
+
await file.sync();
|
|
54
|
+
} finally {
|
|
55
|
+
await file.close();
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
await rename(temporary, path);
|
|
59
|
+
} finally {
|
|
60
|
+
await unlink(temporary).catch(() => {
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function teamRequest(path, method, token, scoped, body) {
|
|
66
|
+
const response = await fetch(`${configuration.serverUrl}${path}`, {
|
|
67
|
+
method,
|
|
68
|
+
redirect: "error",
|
|
69
|
+
headers: { "content-type": "application/json", ...scoped ? { "x-happy-team-token": token } : { authorization: `Bearer ${token}` } },
|
|
70
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
71
|
+
signal: AbortSignal.timeout(25e3)
|
|
72
|
+
}).catch(() => {
|
|
73
|
+
throw new Error("Teams connection failed; outcome may be unknown. Reuse the request ID when retrying.");
|
|
74
|
+
});
|
|
75
|
+
if (!response.ok) throw new Error(`Teams request failed (HTTP ${response.status})`);
|
|
76
|
+
try {
|
|
77
|
+
return await response.json();
|
|
78
|
+
} catch {
|
|
79
|
+
throw new Error("Teams returned an invalid response");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function createTeamsClient(sessionId) {
|
|
83
|
+
const scopePath = () => teamScopePath(sessionId ?? process.env.HAPPY_SESSION_ID);
|
|
84
|
+
return {
|
|
85
|
+
async initialize(input) {
|
|
86
|
+
const path = scopePath();
|
|
87
|
+
if (await readTeamScope(path)) throw new Error("This session already has a team scope; use scoped team tools");
|
|
88
|
+
if (process.env.VH_TEAM_SCOPE_FILE) throw new Error("Assigned team scope is missing; ask the owner to restore it");
|
|
89
|
+
const sid = sessionId ?? process.env.HAPPY_SESSION_ID;
|
|
90
|
+
if (!sid) throw new Error("A managed session identity is required");
|
|
91
|
+
const machineId = (await readSettings()).machineId;
|
|
92
|
+
if (!machineId) throw new Error("Connect this machine to Very Happy before joining a team");
|
|
93
|
+
if (input.machineId && input.machineId !== machineId) throw new Error("Teams must use this session\u2019s local machine");
|
|
94
|
+
const persisted = readPersistedSessions()[sid];
|
|
95
|
+
if (!persisted || persisted.metadata.machineId !== machineId) throw new Error("Team identity must be a persisted session on this machine");
|
|
96
|
+
const credentials = await readCredentialsForConfiguredRelay();
|
|
97
|
+
if (!credentials) throw new Error("Sign in to Very Happy before creating or joining a team");
|
|
98
|
+
let teamId = input.teamId;
|
|
99
|
+
if (!teamId) {
|
|
100
|
+
const created = await teamRequest("/v1/teams", "POST", credentials.token, false, { name: input.name, machineId, requestId: input.requestId });
|
|
101
|
+
teamId = created.team.id;
|
|
102
|
+
} else {
|
|
103
|
+
const existing = await teamRequest(`/v1/teams/${encodeURIComponent(teamId)}`, "GET", credentials.token, false);
|
|
104
|
+
if (existing.team?.machineId !== machineId) throw new Error("Team belongs to another machine");
|
|
105
|
+
}
|
|
106
|
+
const result = await teamRequest(
|
|
107
|
+
`/v1/teams/${encodeURIComponent(teamId)}/actions`,
|
|
108
|
+
"POST",
|
|
109
|
+
credentials.token,
|
|
110
|
+
false,
|
|
111
|
+
{ requestId: input.requestId, action: { type: "join", name: input.name, sessionId: sid, ...input.botId ? { botId: input.botId } : {} } }
|
|
112
|
+
);
|
|
113
|
+
if (!result.credential?.token || !result.credential?.botId) throw new Error("Team join did not issue a credential");
|
|
114
|
+
await writeTeamScope(path, { serverUrl: configuration.serverUrl, teamId, botId: result.credential.botId, scopeToken: result.credential.token });
|
|
115
|
+
return { team: result.team, botId: result.credential.botId };
|
|
116
|
+
},
|
|
117
|
+
async inspect() {
|
|
118
|
+
const scope = await readTeamScope(scopePath());
|
|
119
|
+
if (!scope) throw new Error("Join or create a team first");
|
|
120
|
+
const { credential: _credential, ...safe } = await teamRequest(`/v1/teams/${encodeURIComponent(scope.teamId)}/agent`, "GET", scope.scopeToken, true);
|
|
121
|
+
return safe;
|
|
122
|
+
},
|
|
123
|
+
async action(action, requestId = randomUUID()) {
|
|
124
|
+
const scope = await readTeamScope(scopePath());
|
|
125
|
+
if (!scope) throw new Error("Join or create a team first");
|
|
126
|
+
const result = await teamRequest(`/v1/teams/${encodeURIComponent(scope.teamId)}/agent`, "POST", scope.scopeToken, true, { requestId, action });
|
|
127
|
+
const { credential: _credential, ...safe } = result;
|
|
128
|
+
return safe;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const TEAM_TOOL_NAMES = ["team_create", "team_join", "team_inspect", "team_delegate", "team_message", "team_submit", "team_accept", "team_return", "team_cancel", "team_handoff", "team_schedule_create", "team_schedule_pause", "team_schedule_resume", "team_schedule_cancel"];
|
|
134
|
+
const id = z.string().min(1).max(128);
|
|
135
|
+
const text = z.string().min(1).max(32e3);
|
|
136
|
+
const requestId = id.describe("Unique operation ID. Reuse the same ID and arguments after an uncertain response.");
|
|
137
|
+
function registerTeamsTools(server, sessionId, client = createTeamsClient(sessionId)) {
|
|
138
|
+
const register = (name, description, inputSchema, execute) => {
|
|
139
|
+
server.registerTool(name, { description, inputSchema, annotations: { readOnlyHint: name === "team_inspect", idempotentHint: true, openWorldHint: false, destructiveHint: ["team_cancel", "team_handoff", "team_return", "team_schedule_cancel"].includes(name) } }, async (args) => {
|
|
140
|
+
try {
|
|
141
|
+
return { content: [{ type: "text", text: JSON.stringify(await execute(args)) }], isError: false };
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : "Teams operation failed" }], isError: true };
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
register(
|
|
148
|
+
"team_create",
|
|
149
|
+
"Create an official Very Happy team and join this session as its root bot. Does not grant access outside this account.",
|
|
150
|
+
{ name: id, machineId: id.optional(), requestId },
|
|
151
|
+
(args) => client.initialize(args)
|
|
152
|
+
);
|
|
153
|
+
register(
|
|
154
|
+
"team_join",
|
|
155
|
+
"Join an existing team as a root bot using this session owner\u2019s account. Scoped workers cannot elevate their role.",
|
|
156
|
+
{ teamId: id, name: id, botId: id.optional().describe("Existing root bot to reconnect; preserves its identity"), requestId },
|
|
157
|
+
(args) => client.initialize(args)
|
|
158
|
+
);
|
|
159
|
+
register("team_inspect", "Read your visible team tasks, attempts and messages. Inspect before decisions; submission is not acceptance.", {}, () => client.inspect());
|
|
160
|
+
const action = (name, description, fields) => register(
|
|
161
|
+
`team_${name}`,
|
|
162
|
+
description,
|
|
163
|
+
{ requestId, ...fields },
|
|
164
|
+
({ requestId: key, ...args }) => client.action({ type: name, ...args }, key)
|
|
165
|
+
);
|
|
166
|
+
action("delegate", "Delegate a goal with acceptance criteria. Use parentTaskId for subdelegation. Existing assigneeBotId or a managed worker may execute it.", {
|
|
167
|
+
goal: text,
|
|
168
|
+
acceptance: z.array(text).min(1).max(32),
|
|
169
|
+
parentTaskId: id.optional(),
|
|
170
|
+
assigneeBotId: id.optional(),
|
|
171
|
+
botName: id.optional(),
|
|
172
|
+
directory: z.string().min(1).optional(),
|
|
173
|
+
assistant: z.enum(["claude", "codex", "pi-acp"]).optional()
|
|
174
|
+
});
|
|
175
|
+
action("message", "Send a persistent task-related message. Delivery does not mean the model has read or acted on it.", { taskId: id, body: text, recipientBotId: id.optional() });
|
|
176
|
+
action("submit", "Submit evidence for your current attempt and goal version. The owner still has to accept it.", { taskId: id, attemptId: id, goalVersion: z.number().int().positive(), result: text });
|
|
177
|
+
const current = { attemptId: id, goalVersion: z.number().int().positive() };
|
|
178
|
+
action("accept", "Accept a task you own after checking its evidence. Resource cleanup is tracked separately.", { taskId: id, ...current });
|
|
179
|
+
action("return", "Return your task for further work with a concrete reason.", { taskId: id, reason: text, ...current });
|
|
180
|
+
action("cancel", "Cancel an owned task and its dependent work. Cleanup may remain pending.", { taskId: id, reason: text, ...current });
|
|
181
|
+
action("handoff", "Move an owned task to another bot; prior execution authority is superseded.", { taskId: id, assigneeBotId: id, ...current });
|
|
182
|
+
register("team_schedule_create", "Create a persistent one-time or fixed-interval reminder for your own root bot. Delivery wakes the bound session; it does not prove task completion. Use epoch milliseconds, and inspect before retrying.", { requestId, name: id, botId: id, body: text, runAt: z.number().int().nonnegative(), intervalMs: z.number().int().min(6e4).optional() }, ({ requestId: key, ...args }) => client.action({ type: "schedule-create", ...args }, key));
|
|
183
|
+
for (const operation of ["pause", "resume", "cancel"]) {
|
|
184
|
+
register(`team_schedule_${operation}`, `${operation} your root bot\u2019s schedule using its current version from team_inspect. An already in-flight message may still arrive.`, { requestId, scheduleId: id, version: z.number().int().positive() }, ({ requestId: key, ...args }) => client.action({ type: `schedule-${operation}`, ...args }, key));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function trimIdent(text) {
|
|
189
|
+
const lines = text.split("\n");
|
|
190
|
+
while (lines.length > 0 && lines[0].trim() === "") {
|
|
191
|
+
lines.shift();
|
|
192
|
+
}
|
|
193
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
|
|
194
|
+
lines.pop();
|
|
195
|
+
}
|
|
196
|
+
const minSpaces = lines.reduce((min, line) => {
|
|
197
|
+
if (line.trim() === "") {
|
|
198
|
+
return min;
|
|
199
|
+
}
|
|
200
|
+
const leadingSpaces = line.match(/^\s*/)[0].length;
|
|
201
|
+
return Math.min(min, leadingSpaces);
|
|
202
|
+
}, Infinity);
|
|
203
|
+
const trimmedLines = lines.map((line) => line.slice(minSpaces));
|
|
204
|
+
return trimmedLines.join("\n");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const AGENT_GUIDANCE = (() => trimIdent(`
|
|
208
|
+
You are running inside Happy: the user is reading this session in a web client (often a phone), NOT in the terminal where you run. Two consequences:
|
|
209
|
+
|
|
210
|
+
- Terminal output is not a good delivery channel. When the user asks for a piece of text ("copy that", "give me X", "\u590D\u5236\u7ED9\u6211", "\u53D1\u6211"), hand it over with mcp__happy__copy_to_clipboard instead of printing it for them to select by hand.
|
|
211
|
+
- When you produce or want to show a FILE (a document you wrote, a report, an image, a PDF, a diff), call mcp__happy__open_preview with its path instead of cat-ing it. Their client opens the rendered file.
|
|
212
|
+
|
|
213
|
+
Judgement, not reflex: use these when the user's goal is to RECEIVE something. If they are asking a question you can just answer, answer it. Do not preview a file you only read for your own reasoning, and do not preview the same file repeatedly within a turn.
|
|
214
|
+
|
|
215
|
+
If you are working on a task the user is tracking, call mcp__happy__report_progress at meaningful milestones (started / blocked / needs review / done) so their board reflects reality. Do not report every tool call.
|
|
216
|
+
`))();
|
|
217
|
+
const PREVIEW_TOOL_NAME = "open_preview";
|
|
218
|
+
const PREVIEW_TOOL_TITLE = "Open File Preview for the User";
|
|
219
|
+
const PREVIEW_TOOL_DESCRIPTION = 'Open a file in the preview panel of every web client the user currently has open. Use this whenever you produce or want to show the user a file: a document or report you just wrote, a generated image or diagram, a PDF, a data file, or a source file you want them to look at. Trigger phrases include: "write me a doc", "show me that file", "let me see it", "\u5199\u4E2A\u6587\u6863", "\u7ED9\u6211\u770B\u770B\u8FD9\u4E2A\u6587\u4EF6", "\u6253\u5F00\u770B\u4E0B". Pass an absolute path (or one starting with ~). The file is read on this machine and rendered in their browser \u2014 markdown is rendered, images and PDFs are displayed, other text is syntax-highlighted. Prefer this over printing file contents to the terminal, which the user is probably not looking at. NOTE: this only requests that their client open the file; it cannot confirm the user actually looked at it, so do not assume they have seen it.';
|
|
220
|
+
const REPORT_PROGRESS_TOOL_NAME = "report_progress";
|
|
221
|
+
const REPORT_PROGRESS_TOOL_TITLE = "Report Progress to the Task Board";
|
|
222
|
+
const REPORT_PROGRESS_TOOL_DESCRIPTION = `Report what you are doing to the user's task board, so they can see the state of this session at a glance without reading the transcript. Call it at meaningful milestones only: when you start substantive work, when you get blocked and need the user, when something is ready for their review, and when you are done. Do NOT call it after every tool call or every file edit \u2014 it is a status line, not a log. Set attention to "blocked" when you cannot proceed without the user, "review" when you want them to look at something, and "none" for ordinary progress. Keep progress to one short line describing the current state (the user reads it on a card).`;
|
|
223
|
+
|
|
224
|
+
export { AGENT_GUIDANCE as A, PREVIEW_TOOL_NAME as P, REPORT_PROGRESS_TOOL_NAME as R, TEAM_TOOL_NAMES as T, PREVIEW_TOOL_TITLE as a, PREVIEW_TOOL_DESCRIPTION as b, createTeamsClient as c, trimIdent as d, REPORT_PROGRESS_TOOL_TITLE as e, REPORT_PROGRESS_TOOL_DESCRIPTION as f, registerTeamsTools as r, teamRequest as t };
|