@scotthuang/agent-knock-knock 0.12.18 → 0.12.20
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 +35 -0
- package/README.md +11 -11
- package/dist/src/claude-local-transcript-provider.d.ts +32 -0
- package/dist/src/claude-local-transcript-provider.js +106 -20
- package/dist/src/claude-local-transcript-provider.js.map +1 -1
- package/dist/src/cli-core.js +3 -0
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/openclaw-plugin-command-adapter.js +18 -8
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.js +23 -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/store.d.ts +1 -1
- package/dist/src/store.js +3 -3
- package/dist/src/store.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-command-cli-adapter.d.ts +16 -0
- package/dist/src/terminal-command-cli-adapter.js +67 -6
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-cli-adapter.js +8 -36
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-renderer.js +21 -22
- 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 +45 -2
- package/dist/src/terminal-watch-cli-adapter.js +980 -116
- 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 +12 -21
- package/dist/src/terminal-watch-service.js.map +1 -1
- package/dist/src/terminal-watch-store.d.ts +104 -4
- package/dist/src/terminal-watch-store.js +465 -15
- package/dist/src/terminal-watch-store.js.map +1 -1
- package/docs/quickstart-herdr.md +4 -4
- package/docs/quickstart-tmux.md +7 -7
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +19 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.20 - 2026-08-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add a durable `terminal_activity` / `best_effort` Watch fallback for an exact user-selected terminal when no unique provider task anchor is available. It arms on observed work or approval and reports stable idle only after two consecutive supervision sweeps.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Keep an explicit read-only Watch available across managed ownership, missing action advertisement, lifecycle-binding gaps, agent/artifact version drift, an existing Watch, and malformed unrelated Watch records; these conditions now warn, reuse, or degrade instead of vetoing the user.
|
|
12
|
+
- Recognize the Codex 0.149.1 `item_completed` `UserMessage` record as exact human-root evidence, including exact text and turn-identity validation.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Allow independent controller callback authorities to observe the same terminal while keeping same-target, same-controller Watch creation idempotent. Existing active Watches no longer hide the Watch action on terminal rows.
|
|
17
|
+
- Expose immutable Watch warnings plus `watch_mode` and `confidence` in status and callbacks so exact task completion remains distinct from best-effort terminal activity becoming idle.
|
|
18
|
+
|
|
19
|
+
### Security
|
|
20
|
+
|
|
21
|
+
- Keep manual Watch observation-only: it acquires no terminal mutation authority, sends no input, changes no managed ownership, and never auto-approves. Creation still fails when the exact terminal is absent or unobservable, neither an exact anchor nor screen-status path exists, or durable Watch state cannot be written.
|
|
22
|
+
|
|
23
|
+
## 0.12.19 - 2026-08-27
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Attach a durable Terminal Watch after a successful `terminal_user_explicit` unmanaged physical Send, restoring completion, failure, invalidation, and timeout callbacks without fabricating a managed Turn.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Advance the model-facing action contract to 23, Terminal Watch schema to v2, and Store writer protocol to 6 with compatible migration and normalization of existing records.
|
|
32
|
+
|
|
33
|
+
### Security
|
|
34
|
+
|
|
35
|
+
- Keep the user's explicit Send authoritative: Watch attachment and observation are best-effort, warning-only follow-up work and can never block, revoke, or automatically replay accepted terminal input.
|
|
36
|
+
- Bind fallback callbacks to the exact Codex rollout or Claude transcript task accepted after the Send, including same-request races and completion before the first Watch sweep. Automatic fallback Watches do not emit approval callbacks; approval input remains manual.
|
|
37
|
+
|
|
3
38
|
## 0.12.18 - 2026-08-27
|
|
4
39
|
|
|
5
40
|
### Fixed
|
package/README.md
CHANGED
|
@@ -74,11 +74,11 @@ AKK connects OpenClaw to Codex or Claude Code already running inside a supported
|
|
|
74
74
|
|
|
75
75
|
AKK is local-first. It has no hosted control plane or telemetry and does not change the coding agent's configured permission mode.
|
|
76
76
|
|
|
77
|
-
AKK can also observe
|
|
77
|
+
AKK can also observe any exact Codex or Claude Code terminal the user explicitly selects. Refreshing `/akk list` is the safest way to copy its full `terminal_id` and any advertised `watch` action, but Watch advertisement is a convenience rather than authorization: `/akk watch <exact-terminal-id>` and `watch({terminal_id})` honor that explicit read-only intent even when the current row does not advertise Watch. A coding-agent version warning, missing or incompatible task artifact, or existing AKK-managed ownership is reported but does not veto creation. AKK fails only when the exact terminal does not exist, its endpoint/process cannot be identified, neither a durable exact-task anchor nor a read-only screen-status activity path exists, or the durable Watch Store cannot be created. The returned durable `watch_id` is the only target for `/akk status <watch-id>` or `/akk unwatch <watch-id>`.
|
|
78
78
|
|
|
79
|
-
Terminal Watch is
|
|
79
|
+
A Terminal Watch is a separate schema-v2 aggregate, not an AKK Session or Turn. It sends no terminal input and does not adopt, claim, reserve, block, interrupt, approve, or otherwise change the selected task or any managed ownership. When AKK can construct a privacy-safe Codex rollout or Claude transcript task anchor, `watch_mode="exact_task"` and `confidence="exact"` follow that task and use its durable completion/failure evidence; missing or mismatched version evidence is only a warning and does not weaken an otherwise exact anchor. If artifact, native-task, or boundary evidence cannot establish a unique exact anchor at creation, AKK records warnings and falls back to `watch_mode="terminal_activity"`, `confidence="best_effort"`. That fallback watches only the exact terminal/process activity epoch: it must observe working or approval activity and then stable idle across consecutive sweeps before emitting its completion-shaped callback. The callback explicitly means “observed activity became idle”; it is not proof that one exact task completed successfully and carries no exact-task completion text. Exact-anchor drift still invalidates that exact Watch instead of following a successor task.
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
An active AKK-managed Turn normally already has the better correlated monitor, so use it by preference; an explicit Watch is nevertheless allowed because it is read-only and does not replace or mutate that owner. Manual-Watch approval observations remain notification-only and must be decided by the human in the TUI. Separately, a successful `terminal_user_explicit` unmanaged fallback may automatically attach an exact request-hash-bound Watch after Send. Terminal Watch outcomes and notification outboxes survive AKK or OpenClaw restarts, with leased retry and deterministic callback idempotency.
|
|
82
82
|
|
|
83
83
|
### Terminal, native session, AKK session, and Turn
|
|
84
84
|
|
|
@@ -93,9 +93,9 @@ terminal resource / process incarnation
|
|
|
93
93
|
└─ Turn 3 (turn_id)
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
The
|
|
96
|
+
The v23 model-facing action contract remains semantic-ID only. `send(session_id, request)` is **session-scoped** (`session_exact`): it creates a new `turn_id` in that exact native coding-agent context and never silently follows a different thread now visible in the pane. `send(terminal_id, request)` may be managed follow-current (`terminal_follow_current`) or the user-priority physical-terminal path (`terminal_user_explicit`), exactly as advertised by the current row. `session_id` and `terminal_id` are mutually exclusive; callers may omit both only when AKK must prove one unique send-ready pane. Human-facing slash selectors remain a discovery convenience, but structured tools never carry selectors or opaque authority values. The trusted plugin/CLI derives fresh terminal, binding, candidate, prompt, composer, and handoff fences internally. Every mutation holds the terminal lock; managed operations additionally use Store locks, while unmanaged user-priority fallback deliberately does not depend on Store health. A managed send creates a Turn and binds only the single rollout that durably accepts that exact request. The `turn_id` is not a destination for later ordinary sends; it is the exact identity used for status, approval, cancellation, renewal, callback retry, close, and callback correlation. If a Turn is `waiting_for_openclaw`, `respond(turn_id, answer)` supplies the answer inside that same Turn instead of creating another one.
|
|
97
97
|
|
|
98
|
-
v15 generalized the human-priority Codex path; v18 preserves that safety while removing opaque fences from model arguments. It covers a status-card-only Session with no rollout, a quiescent managed pane whose exact open-rollout inventory is complete but cannot identify the foreground candidate, and a supported manual `/clear` whose new logical thread is visible before its rollout materializes. The complete exact inventory domain binds the provider terminal, PID and process birth, workspace and canonical endpoint, and every open rollout's UUID, descriptor, device, inode, canonical path, and pre-submit byte offset. Native foreground resolution may be unavailable only when that independent domain is complete and exact; an incomplete, missing, stale, or changed domain fails closed. A `/clear` resume hint is advisory only. Under the terminal lock, AKK isolates the old Session, creates a separate zero-UUID provisional Session and Turn, sends only the real task, and promotes that target only after the post-submit monitor finds a unique exact request acceptance in the pinned rollout domain. The resulting native UUID may match or differ from the old Session; it is never silently merged back into the predecessor. A rollout-backed Codex row therefore advertises `terminal_follow_current` with `terminal_id`, not `session_exact`; a cached or direct `session_exact` attempt revalidates under the terminal lock, rejects before task text, and never downgrades itself to the follow-current path. Only released predecessor Turn history from a strictly earlier binding epoch is excluded from current-send authority; unresolved current-epoch state still blocks the managed path. The freshly listed semantic-ID action can work in a narrow pane without `/status`. Until promotion commits, strict `session_id` send, `respond`, managed `approve`, `cancel`, native lifecycle, callback delivery, and `native_inspect` remain unavailable. If terminal delivery or native acceptance is uncertain, AKK does not retry the input. Explicit Close is the user's management escape hatch: AKK closes the selected Turn first, sends no terminal input, leaves the coding agent and pane running, and then best-effort releases only linked AKK metadata. Missing, malformed, stale, or newer cleanup records are preserved and reported as warnings instead of vetoing Close. Refresh the list afterward; if the coding agent is still working, Watch can observe it again. v19 and v20 introduced and expanded `terminal_user_explicit`; v22 supersedes their Composer-dependent Codex behavior. Codex eligibility now depends on the exact live terminal/process and a scanned, non-blocked approval state, not Composer visibility, stability, or exactness. Physical fallback always sends one `C-u`, injects the new request, waits through the paste window, and dispatches Enter exactly once; after injection, Composer observation cannot veto Enter. Claude Code user-explicit Send, native inspection, and native lifecycle input remain exact-empty-only. The managed fast path may require exact empty before input, but after user-explicit Codex text injection it follows the same no-Composer-veto Enter rule. Broken AKK Turn, Session, deferred-transfer, transition, ledger, or Store state cannot veto the user decision. AKK tries the managed fast path where it is eligible; otherwise it delivers the physical operation once as unmanaged work and
|
|
98
|
+
v15 generalized the human-priority Codex path; v18 preserves that safety while removing opaque fences from model arguments. It covers a status-card-only Session with no rollout, a quiescent managed pane whose exact open-rollout inventory is complete but cannot identify the foreground candidate, and a supported manual `/clear` whose new logical thread is visible before its rollout materializes. The complete exact inventory domain binds the provider terminal, PID and process birth, workspace and canonical endpoint, and every open rollout's UUID, descriptor, device, inode, canonical path, and pre-submit byte offset. Native foreground resolution may be unavailable only when that independent domain is complete and exact; an incomplete, missing, stale, or changed domain fails closed. A `/clear` resume hint is advisory only. Under the terminal lock, AKK isolates the old Session, creates a separate zero-UUID provisional Session and Turn, sends only the real task, and promotes that target only after the post-submit monitor finds a unique exact request acceptance in the pinned rollout domain. The resulting native UUID may match or differ from the old Session; it is never silently merged back into the predecessor. A rollout-backed Codex row therefore advertises `terminal_follow_current` with `terminal_id`, not `session_exact`; a cached or direct `session_exact` attempt revalidates under the terminal lock, rejects before task text, and never downgrades itself to the follow-current path. Only released predecessor Turn history from a strictly earlier binding epoch is excluded from current-send authority; unresolved current-epoch state still blocks the managed path. The freshly listed semantic-ID action can work in a narrow pane without `/status`. Until promotion commits, strict `session_id` send, `respond`, managed `approve`, `cancel`, native lifecycle, callback delivery, and `native_inspect` remain unavailable. If terminal delivery or native acceptance is uncertain, AKK does not retry the input. Explicit Close is the user's management escape hatch: AKK closes the selected Turn first, sends no terminal input, leaves the coding agent and pane running, and then best-effort releases only linked AKK metadata. Missing, malformed, stale, or newer cleanup records are preserved and reported as warnings instead of vetoing Close. Refresh the list afterward; if the coding agent is still working, Watch can observe it again. v19 and v20 introduced and expanded `terminal_user_explicit`; v22 supersedes their Composer-dependent Codex behavior. Codex eligibility now depends on the exact live terminal/process and a scanned, non-blocked approval state, not Composer visibility, stability, or exactness. Physical fallback always sends one `C-u`, injects the new request, waits through the paste window, and dispatches Enter exactly once; after injection, Composer observation cannot veto Enter. Claude Code user-explicit Send, native inspection, and native lifecycle input remain exact-empty-only. The managed fast path may require exact empty before input, but after user-explicit Codex text injection it follows the same no-Composer-veto Enter rule. Broken AKK Turn, Session, deferred-transfer, transition, ledger, or Store state cannot veto the user decision. AKK tries the managed fast path where it is eligible; otherwise it delivers the physical operation once as unmanaged work. v23 captures the exact provider byte boundary before that physical mutation and, after Enter succeeds, best-effort attaches a Terminal Watch bound to the request hash and physical Send token. That Watch supplies the completion callback without claiming a managed Turn. Watch preparation or persistence failure is only a callback warning: it never vetoes, revokes, or retries the successful Send. For an omitted target, AKK durably binds the `message_id` to the first selected physical terminal before input whenever runtime durability is available, so a retry cannot move the same request to another pane. An existing or possibly existing same-ID record rejects automatic replay, and a replay reports the same persisted Watch when present. If no record exists and durability is unavailable, user priority wins: AKK proceeds with a warning, and strict cross-process replay or reselection protection is unavailable for that degraded invocation, so callers must not automatically retry its result. Once the sole mutation sequence begins, an uncertain result must not be retried automatically. `watch-status` remains the recovery path for an attached fallback Watch.
|
|
99
99
|
|
|
100
100
|
Approval remains separate from managed attribution. When `list` can prove one exact visible Codex approval prompt, it may advertise managed `approve({turn_id})` or terminal-scoped `approve({terminal_id})`; the latter remains possible when foreground rollout attribution is temporarily unavailable. The user must inspect and explicitly confirm that exact prompt. The plugin/CLI then keeps the confirmation offer private, recaptures the prompt, and revalidates the Store, terminal, process, and prompt fence under lock immediately before sending the exact key once. No token or fingerprint is transported through the model call. Terminal-scoped approval does not attribute the approval to a Turn, mutate Session identity, or create a durable approval receipt, so an uncertain transport result must not be retried blindly. It is never available to auto-approve. A changed prompt, identity, owner, or confirmation offer remains blocked.
|
|
101
101
|
|
|
@@ -103,7 +103,7 @@ The private approval fence remains prompt-scoped. AKK hashes the adapter-isolate
|
|
|
103
103
|
|
|
104
104
|
Human-friendly selectors such as `only`, `codex`, `claude`, a terminal ID, or `@short-ref` remain a slash-command discovery layer. Structured model tools use semantic IDs instead: strict continuation pre-fills `session_id`, while terminal-scoped Send pre-fills `terminal_id`. Merely observing a mismatch or an unbound rollout inventory never mutates the Store. Managed Send remains available only when AKK can prove its complete managed authority and an exact empty Composer before input. A listed `terminal_user_explicit` action has the narrower user-priority boundary instead: one exact live physical terminal/process and a scanned, non-blocked approval state. Codex applies `replace_current_composer_and_submit`; its Composer may be invisible, truncated, unstable, empty, or nonempty. Claude remains exact-empty-only. Parsed working activity and AKK internal management or persistence damage do not suppress that action. Native inspection and native lifecycle input also remain exact-empty-only. Other actions likewise expose only their semantic identities, while opaque freshness fences are derived and consumed inside the trusted plugin/CLI boundary.
|
|
105
105
|
|
|
106
|
-
An exact human switch while the old Session still has one active Turn is a decision point, not an automatic redirect. The terminal row may expose `handoff_decision` instead of a follow-current send. If the user chooses to close the old Turn, explicit Close takes priority and releases only its AKK management; it does not depend on the live native thread remaining unchanged, sends no terminal input, and does not stop the coding agent. Then list again and use only the newly advertised follow-current send
|
|
106
|
+
An exact human switch while the old Session still has one active Turn is a decision point, not an automatic redirect. The terminal row may expose `handoff_decision` instead of a follow-current send. If the user chooses to close the old Turn, explicit Close takes priority and releases only its AKK management; it does not depend on the live native thread remaining unchanged, sends no terminal input, and does not stop the coding agent. Then list again and use only the newly advertised follow-current send. A read-only Watch may be started for the exact selected terminal whether or not the row advertises it or the old Turn remains managed. Choosing `keep_source` changes no AKK state: restore the old native thread in the Codex or Claude TUI, then refresh the list.
|
|
107
107
|
|
|
108
108
|
Native clear/new/resume operations requested through AKK are explicit lifecycle actions, separate from ordinary Turn creation. A successful new/clear creates a new native thread and AKK Session; resume activates the exact historical native thread and its corresponding Session. Each successful lifecycle transition creates no Turn. A human may also run native `/clear`, `/new`, `/resume`, or the equivalent directly in the shared terminal. AKK does not forbid that takeover: the next exact, fresh terminal-scoped follow-current send can absorb the human-driven transition when it is safe, while a `session_id` send deliberately remains pinned to its old context. AKK serializes either transition, verifies the resulting native identity, and advances the terminal binding generation so work and callbacks from the previous context cannot cross the boundary. Exact AKK-driven lifecycle profiles are regression-tested for Codex 0.146.0/0.146.1/0.147.0/0.148.0/0.149.1 and Claude Code 2.1.218/2.1.226/2.1.237. They are verification records, not an allowlist: another complete `x.y.z` version keeps Watch, native inspection, candidate discovery, new, and resume available through the generic runtime protocol, with a compatibility warning. Runtime structure and postconditions still decide success; a possibly submitted but unproven operation is reported as uncertain and is never retried automatically.
|
|
109
109
|
|
|
@@ -250,7 +250,7 @@ The core command surface is intentionally small:
|
|
|
250
250
|
|
|
251
251
|
`/akk list` performs a controlled reconciliation across managed turns, and managed `/akk status` limits reconciliation to the selected turn. This can close records whose idle retention has elapsed and restore eligible missing monitors, but it does not send terminal input or retry managed-Turn callback delivery. A `watch_id` status reads that independent Watch record. The running OpenClaw supervisor coordinates two separate reconciliation passes every five seconds and at startup: one restores eligible `waiting_for_agent` Turn monitors, while the other observes active Terminal Watches and retries their durable callback outboxes. Each pass has its own error boundary, so one failure does not starve the other. Standalone shell queries are read-only unless their command explicitly reconciles, and resolving a selector never changes turn state.
|
|
252
252
|
|
|
253
|
-
Selectors fail closed: `only` works only with one actionable target, `latest` requires a unique newest target, and `codex` or `claude` must identify exactly one eligible pane. These names and `@short-ref` are human-facing slash-command resolution inputs. The
|
|
253
|
+
Selectors fail closed: `only` works only with one actionable target, `latest` requires a unique newest target, and `codex` or `claude` must identify exactly one eligible pane. These names and `@short-ref` are human-facing slash-command resolution inputs. The v23 structured model contract does not expose a `selector`: JSON actions carry an authoritative `session_id`, `terminal_id`, `turn_id`, `watch_id`, or other semantic entity ID. For ordinary send, exactly one of `session_id` and `terminal_id` may be supplied, or both may be omitted to require one unique send-ready pane. Approval likewise uses only `turn_id` or `terminal_id` after explicit confirmation. Before every terminal operation, AKK derives fresh opaque authority internally and revalidates the expected agent PID and provider-owned terminal identity. `terminal_user_explicit` additionally requires a scanned, non-blocked approval state. Codex Composer visibility, stability, exactness, and parsed working activity are not eligibility requirements; Claude still requires an exact empty Composer.
|
|
254
254
|
|
|
255
255
|
To ask AKK itself to change native context, first copy the full `terminal_id` from `/akk list`; lifecycle commands do not accept an ordinary-send `@short-ref` or loose agent selector. `/akk threads <exact-terminal-id>` lists exact, same-workspace candidates with a deterministic number, a collision-safe display-only `@short-id`, and the complete UUID. `/akk resume-thread <exact-terminal-id>` without a selection shows that list. A complete UUID remains compatible. A number or short ID resolves only against the latest list displayed in the same OpenClaw conversation incarnation; both expire after five minutes and fail after terminal, process, workspace, binding, candidate-set, or relevant action changes. AKK resolves that human navigation to the complete UUID and derives fresh private evidence under lock. `previous` (or `刚才那个`) is advertised only when the current Session's latest committed lifecycle transition identifies exactly one currently verified resumable source; it never guesses from title, recency, or static lineage. If `previous` is present, use only its exact prefilled semantic-ID action for a natural-language “刚才那个” request. Structured lifecycle mutations are `new_thread({terminal_id})` and `resume_thread({terminal_id,native_thread_id})`. AKK does not poll bindings or adopt observed switches in the background: a human-driven switch is adopted only as part of an explicit, fresh terminal-scoped send. If a recorded owner process exits, the next lifecycle listing can classify that sole historical binding as resumable, and the resume mutation compare-and-swap detaches it before touching the terminal. Stale, expired, unsupported, busy, ambiguous, active-elsewhere, or unverifiable transitions fail closed. Do not ask AKK to send `/clear`, `/new`, `/resume`, `/status`, Codex `/fork`, `/side`, or `/btw`, Claude `/branch`, or any other first-line native slash command as an ordinary task or answer; use an advertised AKK action, express the request in natural language, or enter an unsupported native command manually in the terminal UI.
|
|
256
256
|
|
|
@@ -258,15 +258,15 @@ The number and short-ID resume forms refer only to that displayed snapshot; neit
|
|
|
258
258
|
|
|
259
259
|
To request a native Codex status card or Claude Status panel, first run `agent_knock_knock_list` and use only that terminal row's advertised `native_inspect({terminal_id,inspection:"status"})` action. The structured tool schema is closed to those two semantic fields; callers cannot provide `/status`, another slash command, or an authority token. AKK serializes the inspection with terminal mutations, derives and revalidates its private binding fence and exact terminal identity, and returns only after it proves one fresh bounded status result and an idle postcondition. Codex status probes additionally require an exact viewport of at least 80 columns to preserve the full Session UUID, cross the versioned paste-settle boundary, and dispatch Enter exactly once. An initially narrow or unknown viewport fails before text input with a widen/zoom diagnostic; post-injection viewport or composer drift fails closed before Enter and leaves the draft for manual inspection. Codex `/status` and that viewport requirement apply only to operations that must prove the UUID before terminal input. An otherwise eligible terminal-scoped ordinary task can send once and bind from exact native acceptance afterward, so it does not run `/status` or fail merely because the pane is narrow. For Claude, the inspection safely dismisses the exact modal once. It never turns ordinary `send` or `respond` into a slash-command escape hatch.
|
|
260
260
|
|
|
261
|
-
The current OpenClaw surface registers 16 tools, and the top-level
|
|
261
|
+
The current OpenClaw surface registers 16 tools, and the top-level v23 `action_contracts` documents Terminal Watch alongside closed native inspection, user-priority current-pane Send/approval, lifecycle, and Turn actions. Every model-facing action carries semantic IDs only; opaque freshness and compare-and-swap fences stay inside the trusted plugin/CLI boundary. `available_actions` remains the authority for ordinary current actions. Read-only Watch is the deliberate exception: once the user supplies one exact listed `terminal_id`, missing Watch advertisement, agent-version or artifact uncertainty, and managed ownership are warnings rather than authorization vetoes. A listed `terminal_user_explicit` Send prioritizes the user's exact live physical terminal/process over broken AKK internal state. Codex physical fallback replaces the current Composer with the new request and submits once without a post-text Composer veto; Claude remains empty-only. AKK attempts managed delivery where eligible, then may deliver unmanaged and attach an exact Terminal Watch callback without creating a managed Turn. Watch failure is reported without changing delivery. Explicit Close is always available for a selected managed Turn after user confirmation, including deferred-transfer and handoff conflicts; it releases AKK management without terminal input or stopping the coding agent. A terminal row may also expose a nested `blocking_turns[].recovery_action` for the same user-owned Close. Invoke the advertised semantic-ID action, then refresh the list before doing anything else.
|
|
262
262
|
|
|
263
263
|
For natural-language tool use, `agent_knock_knock_list` is terminal-first. Each live pane appears exactly once in `terminals[]`; `process_state` reports whether its coding-agent process is alive and `activity_state` reports the parsed screen state. `managed.session_id` identifies the continuing AKK session, `managed.current_turn` is its optional active Turn, and `managed.recent_turn` is retained history; retained Turns do not occupy the terminal. A human-driven native-thread mismatch remains honestly classified as `management_state="conflict"`; its `handoff_state` is `external_handoff_adoptable` only when the row advertises the fenced follow-current `send`, otherwise it is `external_handoff_blocked`. `management_state="unavailable"` means only that AKK's management projection could not be read; the separately observed live terminal facts and any advertised `terminal_user_explicit` Send remain authoritative. Listing never performs the adoption. Pass `all=true` to include older entries in `managed.history`. By default, `unavailable_managed_turns[]` contains attention-needed records whose pane cannot be presented as a live terminal; `all=true` also includes retained unavailable history.
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
Except for read-only Watch, use only an `available_actions` entry returned in the current list, begin with its prefilled semantic IDs, and supply every `missing_required` field. A user-explicit Watch may instead pass one exact listed `terminal_id` directly; the absence of `available_actions.watch` does not veto observation. The only other action sources are a terminal row's nested `handoff_decision.choices.take_over_current.action` and an exact `blocking_turns[].recovery_action`; both require explicit user confirmation. When advertised, a managed Session's strict send uses `send({session_id,request})` and creates a new Turn only in that Session's native context. Terminal-scoped Send uses `send({terminal_id,request})`. A `terminal_user_explicit` action is the user-priority path: healthy AKK state still takes the managed fast path where its exact-empty pre-input requirements hold, while Codex physical fallback always replaces the current Composer with the request and submits it once without a post-text Composer veto. Claude can fall back only from an exact empty Composer. Unmanaged delivery creates no managed callback Turn; after successful Enter, AKK best-effort attaches an exact Terminal Watch callback. Attachment failure is a warning and never changes the Send result. Structured Send never exposes a selector, draft text, or composer authority. `respond` and managed controls use `turn_id`; terminal-scoped approval uses `terminal_id` only after explicit confirmation. Native inspection uses `{terminal_id,inspection:"status"}`. Other raw controls may be used only through the exact action their row advertises. `timeoutSeconds` is unsupported, and monitoring limits should be omitted unless the user explicitly asks to change them. AKK derives fresh opaque fences and revalidates availability under lock before every side effect.
|
|
266
266
|
|
|
267
267
|
On first attach, the target terminal must be explicitly named by the user; AKK never guesses which already-running pane should receive a task.
|
|
268
268
|
|
|
269
|
-
The top-level
|
|
269
|
+
The top-level v23 action contracts include `watch` and `unwatch` as well as `send`, manual `approve`, `native_inspect`, `list_resumable_threads`, `new_thread`, `resume_thread`, and conflict-only `reconcile_binding`. Their model-facing mutation shapes are deliberately small: `watch({terminal_id})`; `send({session_id|terminal_id,request})` with the targets mutually exclusive; managed `approve({turn_id})` or terminal-scoped `approve({terminal_id})`; `native_inspect({terminal_id,inspection})`; `new_thread({terminal_id})`; `resume_thread({terminal_id,native_thread_id})`; and `reconcile_binding({terminal_id,conflicting_session_id})`. Watch status exposes `watch_mode` and `confidence`: exact provider evidence yields `exact_task`/`exact`, while the terminal-activity fallback yields `terminal_activity`/`best_effort` plus creation warnings. The listed Send scope distinguishes managed delivery from `terminal_user_explicit`; the latter is gated by exact terminal/process identity and scanned, non-blocked approval state, not AKK management health or Codex Composer observation. An unmanaged fallback creates no managed Turn but can return `callback_mode="terminal_watch"` and a durable `watch_id`. Approval, handoff takeover, and reconciliation require explicit user confirmation, but none transports a token, revision, binding ID/generation, candidate or composer fence, handoff-only live-native-UUID fence, or approval fingerprint through the model. `native_thread_id` remains the intentional semantic identity for resume. `watch` returns a `watch_id`; Watch status and `unwatch` use only that ID. Human-facing resume numbers and short IDs remain conversation-scoped navigation aids that AKK resolves to the complete native thread ID before the structured action. Orphan close retains `expected_message_id` or `expected_transition_id` because those are entity identities, not opaque authority tokens. The plugin/CLI derives and revalidates all freshness fences privately under the canonical locks. Store format remains 1 and writer protocol remains 6.
|
|
270
270
|
|
|
271
271
|
Workspace is not a routing boundary. AKK can list, inspect, and control verified panes across projects; when more than one target matches, choose one listed `terminal_id` for a structured tool or one listed selector for a slash command.
|
|
272
272
|
|
|
@@ -405,7 +405,7 @@ than a publishing prerequisite.
|
|
|
405
405
|
|
|
406
406
|
Managed state now lives in the stable `~/.agent-knock-knock/store` root. Its manifest prevents an incompatible AKK writer from changing authoritative Session or Turn state. Directories use mode `0700`; state and log files use `0600`.
|
|
407
407
|
|
|
408
|
-
The manifest checks storage format and writer behavior separately. An unknown `format_version` is not read. The current writer protocol is
|
|
408
|
+
The manifest checks storage format and writer behavior separately. An unknown `format_version` is not read. The current writer protocol is 6, and writer protocols 1 through 5 are its supported predecessors: inspection reports them as `upgradeable`. Upgrading protocol 1 or 2 validates predecessor Turn records, deterministically derives and durably materializes authoritative Session records, and quarantines ambiguous Session bindings before atomically publishing protocol 6. Protocols 3, 4, and 5 already have Session authority, so their upgrade is an atomic manifest-only writer fence with no data migration. Protocol 6 prevents an older writer from silently rejecting or damaging v2 Terminal Watch records. Existing Turn state and event logs remain unchanged, and the manifest's `created_at` is preserved. Any other writer-protocol mismatch remains readable for normal queries, while explicit reconciliation reports `skipped` and every mutation fails closed before terminal or Gateway side effects.
|
|
409
409
|
|
|
410
410
|
The former `~/.agent-knock-knock/conversations` directory is left untouched; AKK does not read or migrate it. Existing Codex and Claude Code tmux panes remain available through live discovery, while their old managed-turn IDs, callback associations, and legacy conversation aliases are not carried into the new Store. Compatible future upgrades continue using the stable Store rather than creating a directory per package version.
|
|
411
411
|
|
|
@@ -113,6 +113,22 @@ export type ClaudeDeadProcessCompletionObservation = {
|
|
|
113
113
|
status: "unverifiable";
|
|
114
114
|
reason: string;
|
|
115
115
|
};
|
|
116
|
+
export type ClaudeUserExplicitFallbackTranscriptObservation = {
|
|
117
|
+
status: "pending";
|
|
118
|
+
acceptance?: ClaudeTranscriptAcceptanceEvidence;
|
|
119
|
+
observedEndOffsetBytes?: number;
|
|
120
|
+
} | {
|
|
121
|
+
status: "completed";
|
|
122
|
+
acceptance: ClaudeTranscriptAcceptanceEvidence;
|
|
123
|
+
completion: TerminalCompletionEvidence;
|
|
124
|
+
observedEndOffsetBytes: number;
|
|
125
|
+
} | {
|
|
126
|
+
status: "unavailable";
|
|
127
|
+
reason: string;
|
|
128
|
+
};
|
|
129
|
+
export interface ObserveClaudeUserExplicitFallbackTranscriptOptions extends Omit<DetectClaudeTranscriptCompletionOptions, "agentRows"> {
|
|
130
|
+
acceptanceEvidence?: unknown;
|
|
131
|
+
}
|
|
116
132
|
export interface ClaudeHistoricalSessionSummary {
|
|
117
133
|
id: string;
|
|
118
134
|
cwd: string;
|
|
@@ -203,6 +219,21 @@ export declare function observeClaudeDeadProcessTranscriptCompletion(request: Te
|
|
|
203
219
|
* idle because the row is normally written while Claude is working.
|
|
204
220
|
*/
|
|
205
221
|
export declare function detectClaudeTranscriptAcceptance(request: TerminalDurableCompletionRequest, options: DetectClaudeTranscriptCompletionOptions): ClaudeTranscriptAcceptanceEvidence | undefined;
|
|
222
|
+
/**
|
|
223
|
+
* Observe one user-explicit fallback request without persisting its raw text.
|
|
224
|
+
* The immutable pre-send transcript byte anchor plus the SHA-256 request hash
|
|
225
|
+
* must identify exactly one post-anchor root prompt.
|
|
226
|
+
*/
|
|
227
|
+
export declare function detectClaudeTranscriptAcceptanceByHash(request: TerminalDurableCompletionRequest, options: DetectClaudeTranscriptCompletionOptions): ClaudeTranscriptAcceptanceEvidence | undefined;
|
|
228
|
+
export declare function detectClaudeTranscriptCompletionByHash(request: TerminalDurableCompletionRequest, options: DetectClaudeTranscriptCompletionOptions): TerminalCompletionEvidence | undefined;
|
|
229
|
+
/**
|
|
230
|
+
* Observe a fallback Watch from its immutable pre-send transcript anchor.
|
|
231
|
+
* Once acceptance has been persisted, the native prompt UUID becomes the
|
|
232
|
+
* sole prompt selector so a later identical request cannot redirect the
|
|
233
|
+
* Watch. This path intentionally requires only the durable transcript: a
|
|
234
|
+
* completed callback remains recoverable after the pane or agent row exits.
|
|
235
|
+
*/
|
|
236
|
+
export declare function observeClaudeUserExplicitFallbackTranscript(request: TerminalDurableCompletionRequest, options: ObserveClaudeUserExplicitFallbackTranscriptOptions): ClaudeUserExplicitFallbackTranscriptObservation;
|
|
206
237
|
/**
|
|
207
238
|
* Detects exactly one unresolved foreground Bash tool use for the current
|
|
208
239
|
* AKK-managed Claude turn. It uses the same anchored, owner-private,
|
|
@@ -210,3 +241,4 @@ export declare function detectClaudeTranscriptAcceptance(request: TerminalDurabl
|
|
|
210
241
|
* closed on identity changes, completed turns, background work, or ambiguity.
|
|
211
242
|
*/
|
|
212
243
|
export declare function detectClaudeTranscriptPendingApproval(request: TerminalDurableCompletionRequest, options: DetectClaudeTranscriptCompletionOptions): ClaudeTranscriptPendingApprovalEvidence | undefined;
|
|
244
|
+
export declare function claudeTranscriptAnchorFingerprint(anchor: ClaudeTranscriptAnchor): string;
|
|
@@ -1258,6 +1258,87 @@ export function detectClaudeTranscriptAcceptance(request, options) {
|
|
|
1258
1258
|
}
|
|
1259
1259
|
return acceptanceEvidenceFromSnapshot(snapshot);
|
|
1260
1260
|
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Observe one user-explicit fallback request without persisting its raw text.
|
|
1263
|
+
* The immutable pre-send transcript byte anchor plus the SHA-256 request hash
|
|
1264
|
+
* must identify exactly one post-anchor root prompt.
|
|
1265
|
+
*/
|
|
1266
|
+
export function detectClaudeTranscriptAcceptanceByHash(request, options) {
|
|
1267
|
+
const snapshot = readClaudeTranscriptTurnSnapshot(request, options, undefined, "live_monitor", "hash_only");
|
|
1268
|
+
return snapshot ? acceptanceEvidenceFromSnapshot(snapshot) : undefined;
|
|
1269
|
+
}
|
|
1270
|
+
export function detectClaudeTranscriptCompletionByHash(request, options) {
|
|
1271
|
+
const snapshot = readClaudeTranscriptTurnSnapshot(request, options, "idle", "live_monitor", "hash_only");
|
|
1272
|
+
return snapshot ? completionFromRecords(snapshot) : undefined;
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Observe a fallback Watch from its immutable pre-send transcript anchor.
|
|
1276
|
+
* Once acceptance has been persisted, the native prompt UUID becomes the
|
|
1277
|
+
* sole prompt selector so a later identical request cannot redirect the
|
|
1278
|
+
* Watch. This path intentionally requires only the durable transcript: a
|
|
1279
|
+
* completed callback remains recoverable after the pane or agent row exits.
|
|
1280
|
+
*/
|
|
1281
|
+
export function observeClaudeUserExplicitFallbackTranscript(request, options) {
|
|
1282
|
+
try {
|
|
1283
|
+
const sessionId = nonEmptyString(request.sessionId);
|
|
1284
|
+
const requestHash = nonEmptyString(request.requestHash);
|
|
1285
|
+
if (!sessionId || !requestHash) {
|
|
1286
|
+
throw new Error("Claude fallback transcript request identity is unavailable");
|
|
1287
|
+
}
|
|
1288
|
+
const persistedAcceptance = options.acceptanceEvidence === undefined
|
|
1289
|
+
? undefined
|
|
1290
|
+
: validateTerminalSubmissionAcceptanceEvidence(options.acceptanceEvidence, {
|
|
1291
|
+
source: "claude_transcript",
|
|
1292
|
+
nativeThreadId: sessionId,
|
|
1293
|
+
requestHash
|
|
1294
|
+
});
|
|
1295
|
+
const acceptedPromptUuid = persistedAcceptance
|
|
1296
|
+
? uuidValue(persistedAcceptance.acceptanceId)
|
|
1297
|
+
: undefined;
|
|
1298
|
+
if (persistedAcceptance && !acceptedPromptUuid) {
|
|
1299
|
+
throw new Error("persisted Claude fallback acceptance has no exact prompt UUID");
|
|
1300
|
+
}
|
|
1301
|
+
const snapshot = readClaudeTranscriptTurnSnapshot(request, { ...options, agentRows: [] }, undefined, "durable_fallback", "hash_only", acceptedPromptUuid);
|
|
1302
|
+
if (!snapshot) {
|
|
1303
|
+
return { status: "pending" };
|
|
1304
|
+
}
|
|
1305
|
+
const observedAcceptance = acceptanceEvidenceFromSnapshot(snapshot);
|
|
1306
|
+
if (!observedAcceptance) {
|
|
1307
|
+
if (persistedAcceptance) {
|
|
1308
|
+
throw new Error("the exact accepted Claude fallback prompt is absent from its transcript");
|
|
1309
|
+
}
|
|
1310
|
+
return {
|
|
1311
|
+
status: "pending",
|
|
1312
|
+
observedEndOffsetBytes: snapshot.observedEndOffsetBytes
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
if (persistedAcceptance) {
|
|
1316
|
+
assertSameClaudeTranscriptAcceptance(persistedAcceptance, observedAcceptance, snapshot);
|
|
1317
|
+
}
|
|
1318
|
+
const completion = completionFromRecords(snapshot, {
|
|
1319
|
+
requireVerifiableCompletionSignal: true
|
|
1320
|
+
});
|
|
1321
|
+
if (!completion) {
|
|
1322
|
+
return {
|
|
1323
|
+
status: "pending",
|
|
1324
|
+
acceptance: persistedAcceptance ?? observedAcceptance,
|
|
1325
|
+
observedEndOffsetBytes: snapshot.observedEndOffsetBytes
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
return {
|
|
1329
|
+
status: "completed",
|
|
1330
|
+
acceptance: persistedAcceptance ?? observedAcceptance,
|
|
1331
|
+
completion,
|
|
1332
|
+
observedEndOffsetBytes: snapshot.observedEndOffsetBytes
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
catch (error) {
|
|
1336
|
+
return {
|
|
1337
|
+
status: "unavailable",
|
|
1338
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1261
1342
|
function acceptanceEvidenceFromSnapshot(snapshot) {
|
|
1262
1343
|
const prompt = matchingManagedPrompt(snapshot);
|
|
1263
1344
|
if (!prompt) {
|
|
@@ -1333,9 +1414,9 @@ export function detectClaudeTranscriptPendingApproval(request, options) {
|
|
|
1333
1414
|
}
|
|
1334
1415
|
return pendingApprovalFromRecords(snapshot);
|
|
1335
1416
|
}
|
|
1336
|
-
function readClaudeTranscriptTurnSnapshot(request, options, requiredAgentStatus, readMode = "live_monitor") {
|
|
1417
|
+
function readClaudeTranscriptTurnSnapshot(request, options, requiredAgentStatus, readMode = "live_monitor", requestIdentity = "text_and_hash", acceptedPromptUuid) {
|
|
1337
1418
|
const unavailable = (reason) => {
|
|
1338
|
-
if (readMode
|
|
1419
|
+
if (readMode !== "live_monitor") {
|
|
1339
1420
|
throw new Error(reason);
|
|
1340
1421
|
}
|
|
1341
1422
|
return undefined;
|
|
@@ -1354,9 +1435,10 @@ function readClaudeTranscriptTurnSnapshot(request, options, requiredAgentStatus,
|
|
|
1354
1435
|
if (!sessionId ||
|
|
1355
1436
|
!cwd ||
|
|
1356
1437
|
!expectedRequestHash ||
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1438
|
+
(requestIdentity === "text_and_hash" &&
|
|
1439
|
+
(!requestTextHash ||
|
|
1440
|
+
!expectedPromptText ||
|
|
1441
|
+
expectedRequestHash !== requestTextHash)) ||
|
|
1360
1442
|
startedAtMs === undefined ||
|
|
1361
1443
|
capturedAtMs === undefined) {
|
|
1362
1444
|
return unavailable("Claude transcript request metadata cannot be verified");
|
|
@@ -1371,17 +1453,19 @@ function readClaudeTranscriptTurnSnapshot(request, options, requiredAgentStatus,
|
|
|
1371
1453
|
if (runtimePid === undefined || runtimePid !== anchor.pid) {
|
|
1372
1454
|
throw new Error("Claude transcript anchor PID does not match the active terminal runtime");
|
|
1373
1455
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
agent.
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1456
|
+
if (readMode !== "durable_fallback") {
|
|
1457
|
+
const agent = exactInteractiveAgent(options.agentRows, anchor.pid);
|
|
1458
|
+
if (!agent) {
|
|
1459
|
+
throw new Error("the exact Claude process is absent from the local agent registry");
|
|
1460
|
+
}
|
|
1461
|
+
if (agent.startedAt !== anchor.agent_started_at_ms ||
|
|
1462
|
+
agent.sessionId !== sessionId ||
|
|
1463
|
+
normalizePath(agent.cwd) !== normalizePath(cwd)) {
|
|
1464
|
+
throw new Error("the Claude process session identity changed after the managed send");
|
|
1465
|
+
}
|
|
1466
|
+
if (requiredAgentStatus && agent.status !== requiredAgentStatus) {
|
|
1467
|
+
return unavailable(`the exact Claude process is ${agent.status}, not ${requiredAgentStatus}`);
|
|
1468
|
+
}
|
|
1385
1469
|
}
|
|
1386
1470
|
const projectsRoot = projectsRootPath(path.resolve(options.claudeHome ?? defaultClaudeHome()));
|
|
1387
1471
|
if (!isRealDirectory(projectsRoot)) {
|
|
@@ -1408,7 +1492,7 @@ function readClaudeTranscriptTurnSnapshot(request, options, requiredAgentStatus,
|
|
|
1408
1492
|
if (bytesToRead > maxTurnBytes) {
|
|
1409
1493
|
throw new Error("Claude transcript turn exceeded the bounded local read limit");
|
|
1410
1494
|
}
|
|
1411
|
-
if (readMode
|
|
1495
|
+
if (readMode !== "live_monitor" &&
|
|
1412
1496
|
!fileEndsWithNewline(opened.fd, opened.stat.size)) {
|
|
1413
1497
|
throw new Error("Claude transcript ends with an incomplete JSONL record");
|
|
1414
1498
|
}
|
|
@@ -1428,6 +1512,7 @@ function readClaudeTranscriptTurnSnapshot(request, options, requiredAgentStatus,
|
|
|
1428
1512
|
cwd,
|
|
1429
1513
|
expectedRequestHash,
|
|
1430
1514
|
expectedPromptText,
|
|
1515
|
+
...(acceptedPromptUuid ? { expectedPromptUuid: acceptedPromptUuid } : {}),
|
|
1431
1516
|
transcriptFileId: transcriptFileId(sessionId, fileIdentity),
|
|
1432
1517
|
observedEndOffsetBytes: opened.stat.size
|
|
1433
1518
|
};
|
|
@@ -1441,8 +1526,9 @@ function matchingManagedPrompt(snapshot) {
|
|
|
1441
1526
|
const promptText = userPromptText(record);
|
|
1442
1527
|
const exactPromptIdentity = snapshot.expectedPromptUuid
|
|
1443
1528
|
? uuidValue(record.uuid) === snapshot.expectedPromptUuid
|
|
1444
|
-
: snapshot.expectedPromptText !== undefined
|
|
1445
|
-
exactPromptText(promptText) === snapshot.expectedPromptText
|
|
1529
|
+
: snapshot.expectedPromptText !== undefined
|
|
1530
|
+
? exactPromptText(promptText) === snapshot.expectedPromptText
|
|
1531
|
+
: exactRequestFingerprint(promptText) === snapshot.expectedRequestHash;
|
|
1446
1532
|
return record.type === "user" &&
|
|
1447
1533
|
isRecord(record.message) &&
|
|
1448
1534
|
record.message.role === "user" &&
|
|
@@ -2385,7 +2471,7 @@ function exactRequestFingerprint(value) {
|
|
|
2385
2471
|
const text = String(value ?? "");
|
|
2386
2472
|
return text ? createHash("sha256").update(text).digest("hex") : undefined;
|
|
2387
2473
|
}
|
|
2388
|
-
function claudeTranscriptAnchorFingerprint(anchor) {
|
|
2474
|
+
export function claudeTranscriptAnchorFingerprint(anchor) {
|
|
2389
2475
|
return sha256Hex(JSON.stringify({
|
|
2390
2476
|
schema: "agent-knock-knock/claude-transcript-acceptance-anchor",
|
|
2391
2477
|
version: 1,
|