@scotthuang/agent-knock-knock 0.12.19 → 0.12.21
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 +26 -0
- package/README.md +9 -9
- package/dist/src/codex-lifecycle-compatibility.js +2 -1
- package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
- package/dist/src/openclaw-plugin-command-adapter.js +5 -5
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.js +19 -4
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.js +3 -3
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/terminal-action-projection.d.ts +0 -18
- package/dist/src/terminal-action-projection.js +0 -23
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-agent-bridge.js +6 -1
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-list-cli-adapter.js +4 -33
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-renderer.js +17 -18
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-submission-acceptance.js +48 -12
- package/dist/src/terminal-submission-acceptance.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.map +1 -1
- package/dist/src/terminal-watch-cli-adapter.d.ts +1 -1
- package/dist/src/terminal-watch-cli-adapter.js +379 -110
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-service.d.ts +1 -5
- package/dist/src/terminal-watch-service.js +10 -21
- package/dist/src/terminal-watch-service.js.map +1 -1
- package/dist/src/terminal-watch-store.d.ts +47 -4
- package/dist/src/terminal-watch-store.js +161 -12
- package/dist/src/terminal-watch-store.js.map +1 -1
- package/docs/quickstart-herdr.md +3 -3
- package/docs/quickstart-tmux.md +7 -7
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +17 -13
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { callbackRouteForConversation } from "./callback-route-authority.js";
|
|
3
|
+
import { callbackRouteFingerprint, callbackRouteForConversation } from "./callback-route-authority.js";
|
|
4
4
|
import { parseCallbackRoute } from "./callback-transport.js";
|
|
5
5
|
import { captureClaudeTranscriptAnchor, captureClaudeHumanStartedActiveTaskAnchor, observeClaudeUserExplicitFallbackTranscript, observeClaudeHumanStartedActiveTask } from "./claude-local-transcript-provider.js";
|
|
6
6
|
import { claudeRuntimeCompatibilityWarning } from "./claude-lifecycle-compatibility.js";
|
|
7
7
|
import { codexRuntimeCompatibilityProfile } from "./codex-lifecycle-compatibility.js";
|
|
8
|
-
import { turnIdForConversation } from "./protocol.js";
|
|
9
|
-
import { decideTerminalWatchExternalTaskAuthority } from "./terminal-action-projection.js";
|
|
10
|
-
import { exactTerminalWatchAction } from "./terminal-list-renderer.js";
|
|
11
8
|
import { captureCodexCandidateSetRolloutAcceptanceAnchor, captureCodexRolloutAcceptanceAnchor, detectCodexBoundRolloutCompletion, detectCodexCandidateSetRolloutAcceptance, detectCodexRolloutAcceptance, captureCodexHumanStartedActiveTaskAnchor, observeCodexHumanStartedActiveTask } from "./terminal-submission-acceptance.js";
|
|
12
9
|
import { sameTerminalControlEvidenceIncarnation, terminalControlEvidence } from "./terminal-control-ref.js";
|
|
13
10
|
import { createTerminalWatchCallbackCliAdapter, resolveTerminalWatchOpenClawCallback, resolveTerminalWatchOpenClawCallbackContext } from "./terminal-watch-callback-cli-adapter.js";
|
|
14
11
|
import { createTerminalWatchService, terminalWatchObservationFence } from "./terminal-watch-service.js";
|
|
15
|
-
import { createClaudeUserExplicitFallbackWatchAnchor, createCodexUserExplicitFallbackWatchAnchor, createTerminalWatchStore, isUserExplicitFallbackWatch, terminalUserExplicitFallbackWatchId
|
|
12
|
+
import { createClaudeUserExplicitFallbackWatchAnchor, createCodexUserExplicitFallbackWatchAnchor, createTerminalActivityWatchAnchor, createTerminalWatchStore, isTerminalActivityWatch, isUserExplicitFallbackWatch, terminalUserExplicitFallbackWatchId } from "./terminal-watch-store.js";
|
|
16
13
|
import { isRecord, nonBlankString as stringValue } from "./value-guards.js";
|
|
17
14
|
const DEFAULT_TERMINAL_WATCH_HARD_TIMEOUT_MINUTES = 720;
|
|
18
15
|
export function createTerminalWatchCliAdapter(dependencies) {
|
|
@@ -67,6 +64,11 @@ export function createTerminalWatchCliAdapter(dependencies) {
|
|
|
67
64
|
"string"
|
|
68
65
|
? input.context.legacyOptions.openclawBin
|
|
69
66
|
: undefined,
|
|
67
|
+
origin: metadata.watch_origin === "user_selected_terminal" ||
|
|
68
|
+
metadata.watch_origin === "terminal_activity_fallback" ||
|
|
69
|
+
metadata.watch_origin === "terminal_user_explicit_fallback"
|
|
70
|
+
? metadata.watch_origin
|
|
71
|
+
: undefined,
|
|
70
72
|
detail: typeof metadata.reason_code === "string"
|
|
71
73
|
? metadata.reason_code
|
|
72
74
|
: undefined,
|
|
@@ -245,57 +247,96 @@ export function createTerminalWatchCliAdapter(dependencies) {
|
|
|
245
247
|
: undefined;
|
|
246
248
|
const openclawSession = callbackRoute?.controller_session_id ??
|
|
247
249
|
requiredString(options.openclawSession, "--openclaw-session");
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
250
|
+
const observed = await exactTerminalForWatch(terminalId, options, dependencies);
|
|
251
|
+
const rawTerminal = observed.rawTerminal;
|
|
252
|
+
const projectedTerminal = observed.terminal;
|
|
253
|
+
// Endpoint evidence is the only indispensable observation authority. A
|
|
254
|
+
// manual Watch is read-only and therefore does not acquire terminal input
|
|
255
|
+
// authority or depend on managed-Turn ownership.
|
|
256
|
+
const terminalControl = terminalControlForWatch(rawTerminal);
|
|
257
|
+
const agent = terminalAgent(rawTerminal);
|
|
258
|
+
const warnings = [];
|
|
259
|
+
let anchor;
|
|
254
260
|
try {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (existing) {
|
|
269
|
-
throw new Error(`terminal ${terminalId} is already monitored by Terminal Watch ` +
|
|
270
|
-
`${existing.watch_id}; use watch-status instead of creating another`);
|
|
261
|
+
anchor = captureTerminalWatchAnchor(agent, rawTerminal, options, dependencies);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
warnings.push(`exact_task_anchor_unavailable: ${safeDiagnostic(error)}`);
|
|
265
|
+
}
|
|
266
|
+
if (anchor) {
|
|
267
|
+
warnings.push(...watchAnchorVersionWarnings(anchor, rawTerminal));
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
if (!terminalControl.capabilities.includes("screen_status")) {
|
|
271
|
+
throw new Error("the exact terminal has neither a durable task anchor nor a " +
|
|
272
|
+
"read-only screen-status observation path; no effective Watch " +
|
|
273
|
+
"can be created");
|
|
271
274
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const anchor = captureTerminalWatchAnchor(agent, rawTerminal, options, dependencies);
|
|
275
|
-
if (!anchor) {
|
|
276
|
-
throw new Error("the exact terminal has no unique supported human-started active task");
|
|
275
|
+
if (warnings.length === 0) {
|
|
276
|
+
warnings.push("exact_task_anchor_unavailable: no unique supported task anchor was present");
|
|
277
277
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
openclaw_bin: stringValue(options.openclawBin) ?? "openclaw",
|
|
288
|
-
timeout_ms: positiveMinutes(options.hardTimeoutMinutes, DEFAULT_TERMINAL_WATCH_HARD_TIMEOUT_MINUTES) * 60_000,
|
|
289
|
-
approval_fingerprint: approvalFingerprint(projectedTerminal),
|
|
290
|
-
approval_reason_code: approvalFingerprint(projectedTerminal)
|
|
291
|
-
? "terminal_waiting_for_approval"
|
|
292
|
-
: undefined
|
|
278
|
+
const initialActivityState = terminalActivityState(projectedTerminal);
|
|
279
|
+
anchor = createTerminalActivityWatchAnchor({
|
|
280
|
+
capturedAt: dependencies.now(),
|
|
281
|
+
terminalId,
|
|
282
|
+
pid: positiveInteger(rawTerminal.pid, "terminal PID"),
|
|
283
|
+
initialActivityState,
|
|
284
|
+
nativeProcessUuid: stringValue(rawTerminal.native_agent_process_uuid),
|
|
285
|
+
nativeProcessBirth: stringValue(rawTerminal.native_agent_process_birth),
|
|
286
|
+
agentVersion: stringValue(rawTerminal.agent_version)
|
|
293
287
|
});
|
|
288
|
+
warnings.push("terminal_activity_fallback: observing the exact terminal/process activity epoch instead of claiming exact task completion");
|
|
289
|
+
if (initialActivityState === "idle" || initialActivityState === "unknown") {
|
|
290
|
+
warnings.push("terminal_activity_armed_for_next_activity: no active epoch is visible yet; Watch will wait to observe working or approval activity before stable idle can settle");
|
|
291
|
+
}
|
|
294
292
|
}
|
|
295
|
-
|
|
296
|
-
|
|
293
|
+
const binding = bestEffortBindingTokenForWatch(rawTerminal);
|
|
294
|
+
if (binding.warning)
|
|
295
|
+
warnings.push(binding.warning);
|
|
296
|
+
const terminalIdentity = terminalWatchIdentity(rawTerminal, binding.token);
|
|
297
|
+
const repository = createTerminalWatchStore(dependencies.storeDirFromOptions(options), { acquire: dependencies.acquireFileLock });
|
|
298
|
+
let existingCandidates = [];
|
|
299
|
+
let discoveryWarnings = [];
|
|
300
|
+
try {
|
|
301
|
+
const scan = repository.scanForReconciliation();
|
|
302
|
+
existingCandidates = scan.watches;
|
|
303
|
+
if (scan.errors.length > 0) {
|
|
304
|
+
discoveryWarnings = [
|
|
305
|
+
`terminal_watch_store_entries_skipped: ignored ${scan.errors.length} ` +
|
|
306
|
+
"invalid sibling Watch record(s) during idempotent discovery"
|
|
307
|
+
];
|
|
308
|
+
}
|
|
297
309
|
}
|
|
298
|
-
|
|
310
|
+
catch (error) {
|
|
311
|
+
discoveryWarnings = [
|
|
312
|
+
`terminal_watch_store_discovery_unavailable: ${safeDiagnostic(error)}; ` +
|
|
313
|
+
"attempting the user-requested read-only Watch anyway"
|
|
314
|
+
];
|
|
315
|
+
}
|
|
316
|
+
warnings.push(...discoveryWarnings);
|
|
317
|
+
const watchService = serviceFor(options);
|
|
318
|
+
const openclawBin = stringValue(options.openclawBin) ?? "openclaw";
|
|
319
|
+
const existing = existingCandidates.find((candidate) => sameManualWatchTarget(candidate, agent, terminalIdentity, anchor) &&
|
|
320
|
+
sameManualWatchCallbackAuthority(candidate, callbackRoute, openclawSession, openclawBin));
|
|
321
|
+
const watch = existing ?? watchService.create({
|
|
322
|
+
agent,
|
|
323
|
+
terminal: terminalIdentity,
|
|
324
|
+
anchor,
|
|
325
|
+
warnings,
|
|
326
|
+
...(callbackRoute === undefined
|
|
327
|
+
? {}
|
|
328
|
+
: { callback_route: callbackRoute }),
|
|
329
|
+
openclaw_session: openclawSession,
|
|
330
|
+
openclaw_bin: openclawBin,
|
|
331
|
+
timeout_ms: positiveMinutes(options.hardTimeoutMinutes, DEFAULT_TERMINAL_WATCH_HARD_TIMEOUT_MINUTES) * 60_000,
|
|
332
|
+
approval_fingerprint: approvalFingerprint(projectedTerminal),
|
|
333
|
+
approval_reason_code: approvalFingerprint(projectedTerminal)
|
|
334
|
+
? "terminal_waiting_for_approval"
|
|
335
|
+
: undefined
|
|
336
|
+
});
|
|
337
|
+
dependencies.printJson({
|
|
338
|
+
watch: publicTerminalWatch(watch, existing ? discoveryWarnings : [])
|
|
339
|
+
});
|
|
299
340
|
}
|
|
300
341
|
async function runUnwatch(options) {
|
|
301
342
|
const service = serviceFor(options);
|
|
@@ -326,7 +367,7 @@ export function createTerminalWatchCliAdapter(dependencies) {
|
|
|
326
367
|
function publicTerminalWatches(watches, options) {
|
|
327
368
|
return watches
|
|
328
369
|
.filter((watch) => options.includeAll || watch.status === "active")
|
|
329
|
-
.map(publicTerminalWatch);
|
|
370
|
+
.map((watch) => publicTerminalWatch(watch));
|
|
330
371
|
}
|
|
331
372
|
return Object.freeze({
|
|
332
373
|
prepareUserExplicitFallbackWatch,
|
|
@@ -340,8 +381,13 @@ export function createTerminalWatchCliAdapter(dependencies) {
|
|
|
340
381
|
scanPublicWatchesForExactObservation
|
|
341
382
|
});
|
|
342
383
|
}
|
|
343
|
-
function
|
|
344
|
-
const runningVersion =
|
|
384
|
+
function watchAnchorVersionWarnings(anchor, terminal) {
|
|
385
|
+
const runningVersion = stringValue(terminal.agent_version);
|
|
386
|
+
if (!runningVersion) {
|
|
387
|
+
return [
|
|
388
|
+
"running_agent_version_unavailable: exact task observation remains enabled"
|
|
389
|
+
];
|
|
390
|
+
}
|
|
345
391
|
const artifactVersion = anchor.schema ===
|
|
346
392
|
"agent-knock-knock/codex-human-started-active-task-anchor"
|
|
347
393
|
? anchor.codex_version
|
|
@@ -349,13 +395,16 @@ function assertWatchAnchorVersion(anchor, terminal) {
|
|
|
349
395
|
"agent-knock-knock/claude-human-started-active-task-anchor"
|
|
350
396
|
? anchor.claude_version
|
|
351
397
|
: undefined;
|
|
352
|
-
if (!artifactVersion)
|
|
353
|
-
|
|
354
|
-
}
|
|
398
|
+
if (!artifactVersion)
|
|
399
|
+
return [];
|
|
355
400
|
if (artifactVersion !== runningVersion) {
|
|
356
|
-
|
|
357
|
-
`
|
|
401
|
+
return [
|
|
402
|
+
`the active task artifact reports ${artifactVersion}, not the running ` +
|
|
403
|
+
`coding-agent version ${runningVersion}; Watch remains enabled because ` +
|
|
404
|
+
"version evidence is advisory"
|
|
405
|
+
];
|
|
358
406
|
}
|
|
407
|
+
return [];
|
|
359
408
|
}
|
|
360
409
|
/**
|
|
361
410
|
* A shared native-Host lifecycle owns no single controller session. Bind its
|
|
@@ -416,11 +465,89 @@ async function exactTerminalForWatch(terminalId, options, dependencies) {
|
|
|
416
465
|
function bindingTokenForWatch(terminal) {
|
|
417
466
|
return requiredSha256(terminal.lifecycle_binding_token, "current terminal binding token");
|
|
418
467
|
}
|
|
419
|
-
function
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
468
|
+
function bestEffortBindingTokenForWatch(terminal) {
|
|
469
|
+
try {
|
|
470
|
+
return { token: bindingTokenForWatch(terminal) };
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
const terminalControl = terminalControlForWatch(terminal);
|
|
474
|
+
return {
|
|
475
|
+
token: sha256({
|
|
476
|
+
schema: "agent-knock-knock/terminal-watch-observation-binding",
|
|
477
|
+
version: 1,
|
|
478
|
+
terminal_id: requiredString(terminal.id, "terminal id"),
|
|
479
|
+
agent: terminalAgent(terminal),
|
|
480
|
+
pid: positiveInteger(terminal.pid, "terminal PID"),
|
|
481
|
+
endpoint: terminalControlEvidence(terminalControl),
|
|
482
|
+
process_uuid: stringValue(terminal.native_agent_process_uuid) ?? null,
|
|
483
|
+
process_birth: stringValue(terminal.native_agent_process_birth) ?? null
|
|
484
|
+
}),
|
|
485
|
+
warning: "lifecycle_binding_token_unavailable: Watch used read-only terminal/process observation identity"
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function terminalActivityState(terminal) {
|
|
490
|
+
const state = stringValue(terminal.activity_state);
|
|
491
|
+
return state === "awaiting_approval" || state === "working" ||
|
|
492
|
+
state === "idle"
|
|
493
|
+
? state
|
|
494
|
+
: "unknown";
|
|
495
|
+
}
|
|
496
|
+
function sameManualWatchTarget(watch, agent, terminal, anchor) {
|
|
497
|
+
return watch.status === "active" &&
|
|
498
|
+
watch.agent === agent &&
|
|
499
|
+
watch.terminal.terminal_id === terminal.terminal_id &&
|
|
500
|
+
watch.terminal.workspace === terminal.workspace &&
|
|
501
|
+
sameTerminalControlEvidenceIncarnation(watch.terminal.terminal_endpoint, terminal.terminal_endpoint) &&
|
|
502
|
+
sameManualWatchAnchorTarget(watch.anchor, anchor);
|
|
503
|
+
}
|
|
504
|
+
function sameManualWatchAnchorTarget(left, right) {
|
|
505
|
+
if (left.schema !== right.schema)
|
|
506
|
+
return false;
|
|
507
|
+
if (left.schema ===
|
|
508
|
+
"agent-knock-knock/codex-human-started-active-task-anchor" &&
|
|
509
|
+
right.schema === left.schema) {
|
|
510
|
+
return left.turn_id === right.turn_id &&
|
|
511
|
+
left.process_uuid === right.process_uuid &&
|
|
512
|
+
left.process_birth === right.process_birth &&
|
|
513
|
+
left.rollout.device === right.rollout.device &&
|
|
514
|
+
left.rollout.inode === right.rollout.inode;
|
|
515
|
+
}
|
|
516
|
+
if (left.schema ===
|
|
517
|
+
"agent-knock-knock/claude-human-started-active-task-anchor" &&
|
|
518
|
+
right.schema === left.schema) {
|
|
519
|
+
return left.prompt_uuid === right.prompt_uuid &&
|
|
520
|
+
left.pid === right.pid &&
|
|
521
|
+
left.agent_started_at_ms === right.agent_started_at_ms &&
|
|
522
|
+
left.device === right.device &&
|
|
523
|
+
left.inode === right.inode;
|
|
524
|
+
}
|
|
525
|
+
if (left.schema === "agent-knock-knock/terminal-activity-watch-anchor" &&
|
|
526
|
+
right.schema === left.schema) {
|
|
527
|
+
return left.pid === right.pid &&
|
|
528
|
+
optionalIdentityCompatible(left.native_process_uuid, right.native_process_uuid) &&
|
|
529
|
+
optionalIdentityCompatible(left.native_process_birth, right.native_process_birth);
|
|
423
530
|
}
|
|
531
|
+
return left.anchor_fingerprint === right.anchor_fingerprint;
|
|
532
|
+
}
|
|
533
|
+
function optionalIdentityCompatible(left, right) {
|
|
534
|
+
return left === undefined || right === undefined || left === right;
|
|
535
|
+
}
|
|
536
|
+
function sameManualWatchCallbackAuthority(watch, callbackRoute, openclawSession, openclawBin) {
|
|
537
|
+
if (watch.openclaw_session !== openclawSession)
|
|
538
|
+
return false;
|
|
539
|
+
if (callbackRoute === undefined) {
|
|
540
|
+
return watch.callback_route === undefined &&
|
|
541
|
+
watch.openclaw_bin === openclawBin;
|
|
542
|
+
}
|
|
543
|
+
return watch.callback_route !== undefined &&
|
|
544
|
+
callbackRouteFingerprint(watch.callback_route) ===
|
|
545
|
+
callbackRouteFingerprint(callbackRoute);
|
|
546
|
+
}
|
|
547
|
+
function safeDiagnostic(error) {
|
|
548
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
549
|
+
return message.replace(/[\r\n\0]+/gu, " ").trim().slice(0, 500) ||
|
|
550
|
+
"unknown provider observation error";
|
|
424
551
|
}
|
|
425
552
|
function terminalControlForWatch(terminal) {
|
|
426
553
|
if (!isRecord(terminal.terminal_control)) {
|
|
@@ -428,28 +555,6 @@ function terminalControlForWatch(terminal) {
|
|
|
428
555
|
}
|
|
429
556
|
return terminal.terminal_control;
|
|
430
557
|
}
|
|
431
|
-
function assertExternalTerminalWatchAuthority(storeDir, terminalControl, dependencies) {
|
|
432
|
-
const blockingTurn = dependencies.terminalIncarnationBlockingTurns(storeDir, terminalControl)[0];
|
|
433
|
-
const authority = decideTerminalWatchExternalTaskAuthority({
|
|
434
|
-
blockingTurn,
|
|
435
|
-
dispatchOwnership: blockingTurn
|
|
436
|
-
? { state: "none" }
|
|
437
|
-
: dependencies.terminalDispatchOwnership(terminalControl)
|
|
438
|
-
});
|
|
439
|
-
if (authority.state === "external_task") {
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
if (authority.state === "managed_turn") {
|
|
443
|
-
const turn = authority.conversation;
|
|
444
|
-
throw new Error(`Terminal Watch is unavailable: this task already belongs to AKK Turn ` +
|
|
445
|
-
`${turnIdForConversation(turn)} (${turn.status}) and is covered by the ` +
|
|
446
|
-
"managed Turn monitor/callback path. Use AKK status for that Turn.");
|
|
447
|
-
}
|
|
448
|
-
const reason = stringValue(authority.conflict.reason) ??
|
|
449
|
-
"terminal dispatch ownership is conflicted";
|
|
450
|
-
throw new Error(`Terminal Watch is unavailable because AKK cannot prove this is external ` +
|
|
451
|
-
`work: ${reason}. Refresh AKK list and resolve the ownership conflict.`);
|
|
452
|
-
}
|
|
453
558
|
function captureTerminalWatchAnchor(agent, terminal, options, dependencies) {
|
|
454
559
|
if (agent === "codex") {
|
|
455
560
|
return captureCodexHumanStartedActiveTaskAnchor({
|
|
@@ -476,10 +581,15 @@ async function observeTerminalWatch(watch, options, dependencies) {
|
|
|
476
581
|
const projectedTerminal = exactTerminal.state === "available"
|
|
477
582
|
? exactTerminal.terminal
|
|
478
583
|
: undefined;
|
|
584
|
+
const activityTerminalIdentity = rawTerminal && isTerminalActivityWatch(watch)
|
|
585
|
+
? terminalActivityWatchIdentityMatch(rawTerminal, watch)
|
|
586
|
+
: undefined;
|
|
479
587
|
const terminalMatches = rawTerminal
|
|
480
588
|
? isUserExplicitFallbackWatch(watch)
|
|
481
589
|
? terminalMatchesUserExplicitFallbackWatch(rawTerminal, watch)
|
|
482
|
-
:
|
|
590
|
+
: isTerminalActivityWatch(watch)
|
|
591
|
+
? activityTerminalIdentity === "match"
|
|
592
|
+
: terminalMatchesWatch(rawTerminal, watch)
|
|
483
593
|
: false;
|
|
484
594
|
if (isUserExplicitFallbackWatch(watch)) {
|
|
485
595
|
return observeUserExplicitFallbackTerminalWatch({
|
|
@@ -493,6 +603,15 @@ async function observeTerminalWatch(watch, options, dependencies) {
|
|
|
493
603
|
dependencies
|
|
494
604
|
});
|
|
495
605
|
}
|
|
606
|
+
if (isTerminalActivityWatch(watch)) {
|
|
607
|
+
return observeTerminalActivityWatch({
|
|
608
|
+
watch,
|
|
609
|
+
exactTerminal,
|
|
610
|
+
projectedTerminal,
|
|
611
|
+
terminalIdentityMatch: activityTerminalIdentity ?? "mismatch",
|
|
612
|
+
observedAt
|
|
613
|
+
});
|
|
614
|
+
}
|
|
496
615
|
const observation = watch.anchor.schema ===
|
|
497
616
|
"agent-knock-knock/codex-human-started-active-task-anchor"
|
|
498
617
|
? observeCodexHumanStartedActiveTask({
|
|
@@ -591,6 +710,99 @@ async function observeTerminalWatch(watch, options, dependencies) {
|
|
|
591
710
|
observation_checkpoint: observationCheckpoint
|
|
592
711
|
};
|
|
593
712
|
}
|
|
713
|
+
function observeTerminalActivityWatch(input) {
|
|
714
|
+
const { watch, exactTerminal, projectedTerminal, terminalIdentityMatch, observedAt } = input;
|
|
715
|
+
const fence = terminalWatchObservationFence(watch);
|
|
716
|
+
if (exactTerminal.state === "unavailable") {
|
|
717
|
+
return {
|
|
718
|
+
...fence,
|
|
719
|
+
kind: "unavailable",
|
|
720
|
+
observed_at: observedAt,
|
|
721
|
+
reason_code: "terminal_observation_unavailable"
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
if (exactTerminal.state === "absent") {
|
|
725
|
+
return invalidatedObservation(watch, observedAt, "terminal_process_unavailable");
|
|
726
|
+
}
|
|
727
|
+
if (terminalIdentityMatch === "unavailable") {
|
|
728
|
+
return {
|
|
729
|
+
...fence,
|
|
730
|
+
kind: "unavailable",
|
|
731
|
+
observed_at: observedAt,
|
|
732
|
+
reason_code: "terminal_identity_observation_incomplete"
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
if (terminalIdentityMatch === "mismatch") {
|
|
736
|
+
return invalidatedObservation(watch, observedAt, "terminal_identity_changed");
|
|
737
|
+
}
|
|
738
|
+
if (!projectedTerminal) {
|
|
739
|
+
return {
|
|
740
|
+
...fence,
|
|
741
|
+
kind: "unavailable",
|
|
742
|
+
observed_at: observedAt,
|
|
743
|
+
reason_code: "terminal_activity_unavailable"
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
const state = terminalActivityState(projectedTerminal);
|
|
747
|
+
const current = terminalActivityObservationCheckpoint(watch);
|
|
748
|
+
const active = state === "working" || state === "awaiting_approval";
|
|
749
|
+
const hasSeenActivity = current.has_seen_activity || active;
|
|
750
|
+
const consecutiveIdle = state === "idle" && hasSeenActivity
|
|
751
|
+
? current.last_activity_state === "idle"
|
|
752
|
+
? current.consecutive_idle_observations + 1
|
|
753
|
+
: 1
|
|
754
|
+
: 0;
|
|
755
|
+
const checkpoint = {
|
|
756
|
+
...current,
|
|
757
|
+
has_seen_activity: hasSeenActivity,
|
|
758
|
+
consecutive_idle_observations: consecutiveIdle,
|
|
759
|
+
last_activity_state: state
|
|
760
|
+
};
|
|
761
|
+
if (state === "unknown") {
|
|
762
|
+
return {
|
|
763
|
+
...fence,
|
|
764
|
+
kind: "unavailable",
|
|
765
|
+
observed_at: observedAt,
|
|
766
|
+
observation_checkpoint: checkpoint,
|
|
767
|
+
reason_code: "terminal_activity_unknown"
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
if (state === "awaiting_approval") {
|
|
771
|
+
const approval = approvalFingerprint(projectedTerminal);
|
|
772
|
+
if (approval) {
|
|
773
|
+
return {
|
|
774
|
+
...fence,
|
|
775
|
+
kind: "approval",
|
|
776
|
+
observed_at: observedAt,
|
|
777
|
+
last_activity_at: observedAt,
|
|
778
|
+
observation_checkpoint: checkpoint,
|
|
779
|
+
evidence_fingerprint: approval,
|
|
780
|
+
reason_code: "terminal_waiting_for_approval"
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (state === "idle" && hasSeenActivity && consecutiveIdle >= 2) {
|
|
785
|
+
return {
|
|
786
|
+
...fence,
|
|
787
|
+
kind: "completed",
|
|
788
|
+
observed_at: observedAt,
|
|
789
|
+
observation_checkpoint: checkpoint,
|
|
790
|
+
evidence_fingerprint: sha256({
|
|
791
|
+
watch_id: watch.watch_id,
|
|
792
|
+
reason_code: "terminal_activity_became_stably_idle",
|
|
793
|
+
anchor_fingerprint: watch.anchor.anchor_fingerprint
|
|
794
|
+
}),
|
|
795
|
+
reason_code: "terminal_activity_became_stably_idle"
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
...fence,
|
|
800
|
+
kind: "pending",
|
|
801
|
+
observed_at: observedAt,
|
|
802
|
+
...(active ? { last_activity_at: observedAt } : {}),
|
|
803
|
+
observation_checkpoint: checkpoint
|
|
804
|
+
};
|
|
805
|
+
}
|
|
594
806
|
async function observeUserExplicitFallbackTerminalWatch(input) {
|
|
595
807
|
const { watch, exactTerminal, rawTerminal, terminalMatches, observedAt, options, dependencies } = input;
|
|
596
808
|
const fence = terminalWatchObservationFence(watch);
|
|
@@ -944,20 +1156,49 @@ async function currentTerminalForWatch(watch, options, dependencies) {
|
|
|
944
1156
|
}
|
|
945
1157
|
function terminalMatchesWatch(terminal, watch) {
|
|
946
1158
|
try {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
anchor: watch.anchor
|
|
954
|
-
}) === terminalWatchIdentityFingerprint(watch) &&
|
|
955
|
-
sameTerminalControlEvidenceIncarnation(current.terminal_endpoint, watch.terminal.terminal_endpoint);
|
|
1159
|
+
return terminalAgent(terminal) === watch.agent &&
|
|
1160
|
+
requiredString(terminal.id, "terminal id") ===
|
|
1161
|
+
watch.terminal.terminal_id &&
|
|
1162
|
+
terminalWorkspace(terminal) === watch.terminal.workspace &&
|
|
1163
|
+
isRecord(terminal.terminal_control) &&
|
|
1164
|
+
sameTerminalControlEvidenceIncarnation(terminal.terminal_control, watch.terminal.terminal_endpoint);
|
|
956
1165
|
}
|
|
957
1166
|
catch {
|
|
958
1167
|
return false;
|
|
959
1168
|
}
|
|
960
1169
|
}
|
|
1170
|
+
function terminalActivityWatchIdentityMatch(terminal, watch) {
|
|
1171
|
+
try {
|
|
1172
|
+
if (!isTerminalActivityWatch(watch) ||
|
|
1173
|
+
terminalAgent(terminal) !== watch.agent ||
|
|
1174
|
+
requiredString(terminal.id, "terminal id") !==
|
|
1175
|
+
watch.terminal.terminal_id ||
|
|
1176
|
+
terminalWorkspace(terminal) !== watch.terminal.workspace ||
|
|
1177
|
+
positiveInteger(terminal.pid, "terminal PID") !== watch.anchor.pid) {
|
|
1178
|
+
return "mismatch";
|
|
1179
|
+
}
|
|
1180
|
+
if (!isRecord(terminal.terminal_control)) {
|
|
1181
|
+
return "unavailable";
|
|
1182
|
+
}
|
|
1183
|
+
if (!sameTerminalControlEvidenceIncarnation(terminal.terminal_control, watch.terminal.terminal_endpoint)) {
|
|
1184
|
+
return "mismatch";
|
|
1185
|
+
}
|
|
1186
|
+
const processUuid = stringValue(terminal.native_agent_process_uuid);
|
|
1187
|
+
const processBirth = stringValue(terminal.native_agent_process_birth);
|
|
1188
|
+
if ((watch.anchor.native_process_uuid !== undefined && !processUuid) ||
|
|
1189
|
+
(watch.anchor.native_process_birth !== undefined && !processBirth)) {
|
|
1190
|
+
return "unavailable";
|
|
1191
|
+
}
|
|
1192
|
+
return (watch.anchor.native_process_uuid !== undefined &&
|
|
1193
|
+
processUuid !== watch.anchor.native_process_uuid) || (watch.anchor.native_process_birth !== undefined &&
|
|
1194
|
+
processBirth !== watch.anchor.native_process_birth)
|
|
1195
|
+
? "mismatch"
|
|
1196
|
+
: "match";
|
|
1197
|
+
}
|
|
1198
|
+
catch {
|
|
1199
|
+
return "unavailable";
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
961
1202
|
function terminalMatchesUserExplicitFallbackWatch(terminal, watch) {
|
|
962
1203
|
try {
|
|
963
1204
|
if (!isUserExplicitFallbackWatch(watch) ||
|
|
@@ -983,6 +1224,15 @@ function terminalMatchesUserExplicitFallbackWatch(terminal, watch) {
|
|
|
983
1224
|
return false;
|
|
984
1225
|
}
|
|
985
1226
|
}
|
|
1227
|
+
function terminalActivityObservationCheckpoint(watch) {
|
|
1228
|
+
const checkpoint = watch.observation_checkpoint;
|
|
1229
|
+
if (!("schema" in checkpoint) ||
|
|
1230
|
+
checkpoint.schema !==
|
|
1231
|
+
"agent-knock-knock/terminal-activity-watch-checkpoint") {
|
|
1232
|
+
throw new Error("terminal activity Watch has no activity checkpoint");
|
|
1233
|
+
}
|
|
1234
|
+
return checkpoint;
|
|
1235
|
+
}
|
|
986
1236
|
function terminalWatchIdentity(terminal, bindingToken) {
|
|
987
1237
|
const terminalControl = terminal.terminal_control;
|
|
988
1238
|
return {
|
|
@@ -1021,28 +1271,30 @@ function codexIdentityForWatch(watch) {
|
|
|
1021
1271
|
rollout: watch.anchor.rollout
|
|
1022
1272
|
};
|
|
1023
1273
|
}
|
|
1024
|
-
function publicTerminalWatch(watch) {
|
|
1274
|
+
function publicTerminalWatch(watch, additionalWarnings = []) {
|
|
1025
1275
|
const pending = watch.notification_outbox.filter(({ status }) => status === "pending" || status === "delivering" || status === "failed").length;
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
: watch.agent === "claude" &&
|
|
1034
|
-
(watch.anchor.schema ===
|
|
1035
|
-
"agent-knock-knock/claude-human-started-active-task-anchor" ||
|
|
1036
|
-
watch.anchor.schema ===
|
|
1037
|
-
"agent-knock-knock/claude-user-explicit-fallback-watch-anchor")
|
|
1038
|
-
? claudeRuntimeCompatibilityWarning(watch.anchor.claude_version)
|
|
1039
|
-
: undefined;
|
|
1276
|
+
const capturedAgentVersion = terminalWatchCapturedAgentVersion(watch);
|
|
1277
|
+
const compatibilityWarning = capturedAgentVersion === undefined
|
|
1278
|
+
? undefined
|
|
1279
|
+
: watch.agent === "codex"
|
|
1280
|
+
? codexRuntimeCompatibilityProfile(capturedAgentVersion)
|
|
1281
|
+
?.compatibilityWarning
|
|
1282
|
+
: claudeRuntimeCompatibilityWarning(capturedAgentVersion);
|
|
1040
1283
|
const userExplicitFallback = isUserExplicitFallbackWatch(watch);
|
|
1284
|
+
const terminalActivityFallback = isTerminalActivityWatch(watch);
|
|
1285
|
+
const warnings = [...new Set([
|
|
1286
|
+
...(watch.warnings ?? []),
|
|
1287
|
+
...additionalWarnings
|
|
1288
|
+
])];
|
|
1041
1289
|
return {
|
|
1042
1290
|
watch_id: watch.watch_id,
|
|
1043
1291
|
source: userExplicitFallback
|
|
1044
1292
|
? "terminal_user_explicit_fallback_watch"
|
|
1045
|
-
:
|
|
1293
|
+
: terminalActivityFallback
|
|
1294
|
+
? "terminal_activity_watch"
|
|
1295
|
+
: "user_selected_terminal_watch",
|
|
1296
|
+
watch_mode: terminalActivityFallback ? "terminal_activity" : "exact_task",
|
|
1297
|
+
confidence: terminalActivityFallback ? "best_effort" : "exact",
|
|
1046
1298
|
agent: watch.agent,
|
|
1047
1299
|
terminal_id: watch.terminal.terminal_id,
|
|
1048
1300
|
native_thread_id: terminalWatchNativeThreadId(watch),
|
|
@@ -1056,6 +1308,9 @@ function publicTerminalWatch(watch) {
|
|
|
1056
1308
|
...(compatibilityWarning
|
|
1057
1309
|
? { compatibility_warning: compatibilityWarning }
|
|
1058
1310
|
: {}),
|
|
1311
|
+
...(warnings.length > 0
|
|
1312
|
+
? { warnings }
|
|
1313
|
+
: {}),
|
|
1059
1314
|
callback: {
|
|
1060
1315
|
pending,
|
|
1061
1316
|
delivered: watch.notification_outbox.filter(({ status }) => status === "delivered").length,
|
|
@@ -1091,6 +1346,18 @@ function publicTerminalWatch(watch) {
|
|
|
1091
1346
|
}
|
|
1092
1347
|
};
|
|
1093
1348
|
}
|
|
1349
|
+
function terminalWatchCapturedAgentVersion(watch) {
|
|
1350
|
+
switch (watch.anchor.schema) {
|
|
1351
|
+
case "agent-knock-knock/codex-human-started-active-task-anchor":
|
|
1352
|
+
case "agent-knock-knock/codex-user-explicit-fallback-watch-anchor":
|
|
1353
|
+
return watch.anchor.codex_version;
|
|
1354
|
+
case "agent-knock-knock/claude-human-started-active-task-anchor":
|
|
1355
|
+
case "agent-knock-knock/claude-user-explicit-fallback-watch-anchor":
|
|
1356
|
+
return watch.anchor.claude_version;
|
|
1357
|
+
case "agent-knock-knock/terminal-activity-watch-anchor":
|
|
1358
|
+
return watch.anchor.agent_version;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1094
1361
|
function terminalWatchNativeThreadId(watch) {
|
|
1095
1362
|
switch (watch.anchor.schema) {
|
|
1096
1363
|
case "agent-knock-knock/codex-human-started-active-task-anchor":
|
|
@@ -1103,6 +1370,8 @@ function terminalWatchNativeThreadId(watch) {
|
|
|
1103
1370
|
: undefined;
|
|
1104
1371
|
case "agent-knock-knock/claude-user-explicit-fallback-watch-anchor":
|
|
1105
1372
|
return watch.anchor.transcript_anchor.session_id;
|
|
1373
|
+
case "agent-knock-knock/terminal-activity-watch-anchor":
|
|
1374
|
+
return undefined;
|
|
1106
1375
|
}
|
|
1107
1376
|
}
|
|
1108
1377
|
function approvalFingerprint(terminal) {
|