@scotthuang/agent-knock-knock 0.13.0 → 0.13.2
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 +30 -0
- package/dist/src/claude-lifecycle-compatibility.js +49 -2
- package/dist/src/claude-lifecycle-compatibility.js.map +1 -1
- package/dist/src/claude-terminal-agent-adapter.d.ts +1 -1
- package/dist/src/claude-terminal-agent-adapter.js +2 -2
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/codex-lifecycle-compatibility.js +2 -1
- package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
- package/dist/src/native-thread-transition-verification-adapter.js +1 -0
- package/dist/src/native-thread-transition-verification-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-command-adapter.js +3 -3
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.js +1 -1
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +11 -0
- package/dist/src/terminal-agent-bridge.js +248 -2
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js +63 -1
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
- package/dist/src/terminal-dispatch-recovery-service.d.ts +1 -0
- package/dist/src/terminal-dispatch-recovery-service.js +7 -0
- package/dist/src/terminal-dispatch-recovery-service.js.map +1 -1
- package/dist/src/terminal-monitor-state-reconciliation-service.js +15 -1
- package/dist/src/terminal-monitor-state-reconciliation-service.js.map +1 -1
- package/dist/src/terminal-questionnaire-adapter.d.ts +6 -2
- package/dist/src/terminal-questionnaire-adapter.js +61 -40
- package/dist/src/terminal-questionnaire-adapter.js.map +1 -1
- package/dist/src/terminal-watch-store.js +135 -9
- package/dist/src/terminal-watch-store.js.map +1 -1
- package/docs/quickstart-tmux.md +2 -2
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +4 -4
|
@@ -5,7 +5,7 @@ import { hasCanonicalTerminalEndpoint, sameTerminalControlIncarnation, terminalC
|
|
|
5
5
|
import { CLAUDE_INJECTED_PASTE_FRAME_PROFILE, claudeInjectedPasteProofEligible, revalidateClaudeComposerProof, settleClaudeInjectedPasteComposer } from "./claude-injected-paste-proof.js";
|
|
6
6
|
import { TERMINAL_INTERACTION_SCHEMA, TERMINAL_INTERACTION_VERSION, validateAnyTerminalInteractionResponse, validateTerminalInteractionProjection } from "./terminal-interaction-protocol.js";
|
|
7
7
|
import { captureTerminalInteraction } from "./terminal-interaction-core.js";
|
|
8
|
-
// Verified Codex profiles through 0.
|
|
8
|
+
// Verified Codex profiles through 0.154.0 keep Enter in paste/newline mode for
|
|
9
9
|
// 120ms after burst input. Cross that boundary rather than landing on it, and
|
|
10
10
|
// also require observable composer stability instead of treating this delay
|
|
11
11
|
// alone as acceptance.
|
|
@@ -60,6 +60,10 @@ const CODEX_NATIVE_STATUS_POPUP_BY_PROFILE = {
|
|
|
60
60
|
" /status show current session configuration and token usage",
|
|
61
61
|
" /statusline configure which items appear in the status line"
|
|
62
62
|
],
|
|
63
|
+
"codex-tui-0.154.0": [
|
|
64
|
+
" /status show current session configuration and token usage",
|
|
65
|
+
" /statusline configure which items appear in the status line"
|
|
66
|
+
],
|
|
63
67
|
"codex-tui-generic-v1": [
|
|
64
68
|
" /status show current session configuration and token usage",
|
|
65
69
|
" /statusline configure which items appear in the status line"
|
|
@@ -80,6 +84,7 @@ const CODEX_NATIVE_STATUS_MIN_VIEWPORT_BY_PROFILE = {
|
|
|
80
84
|
"codex-tui-0.151.0": 80,
|
|
81
85
|
"codex-tui-0.153.0": 80,
|
|
82
86
|
"codex-tui-0.153.4": 80,
|
|
87
|
+
"codex-tui-0.154.0": 80,
|
|
83
88
|
"codex-tui-generic-v1": 80
|
|
84
89
|
};
|
|
85
90
|
const CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE = {
|
|
@@ -119,6 +124,18 @@ const CLAUDE_NATIVE_STATUS_POPUP_BY_PROFILE = {
|
|
|
119
124
|
"/ide Manage IDE integrations and show status",
|
|
120
125
|
"/usage Show session cost, plan usage, and activity stats"
|
|
121
126
|
],
|
|
127
|
+
"claude-code-2.1.266-native-status": [
|
|
128
|
+
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
129
|
+
"/statusline Set up Claude Code's status line UI",
|
|
130
|
+
"/ide Manage IDE integrations and show status",
|
|
131
|
+
"/usage Show session cost, plan usage, and activity stats"
|
|
132
|
+
],
|
|
133
|
+
"claude-code-2.1.267-native-status": [
|
|
134
|
+
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
135
|
+
"/statusline Set up Claude Code's status line UI",
|
|
136
|
+
"/ide Manage IDE integrations and show status",
|
|
137
|
+
"/usage Show session cost, plan usage, and activity stats"
|
|
138
|
+
],
|
|
122
139
|
"claude-code-unverified-native-status-v1": [
|
|
123
140
|
"/status Show Claude Code status including version, model, account, API connectivity, and tool statuses",
|
|
124
141
|
"/statusline Set up Claude Code's status line UI",
|
|
@@ -151,6 +168,14 @@ const CLAUDE_NATIVE_STATUS_SETTLE_BY_PROFILE = {
|
|
|
151
168
|
minimumStableMs: 80,
|
|
152
169
|
maximumSettleMs: 5_000
|
|
153
170
|
},
|
|
171
|
+
"claude-code-2.1.266-native-status": {
|
|
172
|
+
minimumStableMs: 80,
|
|
173
|
+
maximumSettleMs: 5_000
|
|
174
|
+
},
|
|
175
|
+
"claude-code-2.1.267-native-status": {
|
|
176
|
+
minimumStableMs: 80,
|
|
177
|
+
maximumSettleMs: 5_000
|
|
178
|
+
},
|
|
154
179
|
"claude-code-unverified-native-status-v1": {
|
|
155
180
|
minimumStableMs: 80,
|
|
156
181
|
maximumSettleMs: 5_000
|
|
@@ -935,6 +960,13 @@ export class TerminalAgentBridge {
|
|
|
935
960
|
// not let historical Esc instructions in scrollback veto Send.
|
|
936
961
|
scrollbackLines: 0
|
|
937
962
|
});
|
|
963
|
+
const asyncQuestionInputMode = inspectCodexAsyncQuestionInputMode(captured.screen);
|
|
964
|
+
if (asyncQuestionInputMode === "expanded" ||
|
|
965
|
+
asyncQuestionInputMode === "ambiguous") {
|
|
966
|
+
throw new Error(asyncQuestionInputMode === "expanded"
|
|
967
|
+
? "the Codex async question editor currently owns terminal input; answer, skip, or return to the main prompt before retrying the explicit user Send"
|
|
968
|
+
: "Codex shows an async-question surface but AKK cannot prove that the main prompt owns terminal input; return to a complete main prompt before retrying the explicit user Send");
|
|
969
|
+
}
|
|
938
970
|
if (captured.inspection.approval.blocked ||
|
|
939
971
|
captured.inspection.activity.state === "awaiting_approval" ||
|
|
940
972
|
codexBlockingModalVisible(captured.screen)) {
|
|
@@ -2891,7 +2923,221 @@ function codexBlockingModalVisible(screen) {
|
|
|
2891
2923
|
.join("\n");
|
|
2892
2924
|
return /\b(?:press|use)\s+(?:esc|escape)\s+to\s+(?:cancel|close|dismiss)\b/iu
|
|
2893
2925
|
.test(tail) ||
|
|
2894
|
-
/\besc\s+to\s+cancel\b/iu.test(tail)
|
|
2926
|
+
/\besc\s+to\s+cancel\b/iu.test(tail) ||
|
|
2927
|
+
codexActiveWriterViewerVisible(tail) ||
|
|
2928
|
+
["expanded", "ambiguous"].includes(inspectCodexAsyncQuestionInputMode(tail));
|
|
2929
|
+
}
|
|
2930
|
+
function codexActiveWriterViewerVisible(styledScreen) {
|
|
2931
|
+
const lines = stripTerminalEscapeSequences(styledScreen)
|
|
2932
|
+
.replace(/\r\n?/gu, "\n")
|
|
2933
|
+
.split("\n")
|
|
2934
|
+
.slice(-80);
|
|
2935
|
+
let titleIndex = -1;
|
|
2936
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
2937
|
+
if (/^ {2}🔒(?:\s|$)/u.test(lines[index])) {
|
|
2938
|
+
titleIndex = index;
|
|
2939
|
+
break;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
if (titleIndex < 0) {
|
|
2943
|
+
return false;
|
|
2944
|
+
}
|
|
2945
|
+
const laterMainComposer = lines.findIndex((line, index) => index > titleIndex && CODEX_COMPOSER_MARKER.test(line));
|
|
2946
|
+
if (laterMainComposer >= 0) {
|
|
2947
|
+
return false;
|
|
2948
|
+
}
|
|
2949
|
+
const frame = lines.slice(titleIndex, titleIndex + 12)
|
|
2950
|
+
.map((line) => line.trim().replace(/\s+/gu, " "))
|
|
2951
|
+
.filter((line) => line.length > 0)
|
|
2952
|
+
.join(" ");
|
|
2953
|
+
return /🔒\s+This conversation is open in another app\s+\S+ to Retry/iu
|
|
2954
|
+
.test(frame) &&
|
|
2955
|
+
/Close it there and press\s+\S+\s+to continue here\./iu.test(frame) &&
|
|
2956
|
+
/\S+ retry\s+\S+ exit(?:\s+\S+ transcript)?/iu.test(frame);
|
|
2957
|
+
}
|
|
2958
|
+
const CODEX_ASYNC_QUESTION_FOOTER_LABEL = /\s(submit|skip|main prompt|prev question|next question|queued messages)(?=\s{2,}|\s*$)/giu;
|
|
2959
|
+
function codexStructuredAsyncFooterEvidence(lines, evidenceFloor) {
|
|
2960
|
+
let strongest = { index: -1, labelCount: 0 };
|
|
2961
|
+
for (let start = evidenceFloor + 1; start < lines.length; start += 1) {
|
|
2962
|
+
const labels = new Set();
|
|
2963
|
+
for (let end = start; end < Math.min(lines.length, start + 5); end += 1) {
|
|
2964
|
+
const line = lines[end];
|
|
2965
|
+
if (!/^ {2}(?![ ↳?])\S/u.test(line)) {
|
|
2966
|
+
break;
|
|
2967
|
+
}
|
|
2968
|
+
for (const match of line.matchAll(CODEX_ASYNC_QUESTION_FOOTER_LABEL)) {
|
|
2969
|
+
labels.add(match[1].toLowerCase());
|
|
2970
|
+
}
|
|
2971
|
+
if (labels.size > strongest.labelCount) {
|
|
2972
|
+
strongest = { index: end, labelCount: labels.size };
|
|
2973
|
+
}
|
|
2974
|
+
if (labels.size >= 2) {
|
|
2975
|
+
return strongest;
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
return strongest;
|
|
2980
|
+
}
|
|
2981
|
+
function codexMainComposerFooterIndex(lines, composerIndex) {
|
|
2982
|
+
for (let index = lines.length - 1; index > composerIndex; index -= 1) {
|
|
2983
|
+
// A real statusline starts at column zero. Draft continuations are inset,
|
|
2984
|
+
// even when their text happens to look exactly like a model statusline.
|
|
2985
|
+
if (CODEX_COMPLETE_COMPOSER_FOOTER.test(lines[index].trimEnd())) {
|
|
2986
|
+
return index;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
return -1;
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Classify Codex 0.154's non-blocking inline-question surface by current input
|
|
2993
|
+
* ownership. The shared `Queued follow-up inputs` heading is not sufficient:
|
|
2994
|
+
* ordinary queued messages use it too. An exact collapsed summary, after any
|
|
2995
|
+
* ordinary queue preview is removed, proves Send-safe ownership even when the
|
|
2996
|
+
* Composer is outside the viewport. Positive editor evidence without a
|
|
2997
|
+
* complete shape remains fail-closed because paste plus Enter would otherwise
|
|
2998
|
+
* submit the user's task as an answer.
|
|
2999
|
+
*/
|
|
3000
|
+
export function inspectCodexAsyncQuestionInputMode(styledScreen) {
|
|
3001
|
+
const lines = stripTerminalEscapeSequences(styledScreen)
|
|
3002
|
+
.replace(/\r\n?/gu, "\n")
|
|
3003
|
+
.replace(/\u00a0/gu, " ")
|
|
3004
|
+
.split("\n")
|
|
3005
|
+
.slice(-80);
|
|
3006
|
+
while (lines.length > 0 && lines.at(-1)?.trim().length === 0) {
|
|
3007
|
+
lines.pop();
|
|
3008
|
+
}
|
|
3009
|
+
const normalized = lines.map((line) => line.trim().replace(/\s+/gu, " "));
|
|
3010
|
+
let mainComposerIndex = -1;
|
|
3011
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
3012
|
+
// The main Composer marker is column zero. Async option and inline-text
|
|
3013
|
+
// markers are inset by the menu surface, even after ANSI is removed.
|
|
3014
|
+
if (CODEX_COMPOSER_MARKER.test(lines[index])) {
|
|
3015
|
+
mainComposerIndex = index;
|
|
3016
|
+
break;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
let headerIndex = -1;
|
|
3020
|
+
let wrappedHeaderRows = 1;
|
|
3021
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
3022
|
+
if (/^•\s+Queued follow-up inputs\s*$/u.test(lines[index]) ||
|
|
3023
|
+
/^•\s+Queued follow-up inpu…\s*$/u.test(lines[index]) ||
|
|
3024
|
+
(/^•\s+Queued follow-up\s*$/u.test(lines[index]) &&
|
|
3025
|
+
/^ {2}inputs\s*$/u.test(lines[index + 1] ?? ""))) {
|
|
3026
|
+
headerIndex = index;
|
|
3027
|
+
wrappedHeaderRows = /^•\s+Queued follow-up\s*$/u.test(lines[index])
|
|
3028
|
+
? 2
|
|
3029
|
+
: 1;
|
|
3030
|
+
break;
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
const headerBodyIndex = headerIndex < 0
|
|
3034
|
+
? -1
|
|
3035
|
+
: headerIndex + wrappedHeaderRows;
|
|
3036
|
+
let questionEvidenceStart = headerBodyIndex;
|
|
3037
|
+
if (headerIndex >= 0) {
|
|
3038
|
+
let cursor = headerBodyIndex;
|
|
3039
|
+
while (cursor < lines.length && lines[cursor].trim().length === 0) {
|
|
3040
|
+
cursor += 1;
|
|
3041
|
+
}
|
|
3042
|
+
let sawQueuedPreview = false;
|
|
3043
|
+
while (cursor < lines.length) {
|
|
3044
|
+
if (/^ {2}↳\s/u.test(lines[cursor])) {
|
|
3045
|
+
sawQueuedPreview = true;
|
|
3046
|
+
cursor += 1;
|
|
3047
|
+
while (cursor < lines.length) {
|
|
3048
|
+
if (lines[cursor].trim().length === 0 ||
|
|
3049
|
+
/^ {4,}\S/u.test(lines[cursor])) {
|
|
3050
|
+
cursor += 1;
|
|
3051
|
+
continue;
|
|
3052
|
+
}
|
|
3053
|
+
break;
|
|
3054
|
+
}
|
|
3055
|
+
continue;
|
|
3056
|
+
}
|
|
3057
|
+
break;
|
|
3058
|
+
}
|
|
3059
|
+
if (sawQueuedPreview) {
|
|
3060
|
+
questionEvidenceStart = cursor;
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
const mainComposerFooterIndex = mainComposerIndex < 0
|
|
3064
|
+
? -1
|
|
3065
|
+
: codexMainComposerFooterIndex(lines, mainComposerIndex);
|
|
3066
|
+
const summaryBeforeMainComposer = headerIndex < 0 || mainComposerIndex < 0
|
|
3067
|
+
? -1
|
|
3068
|
+
: normalized.findIndex((line, index) => index >= questionEvidenceStart && index < mainComposerIndex &&
|
|
3069
|
+
/^\?\s+\d+\s+questions?(?:\s+·\s+\d+s)?$/iu.test(line));
|
|
3070
|
+
if (mainComposerIndex >= 0 && mainComposerFooterIndex < 0) {
|
|
3071
|
+
// A statusline-disabled Composer is the strongest available ownership
|
|
3072
|
+
// anchor. Treat all following wrapped rows as its arbitrary draft instead
|
|
3073
|
+
// of letting question-like user text veto a human-priority Send.
|
|
3074
|
+
return summaryBeforeMainComposer >= 0 ? "collapsed" : "absent";
|
|
3075
|
+
}
|
|
3076
|
+
const evidenceFloor = Math.max(mainComposerFooterIndex, questionEvidenceStart - 1);
|
|
3077
|
+
const structuredFooter = codexStructuredAsyncFooterEvidence(lines, evidenceFloor);
|
|
3078
|
+
if (structuredFooter.labelCount >= 2) {
|
|
3079
|
+
return "expanded";
|
|
3080
|
+
}
|
|
3081
|
+
const clippedFooterIndex = normalized.findIndex((line, index) => index > evidenceFloor && /^ente(?:r)?…$/iu.test(line) &&
|
|
3082
|
+
/^ctrl…$/iu.test(normalized[index + 1] ?? ""));
|
|
3083
|
+
const clippedChoiceIndex = normalized.findIndex((line, index) => index > evidenceFloor &&
|
|
3084
|
+
line === "Expand terminal to read the entire option");
|
|
3085
|
+
if (clippedFooterIndex >= 0 || clippedChoiceIndex >= 0) {
|
|
3086
|
+
return "ambiguous";
|
|
3087
|
+
}
|
|
3088
|
+
if (headerIndex < 0) {
|
|
3089
|
+
const evidence = new Set();
|
|
3090
|
+
normalized.forEach((line, index) => {
|
|
3091
|
+
if (index <= evidenceFloor) {
|
|
3092
|
+
return;
|
|
3093
|
+
}
|
|
3094
|
+
if (/^\d+\s+of(?:\s+\d+)?$/iu.test(line)) {
|
|
3095
|
+
evidence.add("progress");
|
|
3096
|
+
}
|
|
3097
|
+
if (/^›\s+\S/u.test(line)) {
|
|
3098
|
+
evidence.add("selector");
|
|
3099
|
+
}
|
|
3100
|
+
if (/^Type your answer(?:\s*\(optional\))?$/iu.test(line)) {
|
|
3101
|
+
evidence.add("free_text");
|
|
3102
|
+
}
|
|
3103
|
+
});
|
|
3104
|
+
return evidence.size >= 2 ||
|
|
3105
|
+
(evidence.size >= 1 && structuredFooter.labelCount >= 1)
|
|
3106
|
+
? "ambiguous"
|
|
3107
|
+
: "absent";
|
|
3108
|
+
}
|
|
3109
|
+
const weakQuestionIndex = normalized.findIndex((line, index) => index > evidenceFloor && index >= questionEvidenceStart && (/^\d+\s+of(?:\s+\d+)?$/iu.test(line) ||
|
|
3110
|
+
/^›\s+\S/u.test(line) ||
|
|
3111
|
+
/^Type your answer(?:\s*\(optional\))?$/iu.test(line)));
|
|
3112
|
+
if (mainComposerIndex > headerIndex && weakQuestionIndex > evidenceFloor) {
|
|
3113
|
+
return "ambiguous";
|
|
3114
|
+
}
|
|
3115
|
+
const summaryIndex = normalized.findIndex((line, index) => index >= questionEvidenceStart &&
|
|
3116
|
+
(mainComposerIndex < 0 || index < mainComposerIndex) &&
|
|
3117
|
+
/^\?\s+\d+\s+questions?(?:\s+·\s+\d+s)?$/iu.test(line));
|
|
3118
|
+
if (summaryIndex >= 0) {
|
|
3119
|
+
return weakQuestionIndex >= 0 ? "ambiguous" : "collapsed";
|
|
3120
|
+
}
|
|
3121
|
+
if (mainComposerIndex > headerIndex) {
|
|
3122
|
+
return "absent";
|
|
3123
|
+
}
|
|
3124
|
+
const afterHeader = normalized.slice(questionEvidenceStart)
|
|
3125
|
+
.filter((line) => line.length > 0);
|
|
3126
|
+
const positiveAfterHeader = afterHeader.some((line) => /^\d+\s+of(?:\s+\d+)?$/iu.test(line) ||
|
|
3127
|
+
/^›\s+\d+\.(?:\s|$)/u.test(line) ||
|
|
3128
|
+
/^Type your answer(?:\s*\(optional\))?$/iu.test(line));
|
|
3129
|
+
if (positiveAfterHeader) {
|
|
3130
|
+
return "ambiguous";
|
|
3131
|
+
}
|
|
3132
|
+
// A bare heading (optionally followed by ↳ queued-message previews) is not
|
|
3133
|
+
// question authority. Preserve human-explicit Send in that ordinary state.
|
|
3134
|
+
if (afterHeader.length === 0) {
|
|
3135
|
+
return "absent";
|
|
3136
|
+
}
|
|
3137
|
+
// An expanded editor can temporarily replace its footer with a flash. If
|
|
3138
|
+
// there is question-like content under the shared heading and no proven main
|
|
3139
|
+
// Composer, do not guess which input surface will receive the next paste.
|
|
3140
|
+
return "ambiguous";
|
|
2895
3141
|
}
|
|
2896
3142
|
/**
|
|
2897
3143
|
* Classify only the bottom live Codex composer region. A matching transcript
|