@scotthuang/agent-knock-knock 0.12.32 → 0.12.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/src/callback-cli-adapter.d.ts +2 -1
  3. package/dist/src/callback-cli-adapter.js +1 -0
  4. package/dist/src/callback-cli-adapter.js.map +1 -1
  5. package/dist/src/callback-outbox-policy.d.ts +12 -1
  6. package/dist/src/callback-outbox-policy.js +51 -0
  7. package/dist/src/callback-outbox-policy.js.map +1 -1
  8. package/dist/src/callback-outbox-service.d.ts +19 -0
  9. package/dist/src/callback-outbox-service.js +63 -0
  10. package/dist/src/callback-outbox-service.js.map +1 -1
  11. package/dist/src/openclaw-managed-callback-cli-adapter.d.ts +2 -0
  12. package/dist/src/openclaw-managed-callback-cli-adapter.js +9 -1
  13. package/dist/src/openclaw-managed-callback-cli-adapter.js.map +1 -1
  14. package/dist/src/openclaw-plugin-callback-adapter.js +45 -20
  15. package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -1
  16. package/dist/src/openclaw-plugin-command-adapter.js +1 -1
  17. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  18. package/dist/src/openclaw-plugin-schemas.d.ts +34 -62
  19. package/dist/src/openclaw-plugin-schemas.js +28 -64
  20. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  21. package/dist/src/terminal-agent-bridge.d.ts +19 -0
  22. package/dist/src/terminal-agent-bridge.js +61 -11
  23. package/dist/src/terminal-agent-bridge.js.map +1 -1
  24. package/dist/src/terminal-interaction-cli-adapter.js +138 -15
  25. package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
  26. package/dist/src/terminal-monitor-application-service.d.ts +22 -0
  27. package/dist/src/terminal-monitor-application-service.js +161 -1
  28. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  29. package/dist/src/terminal-monitor-cli-adapter.d.ts +37 -1
  30. package/dist/src/terminal-monitor-cli-adapter.js +88 -0
  31. package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
  32. package/dist/src/terminal-monitor-state-cli-adapter.d.ts +1 -1
  33. package/dist/src/terminal-monitor-state-cli-adapter.js +235 -1
  34. package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
  35. package/dist/src/terminal-questionnaire-adapter.js +66 -15
  36. package/dist/src/terminal-questionnaire-adapter.js.map +1 -1
  37. package/package.json +1 -1
  38. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +1 -1
@@ -11,13 +11,14 @@ import { readNdjsonLog } from "./transcript.js";
11
11
  import { createFileLockCliAdapter } from "./file-lock-cli-adapter.js";
12
12
  import { cliEnv, cliExit, cliNow, cliNowMs, cliPid, cliRuntimeLog, cliSleepSync } from "./cli-runtime-context.js";
13
13
  import { expandHome } from "./cli-command-runtime.js";
14
+ import { validateTerminalInteractionProjection } from "./terminal-interaction-protocol.js";
14
15
  import { terminalControlFromTakeover } from "./terminal-runtime-cli-adapter.js";
15
16
  import { terminalControlsShareIncarnation } from "./terminal-authority-policy.js";
16
17
  import { terminalControlEvidenceMatches } from "./terminal-control-ref.js";
17
18
  import { applyTerminalBridgeSubmission, terminalAcceptanceEvidenceForConversation, terminalBridgeEnabled, terminalBridgeRequestFingerprint, terminalBridgeSubmission } from "./terminal-dispatch-receipt.js";
18
19
  import { terminalMonitorActivityPersistIntervalMs, terminalMonitorApprovalCandidate, terminalMonitorDeadlineAt, validTerminalMonitorTimestampMs } from "./terminal-monitor-decision-policy.js";
19
20
  import { runTerminalMonitor as runTerminalMonitorService } from "./terminal-monitor-application-service.js";
20
- import { pollTerminalMonitor, presentTerminalMonitor, reconcileMonitorAcceptance, recordMonitorApprovalNotification, repairLaggingAcceptedMonitorAuthority, recoverPreparedMonitorSubmission, terminalMonitorStoreLeaseTimeout, terminalMonitorStoreOperationTimeout } from "./terminal-monitor-cli-adapter.js";
21
+ import { pollTerminalMonitor, presentTerminalMonitor, reconcileMonitorAcceptance, recordMonitorApprovalNotification, recordMonitorInteractionNotification, repairLaggingAcceptedMonitorAuthority, recoverPreparedMonitorSubmission, terminalMonitorStoreLeaseTimeout, terminalMonitorStoreOperationTimeout } from "./terminal-monitor-cli-adapter.js";
21
22
  import { terminalMonitorReconciliationEligibility } from "./terminal-monitor-reconciliation-eligibility.js";
22
23
  import { reconcileTerminalMonitorStateCandidate } from "./terminal-monitor-state-reconciliation-service.js";
23
24
  import { sameCanonicalStatePath } from "./terminal-dispatch-ledger-codec.js";
@@ -618,6 +619,22 @@ class TerminalMonitorStateCliApplication {
618
619
  approvalInstructions: terminalBridgeApprovalInstructions,
619
620
  approvalCandidate: terminalMonitorApprovalCandidate
620
621
  }
622
+ }),
623
+ recordInteractionNotification: (request) => recordMonitorInteractionNotification({
624
+ ...request,
625
+ ports: {
626
+ record: (recordRequest) => this.#recordInteractionNotification({
627
+ ...recordRequest,
628
+ statePath: input.statePath,
629
+ logPath: input.logPath
630
+ }),
631
+ prepare: (prepareRequest) => this.#dependencies.callbacks.prepareInteractionNotification({
632
+ options: { ...input.options, statePath: input.statePath },
633
+ statePath: input.statePath,
634
+ logPath: input.logPath,
635
+ ...prepareRequest
636
+ })
637
+ }
621
638
  })
622
639
  },
623
640
  authority: {
@@ -1296,6 +1313,215 @@ class TerminalMonitorStateCliApplication {
1296
1313
  recorded
1297
1314
  };
1298
1315
  }
1316
+ #recordInteractionNotification(input) {
1317
+ const storeDir = pathsForConversationDir(path.dirname(input.statePath))
1318
+ .storeDir;
1319
+ return withStoreWriterLease(storeDir, () => {
1320
+ const release = this.#stateFileLock.acquire(`${input.statePath}.lock`);
1321
+ try {
1322
+ const conversation = loadState(input.statePath);
1323
+ if (!this.#interactionSnapshotMatches(conversation, input)) {
1324
+ return {
1325
+ conversation,
1326
+ duplicate: false,
1327
+ stale: true,
1328
+ recorded: undefined
1329
+ };
1330
+ }
1331
+ return this.#recordMatchingInteraction(this.#interactionPersistenceContext(conversation, input));
1332
+ }
1333
+ finally {
1334
+ release();
1335
+ }
1336
+ });
1337
+ }
1338
+ #interactionSnapshotMatches(conversation, input) {
1339
+ const takeover = takeoverFor(conversation);
1340
+ const currentControl = terminalControlFromTakeover(takeover);
1341
+ const projection = input.terminalStatus.interaction_state;
1342
+ return conversation.status === "waiting_for_agent" &&
1343
+ conversation.conversation_id === input.expectedConversation.conversationId &&
1344
+ conversation.status === input.expectedConversation.status &&
1345
+ conversation.updated_at === input.expectedConversation.updatedAt &&
1346
+ takeover?.terminal_bridge === true &&
1347
+ nonBlankString(takeover.terminal_bridge_message_id) ===
1348
+ input.expectedConversation.messageId &&
1349
+ currentControl !== undefined &&
1350
+ terminalControlsShareIncarnation(currentControl, input.terminalControl) &&
1351
+ projection?.interaction_id === input.interactionId &&
1352
+ projection.questions.length === 1 &&
1353
+ projection.questions[0]?.question_id === input.questionId &&
1354
+ input.terminalStatus.interaction_prompt_fingerprint === input.fingerprint;
1355
+ }
1356
+ #interactionPersistenceContext(conversation, input) {
1357
+ const nativeTakeover = { ...takeoverFor(conversation) };
1358
+ const screen = isRecord(input.terminalStatus.screen)
1359
+ ? input.terminalStatus.screen
1360
+ : undefined;
1361
+ const interactionScreenDigest = nonBlankString(screen?.digest);
1362
+ const previousNotification = isRecord(nativeTakeover.terminal_bridge_interaction_notification)
1363
+ ? nativeTakeover.terminal_bridge_interaction_notification
1364
+ : undefined;
1365
+ const previousInteractionState = validInteractionProjection(previousNotification?.interaction_state);
1366
+ const callbackDelivery = isRecord(conversation.callback_delivery)
1367
+ ? conversation.callback_delivery
1368
+ : undefined;
1369
+ const callbackMessage = isRecord(callbackDelivery?.message)
1370
+ ? callbackDelivery.message
1371
+ : undefined;
1372
+ const callbackMetadata = isRecord(callbackMessage?.metadata)
1373
+ ? callbackMessage.metadata
1374
+ : undefined;
1375
+ const callbackInteractionState = validInteractionProjection(callbackMetadata?.interaction_state);
1376
+ const previousCallbackMessageId = nonBlankString(previousNotification?.callback_message_id);
1377
+ const matchingInteractionOutbox = callbackDelivery?.kind === "interaction_notification" &&
1378
+ previousCallbackMessageId !== undefined &&
1379
+ callbackMessage?.id === previousCallbackMessageId &&
1380
+ callbackMetadata?.source === "terminal_bridge" &&
1381
+ callbackMetadata?.reason === "interaction_required" &&
1382
+ callbackInteractionState !== undefined &&
1383
+ callbackInteractionState.interaction_id ===
1384
+ previousNotification?.interaction_id &&
1385
+ callbackInteractionState.questions.length === 1 &&
1386
+ callbackInteractionState.questions[0]?.question_id ===
1387
+ previousNotification?.question_id &&
1388
+ callbackInteractionState.turn_id === turnIdForConversation(conversation);
1389
+ const deliveryStatus = nonBlankString(callbackDelivery?.status);
1390
+ const deliveryAttempts = Number(callbackDelivery?.attempts ?? 0);
1391
+ const activeOutbox = deliveryStatus === "pending" ||
1392
+ (deliveryStatus === "failed" &&
1393
+ Number.isFinite(deliveryAttempts) &&
1394
+ deliveryAttempts <= this.#dependencies.runtime.callbackRetryLimit);
1395
+ const conflictingActiveOutbox = activeOutbox && !matchingInteractionOutbox;
1396
+ return {
1397
+ input,
1398
+ conversation,
1399
+ nativeTakeover,
1400
+ interactionScreenDigest,
1401
+ previousNotification,
1402
+ previousInteractionState,
1403
+ previousCallbackMessageId,
1404
+ matchingInteractionOutbox,
1405
+ activeOutbox,
1406
+ conflictingActiveOutbox
1407
+ };
1408
+ }
1409
+ #recordMatchingInteraction(context) {
1410
+ const duplicate = context.previousNotification?.terminal_bridge_message_id ===
1411
+ context.input.expectedConversation.messageId &&
1412
+ context.previousNotification?.interaction_id ===
1413
+ context.input.interactionId &&
1414
+ context.previousNotification?.question_id === context.input.questionId &&
1415
+ context.previousNotification?.prompt_fingerprint ===
1416
+ context.input.fingerprint &&
1417
+ context.previousInteractionState?.interaction_id ===
1418
+ context.input.interactionId &&
1419
+ context.previousInteractionState.questions.length === 1 &&
1420
+ context.previousInteractionState.questions[0]?.question_id ===
1421
+ context.input.questionId;
1422
+ if (context.conflictingActiveOutbox ||
1423
+ (context.activeOutbox && !duplicate)) {
1424
+ return {
1425
+ conversation: context.conversation,
1426
+ duplicate: false,
1427
+ stale: true,
1428
+ deferred: true,
1429
+ recorded: undefined
1430
+ };
1431
+ }
1432
+ if (!duplicate) {
1433
+ return this.#recordNewInteraction(context);
1434
+ }
1435
+ if (!context.matchingInteractionOutbox) {
1436
+ return this.#recoverInteractionOutbox(context);
1437
+ }
1438
+ return {
1439
+ conversation: context.conversation,
1440
+ duplicate: true,
1441
+ stale: false,
1442
+ recorded: undefined
1443
+ };
1444
+ }
1445
+ #recoverInteractionOutbox(context) {
1446
+ const messageId = context.previousCallbackMessageId ?? `msg-${randomUUID()}`;
1447
+ const messageTs = nonBlankString(context.previousNotification?.callback_message_ts) ?? nonBlankString(context.previousNotification?.notified_at) ??
1448
+ cliNow().toISOString();
1449
+ const conversation = context.previousCallbackMessageId
1450
+ ? context.conversation
1451
+ : {
1452
+ ...context.conversation,
1453
+ native_session_takeover: {
1454
+ ...context.nativeTakeover,
1455
+ terminal_bridge_interaction_notification: {
1456
+ ...context.previousNotification,
1457
+ callback_message_id: messageId,
1458
+ callback_message_ts: messageTs
1459
+ }
1460
+ }
1461
+ };
1462
+ if (!context.previousCallbackMessageId) {
1463
+ saveState(context.input.statePath, conversation);
1464
+ }
1465
+ const recorded = context.input.onRecorded(conversation, {
1466
+ recoverMissingOutbox: true
1467
+ });
1468
+ appendEvent(context.input.logPath, {
1469
+ ts: cliNow().toISOString(),
1470
+ conversation_id: conversation.conversation_id,
1471
+ event: "terminal_bridge_interaction_notification_outbox_recovered",
1472
+ terminal_control: context.input.terminalControl,
1473
+ interaction_id: context.input.interactionId,
1474
+ question_id: context.input.questionId,
1475
+ callback_message_id: messageId
1476
+ });
1477
+ return {
1478
+ conversation: recorded.prepared?.conversation ?? conversation,
1479
+ duplicate: false,
1480
+ recovered: true,
1481
+ stale: false,
1482
+ recorded
1483
+ };
1484
+ }
1485
+ #recordNewInteraction(context) {
1486
+ const now = cliNow().toISOString();
1487
+ const callbackMessageId = `msg-${randomUUID()}`;
1488
+ const conversation = {
1489
+ ...context.conversation,
1490
+ native_session_takeover: {
1491
+ ...context.nativeTakeover,
1492
+ terminal_bridge_interaction_notification: {
1493
+ terminal_bridge_message_id: context.input.expectedConversation.messageId,
1494
+ interaction_id: context.input.interactionId,
1495
+ question_id: context.input.questionId,
1496
+ prompt_fingerprint: context.input.fingerprint,
1497
+ screen_digest: context.interactionScreenDigest,
1498
+ notified_at: now,
1499
+ terminal_control: context.input.terminalControl,
1500
+ interaction_state: context.input.terminalStatus.interaction_state,
1501
+ callback_message_id: callbackMessageId,
1502
+ callback_message_ts: now
1503
+ }
1504
+ },
1505
+ updated_at: now
1506
+ };
1507
+ saveState(context.input.statePath, conversation);
1508
+ appendEvent(context.input.logPath, {
1509
+ ts: now,
1510
+ conversation_id: context.conversation.conversation_id,
1511
+ event: "terminal_bridge_interaction_notification_recorded",
1512
+ terminal_control: context.input.terminalControl,
1513
+ interaction_id: context.input.interactionId,
1514
+ question_id: context.input.questionId,
1515
+ screen_digest: context.interactionScreenDigest
1516
+ });
1517
+ const recorded = context.input.onRecorded(conversation);
1518
+ return {
1519
+ conversation: recorded.prepared?.conversation ?? conversation,
1520
+ duplicate: false,
1521
+ stale: false,
1522
+ recorded
1523
+ };
1524
+ }
1299
1525
  #markApprovalPromptCleared(input) {
1300
1526
  const storeDir = pathsForConversationDir(path.dirname(input.statePath))
1301
1527
  .storeDir;
@@ -2029,6 +2255,14 @@ function approvalCanBeCleared(conversation, takeover, input) {
2029
2255
  validTerminalMonitorTimestampMs(takeover.terminal_bridge_approval_resolved_at) !== undefined &&
2030
2256
  validTerminalMonitorTimestampMs(takeover.terminal_bridge_last_approval_prompt_cleared_at) === undefined;
2031
2257
  }
2258
+ function validInteractionProjection(value) {
2259
+ try {
2260
+ return validateTerminalInteractionProjection(value);
2261
+ }
2262
+ catch {
2263
+ return undefined;
2264
+ }
2265
+ }
2032
2266
  function detectorDiagnostic(input, existing, now) {
2033
2267
  if (input.limitation && input.fingerprint) {
2034
2268
  return {