@scotthuang/agent-knock-knock 0.2.0 → 0.2.1
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 +5 -2
- package/README.md +6 -10
- package/dist/src/cli.js +90 -39
- package/dist/src/cli.js.map +1 -1
- package/dist/src/executors.d.ts +0 -6
- package/dist/src/executors.js +0 -6
- package/dist/src/executors.js.map +1 -1
- package/dist/src/openclaw-plugin.js +6 -17
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/openclaw.plugin.json +1 -5
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +7 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.2.1 - 2026-06-22
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Deprecated the standalone restart recovery path from the OpenClaw-facing tool, slash command, skill, and docs. Conversations that need recovery now present `recover`, `close`, or starting a new independent delegation.
|
|
8
|
+
- Changed `send` to automatically fall back to AKK replay recovery when the previous ACPX session is unavailable. Explicit recovery decisions remain available only through `--recovery-policy explicit`.
|
|
6
9
|
|
|
7
10
|
## 0.2.0 - 2026-06-22
|
|
8
11
|
|
package/README.md
CHANGED
|
@@ -140,7 +140,6 @@ akk list
|
|
|
140
140
|
akk send <conversation-id>: continue with the smaller implementation
|
|
141
141
|
akk cancel <conversation-id>
|
|
142
142
|
akk recover <conversation-id>
|
|
143
|
-
akk restart <conversation-id>
|
|
144
143
|
akk close <conversation-id>
|
|
145
144
|
```
|
|
146
145
|
|
|
@@ -156,7 +155,6 @@ The plugin also registers the `/akk` slash command for channel surfaces that sup
|
|
|
156
155
|
/akk send <conversation-id> <message>
|
|
157
156
|
/akk cancel <conversation-id>
|
|
158
157
|
/akk recover <conversation-id>
|
|
159
|
-
/akk restart <conversation-id>
|
|
160
158
|
/akk close <conversation-id> [reason]
|
|
161
159
|
```
|
|
162
160
|
|
|
@@ -172,7 +170,6 @@ If your OpenClaw config uses a restrictive tool allowlist, allow the tool:
|
|
|
172
170
|
"agent_knock_knock_send",
|
|
173
171
|
"agent_knock_knock_cancel",
|
|
174
172
|
"agent_knock_knock_recover",
|
|
175
|
-
"agent_knock_knock_restart",
|
|
176
173
|
"agent_knock_knock_close"
|
|
177
174
|
]
|
|
178
175
|
}
|
|
@@ -189,13 +186,15 @@ New delegations create a fresh ACPX session by default, using a name like `akk-c
|
|
|
189
186
|
|
|
190
187
|
Background launches also start a small AKK monitor process. The monitor exits when the conversation receives a callback or otherwise leaves the agent-waiting state. If the executor process disappears before a callback, or if no callback arrives before `agentTimeoutMinutes`, the conversation is marked `stalled` and AKK attempts to notify the original OpenClaw session through the callback Gateway route. The default agent timeout is 60 minutes.
|
|
191
188
|
|
|
192
|
-
Some coding agents may not reliably resume a named ACPX session after their backing process disappears.
|
|
189
|
+
Some coding agents may not reliably resume a named ACPX session after their backing process disappears. By default, `AKK send <conversation-id>: <message>` automatically falls back to AKK replay recovery: it starts a fresh ACPX session, gives the agent a bounded summary of AKK's saved protocol history, and includes the pending message. The result includes `auto_recovered: true` when this happens.
|
|
190
|
+
|
|
191
|
+
If a caller uses the explicit recovery policy, or if automatic recovery cannot complete, the AKK conversation can enter `needs_recovery`. The user can then choose:
|
|
193
192
|
|
|
194
193
|
- `AKK recover <conversation-id>`: start a new coding-agent session with AKK's saved protocol history summary plus the pending message.
|
|
195
|
-
- `AKK restart <conversation-id>`: start a new coding-agent session with only the pending message.
|
|
196
194
|
- `AKK close <conversation-id>`: close the AKK task without recovery.
|
|
195
|
+
- Start a new independent AKK delegation if the old task should not be recovered.
|
|
197
196
|
|
|
198
|
-
|
|
197
|
+
Recover is AKK replay recovery, not guaranteed native coding-agent session resume. Codex, Claude Code, and Cursor all use the same default `send` auto-recovery behavior when the existing ACPX session is unavailable.
|
|
199
198
|
|
|
200
199
|
Task status can include a safe executor trace with `--trace` or the OpenClaw status tool's `trace: true` parameter. Trace summaries show client lifecycle events, tool call names and statuses, permission-request markers, monitor events, and short sanitized output previews. Agent thinking content is never returned; it is counted and marked as redacted.
|
|
201
200
|
|
|
@@ -256,14 +255,11 @@ node dist/src/cli.js cancel \
|
|
|
256
255
|
--conversation <conversation-id>
|
|
257
256
|
```
|
|
258
257
|
|
|
259
|
-
Recover
|
|
258
|
+
Recover a task that is waiting for an explicit recovery decision:
|
|
260
259
|
|
|
261
260
|
```bash
|
|
262
261
|
node dist/src/cli.js recover \
|
|
263
262
|
--conversation <conversation-id>
|
|
264
|
-
|
|
265
|
-
node dist/src/cli.js restart \
|
|
266
|
-
--conversation <conversation-id>
|
|
267
263
|
```
|
|
268
264
|
|
|
269
265
|
Inspect and take over native Codex sessions that were started outside AKK:
|
package/dist/src/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import { CodexLocalSessionProvider } from "./codex-local-session-provider.js";
|
|
9
9
|
import { CodexStoreAdapter } from "./codex-store-adapter.js";
|
|
10
10
|
import { applyMessageToConversation, budgetAction, createConversation, createMessage, executorForConversation, extractStructuredMessage, parseMessageJson, resolveExecutor } from "./protocol.js";
|
|
11
|
-
import { EXECUTOR_KINDS, acpxCommandForExecutor, executorDefinitionForKind, modelEnvForExecutor, normalizeModelForExecutor, proxyEnvForExecutor
|
|
11
|
+
import { EXECUTOR_KINDS, acpxCommandForExecutor, executorDefinitionForKind, modelEnvForExecutor, normalizeModelForExecutor, proxyEnvForExecutor } from "./executors.js";
|
|
12
12
|
import { executorBootstrapPrompt } from "./bootstrap.js";
|
|
13
13
|
import { writeRuntimeLog } from "./runtime-log.js";
|
|
14
14
|
import { formatTranscript, readNdjsonLog } from "./transcript.js";
|
|
@@ -100,9 +100,6 @@ async function runCommand(commandName, options) {
|
|
|
100
100
|
else if (commandName === "recover") {
|
|
101
101
|
runRecover(options);
|
|
102
102
|
}
|
|
103
|
-
else if (commandName === "restart") {
|
|
104
|
-
runRestart(options);
|
|
105
|
-
}
|
|
106
103
|
else if (commandName === "close") {
|
|
107
104
|
runClose(options);
|
|
108
105
|
}
|
|
@@ -1154,7 +1151,7 @@ function runSend(options) {
|
|
|
1154
1151
|
throw new Error(`cannot send to ${conversation.conversation_id}; conversation is ${conversation.status}`);
|
|
1155
1152
|
}
|
|
1156
1153
|
if (conversation.status === "needs_recovery") {
|
|
1157
|
-
throw new Error(`cannot send to ${conversation.conversation_id}; choose recover,
|
|
1154
|
+
throw new Error(`cannot send to ${conversation.conversation_id}; choose recover, close, or delegate a new task first`);
|
|
1158
1155
|
}
|
|
1159
1156
|
if (conversation.status === "needs_model_selection" && !options.model) {
|
|
1160
1157
|
throw new Error(`cannot send to ${conversation.conversation_id}; choose a supported model with --model first`);
|
|
@@ -1264,7 +1261,7 @@ function runSend(options) {
|
|
|
1264
1261
|
stderr: textSummary(cleanProcessText(ensureSession.stderr))
|
|
1265
1262
|
});
|
|
1266
1263
|
if (ensureSession.error) {
|
|
1267
|
-
if (requiresExplicitRecoveryDecision(
|
|
1264
|
+
if (requiresExplicitRecoveryDecision(options)) {
|
|
1268
1265
|
printJson(markConversationNeedsRecovery({
|
|
1269
1266
|
conversation: nextConversation,
|
|
1270
1267
|
statePath,
|
|
@@ -1277,10 +1274,22 @@ function runSend(options) {
|
|
|
1277
1274
|
}));
|
|
1278
1275
|
return;
|
|
1279
1276
|
}
|
|
1280
|
-
|
|
1277
|
+
autoRecoverSendFailure({
|
|
1278
|
+
options,
|
|
1279
|
+
conversation: nextConversation,
|
|
1280
|
+
statePath,
|
|
1281
|
+
logPath,
|
|
1282
|
+
executor,
|
|
1283
|
+
message,
|
|
1284
|
+
failedStage: "session_ensure",
|
|
1285
|
+
result: ensureSession,
|
|
1286
|
+
reason: `acpx ${executor.kind} session ensure failed to start: ${ensureSession.error.message}`
|
|
1287
|
+
});
|
|
1288
|
+
return;
|
|
1281
1289
|
}
|
|
1282
1290
|
if (ensureSession.status !== 0) {
|
|
1283
|
-
|
|
1291
|
+
const reason = cleanProcessText(ensureSession.stderr || ensureSession.stdout || `acpx ${executor.kind} sessions ensure exited with status ${ensureSession.status}`);
|
|
1292
|
+
if (requiresExplicitRecoveryDecision(options)) {
|
|
1284
1293
|
printJson(markConversationNeedsRecovery({
|
|
1285
1294
|
conversation: nextConversation,
|
|
1286
1295
|
statePath,
|
|
@@ -1289,11 +1298,22 @@ function runSend(options) {
|
|
|
1289
1298
|
message,
|
|
1290
1299
|
failedStage: "session_ensure",
|
|
1291
1300
|
result: ensureSession,
|
|
1292
|
-
reason
|
|
1301
|
+
reason
|
|
1293
1302
|
}));
|
|
1294
1303
|
return;
|
|
1295
1304
|
}
|
|
1296
|
-
|
|
1305
|
+
autoRecoverSendFailure({
|
|
1306
|
+
options,
|
|
1307
|
+
conversation: nextConversation,
|
|
1308
|
+
statePath,
|
|
1309
|
+
logPath,
|
|
1310
|
+
executor,
|
|
1311
|
+
message,
|
|
1312
|
+
failedStage: "session_ensure",
|
|
1313
|
+
result: ensureSession,
|
|
1314
|
+
reason
|
|
1315
|
+
});
|
|
1316
|
+
return;
|
|
1297
1317
|
}
|
|
1298
1318
|
const acpxArgs = buildAcpxPromptArgs({ executor, payload, model: executorModel });
|
|
1299
1319
|
if (options.background) {
|
|
@@ -1386,7 +1406,7 @@ function runSend(options) {
|
|
|
1386
1406
|
stderr: textSummary(cleanProcessText(sendResult.stderr))
|
|
1387
1407
|
});
|
|
1388
1408
|
if (sendResult.error) {
|
|
1389
|
-
if (requiresExplicitRecoveryDecision(
|
|
1409
|
+
if (requiresExplicitRecoveryDecision(options)) {
|
|
1390
1410
|
printJson(markConversationNeedsRecovery({
|
|
1391
1411
|
conversation: nextConversation,
|
|
1392
1412
|
statePath,
|
|
@@ -1399,10 +1419,22 @@ function runSend(options) {
|
|
|
1399
1419
|
}));
|
|
1400
1420
|
return;
|
|
1401
1421
|
}
|
|
1402
|
-
|
|
1422
|
+
autoRecoverSendFailure({
|
|
1423
|
+
options,
|
|
1424
|
+
conversation: nextConversation,
|
|
1425
|
+
statePath,
|
|
1426
|
+
logPath,
|
|
1427
|
+
executor,
|
|
1428
|
+
message,
|
|
1429
|
+
failedStage: "message_send",
|
|
1430
|
+
result: sendResult,
|
|
1431
|
+
reason: `acpx ${executor.kind} send failed to start: ${sendResult.error.message}`
|
|
1432
|
+
});
|
|
1433
|
+
return;
|
|
1403
1434
|
}
|
|
1404
1435
|
if (sendResult.status !== 0) {
|
|
1405
|
-
|
|
1436
|
+
const reason = cleanProcessText(sendResult.stderr || sendResult.stdout || `acpx ${executor.kind} send exited with status ${sendResult.status}`);
|
|
1437
|
+
if (requiresExplicitRecoveryDecision(options)) {
|
|
1406
1438
|
printJson(markConversationNeedsRecovery({
|
|
1407
1439
|
conversation: nextConversation,
|
|
1408
1440
|
statePath,
|
|
@@ -1411,11 +1443,22 @@ function runSend(options) {
|
|
|
1411
1443
|
message,
|
|
1412
1444
|
failedStage: "message_send",
|
|
1413
1445
|
result: sendResult,
|
|
1414
|
-
reason
|
|
1446
|
+
reason
|
|
1415
1447
|
}));
|
|
1416
1448
|
return;
|
|
1417
1449
|
}
|
|
1418
|
-
|
|
1450
|
+
autoRecoverSendFailure({
|
|
1451
|
+
options,
|
|
1452
|
+
conversation: nextConversation,
|
|
1453
|
+
statePath,
|
|
1454
|
+
logPath,
|
|
1455
|
+
executor,
|
|
1456
|
+
message,
|
|
1457
|
+
failedStage: "message_send",
|
|
1458
|
+
result: sendResult,
|
|
1459
|
+
reason
|
|
1460
|
+
});
|
|
1461
|
+
return;
|
|
1419
1462
|
}
|
|
1420
1463
|
const deliveredConversation = markTakeoverBootstrapped({
|
|
1421
1464
|
conversation: nextConversation,
|
|
@@ -1715,11 +1758,35 @@ function markForkSessionBootstrapped({ conversation, statePath, logPath, executo
|
|
|
1715
1758
|
});
|
|
1716
1759
|
return nextConversation;
|
|
1717
1760
|
}
|
|
1718
|
-
function requiresExplicitRecoveryDecision(
|
|
1761
|
+
function requiresExplicitRecoveryDecision(options = {}) {
|
|
1719
1762
|
if (options.recoveryPolicy === "explicit" || options.recoveryPolicy === "explicit-decision") {
|
|
1720
1763
|
return true;
|
|
1721
1764
|
}
|
|
1722
|
-
return
|
|
1765
|
+
return false;
|
|
1766
|
+
}
|
|
1767
|
+
function autoRecoverSendFailure({ options, conversation, statePath, logPath, executor, message, failedStage, result, reason }) {
|
|
1768
|
+
markConversationNeedsRecovery({
|
|
1769
|
+
conversation,
|
|
1770
|
+
statePath,
|
|
1771
|
+
logPath,
|
|
1772
|
+
executor,
|
|
1773
|
+
message,
|
|
1774
|
+
failedStage,
|
|
1775
|
+
result,
|
|
1776
|
+
reason
|
|
1777
|
+
});
|
|
1778
|
+
runtimeLog("info", "conversation_auto_recovery_start", {
|
|
1779
|
+
conversation_id: conversation.conversation_id,
|
|
1780
|
+
agent: executor.kind,
|
|
1781
|
+
executor_session: executor.session,
|
|
1782
|
+
failed_stage: failedStage,
|
|
1783
|
+
reason: textSummary(reason)
|
|
1784
|
+
});
|
|
1785
|
+
runRecoveryDecision({
|
|
1786
|
+
...options,
|
|
1787
|
+
mode: "recover",
|
|
1788
|
+
autoRecovered: true
|
|
1789
|
+
});
|
|
1723
1790
|
}
|
|
1724
1791
|
function markConversationNeedsRecovery({ conversation, statePath, logPath, executor, message, failedStage, result, reason }) {
|
|
1725
1792
|
const now = new Date().toISOString();
|
|
@@ -1733,7 +1800,7 @@ function markConversationNeedsRecovery({ conversation, statePath, logPath, execu
|
|
|
1733
1800
|
failed_message_id: message.id,
|
|
1734
1801
|
pending_message: message,
|
|
1735
1802
|
previous_executor: executor,
|
|
1736
|
-
options: ["recover", "
|
|
1803
|
+
options: ["recover", "close", "delegate"]
|
|
1737
1804
|
};
|
|
1738
1805
|
const nextConversation = {
|
|
1739
1806
|
...conversation,
|
|
@@ -1837,9 +1904,6 @@ function runCancel(options) {
|
|
|
1837
1904
|
function runRecover(options) {
|
|
1838
1905
|
runRecoveryDecision({ ...options, mode: "recover" });
|
|
1839
1906
|
}
|
|
1840
|
-
function runRestart(options) {
|
|
1841
|
-
runRecoveryDecision({ ...options, mode: "restart" });
|
|
1842
|
-
}
|
|
1843
1907
|
function runRecoveryDecision(options) {
|
|
1844
1908
|
cleanupIdleConversations(storeDirFromOptions(options), options);
|
|
1845
1909
|
const { conversation, statePath, logPath } = loadConversationFromOptions(options);
|
|
@@ -1871,9 +1935,7 @@ function runRecoveryDecision(options) {
|
|
|
1871
1935
|
updated_at: now
|
|
1872
1936
|
};
|
|
1873
1937
|
saveState(statePath, recoveredConversation);
|
|
1874
|
-
const payload =
|
|
1875
|
-
? buildRecoverPayload({ conversation, pendingMessage, logPath })
|
|
1876
|
-
: buildRestartPayload({ pendingMessage });
|
|
1938
|
+
const payload = buildRecoverPayload({ conversation, pendingMessage, logPath });
|
|
1877
1939
|
const acpxPath = resolveExecutable("acpx");
|
|
1878
1940
|
const executorEnv = environmentForExecutor(executor, {
|
|
1879
1941
|
allProxy: options.allProxy ?? conversation.executor_all_proxy
|
|
@@ -1935,8 +1997,8 @@ function runRecoveryDecision(options) {
|
|
|
1935
1997
|
});
|
|
1936
1998
|
printJson({
|
|
1937
1999
|
conversation: recoveredConversation,
|
|
1938
|
-
recovered:
|
|
1939
|
-
|
|
2000
|
+
recovered: true,
|
|
2001
|
+
auto_recovered: Boolean(options.autoRecovered),
|
|
1940
2002
|
background: true,
|
|
1941
2003
|
pid: child.pid ?? null,
|
|
1942
2004
|
monitor_pid: monitor.pid ?? null,
|
|
@@ -1970,8 +2032,8 @@ function runRecoveryDecision(options) {
|
|
|
1970
2032
|
}
|
|
1971
2033
|
printJson({
|
|
1972
2034
|
conversation: recoveredConversation,
|
|
1973
|
-
recovered:
|
|
1974
|
-
|
|
2035
|
+
recovered: true,
|
|
2036
|
+
auto_recovered: Boolean(options.autoRecovered),
|
|
1975
2037
|
delivered: true,
|
|
1976
2038
|
executor,
|
|
1977
2039
|
budget: budgetAction(recoveredConversation)
|
|
@@ -1994,16 +2056,6 @@ function buildRecoverPayload({ conversation, pendingMessage, logPath }) {
|
|
|
1994
2056
|
JSON.stringify(pendingMessage)
|
|
1995
2057
|
].join("\n");
|
|
1996
2058
|
}
|
|
1997
|
-
function buildRestartPayload({ pendingMessage }) {
|
|
1998
|
-
return [
|
|
1999
|
-
"Restart this Agent Knock Knock task in a new ACPX session.",
|
|
2000
|
-
"Do not assume the previous coding-agent session context is available.",
|
|
2001
|
-
"Follow only the pending OpenClaw message below.",
|
|
2002
|
-
"Continue to report back only through the callback command already provided for this conversation.",
|
|
2003
|
-
"",
|
|
2004
|
-
JSON.stringify(pendingMessage)
|
|
2005
|
-
].join("\n");
|
|
2006
|
-
}
|
|
2007
2059
|
function formatProtocolHistoryForRecovery(events) {
|
|
2008
2060
|
const lines = events
|
|
2009
2061
|
.filter((event) => event.event === "message")
|
|
@@ -3467,7 +3519,6 @@ function usage() {
|
|
|
3467
3519
|
agent-knock-knock send --conversation <id> --message <text> [--type answer|task|control] [--all-proxy <url>] [--agent-timeout-minutes <minutes>]
|
|
3468
3520
|
agent-knock-knock cancel --conversation <id> [--all-proxy <url>]
|
|
3469
3521
|
agent-knock-knock recover --conversation <id> [--session <name>] [--all-proxy <url>]
|
|
3470
|
-
agent-knock-knock restart --conversation <id> [--session <name>] [--all-proxy <url>]
|
|
3471
3522
|
agent-knock-knock close --conversation <id> [--reason <text>]
|
|
3472
3523
|
agent-knock-knock install-openclaw [--openclaw-bin <path>] [--skill-path <path>] [--no-restart]
|
|
3473
3524
|
agent-knock-knock doctor
|