@scotthuang/agent-knock-knock 0.12.28 → 0.12.30
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 +17 -0
- package/dist/src/claude-injected-paste-proof.d.ts +59 -0
- package/dist/src/claude-injected-paste-proof.js +153 -0
- package/dist/src/claude-injected-paste-proof.js.map +1 -0
- package/dist/src/claude-terminal-agent-adapter.js +20 -1
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli-core.js +50 -35
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.d.ts +2 -1
- package/dist/src/codex-terminal-agent-adapter.js +40 -4
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/host-adapter.d.ts +1 -1
- package/dist/src/host-adapter.js +1 -1
- package/dist/src/host-bridge-tools.js +2 -2
- package/dist/src/openclaw-plugin-command-adapter.js +155 -24
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +2 -0
- package/dist/src/openclaw-plugin-helpers.js +28 -7
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +104 -0
- package/dist/src/openclaw-plugin-schemas.js +108 -0
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/openclaw-private-authority-offers.d.ts +2 -0
- package/dist/src/openclaw-private-authority-offers.js +12 -0
- package/dist/src/openclaw-private-authority-offers.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +34 -0
- package/dist/src/terminal-agent-adapter.js +54 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +76 -1
- package/dist/src/terminal-agent-bridge.js +873 -153
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-command-cli-adapter.d.ts +2 -1
- package/dist/src/terminal-command-cli-adapter.js +46 -8
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-dispatch-execution.js +15 -0
- package/dist/src/terminal-dispatch-execution.js.map +1 -1
- package/dist/src/terminal-identity-authority-cli-adapter.d.ts +1 -0
- package/dist/src/terminal-identity-authority-cli-adapter.js +10 -1
- package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-authority.d.ts +33 -0
- package/dist/src/terminal-interaction-authority.js +3 -0
- package/dist/src/terminal-interaction-authority.js.map +1 -0
- package/dist/src/terminal-interaction-cli-adapter.d.ts +66 -0
- package/dist/src/terminal-interaction-cli-adapter.js +354 -0
- package/dist/src/terminal-interaction-cli-adapter.js.map +1 -0
- package/dist/src/terminal-interaction-protocol.d.ts +111 -0
- package/dist/src/terminal-interaction-protocol.js +432 -0
- package/dist/src/terminal-interaction-protocol.js.map +1 -0
- package/dist/src/terminal-list-renderer.js +31 -2
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-monitor-cli-adapter.js +12 -3
- package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-decision-policy.d.ts +1 -1
- package/dist/src/terminal-monitor-state-cli-adapter.js +15 -1
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
- package/dist/src/terminal-mutation-cli-runtime.d.ts +44 -0
- package/dist/src/terminal-mutation-cli-runtime.js +41 -0
- package/dist/src/terminal-mutation-cli-runtime.js.map +1 -0
- package/dist/src/terminal-questionnaire-adapter.d.ts +84 -0
- package/dist/src/terminal-questionnaire-adapter.js +677 -0
- package/dist/src/terminal-questionnaire-adapter.js.map +1 -0
- package/docs/host-bridge-profiles.md +2 -2
- package/docs/quickstart-herdr.md +1 -1
- package/docs/quickstart-tmux.md +3 -1
- package/openclaw.plugin.json +4 -0
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +11 -8
|
@@ -43,7 +43,7 @@ export interface HostAdapter {
|
|
|
43
43
|
disposeContext(authority: object): void;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Adapt AKK's established slash command and
|
|
46
|
+
* Adapt AKK's established slash command and 17 semantic tools to a native Host.
|
|
47
47
|
*
|
|
48
48
|
* Public metadata is captured exactly once. Executable registries are created
|
|
49
49
|
* lazily per exact Host authority object so private approvals, action offers,
|
package/dist/src/host-adapter.js
CHANGED
|
@@ -9,7 +9,7 @@ const METADATA_CONTEXT = Object.freeze({
|
|
|
9
9
|
sessionId: "host-adapter:metadata"
|
|
10
10
|
});
|
|
11
11
|
/**
|
|
12
|
-
* Adapt AKK's established slash command and
|
|
12
|
+
* Adapt AKK's established slash command and 17 semantic tools to a native Host.
|
|
13
13
|
*
|
|
14
14
|
* Public metadata is captured exactly once. Executable registries are created
|
|
15
15
|
* lazily per exact Host authority object so private approvals, action offers,
|
|
@@ -97,8 +97,8 @@ export function createHostBridgeToolRegistry(options) {
|
|
|
97
97
|
if (!commandDescriptor) {
|
|
98
98
|
throw new Error("host bridge did not capture the akk command");
|
|
99
99
|
}
|
|
100
|
-
if (descriptors.length !==
|
|
101
|
-
throw new Error(`host bridge expected
|
|
100
|
+
if (descriptors.length !== 17) {
|
|
101
|
+
throw new Error(`host bridge expected 17 semantic tools, received ${descriptors.length}`);
|
|
102
102
|
}
|
|
103
103
|
const listed = Object.freeze([...descriptors]);
|
|
104
104
|
const capturedCommand = commandDescriptor;
|
|
@@ -3,10 +3,12 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { executorDefinitionForKind } from "./executors.js";
|
|
6
|
+
import { isTerminalApprovalDecision } from "./terminal-agent-adapter.js";
|
|
6
7
|
import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
|
|
7
8
|
import { AKK_CALLBACK_METHOD, akkUsageText, buildAkkCommandCliArgs, formatAkkListCommandResult, formatAkkRespondCommandResult, formatAkkTerminalWatchHint, formatAkkThreadsCommandResult, formatAkkThreadTransitionCommandResult, formatAkkUnwatchCommandResult, formatAkkWatchCommandResult, formatAkkWatchStatusCommandResult, isAkkModelFacingDiagnosticField, isAkkModelFacingPrivateAuthorityField, isAkkNativeSubmissionAccepted, isAkkThreadTransitionSuccess, normalizeAkkModelFacingFieldName, parseAkkCommand, resolvePluginStoreDir, sanitizeAkkModelFacingDiagnosticText, sanitizeAkkModelFacingLegacyAuthorityInstructionText } from "./openclaw-plugin-helpers.js";
|
|
8
|
-
import { approveParameters, cancelParameters, closeParameters, listParameters, listResumableThreadsParameters, nativeInspectParameters, newThreadParameters, reconcileBindingParameters, renewParameters, respondParameters, resumeThreadParameters, retryCallbackParameters, sendParameters, statusParameters, unwatchParameters, watchParameters } from "./openclaw-plugin-schemas.js";
|
|
9
|
-
import { consumeOpenClawPrivateAuthorityOffer, openClawApprovalAuthorityOfferKey, rememberOpenClawPrivateAuthorityOffer } from "./openclaw-private-authority-offers.js";
|
|
9
|
+
import { approveParameters, cancelParameters, closeParameters, listParameters, listResumableThreadsParameters, nativeInspectParameters, newThreadParameters, reconcileBindingParameters, renewParameters, respondInteractionParameters, respondParameters, resumeThreadParameters, retryCallbackParameters, sendParameters, statusParameters, unwatchParameters, watchParameters } from "./openclaw-plugin-schemas.js";
|
|
10
|
+
import { consumeOpenClawPrivateAuthorityOffer, openClawApprovalAuthorityOfferKey, openClawInteractionAuthorityOfferKey, rememberOpenClawPrivateAuthorityOffer } from "./openclaw-private-authority-offers.js";
|
|
11
|
+
import { TERMINAL_INTERACTION_LIMITS, validateTerminalInteractionProjection, validateTerminalInteractionResponse } from "./terminal-interaction-protocol.js";
|
|
10
12
|
const MAX_DISPLAYED_RESUME_SNAPSHOTS = 512;
|
|
11
13
|
const OPENCLAW_HANDOFF_AUTHORITY_KIND = "handoff";
|
|
12
14
|
const OPENCLAW_RECONCILE_BINDING_AUTHORITY_KIND = "reconcile_binding";
|
|
@@ -272,6 +274,7 @@ export function registerOpenClawCommands(api, displayedResumeSnapshots) {
|
|
|
272
274
|
const result = await runHostAwareCli(api, buildStatusCliArgs(api, isRecord(params) ? params : {}));
|
|
273
275
|
const rendered = toolResult(result);
|
|
274
276
|
rememberDisplayedApprovalOffer(api, toolContext?.sessionKey, toolContext?.sessionId, result);
|
|
277
|
+
rememberDisplayedInteractionOffer(api, toolContext?.sessionKey, toolContext?.sessionId, result);
|
|
275
278
|
return rendered;
|
|
276
279
|
}
|
|
277
280
|
catch (error) {
|
|
@@ -324,9 +327,18 @@ export function registerOpenClawCommands(api, displayedResumeSnapshots) {
|
|
|
324
327
|
return args;
|
|
325
328
|
}
|
|
326
329
|
});
|
|
330
|
+
registerCliTool(api, {
|
|
331
|
+
name: "agent_knock_knock_respond_interaction",
|
|
332
|
+
description: "Answer exactly one current native questionnaire step shown by agent_knock_knock_status in this controller conversation. Supply only the exact turn_id, interaction_id, and typed semantic answer using advertised question_id and option_id values. AKK consumes the displayed private offer and revalidates its prompt, expiry, owner, and terminal authority before any input. Raw keys, menu indexes, rendered labels, fingerprints, versions, and terminal commands are never accepted. An uncertain response must never be retried blindly.",
|
|
333
|
+
parameters: respondInteractionParameters,
|
|
334
|
+
buildArgs: (params, toolContext) => buildPrivateInteractionResponseArgs(api, params, {
|
|
335
|
+
sessionKey: requiredOpenClawSessionKey(toolContext?.sessionKey),
|
|
336
|
+
sessionId: requiredOpenClawSessionId(toolContext?.sessionId)
|
|
337
|
+
})
|
|
338
|
+
});
|
|
327
339
|
registerCliTool(api, {
|
|
328
340
|
name: "agent_knock_knock_approve",
|
|
329
|
-
description: "
|
|
341
|
+
description: "Dispatch one closed semantic decision for the current exact permission request only after the user reviews and explicitly chooses it. decision defaults to approve_once for compatibility; reject is available only on a managed Turn when the adapter proves a safe native reject choice. Use turn_id for a managed Turn or terminal_id for a separately advertised approve_once-only terminal action. AKK privately refreshes the prompt and authority, then recaptures it under lock. Raw keys, indexes, and labels are not accepted. Never retry an interrupted decision blindly.",
|
|
330
342
|
parameters: approveParameters,
|
|
331
343
|
buildArgs: (params, toolContext) => buildPrivateApprovalArgs(api, params, {
|
|
332
344
|
sessionKey: requiredOpenClawSessionKey(toolContext?.sessionKey),
|
|
@@ -459,7 +471,10 @@ async function handleAkkCommand(api, ctx, displayedResumeSnapshots) {
|
|
|
459
471
|
return await handleAkkLifecycleCommand(api, ctx, parsed, config, displayedResumeSnapshots);
|
|
460
472
|
}
|
|
461
473
|
const args = parsed.action === "approve"
|
|
462
|
-
? await buildPrivateApprovalArgs(api, {
|
|
474
|
+
? await buildPrivateApprovalArgs(api, {
|
|
475
|
+
turn_id: parsed.turnId,
|
|
476
|
+
decision: parsed.decision
|
|
477
|
+
}, {
|
|
463
478
|
sessionKey: requiredOpenClawSessionKey(ctx.sessionKey),
|
|
464
479
|
sessionId: requiredOpenClawSessionId(ctx.sessionId)
|
|
465
480
|
})
|
|
@@ -1261,13 +1276,45 @@ function rememberDisplayedApprovalOffer(api, sessionKeyValue, sessionIdValue, re
|
|
|
1261
1276
|
const sessionId = stringValue(sessionIdValue);
|
|
1262
1277
|
if (!sessionKey || !sessionId || !isRecord(result))
|
|
1263
1278
|
return;
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1279
|
+
const decisionFingerprints = currentApprovalDecisionFingerprints(result);
|
|
1280
|
+
const fingerprint = decisionFingerprints.approve_once;
|
|
1281
|
+
if (!fingerprint)
|
|
1266
1282
|
return;
|
|
1267
1283
|
const target = approvalTargetFromStatus(result);
|
|
1268
1284
|
if (!target)
|
|
1269
1285
|
return;
|
|
1270
|
-
rememberOpenClawPrivateAuthorityOffer(api, openClawApprovalAuthorityOfferKey(sessionKey, sessionId, target), {
|
|
1286
|
+
rememberOpenClawPrivateAuthorityOffer(api, openClawApprovalAuthorityOfferKey(sessionKey, sessionId, target), {
|
|
1287
|
+
fingerprint,
|
|
1288
|
+
decision_fingerprints: decisionFingerprints
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
function rememberDisplayedInteractionOffer(api, sessionKeyValue, sessionIdValue, result) {
|
|
1292
|
+
const sessionKey = stringValue(sessionKeyValue);
|
|
1293
|
+
const sessionId = stringValue(sessionIdValue);
|
|
1294
|
+
if (!sessionKey || !sessionId || !isRecord(result))
|
|
1295
|
+
return;
|
|
1296
|
+
const terminalStatus = isRecord(result.terminal_status)
|
|
1297
|
+
? result.terminal_status
|
|
1298
|
+
: undefined;
|
|
1299
|
+
const fingerprint = stringValue(terminalStatus?.interaction_prompt_fingerprint);
|
|
1300
|
+
if (!isExactInteractionFingerprint(fingerprint))
|
|
1301
|
+
return;
|
|
1302
|
+
let interactionState;
|
|
1303
|
+
try {
|
|
1304
|
+
interactionState = validateTerminalInteractionProjection(terminalStatus?.interaction_state);
|
|
1305
|
+
}
|
|
1306
|
+
catch {
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
if (interactionState.state !== "pending" ||
|
|
1310
|
+
interactionState.capabilities.respond !== true ||
|
|
1311
|
+
publicTurnIdentity(result).turnId !== interactionState.turn_id) {
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
rememberOpenClawPrivateAuthorityOffer(api, openClawInteractionAuthorityOfferKey(sessionKey, sessionId, interactionState.turn_id, interactionState.interaction_id), {
|
|
1315
|
+
fingerprint,
|
|
1316
|
+
interaction_state: interactionState
|
|
1317
|
+
});
|
|
1271
1318
|
}
|
|
1272
1319
|
function approvalTargetFromStatus(result) {
|
|
1273
1320
|
const conversationId = stringValue(result.conversation_id);
|
|
@@ -1340,27 +1387,43 @@ function authoritativeHandoffActionArguments(terminals) {
|
|
|
1340
1387
|
: [];
|
|
1341
1388
|
});
|
|
1342
1389
|
}
|
|
1343
|
-
function currentApprovalFingerprint(result) {
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1390
|
+
function currentApprovalFingerprint(result, decision) {
|
|
1391
|
+
const fingerprint = isRecord(result)
|
|
1392
|
+
? currentApprovalDecisionFingerprints(result)[decision]
|
|
1393
|
+
: undefined;
|
|
1394
|
+
if (!fingerprint) {
|
|
1395
|
+
throw new Error(`the current status does not contain one exact ${decision} choice; refresh status and ask the user to review it again`);
|
|
1347
1396
|
}
|
|
1348
|
-
return
|
|
1397
|
+
return fingerprint;
|
|
1349
1398
|
}
|
|
1350
1399
|
async function buildPrivateApprovalArgs(api, params, { sessionKey, sessionId }) {
|
|
1351
1400
|
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
1352
1401
|
const turnId = stringValue(params.turn_id);
|
|
1353
1402
|
const terminalId = stringValue(params.terminal_id);
|
|
1403
|
+
const decisionValue = params.decision ?? "approve_once";
|
|
1404
|
+
if (!isTerminalApprovalDecision(decisionValue)) {
|
|
1405
|
+
throw new Error("decision must be one of: approve_once, reject");
|
|
1406
|
+
}
|
|
1407
|
+
const decision = decisionValue;
|
|
1354
1408
|
if (Boolean(turnId) === Boolean(terminalId)) {
|
|
1355
1409
|
throw new Error("approve requires exactly one of turn_id or terminal_id");
|
|
1356
1410
|
}
|
|
1411
|
+
if (terminalId && decision !== "approve_once") {
|
|
1412
|
+
throw new Error("terminal-scoped approval supports approve_once only; reject requires an exact managed Turn");
|
|
1413
|
+
}
|
|
1357
1414
|
const target = terminalId
|
|
1358
1415
|
? { type: "terminal_id", id: terminalId }
|
|
1359
1416
|
: { type: "turn_id", id: requiredString(turnId, "turn_id") };
|
|
1360
1417
|
const offered = consumeOpenClawPrivateAuthorityOffer(api, openClawApprovalAuthorityOfferKey(sessionKey, sessionId, target));
|
|
1361
|
-
const
|
|
1418
|
+
const offeredDecisions = isRecord(offered?.decision_fingerprints)
|
|
1419
|
+
? offered.decision_fingerprints
|
|
1420
|
+
: undefined;
|
|
1421
|
+
const offeredFingerprint = stringValue(offeredDecisions?.[decision]) ??
|
|
1422
|
+
(decision === "approve_once" ? stringValue(offered?.fingerprint) : undefined) ??
|
|
1362
1423
|
(isRecord(offered?.args)
|
|
1363
|
-
?
|
|
1424
|
+
? decision === "approve_once"
|
|
1425
|
+
? stringValue(offered.args.expected_approval_fingerprint)
|
|
1426
|
+
: undefined
|
|
1364
1427
|
: undefined);
|
|
1365
1428
|
if (!isExactApprovalFingerprint(offeredFingerprint)) {
|
|
1366
1429
|
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");
|
|
@@ -1387,10 +1450,11 @@ async function buildPrivateApprovalArgs(api, params, { sessionKey, sessionId })
|
|
|
1387
1450
|
else {
|
|
1388
1451
|
args.push("--turn", requiredString(turnId, "turn_id"));
|
|
1389
1452
|
}
|
|
1390
|
-
const currentFingerprint = currentApprovalFingerprint(await runHostAwareCli(api, statusArgs));
|
|
1453
|
+
const currentFingerprint = currentApprovalFingerprint(await runHostAwareCli(api, statusArgs), decision);
|
|
1391
1454
|
if (currentFingerprint !== offeredFingerprint) {
|
|
1392
1455
|
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");
|
|
1393
1456
|
}
|
|
1457
|
+
args.push("--decision", decision);
|
|
1394
1458
|
args.push("--expected-approval-fingerprint", currentFingerprint);
|
|
1395
1459
|
if (terminalId) {
|
|
1396
1460
|
args.push("--expected-terminal-token", requiredString(action?.expected_terminal_token, "current internal terminal approval authority"));
|
|
@@ -1398,10 +1462,43 @@ async function buildPrivateApprovalArgs(api, params, { sessionKey, sessionId })
|
|
|
1398
1462
|
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
1399
1463
|
return args;
|
|
1400
1464
|
}
|
|
1465
|
+
function buildPrivateInteractionResponseArgs(api, params, { sessionKey, sessionId }) {
|
|
1466
|
+
const config = isRecord(api.pluginConfig) ? api.pluginConfig : {};
|
|
1467
|
+
const turnId = requiredTerminalInteractionIdentifier(params.turn_id, "turn_id");
|
|
1468
|
+
const interactionId = requiredTerminalInteractionIdentifier(params.interaction_id, "interaction_id");
|
|
1469
|
+
const offered = consumeOpenClawPrivateAuthorityOffer(api, openClawInteractionAuthorityOfferKey(sessionKey, sessionId, turnId, interactionId));
|
|
1470
|
+
const fingerprint = stringValue(offered?.fingerprint);
|
|
1471
|
+
if (!isExactInteractionFingerprint(fingerprint) ||
|
|
1472
|
+
offered?.interaction_state === undefined) {
|
|
1473
|
+
throw new Error("respond_interaction requires a current pending interaction shown by agent_knock_knock_status in this controller conversation; refresh status, review the current questions, and respond again");
|
|
1474
|
+
}
|
|
1475
|
+
const projection = validateTerminalInteractionProjection(offered.interaction_state);
|
|
1476
|
+
const response = validateTerminalInteractionResponse(params, projection);
|
|
1477
|
+
const args = [
|
|
1478
|
+
"respond-interaction",
|
|
1479
|
+
"--turn",
|
|
1480
|
+
response.turn_id,
|
|
1481
|
+
"--interaction",
|
|
1482
|
+
response.interaction_id,
|
|
1483
|
+
"--response-json",
|
|
1484
|
+
JSON.stringify(response),
|
|
1485
|
+
"--expected-interaction-fingerprint",
|
|
1486
|
+
fingerprint,
|
|
1487
|
+
"--expected-interaction-expires-at",
|
|
1488
|
+
projection.expires_at,
|
|
1489
|
+
"--openclaw-session",
|
|
1490
|
+
sessionKey
|
|
1491
|
+
];
|
|
1492
|
+
pushOptional(args, "--store-dir", resolvePluginStoreDir(config));
|
|
1493
|
+
return args;
|
|
1494
|
+
}
|
|
1401
1495
|
function isExactApprovalFingerprint(value) {
|
|
1402
1496
|
return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
|
|
1403
1497
|
}
|
|
1404
|
-
function
|
|
1498
|
+
function isExactInteractionFingerprint(value) {
|
|
1499
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
|
|
1500
|
+
}
|
|
1501
|
+
function currentApprovalDecisionFingerprints(result) {
|
|
1405
1502
|
const terminalStatus = isRecord(result.terminal_status)
|
|
1406
1503
|
? result.terminal_status
|
|
1407
1504
|
: undefined;
|
|
@@ -1411,14 +1508,31 @@ function currentApprovalFingerprints(result) {
|
|
|
1411
1508
|
? terminalStatus.approval_state
|
|
1412
1509
|
: undefined
|
|
1413
1510
|
];
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1511
|
+
const found = new Map();
|
|
1512
|
+
const remember = (decision, value) => {
|
|
1513
|
+
if (!isExactApprovalFingerprint(value))
|
|
1514
|
+
return;
|
|
1515
|
+
const fingerprints = found.get(decision) ?? new Set();
|
|
1516
|
+
fingerprints.add(value);
|
|
1517
|
+
found.set(decision, fingerprints);
|
|
1518
|
+
};
|
|
1519
|
+
for (const state of states) {
|
|
1520
|
+
const fingerprint = state?.approvable === true
|
|
1521
|
+
? stringValue(state.fingerprint)
|
|
1522
|
+
: undefined;
|
|
1523
|
+
remember("approve_once", fingerprint);
|
|
1524
|
+
if (state?.approvable !== true || !Array.isArray(state.choices))
|
|
1525
|
+
continue;
|
|
1526
|
+
for (const choice of state.choices) {
|
|
1527
|
+
if (isRecord(choice) &&
|
|
1528
|
+
isTerminalApprovalDecision(choice.decision)) {
|
|
1529
|
+
remember(choice.decision, choice.fingerprint);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
return Object.fromEntries([...found.entries()].flatMap(([decision, fingerprints]) => fingerprints.size === 1
|
|
1534
|
+
? [[decision, [...fingerprints][0]]]
|
|
1535
|
+
: []));
|
|
1422
1536
|
}
|
|
1423
1537
|
function sanitizeModelFacingValue(value, parentKey, path = []) {
|
|
1424
1538
|
if (Array.isArray(value)) {
|
|
@@ -1449,6 +1563,15 @@ function sanitizeModelFacingValue(value, parentKey, path = []) {
|
|
|
1449
1563
|
(key === "fingerprint" && parentKey === "approval_state")) {
|
|
1450
1564
|
continue;
|
|
1451
1565
|
}
|
|
1566
|
+
if (key === "interaction_state") {
|
|
1567
|
+
try {
|
|
1568
|
+
output[key] = validateTerminalInteractionProjection(item);
|
|
1569
|
+
}
|
|
1570
|
+
catch {
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1452
1575
|
output[key] = sanitizeModelFacingValue(item, key, [...path, key]);
|
|
1453
1576
|
}
|
|
1454
1577
|
if (output.tool === "agent_knock_knock_send" && isRecord(output.arguments)) {
|
|
@@ -2000,6 +2123,14 @@ function requiredOpenClawSessionKey(value) {
|
|
|
2000
2123
|
function requiredOpenClawSessionId(value) {
|
|
2001
2124
|
return requiredString(value, "Controller conversation incarnation for this confirmed action");
|
|
2002
2125
|
}
|
|
2126
|
+
function requiredTerminalInteractionIdentifier(value, name) {
|
|
2127
|
+
const identifier = requiredString(value, name);
|
|
2128
|
+
if (identifier.length > TERMINAL_INTERACTION_LIMITS.maxIdentifierLength ||
|
|
2129
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u.test(identifier)) {
|
|
2130
|
+
throw new Error(`${name} must be an exact safe interaction identifier`);
|
|
2131
|
+
}
|
|
2132
|
+
return identifier;
|
|
2133
|
+
}
|
|
2003
2134
|
function parseJson(text) {
|
|
2004
2135
|
try {
|
|
2005
2136
|
return JSON.parse(text);
|