@scotthuang/agent-knock-knock 0.12.15 → 0.12.16
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/CHANGELOG.md +21 -0
- package/README.md +5 -1
- package/dist/src/cli-core.js +61 -2
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/command-json-callback-transport.d.ts +38 -0
- package/dist/src/command-json-callback-transport.js +298 -0
- package/dist/src/command-json-callback-transport.js.map +1 -0
- package/dist/src/host-adapter.d.ts +52 -0
- package/dist/src/host-adapter.js +123 -0
- package/dist/src/host-adapter.js.map +1 -0
- package/dist/src/host-bridge-mcp.d.ts +22 -0
- package/dist/src/host-bridge-mcp.js +145 -0
- package/dist/src/host-bridge-mcp.js.map +1 -0
- package/dist/src/host-bridge-tools.d.ts +61 -0
- package/dist/src/host-bridge-tools.js +130 -0
- package/dist/src/host-bridge-tools.js.map +1 -0
- package/dist/src/host-bridge.d.ts +28 -0
- package/dist/src/host-bridge.js +156 -0
- package/dist/src/host-bridge.js.map +1 -0
- package/dist/src/host-lifecycle-service.d.ts +39 -0
- package/dist/src/host-lifecycle-service.js +101 -0
- package/dist/src/host-lifecycle-service.js.map +1 -0
- package/dist/src/host-profile-callback-transport.d.ts +14 -0
- package/dist/src/host-profile-callback-transport.js +70 -0
- package/dist/src/host-profile-callback-transport.js.map +1 -0
- package/dist/src/host-profile-cli-adapter.d.ts +8 -0
- package/dist/src/host-profile-cli-adapter.js +169 -0
- package/dist/src/host-profile-cli-adapter.js.map +1 -0
- package/dist/src/host-profile-runtime.d.ts +55 -0
- package/dist/src/host-profile-runtime.js +152 -0
- package/dist/src/host-profile-runtime.js.map +1 -0
- package/dist/src/host-profile.d.ts +121 -0
- package/dist/src/host-profile.js +723 -0
- package/dist/src/host-profile.js.map +1 -0
- package/dist/src/native-thread-resume-snapshot.js +1 -1
- package/dist/src/native-thread-resume-snapshot.js.map +1 -1
- package/dist/src/openclaw-plugin-command-adapter.d.ts +13 -0
- package/dist/src/openclaw-plugin-command-adapter.js +125 -47
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.js +1 -1
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-supervisor.d.ts +1 -1
- package/dist/src/openclaw-plugin-supervisor.js +44 -72
- package/dist/src/openclaw-plugin-supervisor.js.map +1 -1
- package/dist/src/terminal-command-cli-adapter.js +4 -4
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-dispatch-presenter.js +1 -1
- package/dist/src/terminal-dispatch-presenter.js.map +1 -1
- package/dist/src/terminal-watch-callback-cli-adapter.d.ts +2 -1
- package/dist/src/terminal-watch-callback-cli-adapter.js +4 -3
- package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-cli-adapter.d.ts +4 -0
- package/dist/src/terminal-watch-cli-adapter.js +33 -3
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-service.d.ts +5 -0
- package/dist/src/terminal-watch-service.js +6 -1
- package/dist/src/terminal-watch-service.js.map +1 -1
- package/dist/src/terminal-watch-store.d.ts +2 -0
- package/dist/src/terminal-watch-store.js +13 -0
- package/dist/src/terminal-watch-store.js.map +1 -1
- package/docs/host-bridge-profiles.md +325 -0
- package/examples/host-profiles/command-json-starter.json +62 -0
- package/package.json +30 -2
- package/schemas/host-profile-v1.schema.json +266 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { executorDefinitionForKind } from "./executors.js";
|
|
@@ -12,9 +13,37 @@ const OPENCLAW_RECONCILE_BINDING_AUTHORITY_KIND = "reconcile_binding";
|
|
|
12
13
|
const CALLBACK_METHOD = AKK_CALLBACK_METHOD;
|
|
13
14
|
export const defaultOpenClawRelayPath = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
14
15
|
const relayPathByApi = new WeakMap();
|
|
16
|
+
const relayEnvironmentByApi = new WeakMap();
|
|
17
|
+
const hostBridgePresentationApis = new WeakSet();
|
|
18
|
+
const hostBridgeAsyncRelayApis = new WeakSet();
|
|
19
|
+
const hostBridgeInvocationStorage = new AsyncLocalStorage();
|
|
15
20
|
export function bindOpenClawRelayPath(api, relayPath) {
|
|
16
21
|
relayPathByApi.set(api, relayPath);
|
|
17
22
|
}
|
|
23
|
+
/** Bind a private child-process environment for a Host adapter instance. */
|
|
24
|
+
export function bindOpenClawRelayEnvironment(api, environment) {
|
|
25
|
+
relayEnvironmentByApi.set(api, Object.freeze({ ...environment }));
|
|
26
|
+
}
|
|
27
|
+
/** Keep one shared tool implementation while selecting Host-neutral output. */
|
|
28
|
+
export function bindHostBridgeToolPresentation(api) {
|
|
29
|
+
hostBridgePresentationApis.add(api);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Keep an embedding Host's event loop responsive while AKK CLI work runs.
|
|
33
|
+
*
|
|
34
|
+
* OpenClaw deliberately retains its established synchronous relay behavior;
|
|
35
|
+
* only public Host adapters opt into the asynchronous child-process runner.
|
|
36
|
+
*/
|
|
37
|
+
export function bindHostBridgeAsyncRelay(api) {
|
|
38
|
+
hostBridgeAsyncRelayApis.add(api);
|
|
39
|
+
}
|
|
40
|
+
/** Scope one Host invocation's cancellation without sharing mutable state. */
|
|
41
|
+
export function withHostBridgeInvocationSignal(signal, operation) {
|
|
42
|
+
if (signal?.aborted) {
|
|
43
|
+
return Promise.reject(hostBridgeAbortError());
|
|
44
|
+
}
|
|
45
|
+
return hostBridgeInvocationStorage.run({ signal }, async () => operation());
|
|
46
|
+
}
|
|
18
47
|
export function registerOpenClawCommands(api, displayedResumeSnapshots) {
|
|
19
48
|
api.registerCommand?.({
|
|
20
49
|
name: "akk",
|
|
@@ -112,14 +141,14 @@ export function registerOpenClawCommands(api, displayedResumeSnapshots) {
|
|
|
112
141
|
description: "Execute one closed, exact-version native status inspection in an exact Codex or Claude Code terminal. Pass only terminal_id and inspection=status; AKK refreshes binding authority privately. Arbitrary slash commands remain unavailable. This creates no AKK Session, Turn, receipt, monitor, or callback.",
|
|
113
142
|
parameters: nativeInspectParameters,
|
|
114
143
|
normalizeTurnIdentity: false,
|
|
115
|
-
buildArgs: (params) => {
|
|
144
|
+
buildArgs: async (params) => {
|
|
116
145
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
117
146
|
const inspection = requiredString(params.inspection, "inspection");
|
|
118
147
|
if (inspection !== "status") {
|
|
119
148
|
throw new Error("inspection must be status");
|
|
120
149
|
}
|
|
121
150
|
const terminalId = requiredString(params.terminal_id, "terminal_id");
|
|
122
|
-
const action = privateTerminalActionArguments(api, terminalId, "agent_knock_knock_native_inspect");
|
|
151
|
+
const action = await privateTerminalActionArguments(api, terminalId, "agent_knock_knock_native_inspect");
|
|
123
152
|
const args = [
|
|
124
153
|
"native-inspect",
|
|
125
154
|
"--terminal",
|
|
@@ -140,10 +169,10 @@ export function registerOpenClawCommands(api, displayedResumeSnapshots) {
|
|
|
140
169
|
parameters: newThreadParameters,
|
|
141
170
|
normalizeTurnIdentity: false,
|
|
142
171
|
isErrorResult: (result) => !isAkkThreadTransitionSuccess(result),
|
|
143
|
-
buildArgs: (params) => {
|
|
172
|
+
buildArgs: async (params) => {
|
|
144
173
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
145
174
|
const terminalId = requiredString(params.terminal_id, "terminal_id");
|
|
146
|
-
const action = privateTerminalActionArguments(api, terminalId, "agent_knock_knock_new_thread");
|
|
175
|
+
const action = await privateTerminalActionArguments(api, terminalId, "agent_knock_knock_new_thread");
|
|
147
176
|
const args = [
|
|
148
177
|
"new-thread",
|
|
149
178
|
"--terminal",
|
|
@@ -161,11 +190,11 @@ export function registerOpenClawCommands(api, displayedResumeSnapshots) {
|
|
|
161
190
|
description: "Detach one exact conflicting managed Session binding without adopting the live replacement thread. Pass only the advertised terminal_id and conflicting_session_id after explicit user confirmation; AKK refreshes all revision and binding authority privately. This sends no coding-agent input and creates no Turn.",
|
|
162
191
|
parameters: reconcileBindingParameters,
|
|
163
192
|
normalizeTurnIdentity: false,
|
|
164
|
-
buildArgs: (params, toolContext) => {
|
|
193
|
+
buildArgs: async (params, toolContext) => {
|
|
165
194
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
166
195
|
const terminalId = requiredString(params.terminal_id, "terminal_id");
|
|
167
196
|
const conflictingSessionId = requiredString(params.conflicting_session_id, "conflicting_session_id");
|
|
168
|
-
const action = consumeDisplayedPrivateAction(api, {
|
|
197
|
+
const action = await consumeDisplayedPrivateAction(api, {
|
|
169
198
|
sessionKey: requiredOpenClawSessionKey(toolContext?.sessionKey),
|
|
170
199
|
sessionId: requiredOpenClawSessionId(toolContext?.sessionId),
|
|
171
200
|
kind: OPENCLAW_RECONCILE_BINDING_AUTHORITY_KIND,
|
|
@@ -204,11 +233,11 @@ export function registerOpenClawCommands(api, displayedResumeSnapshots) {
|
|
|
204
233
|
parameters: resumeThreadParameters,
|
|
205
234
|
normalizeTurnIdentity: false,
|
|
206
235
|
isErrorResult: (result) => !isAkkThreadTransitionSuccess(result),
|
|
207
|
-
buildArgs: (params) => {
|
|
236
|
+
buildArgs: async (params) => {
|
|
208
237
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
209
238
|
const terminalId = requiredString(params.terminal_id, "terminal_id");
|
|
210
239
|
const nativeThreadId = requiredString(params.native_thread_id, "native_thread_id");
|
|
211
|
-
const discovery = privateThreadDiscovery(api, terminalId);
|
|
240
|
+
const discovery = await privateThreadDiscovery(api, terminalId);
|
|
212
241
|
const candidate = Array.isArray(discovery.threads)
|
|
213
242
|
? discovery.threads.find((thread) => isRecord(thread) &&
|
|
214
243
|
thread.resumable === true &&
|
|
@@ -240,7 +269,7 @@ export function registerOpenClawCommands(api, displayedResumeSnapshots) {
|
|
|
240
269
|
parameters: statusParameters,
|
|
241
270
|
async execute(_toolCallId, params) {
|
|
242
271
|
try {
|
|
243
|
-
const result =
|
|
272
|
+
const result = await runHostAwareCli(api, buildStatusCliArgs(api, isRecord(params) ? params : {}));
|
|
244
273
|
const rendered = toolResult(result);
|
|
245
274
|
rememberDisplayedApprovalOffer(api, toolContext?.sessionKey, toolContext?.sessionId, result);
|
|
246
275
|
return rendered;
|
|
@@ -319,7 +348,7 @@ export function registerOpenClawCommands(api, displayedResumeSnapshots) {
|
|
|
319
348
|
});
|
|
320
349
|
registerCliTool(api, {
|
|
321
350
|
name: "agent_knock_knock_retry_callback",
|
|
322
|
-
description: "Retry a persisted AKK callback for an exact turn that failed before reaching
|
|
351
|
+
description: "Retry a persisted AKK callback for an exact turn that failed before reaching the controller Host. The original callback message id and turn identity are reused for idempotent delivery.",
|
|
323
352
|
parameters: retryCallbackParameters,
|
|
324
353
|
buildArgs: (params) => {
|
|
325
354
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
@@ -361,14 +390,14 @@ export function registerOpenClawCommands(api, displayedResumeSnapshots) {
|
|
|
361
390
|
});
|
|
362
391
|
}
|
|
363
392
|
function resumeSelectionScope(sessionKey, sessionId) {
|
|
364
|
-
const key = requiredString(sessionKey, "
|
|
393
|
+
const key = requiredString(sessionKey, "Controller session identity is required for snapshot-bound Resume");
|
|
365
394
|
const incarnation = stringValue(sessionId) ?? null;
|
|
366
395
|
return `openclaw:${createHash("sha256")
|
|
367
396
|
.update(JSON.stringify([key, incarnation]))
|
|
368
397
|
.digest("hex")}`;
|
|
369
398
|
}
|
|
370
399
|
function resumeSnapshotCacheKey(sessionKey, sessionId, terminalId) {
|
|
371
|
-
const key = requiredString(sessionKey, "
|
|
400
|
+
const key = requiredString(sessionKey, "Controller session identity is required for snapshot-bound Resume");
|
|
372
401
|
return JSON.stringify([key, stringValue(sessionId) ?? null, terminalId]);
|
|
373
402
|
}
|
|
374
403
|
function rememberDisplayedResumeSnapshot(snapshots, key, snapshotId, expiresAtMs) {
|
|
@@ -430,7 +459,7 @@ async function handleAkkCommand(api, ctx, displayedResumeSnapshots) {
|
|
|
430
459
|
return await handleAkkLifecycleCommand(api, ctx, parsed, config, displayedResumeSnapshots);
|
|
431
460
|
}
|
|
432
461
|
const args = parsed.action === "approve"
|
|
433
|
-
? buildPrivateApprovalArgs(api, { turn_id: parsed.turnId }, {
|
|
462
|
+
? await buildPrivateApprovalArgs(api, { turn_id: parsed.turnId }, {
|
|
434
463
|
sessionKey: requiredOpenClawSessionKey(ctx.sessionKey),
|
|
435
464
|
sessionId: requiredOpenClawSessionId(ctx.sessionId)
|
|
436
465
|
})
|
|
@@ -447,7 +476,7 @@ async function handleAkkCommand(api, ctx, displayedResumeSnapshots) {
|
|
|
447
476
|
// check. Keep the Gateway event loop free while that child CLI runs.
|
|
448
477
|
const result = parsed.action === "doctor"
|
|
449
478
|
? await runCliAsync(api, args, { allowNonzeroJson: true })
|
|
450
|
-
:
|
|
479
|
+
: await runHostAwareCli(api, args);
|
|
451
480
|
switch (parsed.action) {
|
|
452
481
|
case "doctor":
|
|
453
482
|
return {
|
|
@@ -485,6 +514,9 @@ async function handleAkkCommand(api, ctx, displayedResumeSnapshots) {
|
|
|
485
514
|
}
|
|
486
515
|
}
|
|
487
516
|
catch (error) {
|
|
517
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
518
|
+
throw error;
|
|
519
|
+
}
|
|
488
520
|
const message = modelFacingErrorMessage(error);
|
|
489
521
|
return {
|
|
490
522
|
text: `AKK command failed: ${message}`,
|
|
@@ -506,7 +538,7 @@ async function handleAkkLifecycleCommand(api, ctx, parsed, config, displayedResu
|
|
|
506
538
|
if (parsed.action === "resume-thread" &&
|
|
507
539
|
parsed.selection &&
|
|
508
540
|
["number", "short-id"].includes(parsed.selection.kind)) {
|
|
509
|
-
requiredString(ctx.sessionId, "
|
|
541
|
+
requiredString(ctx.sessionId, "Controller conversation incarnation is required for number or short-id Resume; run /akk threads again in the current conversation or use the complete UUID");
|
|
510
542
|
const mutationArgs = buildAkkCommandCliArgs(parsed, config, {
|
|
511
543
|
sessionKey: ctx.sessionKey,
|
|
512
544
|
selectionScope,
|
|
@@ -515,7 +547,7 @@ async function handleAkkLifecycleCommand(api, ctx, parsed, config, displayedResu
|
|
|
515
547
|
if (!mutationArgs) {
|
|
516
548
|
throw new Error("could not build snapshot-bound resume command");
|
|
517
549
|
}
|
|
518
|
-
const transitionResult =
|
|
550
|
+
const transitionResult = await runHostAwareCli(api, mutationArgs);
|
|
519
551
|
if (isAkkThreadTransitionSuccess(transitionResult)) {
|
|
520
552
|
displayedResumeSnapshots.delete(snapshotCacheKey);
|
|
521
553
|
}
|
|
@@ -531,7 +563,7 @@ async function handleAkkLifecycleCommand(api, ctx, parsed, config, displayedResu
|
|
|
531
563
|
if (!discoveryArgs) {
|
|
532
564
|
throw new Error("could not build native-thread discovery command");
|
|
533
565
|
}
|
|
534
|
-
const discovery =
|
|
566
|
+
const discovery = await runHostAwareCli(api, discoveryArgs);
|
|
535
567
|
if (parsed.action === "list-resumable-threads" ||
|
|
536
568
|
(parsed.action === "resume-thread" &&
|
|
537
569
|
!parsed.selection)) {
|
|
@@ -601,7 +633,7 @@ async function handleAkkLifecycleCommand(api, ctx, parsed, config, displayedResu
|
|
|
601
633
|
if (!mutationArgs) {
|
|
602
634
|
throw new Error("could not build native-thread lifecycle command");
|
|
603
635
|
}
|
|
604
|
-
const transitionResult =
|
|
636
|
+
const transitionResult = await runHostAwareCli(api, mutationArgs);
|
|
605
637
|
if (isAkkThreadTransitionSuccess(transitionResult)) {
|
|
606
638
|
displayedResumeSnapshots.delete(snapshotCacheKey);
|
|
607
639
|
}
|
|
@@ -661,7 +693,7 @@ function formatDelegateCommandResult(result) {
|
|
|
661
693
|
`session: ${sessionId}`,
|
|
662
694
|
`turn: ${turnId}`,
|
|
663
695
|
`status: ${result.conversation_status ?? result.status ?? "unknown"}`,
|
|
664
|
-
"The result will return to this
|
|
696
|
+
"The result will return to this controller session through the callback."
|
|
665
697
|
].join("\n");
|
|
666
698
|
}
|
|
667
699
|
function executorDisplayName(kind) {
|
|
@@ -1053,7 +1085,7 @@ async function runSendRequest(api, params, toolContext, messageId) {
|
|
|
1053
1085
|
throw new Error("send retry_submission accepts exactly turn_id; do not pass request, terminal_id, session_id, timeout overrides, or callback route data");
|
|
1054
1086
|
}
|
|
1055
1087
|
const turnId = authoritativeManagedId(params.turn_id, "turn_id");
|
|
1056
|
-
privateActionArguments(api, {
|
|
1088
|
+
await privateActionArguments(api, {
|
|
1057
1089
|
tool: "agent_knock_knock_send",
|
|
1058
1090
|
matches: (argumentsValue) => Object.keys(argumentsValue).length === 1 &&
|
|
1059
1091
|
stringValue(argumentsValue.turn_id) === turnId
|
|
@@ -1061,7 +1093,7 @@ async function runSendRequest(api, params, toolContext, messageId) {
|
|
|
1061
1093
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
1062
1094
|
const args = ["send", "--turn", turnId];
|
|
1063
1095
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
1064
|
-
return
|
|
1096
|
+
return runHostAwareCli(api, args);
|
|
1065
1097
|
}
|
|
1066
1098
|
const requestedType = Object.hasOwn(params, "type")
|
|
1067
1099
|
? stringValue(params.type)
|
|
@@ -1085,7 +1117,7 @@ async function runSendRequest(api, params, toolContext, messageId) {
|
|
|
1085
1117
|
return runDelegate(api, { ...params, messageId }, toolContext);
|
|
1086
1118
|
}
|
|
1087
1119
|
const terminalAction = terminalId
|
|
1088
|
-
? privateActionArguments(api, {
|
|
1120
|
+
? await privateActionArguments(api, {
|
|
1089
1121
|
tool: "agent_knock_knock_send",
|
|
1090
1122
|
terminalId,
|
|
1091
1123
|
matches: (argumentsValue) => stringValue(argumentsValue.selector) === terminalId ||
|
|
@@ -1122,7 +1154,7 @@ async function runSendRequest(api, params, toolContext, messageId) {
|
|
|
1122
1154
|
pushOptional(args, "--gateway-method", CALLBACK_METHOD);
|
|
1123
1155
|
pushOptional(args, "--gateway-session", openclawSession);
|
|
1124
1156
|
pushOptional(args, "--openclaw-bin", stringValue(config.openclawBin));
|
|
1125
|
-
return
|
|
1157
|
+
return runHostAwareCli(api, args);
|
|
1126
1158
|
}
|
|
1127
1159
|
function toolResult(result, { submissionErrors = false, normalizeTurnIdentity = true, forceError = false } = {}) {
|
|
1128
1160
|
const normalized = normalizeTurnIdentity ? withTurnIdentity(result) : result;
|
|
@@ -1139,14 +1171,14 @@ function toolResult(result, { submissionErrors = false, normalizeTurnIdentity =
|
|
|
1139
1171
|
...(submissionError || forceError ? { isError: true } : {})
|
|
1140
1172
|
};
|
|
1141
1173
|
}
|
|
1142
|
-
function privateList(api) {
|
|
1174
|
+
async function privateList(api) {
|
|
1143
1175
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
1144
1176
|
const args = ["list", "--reconcile"];
|
|
1145
1177
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
1146
1178
|
pushOptional(args, "--idle-timeout-minutes", numberString(config.idleTimeoutMinutes));
|
|
1147
|
-
return
|
|
1179
|
+
return runHostAwareCli(api, args);
|
|
1148
1180
|
}
|
|
1149
|
-
function privateThreadDiscovery(api, terminalId) {
|
|
1181
|
+
async function privateThreadDiscovery(api, terminalId) {
|
|
1150
1182
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
1151
1183
|
const args = [
|
|
1152
1184
|
"list-resumable-threads",
|
|
@@ -1155,17 +1187,17 @@ function privateThreadDiscovery(api, terminalId) {
|
|
|
1155
1187
|
];
|
|
1156
1188
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
1157
1189
|
pushOptional(args, "--codex-home", stringValue(config.codexHome));
|
|
1158
|
-
return
|
|
1190
|
+
return runHostAwareCli(api, args);
|
|
1159
1191
|
}
|
|
1160
|
-
function privateTerminalActionArguments(api, terminalId, tool) {
|
|
1192
|
+
async function privateTerminalActionArguments(api, terminalId, tool) {
|
|
1161
1193
|
return privateActionArguments(api, {
|
|
1162
1194
|
tool,
|
|
1163
1195
|
terminalId,
|
|
1164
1196
|
matches: (argumentsValue) => stringValue(argumentsValue.terminal_id) === terminalId
|
|
1165
1197
|
});
|
|
1166
1198
|
}
|
|
1167
|
-
function privateActionArguments(api, input) {
|
|
1168
|
-
const result = privateList(api);
|
|
1199
|
+
async function privateActionArguments(api, input) {
|
|
1200
|
+
const result = await privateList(api);
|
|
1169
1201
|
const terminals = input.terminalId
|
|
1170
1202
|
? terminalRows(result).filter((terminal) => stringValue(terminal.id) === input.terminalId)
|
|
1171
1203
|
: terminalRows(result);
|
|
@@ -1247,7 +1279,7 @@ function uniqueArgumentObjects(candidates) {
|
|
|
1247
1279
|
function privateAuthorityOfferKey(sessionKey, sessionId, kind, target) {
|
|
1248
1280
|
return { sessionKey, sessionId, kind, target };
|
|
1249
1281
|
}
|
|
1250
|
-
function consumeDisplayedPrivateAction(api, input) {
|
|
1282
|
+
async function consumeDisplayedPrivateAction(api, input) {
|
|
1251
1283
|
const offered = consumeOpenClawPrivateAuthorityOffer(api, {
|
|
1252
1284
|
sessionKey: input.sessionKey,
|
|
1253
1285
|
sessionId: input.sessionId,
|
|
@@ -1255,9 +1287,9 @@ function consumeDisplayedPrivateAction(api, input) {
|
|
|
1255
1287
|
target: input.target
|
|
1256
1288
|
});
|
|
1257
1289
|
if (!offered || !isRecord(offered.args)) {
|
|
1258
|
-
throw new Error(`${input.tool} requires a current action shown by AKK list in this
|
|
1290
|
+
throw new Error(`${input.tool} requires a current action shown by AKK list in this controller session; refresh list, review it, and explicitly confirm again`);
|
|
1259
1291
|
}
|
|
1260
|
-
const current = privateActionArguments(api, input);
|
|
1292
|
+
const current = await privateActionArguments(api, input);
|
|
1261
1293
|
if (JSON.stringify(current) !== JSON.stringify(offered.args)) {
|
|
1262
1294
|
throw new Error(`${input.tool} authority changed after it was shown; refresh AKK list, review the current action, and explicitly confirm again`);
|
|
1263
1295
|
}
|
|
@@ -1303,7 +1335,7 @@ function currentApprovalFingerprint(result) {
|
|
|
1303
1335
|
}
|
|
1304
1336
|
return unique[0];
|
|
1305
1337
|
}
|
|
1306
|
-
function buildPrivateApprovalArgs(api, params, { sessionKey, sessionId }) {
|
|
1338
|
+
async function buildPrivateApprovalArgs(api, params, { sessionKey, sessionId }) {
|
|
1307
1339
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
1308
1340
|
const turnId = stringValue(params.turn_id);
|
|
1309
1341
|
const terminalId = stringValue(params.terminal_id);
|
|
@@ -1319,10 +1351,10 @@ function buildPrivateApprovalArgs(api, params, { sessionKey, sessionId }) {
|
|
|
1319
1351
|
? stringValue(offered.args.expected_approval_fingerprint)
|
|
1320
1352
|
: undefined);
|
|
1321
1353
|
if (!isExactApprovalFingerprint(offeredFingerprint)) {
|
|
1322
|
-
throw new Error("approve requires a current approval request shown by agent_knock_knock_status in this
|
|
1354
|
+
throw new Error("approve requires a current approval request shown by agent_knock_knock_status in this controller conversation; refresh status, ask the user to review it, and explicitly confirm again");
|
|
1323
1355
|
}
|
|
1324
1356
|
const action = terminalId
|
|
1325
|
-
? privateActionArguments(api, {
|
|
1357
|
+
? await privateActionArguments(api, {
|
|
1326
1358
|
tool: "agent_knock_knock_approve",
|
|
1327
1359
|
terminalId,
|
|
1328
1360
|
matches: (argumentsValue) => stringValue(argumentsValue.terminal_id ?? argumentsValue.conversation_id) === terminalId
|
|
@@ -1343,7 +1375,7 @@ function buildPrivateApprovalArgs(api, params, { sessionKey, sessionId }) {
|
|
|
1343
1375
|
else {
|
|
1344
1376
|
args.push("--turn", requiredString(turnId, "turn_id"));
|
|
1345
1377
|
}
|
|
1346
|
-
const currentFingerprint = currentApprovalFingerprint(
|
|
1378
|
+
const currentFingerprint = currentApprovalFingerprint(await runHostAwareCli(api, statusArgs));
|
|
1347
1379
|
if (currentFingerprint !== offeredFingerprint) {
|
|
1348
1380
|
throw new Error("the approval request changed after it was shown; refresh AKK status, ask the user to review the current request, and explicitly confirm again");
|
|
1349
1381
|
}
|
|
@@ -1442,6 +1474,9 @@ function isLegacyAuthorityInstructionPath(path) {
|
|
|
1442
1474
|
return callbackMessageBody || normalized.includes("recentevents");
|
|
1443
1475
|
}
|
|
1444
1476
|
function modelFacingToolError(error) {
|
|
1477
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1478
|
+
return error;
|
|
1479
|
+
}
|
|
1445
1480
|
return new Error(modelFacingErrorMessage(error));
|
|
1446
1481
|
}
|
|
1447
1482
|
function modelFacingErrorMessage(error) {
|
|
@@ -1644,7 +1679,9 @@ async function runDelegate(api, params, toolContext) {
|
|
|
1644
1679
|
replayed: parsed.replayed === true,
|
|
1645
1680
|
background: parsed.background === true,
|
|
1646
1681
|
pid: parsed.pid ?? parsedTerminalControl?.panePid ?? null,
|
|
1647
|
-
callback_method:
|
|
1682
|
+
callback_method: hostBridgePresentationApis.has(api)
|
|
1683
|
+
? "command_json_v1"
|
|
1684
|
+
: CALLBACK_METHOD,
|
|
1648
1685
|
...(submissionUnfenced
|
|
1649
1686
|
? {
|
|
1650
1687
|
submission_outcome: "submitted",
|
|
@@ -1698,11 +1735,11 @@ async function runDelegate(api, params, toolContext) {
|
|
|
1698
1735
|
: {
|
|
1699
1736
|
openclaw_next_action: {
|
|
1700
1737
|
action: "yield",
|
|
1701
|
-
reason: "The coding agent is working in the shared terminal. End this
|
|
1738
|
+
reason: "The coding agent is working in the shared terminal. End this controller turn now and wait for an Agent Knock Knock callback.",
|
|
1702
1739
|
do_not: "Do not poll terminal internals while waiting. Further communication must use Agent Knock Knock tools so the same shared terminal remains authoritative.",
|
|
1703
|
-
expected_callback: "The callback will be injected
|
|
1740
|
+
expected_callback: "The callback will be injected into this controller session by its configured callback transport."
|
|
1704
1741
|
},
|
|
1705
|
-
note: "The task was sent to the shared terminal.
|
|
1742
|
+
note: "The task was sent to the shared terminal. The controller Host should yield now and wait for the callback turn."
|
|
1706
1743
|
})
|
|
1707
1744
|
};
|
|
1708
1745
|
}
|
|
@@ -1729,7 +1766,7 @@ function registerCliTool(api, { name, description, parameters, buildArgs, normal
|
|
|
1729
1766
|
parameters,
|
|
1730
1767
|
async execute(toolCallId, params) {
|
|
1731
1768
|
try {
|
|
1732
|
-
const result =
|
|
1769
|
+
const result = await runHostAwareCli(api, await buildArgs(isRecord(params) ? params : {}, toolContext, toolCallId));
|
|
1733
1770
|
const rendered = toolResult(result, {
|
|
1734
1771
|
submissionErrors: name === "agent_knock_knock_respond",
|
|
1735
1772
|
normalizeTurnIdentity,
|
|
@@ -1760,7 +1797,8 @@ export function runCli(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = f
|
|
|
1760
1797
|
const spawned = spawnSync(process.execPath, [binPath, ...cliArgs], {
|
|
1761
1798
|
encoding: "utf8",
|
|
1762
1799
|
maxBuffer: 1024 * 1024 * 10,
|
|
1763
|
-
cwd
|
|
1800
|
+
cwd,
|
|
1801
|
+
env: relayEnvironmentForApi(api)
|
|
1764
1802
|
});
|
|
1765
1803
|
if (spawned.error) {
|
|
1766
1804
|
throw new Error(`agent-knock-knock ${cliArgs[0]} failed to start: ${spawned.error.message}`);
|
|
@@ -1776,9 +1814,14 @@ export function runCli(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = f
|
|
|
1776
1814
|
export function runCliAsync(api, cliArgs, { cwd = process.cwd(), allowNonzeroJson = false, timeoutMs = 90_000 } = {}) {
|
|
1777
1815
|
const binPath = relayPathForApi(api);
|
|
1778
1816
|
const maxBuffer = 10 * 1024 * 1024;
|
|
1817
|
+
const signal = hostBridgeInvocationStorage.getStore()?.signal;
|
|
1818
|
+
if (signal?.aborted) {
|
|
1819
|
+
return Promise.reject(hostBridgeAbortError());
|
|
1820
|
+
}
|
|
1779
1821
|
return new Promise((resolve, reject) => {
|
|
1780
1822
|
const child = spawn(process.execPath, [binPath, ...cliArgs], {
|
|
1781
1823
|
cwd,
|
|
1824
|
+
env: relayEnvironmentForApi(api),
|
|
1782
1825
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1783
1826
|
windowsHide: true
|
|
1784
1827
|
});
|
|
@@ -1788,6 +1831,15 @@ export function runCliAsync(api, cliArgs, { cwd = process.cwd(), allowNonzeroJso
|
|
|
1788
1831
|
let stderr = "";
|
|
1789
1832
|
let overflow = false;
|
|
1790
1833
|
let timedOut = false;
|
|
1834
|
+
let aborted = false;
|
|
1835
|
+
const onAbort = () => {
|
|
1836
|
+
aborted = true;
|
|
1837
|
+
child.kill("SIGKILL");
|
|
1838
|
+
};
|
|
1839
|
+
const cleanup = () => {
|
|
1840
|
+
clearTimeout(timeout);
|
|
1841
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1842
|
+
};
|
|
1791
1843
|
const append = (current, chunk) => {
|
|
1792
1844
|
const next = current + chunk;
|
|
1793
1845
|
if (Buffer.byteLength(next, "utf8") > maxBuffer) {
|
|
@@ -1807,12 +1859,24 @@ export function runCliAsync(api, cliArgs, { cwd = process.cwd(), allowNonzeroJso
|
|
|
1807
1859
|
child.kill("SIGKILL");
|
|
1808
1860
|
}, timeoutMs);
|
|
1809
1861
|
timeout.unref();
|
|
1862
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1863
|
+
if (signal?.aborted) {
|
|
1864
|
+
onAbort();
|
|
1865
|
+
}
|
|
1810
1866
|
child.once("error", (error) => {
|
|
1811
|
-
|
|
1867
|
+
cleanup();
|
|
1868
|
+
if (aborted) {
|
|
1869
|
+
reject(hostBridgeAbortError());
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1812
1872
|
reject(new Error(`agent-knock-knock ${cliArgs[0]} failed to start: ${error.message}`));
|
|
1813
1873
|
});
|
|
1814
1874
|
child.once("close", (status) => {
|
|
1815
|
-
|
|
1875
|
+
cleanup();
|
|
1876
|
+
if (aborted) {
|
|
1877
|
+
reject(hostBridgeAbortError());
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1816
1880
|
if (timedOut) {
|
|
1817
1881
|
reject(new Error(`agent-knock-knock ${cliArgs[0]} timed out`));
|
|
1818
1882
|
return;
|
|
@@ -1845,9 +1909,23 @@ export function runCliAsync(api, cliArgs, { cwd = process.cwd(), allowNonzeroJso
|
|
|
1845
1909
|
});
|
|
1846
1910
|
});
|
|
1847
1911
|
}
|
|
1912
|
+
function hostBridgeAbortError() {
|
|
1913
|
+
const error = new Error("agent-knock-knock Host invocation was aborted");
|
|
1914
|
+
error.name = "AbortError";
|
|
1915
|
+
return error;
|
|
1916
|
+
}
|
|
1917
|
+
async function runHostAwareCli(api, cliArgs, options = {}) {
|
|
1918
|
+
if (hostBridgeAsyncRelayApis.has(api)) {
|
|
1919
|
+
return runCliAsync(api, cliArgs, options);
|
|
1920
|
+
}
|
|
1921
|
+
return runCli(api, cliArgs, options);
|
|
1922
|
+
}
|
|
1848
1923
|
function relayPathForApi(api) {
|
|
1849
1924
|
return relayPathByApi.get(api) ?? defaultOpenClawRelayPath;
|
|
1850
1925
|
}
|
|
1926
|
+
function relayEnvironmentForApi(api) {
|
|
1927
|
+
return relayEnvironmentByApi.get(api) ?? process.env;
|
|
1928
|
+
}
|
|
1851
1929
|
function pushTurnTarget(args, params) {
|
|
1852
1930
|
if (Object.hasOwn(params, "turn_id") && Object.hasOwn(params, "conversation_id")) {
|
|
1853
1931
|
throw new Error("turn-target tools accept only one of turn_id or conversation_id");
|
|
@@ -1903,10 +1981,10 @@ function requiredString(value, name) {
|
|
|
1903
1981
|
return value;
|
|
1904
1982
|
}
|
|
1905
1983
|
function requiredOpenClawSessionKey(value) {
|
|
1906
|
-
return requiredString(value, "
|
|
1984
|
+
return requiredString(value, "Controller session identity for this confirmed action");
|
|
1907
1985
|
}
|
|
1908
1986
|
function requiredOpenClawSessionId(value) {
|
|
1909
|
-
return requiredString(value, "
|
|
1987
|
+
return requiredString(value, "Controller conversation incarnation for this confirmed action");
|
|
1910
1988
|
}
|
|
1911
1989
|
function parseJson(text) {
|
|
1912
1990
|
try {
|