@scotthuang/agent-knock-knock 0.11.3 → 0.11.5
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 +22 -0
- package/README.md +15 -5
- package/dist/src/claude-terminal-agent-adapter.d.ts +13 -1
- package/dist/src/claude-terminal-agent-adapter.js +424 -0
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli.js +587 -12
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.d.ts +5 -1
- package/dist/src/codex-terminal-agent-adapter.js +360 -0
- package/dist/src/codex-terminal-agent-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin.js +50 -2
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +113 -0
- package/dist/src/terminal-agent-adapter.js +8 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +110 -1
- package/dist/src/terminal-agent-bridge.js +505 -0
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-control-provider.js +14 -1
- package/dist/src/terminal-control-provider.js.map +1 -1
- package/docs/quickstart-tmux.md +2 -0
- package/openclaw.plugin.json +5 -1
- package/package.json +8 -2
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +13 -4
package/dist/src/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ import { StaticTerminalControlProvider, TmuxTerminalControlProvider, terminalPan
|
|
|
24
24
|
import { parseTerminalConversationId } from "./terminal-agent-adapter.js";
|
|
25
25
|
import { createProductionTerminalAgentRegistry } from "./terminal-agent-registry.js";
|
|
26
26
|
import { parseProcessElapsedSeconds, StaticTerminalProcessSource, SystemTerminalProcessSource } from "./terminal-process-source.js";
|
|
27
|
-
import { TerminalAgentBridge, TerminalInputNotStartedError } from "./terminal-agent-bridge.js";
|
|
27
|
+
import { isExactClaudeNativeInspectionIdleComposer, NativeInspectionSubmissionError, TerminalAgentBridge, TerminalInputNotStartedError } from "./terminal-agent-bridge.js";
|
|
28
28
|
import { evaluateApprovalPolicy } from "./approval-policy.js";
|
|
29
29
|
import { evaluateDoctorCapabilities, runDoctorCapabilityProbes } from "./doctor-capabilities.js";
|
|
30
30
|
import { runOpenClawChainDiagnostics } from "./openclaw-doctor.js";
|
|
@@ -256,6 +256,9 @@ async function runCommand(commandName, options) {
|
|
|
256
256
|
else if (commandName === "list-resumable-threads" || commandName === "threads") {
|
|
257
257
|
await runListResumableThreads(options);
|
|
258
258
|
}
|
|
259
|
+
else if (commandName === "native-inspect" || commandName === "native-status") {
|
|
260
|
+
await runNativeInspect(options);
|
|
261
|
+
}
|
|
259
262
|
else if (commandName === "resume-thread") {
|
|
260
263
|
await runResumeThread(options);
|
|
261
264
|
}
|
|
@@ -706,7 +709,10 @@ function loadClaudeAgentRows(options = {}, observation = {}) {
|
|
|
706
709
|
...(Number.isSafeInteger(Number(row.startedAt)) && Number(row.startedAt) > 0
|
|
707
710
|
? { startedAt: Number(row.startedAt) }
|
|
708
711
|
: {}),
|
|
709
|
-
...(stringValue(row.status) ? { status: stringValue(row.status) } : {})
|
|
712
|
+
...(stringValue(row.status) ? { status: stringValue(row.status) } : {}),
|
|
713
|
+
...(stringValue(row.waitingFor)
|
|
714
|
+
? { waitingFor: stringValue(row.waitingFor) }
|
|
715
|
+
: {})
|
|
710
716
|
}];
|
|
711
717
|
});
|
|
712
718
|
}
|
|
@@ -904,6 +910,9 @@ function createTerminalAgentBridge(options, terminalProvider = createTerminalCon
|
|
|
904
910
|
runtime?.nativeProcessBirth ||
|
|
905
911
|
runtime?.nativeRollout ||
|
|
906
912
|
runtime?.requireNativeProcessUuid ||
|
|
913
|
+
runtime?.requireExactClaudeAgentRow ||
|
|
914
|
+
runtime?.nativeProcessStartedAt ||
|
|
915
|
+
runtime?.exactClaudeAgentState ||
|
|
907
916
|
runtime?.requireNativeRolloutIdentity ||
|
|
908
917
|
runtime?.expectedNativeSessionId ||
|
|
909
918
|
runtime?.expectedEmptyNativeSession ||
|
|
@@ -931,6 +940,38 @@ function createTerminalAgentBridge(options, terminalProvider = createTerminalCon
|
|
|
931
940
|
agent,
|
|
932
941
|
pid
|
|
933
942
|
});
|
|
943
|
+
if (runtime?.requireExactClaudeAgentRow === true) {
|
|
944
|
+
if (agent !== "claude" ||
|
|
945
|
+
!Number.isSafeInteger(runtime.nativeProcessStartedAt) ||
|
|
946
|
+
Number(runtime.nativeProcessStartedAt) <= 0 ||
|
|
947
|
+
!runtime.nativeSessionId) {
|
|
948
|
+
throw new Error(`native ${agent} inspection has an incomplete exact process identity; ` +
|
|
949
|
+
"refresh list before controlling the terminal");
|
|
950
|
+
}
|
|
951
|
+
const agentRows = loadClaudeAgentRows(options, { required: true });
|
|
952
|
+
const observation = adapter.observeThreadLifecycle?.({
|
|
953
|
+
operation: { kind: "new_thread" },
|
|
954
|
+
phase: "before",
|
|
955
|
+
pid,
|
|
956
|
+
processStartedAt: runtime.nativeProcessStartedAt,
|
|
957
|
+
cwd: snapshot.cwd ?? pane.currentPath,
|
|
958
|
+
agentRows
|
|
959
|
+
});
|
|
960
|
+
const exactRows = agentRows.filter((row) => row.pid === pid);
|
|
961
|
+
const expectedState = runtime.exactClaudeAgentState ?? "idle";
|
|
962
|
+
const stateMatches = expectedState === "idle"
|
|
963
|
+
? observation?.idle === true
|
|
964
|
+
: (observation?.idle === false &&
|
|
965
|
+
exactRows.length === 1 &&
|
|
966
|
+
exactRows[0].status === "waiting" &&
|
|
967
|
+
exactRows[0].waitingFor === "dialog open");
|
|
968
|
+
if (observation?.status !== "observed" ||
|
|
969
|
+
!stateMatches ||
|
|
970
|
+
observation.nativeThreadId !== runtime.nativeSessionId) {
|
|
971
|
+
throw new Error(`native Claude agents identity, cwd, or idle state changed for process ${pid}; ` +
|
|
972
|
+
"refresh list before controlling the terminal");
|
|
973
|
+
}
|
|
974
|
+
}
|
|
934
975
|
return {
|
|
935
976
|
terminalControl: {
|
|
936
977
|
...terminalControl,
|
|
@@ -2782,6 +2823,13 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
2782
2823
|
resumeExact: false,
|
|
2783
2824
|
reason: "native thread lifecycle is unavailable"
|
|
2784
2825
|
};
|
|
2826
|
+
const nativeInspectionCapability = bridge.registry.require(session.agent)
|
|
2827
|
+
.probeNativeInspection?.(agentVersion) ?? {
|
|
2828
|
+
status: "unsupported",
|
|
2829
|
+
agentVersion,
|
|
2830
|
+
statusInspection: false,
|
|
2831
|
+
reason: "native inspection is unavailable"
|
|
2832
|
+
};
|
|
2785
2833
|
const lifecycleBindingToken = unmanagedTerminalBindingToken({
|
|
2786
2834
|
terminalId: bridge.terminalConversationId(session),
|
|
2787
2835
|
terminalControl,
|
|
@@ -2795,6 +2843,10 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
2795
2843
|
});
|
|
2796
2844
|
const codexLifecycleIncarnationAvailable = session.agent !== "codex" ||
|
|
2797
2845
|
Boolean(nativeProcessUuid && nativeProcessBirth);
|
|
2846
|
+
const nativeInspectionHasBlockingTurn = listConversations(storeDirFromOptions(options)).some((turn) => isDiscoverableTmuxConversation(turn) &&
|
|
2847
|
+
terminalKeyForManagedConversation(turn) ===
|
|
2848
|
+
terminalControlSelectorKey(terminalControl) &&
|
|
2849
|
+
SESSION_SEND_BLOCKING_STATUSES.has(turn.status));
|
|
2798
2850
|
const entry = {
|
|
2799
2851
|
id: bridge.terminalConversationId(session),
|
|
2800
2852
|
short_ref: sessionShortRef(bridge.terminalConversationId(session)),
|
|
@@ -2815,6 +2867,7 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
2815
2867
|
native_agent_identity_evidence: nativeProcessEvidence,
|
|
2816
2868
|
agent_version: agentVersion,
|
|
2817
2869
|
native_thread_lifecycle: lifecycleCapability,
|
|
2870
|
+
native_inspection: nativeInspectionCapability,
|
|
2818
2871
|
lifecycle_binding_token: lifecycleBindingToken,
|
|
2819
2872
|
confidence: session.confidence,
|
|
2820
2873
|
reason: session.reason,
|
|
@@ -2849,7 +2902,18 @@ async function terminalControlledListEntry(session, activeSessions, options, bri
|
|
|
2849
2902
|
codexLifecycleIncarnationAvailable,
|
|
2850
2903
|
list_resumable_threads: lifecycleCapability.status === "supported" &&
|
|
2851
2904
|
lifecycleCapability.resumeExact === true &&
|
|
2852
|
-
codexLifecycleIncarnationAvailable
|
|
2905
|
+
codexLifecycleIncarnationAvailable,
|
|
2906
|
+
native_inspect: nativeInspectionCapability.status === "supported" &&
|
|
2907
|
+
nativeInspectionCapability.statusInspection === true &&
|
|
2908
|
+
nativeInspectionComposerEmpty(session.agent, terminalState.screen_excerpt) &&
|
|
2909
|
+
terminalControl.capabilities.includes("send_keys") &&
|
|
2910
|
+
terminalControl.capabilities.includes("screen_status") &&
|
|
2911
|
+
(session.agent === "codex"
|
|
2912
|
+
? codexLifecycleIncarnationAvailable
|
|
2913
|
+
: Boolean(nativeAgentIdentity?.sessionId &&
|
|
2914
|
+
nativeAgentIdentity.processUuid)) &&
|
|
2915
|
+
orphanedDispatch === undefined &&
|
|
2916
|
+
!nativeInspectionHasBlockingTurn
|
|
2853
2917
|
}
|
|
2854
2918
|
};
|
|
2855
2919
|
const availableActions = availableListActions(entry);
|
|
@@ -2973,9 +3037,13 @@ function terminalFirstListProjection({ storeDir, terminals, managedSessions, ses
|
|
|
2973
3037
|
const rawActions = mutationsAllowed
|
|
2974
3038
|
? discoveredRawActions
|
|
2975
3039
|
: readOnlyListActions(discoveredRawActions);
|
|
2976
|
-
const
|
|
3040
|
+
const bindingAwareRawActions = authoritativeSession
|
|
2977
3041
|
? actionsForManagedSessionBinding(rawActions, authoritativeSession)
|
|
2978
3042
|
: rawActions;
|
|
3043
|
+
const sessionAwareRawActions = authoritativeSession &&
|
|
3044
|
+
managedSessionHasUnresolvedNativeTransition(storeDir, authoritativeSession)
|
|
3045
|
+
? Object.fromEntries(Object.entries(bindingAwareRawActions).filter(([actionName]) => actionName !== "native_inspect"))
|
|
3046
|
+
: bindingAwareRawActions;
|
|
2979
3047
|
const soleBindingConflict = conflictingBoundSessionClaims.length === 1
|
|
2980
3048
|
? conflictingBoundSessionClaims[0]
|
|
2981
3049
|
: undefined;
|
|
@@ -3595,7 +3663,7 @@ function sendActionForManagedSession(action, sessionId) {
|
|
|
3595
3663
|
function actionsForManagedSessionBinding(actions, session) {
|
|
3596
3664
|
const token = managedSessionBindingToken(session);
|
|
3597
3665
|
const next = { ...actions };
|
|
3598
|
-
for (const actionName of ["new_thread", "resume_thread"]) {
|
|
3666
|
+
for (const actionName of ["new_thread", "resume_thread", "native_inspect"]) {
|
|
3599
3667
|
const action = isRecord(next[actionName]) ? next[actionName] : undefined;
|
|
3600
3668
|
if (!action) {
|
|
3601
3669
|
continue;
|
|
@@ -3863,11 +3931,12 @@ function managedListApprovalState(conversation) {
|
|
|
3863
3931
|
}
|
|
3864
3932
|
function listActionContracts() {
|
|
3865
3933
|
return {
|
|
3866
|
-
version:
|
|
3934
|
+
version: 7,
|
|
3867
3935
|
instructions: [
|
|
3868
3936
|
"Treat terminals[] as the primary resource and use only actions present in available_actions.",
|
|
3869
3937
|
"An existing managed session's ordinary send targets session_id and creates a new turn. A turn id is never an ordinary send target.",
|
|
3870
3938
|
"Read-only native-thread listing targets an exact terminal_id. Native-thread new/resume mutations also use the listed expected_binding_token and never create a Turn.",
|
|
3939
|
+
"Native inspection is a separate terminal action: use only its closed inspection enum and current exact terminal_id/token; AKK status does not execute a native slash command.",
|
|
3871
3940
|
"A binding conflict may be detached only through its exact reconcile_binding action, which is snapshot-bound to the Session revision, binding, and live terminal identity and never adopts a replacement native thread.",
|
|
3872
3941
|
"List resumable threads before resume; use only a complete native_thread_id and the action returned for that candidate.",
|
|
3873
3942
|
"For first attach only, use a discovery selector explicitly named by the user or the selector prefilled by that unmanaged raw-terminal row's available send action; never infer, guess, or reuse one.",
|
|
@@ -3934,6 +4003,17 @@ function listActionContracts() {
|
|
|
3934
4003
|
required: ["terminal_id"],
|
|
3935
4004
|
side_effect_free: true
|
|
3936
4005
|
},
|
|
4006
|
+
native_inspect: {
|
|
4007
|
+
tool: "agent_knock_knock_native_inspect",
|
|
4008
|
+
target_argument: "terminal_id",
|
|
4009
|
+
required: ["terminal_id", "inspection", "expected_binding_token"],
|
|
4010
|
+
supported_inspections: ["status"],
|
|
4011
|
+
creates_turn: false,
|
|
4012
|
+
creates_session: false,
|
|
4013
|
+
mutates_store: false,
|
|
4014
|
+
sends_terminal_input: true,
|
|
4015
|
+
candidate_source: "terminals[].available_actions.native_inspect"
|
|
4016
|
+
},
|
|
3937
4017
|
resume_thread: {
|
|
3938
4018
|
tool: "agent_knock_knock_resume_thread",
|
|
3939
4019
|
target_argument: "terminal_id",
|
|
@@ -4084,6 +4164,20 @@ function availableListActions(entry, { conversation } = {}) {
|
|
|
4084
4164
|
arguments: { terminal_id: id }
|
|
4085
4165
|
};
|
|
4086
4166
|
}
|
|
4167
|
+
if (terminalControlled &&
|
|
4168
|
+
commands.native_inspect === true &&
|
|
4169
|
+
entry.activity_state === "idle" &&
|
|
4170
|
+
approvalState.blocked !== true &&
|
|
4171
|
+
lifecycleBindingToken) {
|
|
4172
|
+
actions.native_inspect = {
|
|
4173
|
+
tool: "agent_knock_knock_native_inspect",
|
|
4174
|
+
arguments: {
|
|
4175
|
+
terminal_id: id,
|
|
4176
|
+
inspection: "status",
|
|
4177
|
+
expected_binding_token: lifecycleBindingToken
|
|
4178
|
+
}
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4087
4181
|
if (managed &&
|
|
4088
4182
|
commands.respond === true &&
|
|
4089
4183
|
terminalBridgeReady &&
|
|
@@ -5965,6 +6059,452 @@ async function runListResumableThreads(options) {
|
|
|
5965
6059
|
}))
|
|
5966
6060
|
});
|
|
5967
6061
|
}
|
|
6062
|
+
function assertSameNativeInspectionTerminal(expected, actual, stage) {
|
|
6063
|
+
const expectedPath = expected.terminalControl.currentPath;
|
|
6064
|
+
const actualPath = actual.terminalControl.currentPath;
|
|
6065
|
+
if (actual.conversationId !== expected.conversationId ||
|
|
6066
|
+
actual.agent !== expected.agent ||
|
|
6067
|
+
actual.pid !== expected.pid ||
|
|
6068
|
+
terminalControlSelectorKey(actual.terminalControl) !==
|
|
6069
|
+
terminalControlSelectorKey(expected.terminalControl) ||
|
|
6070
|
+
!expectedPath ||
|
|
6071
|
+
!actualPath ||
|
|
6072
|
+
path.resolve(actualPath) !== path.resolve(expectedPath)) {
|
|
6073
|
+
throw new Error(`terminal identity, pane, or cwd changed ${stage}; refresh AKK list`);
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
6076
|
+
function assertTerminalNativeInspectionReady({ options, terminal, terminalStatus, session }) {
|
|
6077
|
+
if (terminalStatus &&
|
|
6078
|
+
(terminalStatus.reachable !== true ||
|
|
6079
|
+
terminalStatus.activity_state !== "idle" ||
|
|
6080
|
+
terminalStatus.approval_state.blocked === true)) {
|
|
6081
|
+
throw new Error(`terminal ${terminal.terminalControl.target} is not at a verified idle prompt ` +
|
|
6082
|
+
`(${terminalStatus.activity_state}: ${terminalStatus.activity_reason})`);
|
|
6083
|
+
}
|
|
6084
|
+
const blocker = listConversations(storeDirFromOptions(options))
|
|
6085
|
+
.filter(isDiscoverableTmuxConversation)
|
|
6086
|
+
.find((turn) => terminalKeyForManagedConversation(turn) ===
|
|
6087
|
+
terminalControlSelectorKey(terminal.terminalControl) &&
|
|
6088
|
+
SESSION_SEND_BLOCKING_STATUSES.has(turn.status));
|
|
6089
|
+
if (blocker) {
|
|
6090
|
+
throw new Error(`terminal ${terminal.terminalControl.target} still has unresolved Turn ` +
|
|
6091
|
+
`${turnIdForConversation(blocker)} (${blocker.status})`);
|
|
6092
|
+
}
|
|
6093
|
+
if (session &&
|
|
6094
|
+
managedSessionHasUnresolvedNativeTransition(storeDirFromOptions(options), session)) {
|
|
6095
|
+
throw new Error(`managed Session ${session.session_id} has an unresolved native-thread transition`);
|
|
6096
|
+
}
|
|
6097
|
+
const ownership = terminalDispatchOwnership(terminal.terminalControl);
|
|
6098
|
+
if (ownership.state !== "none") {
|
|
6099
|
+
throw new Error(`terminal ${terminal.terminalControl.target} has unresolved dispatch ` +
|
|
6100
|
+
"ownership; resolve it before native inspection");
|
|
6101
|
+
}
|
|
6102
|
+
const orphaned = orphanedTerminalDispatchForRecovery(terminal.terminalControl);
|
|
6103
|
+
if (orphaned) {
|
|
6104
|
+
throw new Error(`terminal ${terminal.terminalControl.target} has unresolved ` +
|
|
6105
|
+
`${String(orphaned.kind ?? "terminal")} input ` +
|
|
6106
|
+
`(${String(orphaned.status ?? "unknown")})`);
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
6109
|
+
function nativeInspectionRuntime({ terminal, snapshot }) {
|
|
6110
|
+
if (terminal.agent === "claude") {
|
|
6111
|
+
const identity = snapshot.runtimeIdentity;
|
|
6112
|
+
if (!identity?.sessionId ||
|
|
6113
|
+
!identity.processUuid ||
|
|
6114
|
+
identity.sessionId !== snapshot.identity?.sessionId) {
|
|
6115
|
+
throw new Error("Claude native status inspection requires one exact claude agents Session and process incarnation");
|
|
6116
|
+
}
|
|
6117
|
+
return {
|
|
6118
|
+
...terminalRuntimeForLiveIdentity({ terminal, identity }),
|
|
6119
|
+
requireExactClaudeAgentRow: true,
|
|
6120
|
+
nativeProcessStartedAt: identity.processStartedAt,
|
|
6121
|
+
exactClaudeAgentState: "idle"
|
|
6122
|
+
};
|
|
6123
|
+
}
|
|
6124
|
+
const exactRuntimeIdentity = snapshot.runtimeIdentity?.sessionId === snapshot.identity?.sessionId
|
|
6125
|
+
? snapshot.runtimeIdentity
|
|
6126
|
+
: undefined;
|
|
6127
|
+
const runtime = exactRuntimeIdentity
|
|
6128
|
+
? terminalRuntimeForLiveIdentity({
|
|
6129
|
+
terminal,
|
|
6130
|
+
identity: exactRuntimeIdentity
|
|
6131
|
+
})
|
|
6132
|
+
: {
|
|
6133
|
+
...terminalRuntimeForLiveIdentity({
|
|
6134
|
+
terminal,
|
|
6135
|
+
expectedEmptyNativeSession: true
|
|
6136
|
+
}),
|
|
6137
|
+
...(snapshot.identity?.processUuid
|
|
6138
|
+
? { nativeProcessUuid: snapshot.identity.processUuid }
|
|
6139
|
+
: {}),
|
|
6140
|
+
...(snapshot.identity?.processBirth
|
|
6141
|
+
? { nativeProcessBirth: snapshot.identity.processBirth }
|
|
6142
|
+
: {}),
|
|
6143
|
+
...(snapshot.identity?.sessionId
|
|
6144
|
+
? { expectedNativeSessionId: snapshot.identity.sessionId }
|
|
6145
|
+
: {})
|
|
6146
|
+
};
|
|
6147
|
+
return withCodexCompanionFences(runtime, snapshot.codexCompanions);
|
|
6148
|
+
}
|
|
6149
|
+
function assertNativeInspectionSnapshotUnchanged({ options, expectedTerminal, actualTerminal, expectedBindingToken, expectedVersion, actualSnapshot, stage, expectedClaudeState = "idle" }) {
|
|
6150
|
+
assertSameNativeInspectionTerminal(expectedTerminal, actualTerminal, stage);
|
|
6151
|
+
if (actualSnapshot.bindingToken !== expectedBindingToken) {
|
|
6152
|
+
throw new Error(`terminal binding changed ${stage}; refresh AKK list`);
|
|
6153
|
+
}
|
|
6154
|
+
if (actualSnapshot.version !== expectedVersion) {
|
|
6155
|
+
throw new Error(`coding-agent version changed ${stage}; refresh AKK list`);
|
|
6156
|
+
}
|
|
6157
|
+
const capability = actualSnapshot.adapter.probeNativeInspection?.(actualSnapshot.version);
|
|
6158
|
+
if (capability?.status !== "supported" ||
|
|
6159
|
+
capability.statusInspection !== true) {
|
|
6160
|
+
throw new Error(capability?.reason ??
|
|
6161
|
+
"native status inspection became unsupported; refresh AKK list");
|
|
6162
|
+
}
|
|
6163
|
+
assertNativeInspectionAgentIdentity({
|
|
6164
|
+
options,
|
|
6165
|
+
terminal: actualTerminal,
|
|
6166
|
+
snapshot: actualSnapshot,
|
|
6167
|
+
stage,
|
|
6168
|
+
expectedClaudeState
|
|
6169
|
+
});
|
|
6170
|
+
}
|
|
6171
|
+
function assertNativeInspectionAgentIdentity({ options, terminal, snapshot, stage, expectedClaudeState = "idle" }) {
|
|
6172
|
+
if (terminal.agent !== "claude") {
|
|
6173
|
+
return;
|
|
6174
|
+
}
|
|
6175
|
+
const identity = snapshot.runtimeIdentity;
|
|
6176
|
+
if (!identity?.sessionId ||
|
|
6177
|
+
!identity.processUuid ||
|
|
6178
|
+
!Number.isSafeInteger(identity.processStartedAt) ||
|
|
6179
|
+
Number(identity.processStartedAt) <= 0) {
|
|
6180
|
+
throw new Error(`Claude process identity is incomplete ${stage}; refresh AKK list`);
|
|
6181
|
+
}
|
|
6182
|
+
const agentRows = loadClaudeAgentRows(options, { required: true });
|
|
6183
|
+
const observation = snapshot.adapter.observeThreadLifecycle?.({
|
|
6184
|
+
operation: { kind: "new_thread" },
|
|
6185
|
+
phase: "before",
|
|
6186
|
+
pid: terminal.pid,
|
|
6187
|
+
processStartedAt: identity.processStartedAt,
|
|
6188
|
+
cwd: terminal.terminalControl.currentPath,
|
|
6189
|
+
agentRows
|
|
6190
|
+
});
|
|
6191
|
+
const exactRows = agentRows.filter((row) => row.pid === terminal.pid);
|
|
6192
|
+
const stateMatches = expectedClaudeState === "idle"
|
|
6193
|
+
? observation?.idle === true
|
|
6194
|
+
: (observation?.idle === false &&
|
|
6195
|
+
exactRows.length === 1 &&
|
|
6196
|
+
exactRows[0].status === "waiting" &&
|
|
6197
|
+
exactRows[0].waitingFor === "dialog open");
|
|
6198
|
+
if (observation?.status !== "observed" ||
|
|
6199
|
+
!stateMatches ||
|
|
6200
|
+
observation.nativeThreadId !== identity.sessionId) {
|
|
6201
|
+
throw new Error(`Claude agents identity, cwd, or idle state changed ${stage}; refresh AKK list`);
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
async function assertNativeInspectionExclusiveOwnership({ options, terminal, snapshot }) {
|
|
6205
|
+
// Claude's exact `claude agents` mapping makes global active ownership part
|
|
6206
|
+
// of the 2.1.218 inspection profile. Keep the existing Codex #112 path
|
|
6207
|
+
// unchanged: an unmanaged Codex pane may be inspected before a rollout
|
|
6208
|
+
// identity exists, and its status card is the bounded identity evidence.
|
|
6209
|
+
if (terminal.agent !== "claude") {
|
|
6210
|
+
return;
|
|
6211
|
+
}
|
|
6212
|
+
const nativeThreadId = snapshot.identity?.sessionId ??
|
|
6213
|
+
snapshot.session?.binding?.native_thread_id;
|
|
6214
|
+
if (!isExactNativeThreadId(nativeThreadId)) {
|
|
6215
|
+
throw new Error("native status inspection requires one exact current native Session identity");
|
|
6216
|
+
}
|
|
6217
|
+
await assertNativeThreadHasExclusiveOwnership({
|
|
6218
|
+
options,
|
|
6219
|
+
agent: terminal.agent,
|
|
6220
|
+
currentPid: terminal.pid,
|
|
6221
|
+
nativeThreadId,
|
|
6222
|
+
storeDir: storeDirFromOptions(options),
|
|
6223
|
+
terminalControl: terminal.terminalControl,
|
|
6224
|
+
excludedManagedSessionId: snapshot.session?.session_id
|
|
6225
|
+
});
|
|
6226
|
+
}
|
|
6227
|
+
async function runNativeInspect(options) {
|
|
6228
|
+
const inspection = required(stringValue(options.inspection), "--inspection is required");
|
|
6229
|
+
if (inspection !== "status") {
|
|
6230
|
+
throw new Error("--inspection must be the closed value status; arbitrary native slash commands are not accepted");
|
|
6231
|
+
}
|
|
6232
|
+
if (options.command !== undefined || options.message !== undefined) {
|
|
6233
|
+
throw new Error("native inspection does not accept a command or message payload");
|
|
6234
|
+
}
|
|
6235
|
+
const expectedBindingToken = required(stringValue(options.expectedBindingToken), "--expected-binding-token is required");
|
|
6236
|
+
const storeDir = storeDirFromOptions(options);
|
|
6237
|
+
const store = inspectStoreCompatibility(storeDir);
|
|
6238
|
+
if (store.writable !== true) {
|
|
6239
|
+
throw new Error("native inspection requires a compatible AKK Store so binding authority can be verified");
|
|
6240
|
+
}
|
|
6241
|
+
const initiallyResolved = await resolveLifecycleTerminal(options);
|
|
6242
|
+
const releaseTerminalLock = acquireFileLock(terminalBridgeSendLockPath(storeDir, initiallyResolved.terminalControl), { timeoutMs: 30000 });
|
|
6243
|
+
try {
|
|
6244
|
+
const terminal = await resolveLifecycleTerminal(options);
|
|
6245
|
+
assertSameNativeInspectionTerminal(initiallyResolved, terminal, "while waiting for native-inspection control");
|
|
6246
|
+
const snapshot = await currentLifecycleSnapshot(options, terminal);
|
|
6247
|
+
if (snapshot.bindingToken !== expectedBindingToken) {
|
|
6248
|
+
throw new Error("terminal binding changed after it was listed; refresh AKK list and retry");
|
|
6249
|
+
}
|
|
6250
|
+
const capability = snapshot.adapter.probeNativeInspection?.(snapshot.version);
|
|
6251
|
+
if (capability?.status !== "supported" ||
|
|
6252
|
+
capability.statusInspection !== true) {
|
|
6253
|
+
throw new Error(capability?.reason ??
|
|
6254
|
+
"native status inspection is unavailable for this agent version");
|
|
6255
|
+
}
|
|
6256
|
+
const plan = snapshot.adapter.planNativeInspection?.({ kind: "status" }, capability);
|
|
6257
|
+
if (!plan ||
|
|
6258
|
+
plan.operation.kind !== "status" ||
|
|
6259
|
+
plan.command !== "/status" ||
|
|
6260
|
+
plan.effect !== "read_only") {
|
|
6261
|
+
throw new Error("the agent adapter did not produce the closed native status inspection plan");
|
|
6262
|
+
}
|
|
6263
|
+
assertNativeInspectionAgentIdentity({
|
|
6264
|
+
options,
|
|
6265
|
+
terminal,
|
|
6266
|
+
snapshot,
|
|
6267
|
+
stage: "before native status inspection"
|
|
6268
|
+
});
|
|
6269
|
+
await assertNativeInspectionExclusiveOwnership({
|
|
6270
|
+
options,
|
|
6271
|
+
terminal,
|
|
6272
|
+
snapshot
|
|
6273
|
+
});
|
|
6274
|
+
const runtime = nativeInspectionRuntime({ terminal, snapshot });
|
|
6275
|
+
const bridge = createTerminalAgentBridge(options);
|
|
6276
|
+
const initialStatus = await bridge.status(terminal.agent, terminal.terminalControl, { runtime });
|
|
6277
|
+
assertTerminalNativeInspectionReady({
|
|
6278
|
+
options,
|
|
6279
|
+
terminal,
|
|
6280
|
+
terminalStatus: initialStatus,
|
|
6281
|
+
session: snapshot.session
|
|
6282
|
+
});
|
|
6283
|
+
await assertNativeInspectionComposerReadyForAutomatedInput({
|
|
6284
|
+
options,
|
|
6285
|
+
terminal
|
|
6286
|
+
});
|
|
6287
|
+
let submission;
|
|
6288
|
+
try {
|
|
6289
|
+
submission = await bridge.submitNativeInspection(terminal.agent, terminal.terminalControl, plan, {
|
|
6290
|
+
runtime,
|
|
6291
|
+
beforeEnter: async () => {
|
|
6292
|
+
const finalTerminal = await resolveLifecycleTerminal(options);
|
|
6293
|
+
const finalSnapshot = await currentLifecycleSnapshot(options, finalTerminal);
|
|
6294
|
+
assertNativeInspectionSnapshotUnchanged({
|
|
6295
|
+
options,
|
|
6296
|
+
expectedTerminal: terminal,
|
|
6297
|
+
actualTerminal: finalTerminal,
|
|
6298
|
+
expectedBindingToken,
|
|
6299
|
+
expectedVersion: snapshot.version,
|
|
6300
|
+
actualSnapshot: finalSnapshot,
|
|
6301
|
+
stage: "immediately before native status submission"
|
|
6302
|
+
});
|
|
6303
|
+
await assertNativeInspectionExclusiveOwnership({
|
|
6304
|
+
options,
|
|
6305
|
+
terminal: finalTerminal,
|
|
6306
|
+
snapshot: finalSnapshot
|
|
6307
|
+
});
|
|
6308
|
+
assertTerminalNativeInspectionReady({
|
|
6309
|
+
options,
|
|
6310
|
+
terminal: finalTerminal,
|
|
6311
|
+
session: finalSnapshot.session
|
|
6312
|
+
});
|
|
6313
|
+
}
|
|
6314
|
+
});
|
|
6315
|
+
}
|
|
6316
|
+
catch (error) {
|
|
6317
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
6318
|
+
if (error instanceof NativeInspectionSubmissionError &&
|
|
6319
|
+
error.doNotRetry !== true) {
|
|
6320
|
+
throw new Error(`native status inspection did not start; refresh AKK list and retry if still desired: ${detail}`);
|
|
6321
|
+
}
|
|
6322
|
+
throw new Error("native status inspection did not cross a proven completion boundary; " +
|
|
6323
|
+
`do not retry automatically: ${detail}`);
|
|
6324
|
+
}
|
|
6325
|
+
try {
|
|
6326
|
+
const expectedNativeThreadId = snapshot.session?.binding?.native_thread_id ??
|
|
6327
|
+
snapshot.identity?.sessionId;
|
|
6328
|
+
const observationRequest = {
|
|
6329
|
+
operation: plan.operation,
|
|
6330
|
+
previousScreenFingerprint: submission.preEnterScreenDigest,
|
|
6331
|
+
preEnterEvidenceInventory: submission.preEnterEvidenceInventory,
|
|
6332
|
+
expectedNativeThreadId,
|
|
6333
|
+
expectedAgentVersion: snapshot.version,
|
|
6334
|
+
expectedCwd: terminal.terminalControl.currentPath
|
|
6335
|
+
};
|
|
6336
|
+
const postEnterRuntime = plan.expectedResult.presentation === "modal"
|
|
6337
|
+
? { ...runtime, exactClaudeAgentState: "status_dialog" }
|
|
6338
|
+
: runtime;
|
|
6339
|
+
let stableEvidenceFingerprint;
|
|
6340
|
+
let stableObservation;
|
|
6341
|
+
let stableCount = 0;
|
|
6342
|
+
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
6343
|
+
const observed = await bridge.observeNativeInspection(terminal.agent, terminal.terminalControl, observationRequest, { runtime: postEnterRuntime, scrollbackLines: 240 });
|
|
6344
|
+
const observation = observed.observation;
|
|
6345
|
+
if (observed.status.reachable === true &&
|
|
6346
|
+
(plan.expectedResult.presentation === "inline"
|
|
6347
|
+
? observed.status.activity_state === "idle"
|
|
6348
|
+
: ![
|
|
6349
|
+
"working",
|
|
6350
|
+
"awaiting_approval"
|
|
6351
|
+
].includes(observed.status.activity_state)) &&
|
|
6352
|
+
observed.status.approval_state.blocked !== true &&
|
|
6353
|
+
observed.screenDigest !== submission.preEnterScreenDigest &&
|
|
6354
|
+
observation.status === "observed" &&
|
|
6355
|
+
observation.result?.kind === "native_status" &&
|
|
6356
|
+
isExactNativeThreadId(observation.nativeThreadId) &&
|
|
6357
|
+
observation.evidenceFingerprint) {
|
|
6358
|
+
if (stableEvidenceFingerprint === observation.evidenceFingerprint) {
|
|
6359
|
+
stableCount += 1;
|
|
6360
|
+
}
|
|
6361
|
+
else {
|
|
6362
|
+
stableEvidenceFingerprint = observation.evidenceFingerprint;
|
|
6363
|
+
stableObservation = observation;
|
|
6364
|
+
stableCount = 1;
|
|
6365
|
+
}
|
|
6366
|
+
if (stableCount >= 2) {
|
|
6367
|
+
stableObservation = observation;
|
|
6368
|
+
break;
|
|
6369
|
+
}
|
|
6370
|
+
}
|
|
6371
|
+
else {
|
|
6372
|
+
stableEvidenceFingerprint = undefined;
|
|
6373
|
+
stableObservation = undefined;
|
|
6374
|
+
stableCount = 0;
|
|
6375
|
+
}
|
|
6376
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
6377
|
+
}
|
|
6378
|
+
if (!stableObservation || stableCount < 2) {
|
|
6379
|
+
throw new Error("native status inspection Enter was dispatched exactly once, but a fresh exact status result was not proven; do not retry automatically");
|
|
6380
|
+
}
|
|
6381
|
+
let dismissal;
|
|
6382
|
+
if (plan.expectedResult.presentation === "modal") {
|
|
6383
|
+
if (!stableObservation.evidenceFingerprint) {
|
|
6384
|
+
throw new Error("native status modal lacks exact dismissal evidence; do not retry automatically");
|
|
6385
|
+
}
|
|
6386
|
+
try {
|
|
6387
|
+
dismissal = await bridge.dismissNativeInspection(terminal.agent, terminal.terminalControl, plan, observationRequest, stableObservation.evidenceFingerprint, {
|
|
6388
|
+
runtime: postEnterRuntime,
|
|
6389
|
+
scrollbackLines: 240,
|
|
6390
|
+
beforeDismiss: async () => {
|
|
6391
|
+
const dismissTerminal = await resolveLifecycleTerminal(options);
|
|
6392
|
+
const dismissSnapshot = await currentLifecycleSnapshot(options, dismissTerminal);
|
|
6393
|
+
assertNativeInspectionSnapshotUnchanged({
|
|
6394
|
+
options,
|
|
6395
|
+
expectedTerminal: terminal,
|
|
6396
|
+
actualTerminal: dismissTerminal,
|
|
6397
|
+
expectedBindingToken,
|
|
6398
|
+
expectedVersion: snapshot.version,
|
|
6399
|
+
actualSnapshot: dismissSnapshot,
|
|
6400
|
+
stage: "immediately before native status dismissal",
|
|
6401
|
+
expectedClaudeState: "status_dialog"
|
|
6402
|
+
});
|
|
6403
|
+
await assertNativeInspectionExclusiveOwnership({
|
|
6404
|
+
options,
|
|
6405
|
+
terminal: dismissTerminal,
|
|
6406
|
+
snapshot: dismissSnapshot
|
|
6407
|
+
});
|
|
6408
|
+
assertTerminalNativeInspectionReady({
|
|
6409
|
+
options,
|
|
6410
|
+
terminal: dismissTerminal,
|
|
6411
|
+
session: dismissSnapshot.session
|
|
6412
|
+
});
|
|
6413
|
+
}
|
|
6414
|
+
});
|
|
6415
|
+
}
|
|
6416
|
+
catch (error) {
|
|
6417
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
6418
|
+
throw new Error("native status panel was proven but safe dismissal failed; " +
|
|
6419
|
+
`do not retry automatically and dismiss it manually if still visible: ${detail}`);
|
|
6420
|
+
}
|
|
6421
|
+
let restoredIdle = false;
|
|
6422
|
+
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
6423
|
+
const restored = await bridge.status(terminal.agent, terminal.terminalControl, { runtime });
|
|
6424
|
+
if (restored.reachable === true &&
|
|
6425
|
+
restored.activity_state === "idle" &&
|
|
6426
|
+
restored.approval_state.blocked !== true &&
|
|
6427
|
+
nativeInspectionComposerEmpty(terminal.agent, restored.screen.excerpt)) {
|
|
6428
|
+
restoredIdle = true;
|
|
6429
|
+
break;
|
|
6430
|
+
}
|
|
6431
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
6432
|
+
}
|
|
6433
|
+
if (!restoredIdle) {
|
|
6434
|
+
throw new Error("Claude Status panel dismissal was dispatched exactly once, but the original idle composer was not restored; do not retry automatically");
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
const finalTerminal = await resolveLifecycleTerminal(options);
|
|
6438
|
+
const finalSnapshot = await currentLifecycleSnapshot(options, finalTerminal);
|
|
6439
|
+
assertNativeInspectionSnapshotUnchanged({
|
|
6440
|
+
options,
|
|
6441
|
+
expectedTerminal: terminal,
|
|
6442
|
+
actualTerminal: finalTerminal,
|
|
6443
|
+
expectedBindingToken,
|
|
6444
|
+
expectedVersion: snapshot.version,
|
|
6445
|
+
actualSnapshot: finalSnapshot,
|
|
6446
|
+
stage: "after native status inspection"
|
|
6447
|
+
});
|
|
6448
|
+
await assertNativeInspectionExclusiveOwnership({
|
|
6449
|
+
options,
|
|
6450
|
+
terminal: finalTerminal,
|
|
6451
|
+
snapshot: finalSnapshot
|
|
6452
|
+
});
|
|
6453
|
+
const finalStatus = await bridge.status(finalTerminal.agent, finalTerminal.terminalControl, { runtime });
|
|
6454
|
+
assertTerminalNativeInspectionReady({
|
|
6455
|
+
options,
|
|
6456
|
+
terminal: finalTerminal,
|
|
6457
|
+
terminalStatus: finalStatus,
|
|
6458
|
+
session: finalSnapshot.session
|
|
6459
|
+
});
|
|
6460
|
+
await assertNativeInspectionComposerReadyForAutomatedInput({
|
|
6461
|
+
options,
|
|
6462
|
+
terminal: finalTerminal
|
|
6463
|
+
});
|
|
6464
|
+
printJson({
|
|
6465
|
+
status: "observed",
|
|
6466
|
+
inspection: "status",
|
|
6467
|
+
terminal_id: terminal.conversationId,
|
|
6468
|
+
agent: terminal.agent,
|
|
6469
|
+
agent_version: snapshot.version,
|
|
6470
|
+
behavior_profile: plan.behaviorProfile,
|
|
6471
|
+
native_thread_id: stableObservation.nativeThreadId,
|
|
6472
|
+
native_status: stableObservation.result,
|
|
6473
|
+
terminal_submission: {
|
|
6474
|
+
command: plan.command,
|
|
6475
|
+
enter_count: submission.enterCount,
|
|
6476
|
+
materialization: submission.materialization
|
|
6477
|
+
},
|
|
6478
|
+
...(dismissal
|
|
6479
|
+
? {
|
|
6480
|
+
terminal_dismissal: {
|
|
6481
|
+
keys: dismissal.keys,
|
|
6482
|
+
dismiss_count: dismissal.dismissCount,
|
|
6483
|
+
restored_idle: true
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
: {}),
|
|
6487
|
+
store_mutation: false,
|
|
6488
|
+
session_created: false,
|
|
6489
|
+
turn_created: false,
|
|
6490
|
+
receipt_created: false,
|
|
6491
|
+
monitor_created: false,
|
|
6492
|
+
callback_created: false
|
|
6493
|
+
});
|
|
6494
|
+
}
|
|
6495
|
+
catch (error) {
|
|
6496
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
6497
|
+
if (/do not retry automatically/iu.test(detail)) {
|
|
6498
|
+
throw error;
|
|
6499
|
+
}
|
|
6500
|
+
throw new Error("native status inspection Enter was dispatched exactly once, but its " +
|
|
6501
|
+
`postcondition became uncertain; do not retry automatically: ${detail}`);
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
finally {
|
|
6505
|
+
releaseTerminalLock();
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
5968
6508
|
function previousCommittedResumeCandidate({ storeDir, terminal, currentSession, candidates }) {
|
|
5969
6509
|
if (!currentSession?.last_transition_id) {
|
|
5970
6510
|
return undefined;
|
|
@@ -10632,8 +11172,8 @@ function assertOrdinaryTerminalPayloadDoesNotInvokeNativeLifecycle(payload) {
|
|
|
10632
11172
|
return;
|
|
10633
11173
|
}
|
|
10634
11174
|
throw new Error(`ordinary send/respond cannot invoke native slash command /${reserved[1].toLowerCase()}; ` +
|
|
10635
|
-
"use
|
|
10636
|
-
"
|
|
11175
|
+
"use an advertised dedicated native action when one exists, or enter " +
|
|
11176
|
+
"the unsupported native command manually in tmux");
|
|
10637
11177
|
}
|
|
10638
11178
|
function isCompleteNativeRollout(value) {
|
|
10639
11179
|
return isRecord(value) &&
|
|
@@ -10814,6 +11354,13 @@ function assertNativeAgentIdentityForRuntime({ runtime, currentIdentity, agent,
|
|
|
10814
11354
|
throw new Error(`native ${agent} process incarnation cannot be verified for process ${pid}; ` +
|
|
10815
11355
|
"refresh list before controlling the terminal");
|
|
10816
11356
|
}
|
|
11357
|
+
if (runtime?.nativeProcessStartedAt !== undefined &&
|
|
11358
|
+
(!Number.isSafeInteger(runtime.nativeProcessStartedAt) ||
|
|
11359
|
+
Number(runtime.nativeProcessStartedAt) <= 0 ||
|
|
11360
|
+
currentIdentity?.processStartedAt !== runtime.nativeProcessStartedAt)) {
|
|
11361
|
+
throw new Error(`native ${agent} process start changed for process ${pid}; ` +
|
|
11362
|
+
"refresh list before controlling the terminal");
|
|
11363
|
+
}
|
|
10817
11364
|
if (!runtime?.nativeSessionId) {
|
|
10818
11365
|
return;
|
|
10819
11366
|
}
|
|
@@ -15695,6 +16242,27 @@ function codexComposerVisible(screen) {
|
|
|
15695
16242
|
.slice(-8)
|
|
15696
16243
|
.some((line) => /^[›»](?:\s|$)/u.test(line.trimEnd()));
|
|
15697
16244
|
}
|
|
16245
|
+
function nativeInspectionComposerEmpty(agent, screen) {
|
|
16246
|
+
return agent === "codex"
|
|
16247
|
+
? codexComposerEmpty(screen)
|
|
16248
|
+
: isExactClaudeNativeInspectionIdleComposer(String(screen ?? ""));
|
|
16249
|
+
}
|
|
16250
|
+
async function assertNativeInspectionComposerReadyForAutomatedInput({ options, terminal }) {
|
|
16251
|
+
if (terminal.agent === "codex") {
|
|
16252
|
+
await assertCodexComposerReadyForAutomatedInput({
|
|
16253
|
+
options,
|
|
16254
|
+
terminalControl: terminal.terminalControl
|
|
16255
|
+
});
|
|
16256
|
+
return;
|
|
16257
|
+
}
|
|
16258
|
+
const screen = await createTerminalControlProvider(options).capture(terminal.terminalControl.target, {
|
|
16259
|
+
scrollbackLines: 40,
|
|
16260
|
+
socketPath: terminal.terminalControl.socketPath
|
|
16261
|
+
});
|
|
16262
|
+
if (!isExactClaudeNativeInspectionIdleComposer(screen)) {
|
|
16263
|
+
throw new Error("Claude composer contains input or is not at the exact idle frame; refusing automated terminal input");
|
|
16264
|
+
}
|
|
16265
|
+
}
|
|
15698
16266
|
function codexComposerEmpty(screen) {
|
|
15699
16267
|
const composers = String(screen ?? "")
|
|
15700
16268
|
.split(/\r?\n/u)
|
|
@@ -15768,14 +16336,20 @@ function codexStyledComposerEmpty(screen) {
|
|
|
15768
16336
|
return content.length === 0 || content.every((entry) => entry.dim);
|
|
15769
16337
|
}
|
|
15770
16338
|
function claudeComposerVisible(screen) {
|
|
15771
|
-
|
|
15772
|
-
|
|
16339
|
+
const lines = String(screen ?? "").split(/\r?\n/u);
|
|
16340
|
+
while (lines.length > 0 && lines.at(-1)?.trim() === "") {
|
|
16341
|
+
lines.pop();
|
|
16342
|
+
}
|
|
16343
|
+
return lines
|
|
15773
16344
|
.slice(-8)
|
|
15774
16345
|
.some((line) => /^\s*❯(?:\s|$)/u.test(line));
|
|
15775
16346
|
}
|
|
15776
16347
|
function claudeComposerEmpty(screen) {
|
|
15777
|
-
const
|
|
15778
|
-
|
|
16348
|
+
const lines = String(screen ?? "").split(/\r?\n/u);
|
|
16349
|
+
while (lines.length > 0 && lines.at(-1)?.trim() === "") {
|
|
16350
|
+
lines.pop();
|
|
16351
|
+
}
|
|
16352
|
+
const composers = lines
|
|
15779
16353
|
.slice(-8)
|
|
15780
16354
|
.filter((line) => /^\s*❯(?:\s|$)/u.test(line));
|
|
15781
16355
|
return composers.length === 1 && /^\s*❯\s*$/u.test(composers[0]);
|
|
@@ -19112,6 +19686,7 @@ function usage() {
|
|
|
19112
19686
|
agent-knock-knock new-thread --terminal <exact-terminal-id> --expected-binding-token <token>
|
|
19113
19687
|
agent-knock-knock clear-thread --terminal <exact-terminal-id> --expected-binding-token <token>
|
|
19114
19688
|
agent-knock-knock list-resumable-threads --terminal <exact-terminal-id> [--selection-scope <opaque-scope>]
|
|
19689
|
+
agent-knock-knock native-inspect --terminal <exact-terminal-id> --inspection status --expected-binding-token <token>
|
|
19115
19690
|
agent-knock-knock resume-thread --terminal <exact-terminal-id> --native-thread <uuid> --expected-binding-token <token> --candidate-token <token>
|
|
19116
19691
|
agent-knock-knock resume-thread --terminal <exact-terminal-id> (--selection-handle <handle> | --selection-snapshot <id> (--selection-number <n> | --selection-short-id <@id>)) --selection-scope <opaque-scope>
|
|
19117
19692
|
agent-knock-knock reconcile-binding --terminal <exact-terminal-id> --conflicting-session <session-id> --expected-session-revision <n> --expected-binding-token <token> --expected-terminal-token <token>
|