@scotthuang/agent-knock-knock 0.3.2 → 0.4.0
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 +9 -571
- package/README.md +97 -123
- package/dist/src/agent-session-provider.d.ts +1 -2
- package/dist/src/approval-policy.d.ts +1 -1
- package/dist/src/approval-policy.js +1 -1
- package/dist/src/approval-policy.js.map +1 -1
- package/dist/src/claude-terminal-agent-adapter.d.ts +2 -17
- package/dist/src/claude-terminal-agent-adapter.js +4 -489
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli.js +1489 -3406
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex-local-session-provider.d.ts +1 -2
- package/dist/src/codex-local-session-provider.js +1 -9
- package/dist/src/codex-local-session-provider.js.map +1 -1
- package/dist/src/codex-session-provider.d.ts +1 -8
- package/dist/src/codex-session-provider.js +0 -56
- package/dist/src/codex-session-provider.js.map +1 -1
- package/dist/src/codex-terminal-agent-adapter.d.ts +1 -1
- package/dist/src/doctor-capabilities.d.ts +5 -13
- package/dist/src/doctor-capabilities.js +15 -60
- package/dist/src/doctor-capabilities.js.map +1 -1
- package/dist/src/executors.d.ts +5 -51
- package/dist/src/executors.js +4 -68
- package/dist/src/executors.js.map +1 -1
- package/dist/src/openclaw-doctor.d.ts +1 -1
- package/dist/src/openclaw-doctor.js +1 -1
- package/dist/src/openclaw-doctor.js.map +1 -1
- package/dist/src/openclaw-plugin-helpers.d.ts +5 -8
- package/dist/src/openclaw-plugin-helpers.js +57 -40
- package/dist/src/openclaw-plugin-helpers.js.map +1 -1
- package/dist/src/openclaw-plugin.js +138 -248
- package/dist/src/openclaw-plugin.js.map +1 -1
- package/dist/src/protocol.d.ts +1 -5
- package/dist/src/protocol.js +1 -1
- package/dist/src/protocol.js.map +1 -1
- package/dist/src/session-selector.js +1 -1
- package/dist/src/session-selector.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +3 -17
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +3 -3
- package/dist/src/terminal-agent-bridge.js +0 -75
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/transcript.js +2 -3
- package/dist/src/transcript.js.map +1 -1
- package/docs/quickstart-tmux.md +7 -7
- package/openclaw.plugin.json +4 -83
- package/package.json +7 -15
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +94 -147
- package/dist/src/acpx-output.d.ts +0 -18
- package/dist/src/acpx-output.js +0 -223
- package/dist/src/acpx-output.js.map +0 -1
- package/dist/src/bootstrap.d.ts +0 -8
- package/dist/src/bootstrap.js +0 -45
- package/dist/src/bootstrap.js.map +0 -1
- package/dist/src/claude-hook-installer.d.ts +0 -65
- package/dist/src/claude-hook-installer.js +0 -410
- package/dist/src/claude-hook-installer.js.map +0 -1
- package/dist/src/claude-hook-protocol.d.ts +0 -89
- package/dist/src/claude-hook-protocol.js +0 -243
- package/dist/src/claude-hook-protocol.js.map +0 -1
- package/dist/src/claude-hook-store.d.ts +0 -247
- package/dist/src/claude-hook-store.js +0 -1075
- package/dist/src/claude-hook-store.js.map +0 -1
- package/dist/src/session-takeover-planner.d.ts +0 -37
- package/dist/src/session-takeover-planner.js +0 -57
- package/dist/src/session-takeover-planner.js.map +0 -1
- package/docs/quickstart-managed-acpx.md +0 -43
- package/scripts/smoke-acpx.js +0 -139
- package/scripts/smoke-tmux.js +0 -140
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import { createHash } from "node:crypto";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { redactString } from "./runtime-log.js";
|
|
@@ -73,33 +72,12 @@ const CLAUDE_EXCERPT_LINES = 80;
|
|
|
73
72
|
const CLAUDE_PERMISSION_DETAIL_LENGTH = 600;
|
|
74
73
|
const CLAUDE_AUTO_APPROVAL_COMMAND_LENGTH = 2000;
|
|
75
74
|
const CLAUDE_NATIVE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/u;
|
|
76
|
-
const TRUSTED_CLAUDE_SHELL_PATHS = new Set([
|
|
77
|
-
"/bin/bash",
|
|
78
|
-
"/bin/dash",
|
|
79
|
-
"/bin/sh",
|
|
80
|
-
"/bin/zsh",
|
|
81
|
-
"/usr/bin/bash",
|
|
82
|
-
"/usr/bin/dash",
|
|
83
|
-
"/usr/bin/sh",
|
|
84
|
-
"/usr/bin/zsh"
|
|
85
|
-
]);
|
|
86
|
-
const TRUSTED_CLAUDE_SHELL_TARGETS = new Set([...TRUSTED_CLAUDE_SHELL_PATHS].flatMap((shell) => {
|
|
87
|
-
try {
|
|
88
|
-
return [fs.realpathSync(shell)];
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return [];
|
|
92
|
-
}
|
|
93
|
-
}));
|
|
94
75
|
const ANSI_ESCAPE_PATTERN = /\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/gu;
|
|
95
76
|
export function createClaudeTerminalAgentAdapter(options = {}) {
|
|
96
77
|
const agentRows = options.agentRows ?? [];
|
|
97
|
-
const hookStore = options.hookStore;
|
|
98
78
|
const transcriptCompletionDetector = options.detectDurableCompletion;
|
|
99
79
|
const transcriptApprovalDetector = options.detectPendingApproval;
|
|
100
|
-
const
|
|
101
|
-
const durableCompletion = hookStore !== undefined ||
|
|
102
|
-
transcriptCompletionDetector !== undefined;
|
|
80
|
+
const durableCompletion = transcriptCompletionDetector !== undefined;
|
|
103
81
|
return {
|
|
104
82
|
agent: "claude",
|
|
105
83
|
displayName: "Claude Code",
|
|
@@ -117,23 +95,11 @@ export function createClaudeTerminalAgentAdapter(options = {}) {
|
|
|
117
95
|
return classifyClaudeProcess(snapshot, agentRows);
|
|
118
96
|
},
|
|
119
97
|
inspectScreen(screenOptions) {
|
|
120
|
-
return
|
|
98
|
+
return transcriptApprovalScreenInspection(inspectClaudeScreen(screenOptions), screenOptions, transcriptApprovalDetector);
|
|
121
99
|
},
|
|
122
|
-
...(hookStore
|
|
123
|
-
? {
|
|
124
|
-
async resolveApproval(request) {
|
|
125
|
-
return resolveClaudeHookApproval(hookStore, request);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
: {}),
|
|
129
100
|
...(durableCompletion
|
|
130
101
|
? {
|
|
131
102
|
async detectDurableCompletion(request) {
|
|
132
|
-
// A configured legacy hook store is authoritative. Falling back when it
|
|
133
|
-
// reports pending could bypass its background-task and cron evidence.
|
|
134
|
-
if (hookStore) {
|
|
135
|
-
return detectClaudeHookCompletion(hookStore, request);
|
|
136
|
-
}
|
|
137
103
|
return transcriptCompletionDetector?.(request);
|
|
138
104
|
}
|
|
139
105
|
}
|
|
@@ -231,34 +197,6 @@ function hasManagedClaudeApprovalIdentity(runtime) {
|
|
|
231
197
|
Boolean(nonEmptyString(runtime?.messageId)) &&
|
|
232
198
|
Boolean(nonEmptyString(runtime?.terminalTarget));
|
|
233
199
|
}
|
|
234
|
-
function inspectClaudeScreenWithHooks(options, hookStore, transcriptApprovalDetector, trustedTokenjuiceLaunchers) {
|
|
235
|
-
const screenInspection = inspectClaudeScreen(options);
|
|
236
|
-
if (!hookStore) {
|
|
237
|
-
return transcriptApprovalScreenInspection(screenInspection, options, transcriptApprovalDetector);
|
|
238
|
-
}
|
|
239
|
-
const identity = exactClaudeHookIdentity(options.runtime);
|
|
240
|
-
if (!identity) {
|
|
241
|
-
return screenInspection.approval.blocked
|
|
242
|
-
? unverifiedHookApproval(screenInspection, "Claude hook permission could not be verified without an exact session id or pid")
|
|
243
|
-
: screenInspection;
|
|
244
|
-
}
|
|
245
|
-
let hookInspection;
|
|
246
|
-
try {
|
|
247
|
-
hookInspection = hookStore.inspectPermission(identity);
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
return screenInspection.approval.blocked
|
|
251
|
-
? unverifiedHookApproval(screenInspection, `Claude hook permission identity could not be verified: ${errorMessage(error)}`)
|
|
252
|
-
: screenInspection;
|
|
253
|
-
}
|
|
254
|
-
if (hookInspection.blocked) {
|
|
255
|
-
return hookPermissionScreenInspection(screenInspection, hookInspection, trustedTokenjuiceLaunchers);
|
|
256
|
-
}
|
|
257
|
-
if (screenInspection.approval.blocked) {
|
|
258
|
-
return unverifiedHookApproval(screenInspection, `Claude terminal permission is visible but no current structured hook request matched: ${hookInspection.reason}`);
|
|
259
|
-
}
|
|
260
|
-
return screenInspection;
|
|
261
|
-
}
|
|
262
200
|
function transcriptApprovalScreenInspection(screenInspection, options, detector) {
|
|
263
201
|
const managedPermissionInspection = (options.managedRequest || hasManagedClaudeApprovalIdentity(options.runtime)) &&
|
|
264
202
|
screenInspection.approval.blocked &&
|
|
@@ -364,105 +302,8 @@ function privacySafeManagedClaudePermissionInspection(screenInspection, screen,
|
|
|
364
302
|
screenExcerpt: omitClaudePermissionDetails(screen, undefined, maxExcerptLength)
|
|
365
303
|
};
|
|
366
304
|
}
|
|
367
|
-
function
|
|
368
|
-
|
|
369
|
-
return {
|
|
370
|
-
...screenInspection,
|
|
371
|
-
activity: {
|
|
372
|
-
state: "awaiting_approval",
|
|
373
|
-
reason: hookInspection.reason
|
|
374
|
-
},
|
|
375
|
-
approval: {
|
|
376
|
-
blocked: true,
|
|
377
|
-
approvable: false,
|
|
378
|
-
reason: hookInspection.reason,
|
|
379
|
-
promptKind: "claude_permission"
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
const display = claudePermissionDisplay(hookInspection.toolName, hookInspection.toolInput);
|
|
384
|
-
const command = hookInspection.command === undefined
|
|
385
|
-
? undefined
|
|
386
|
-
: claudePermissionCommandForApproval(hookInspection.command, trustedTokenjuiceLaunchers);
|
|
387
|
-
const requestDetail = command && command.command === undefined
|
|
388
|
-
? [
|
|
389
|
-
command.display ? `Command: ${command.display}` : undefined,
|
|
390
|
-
display.requestDetail
|
|
391
|
-
].filter((value) => Boolean(value))
|
|
392
|
-
.join("; ")
|
|
393
|
-
.slice(0, CLAUDE_PERMISSION_DETAIL_LENGTH)
|
|
394
|
-
: display.requestDetail;
|
|
395
|
-
return {
|
|
396
|
-
...screenInspection,
|
|
397
|
-
activity: {
|
|
398
|
-
state: "awaiting_approval",
|
|
399
|
-
reason: hookInspection.reason
|
|
400
|
-
},
|
|
401
|
-
approval: {
|
|
402
|
-
blocked: true,
|
|
403
|
-
approvable: true,
|
|
404
|
-
promptKind: hookInspection.promptKind,
|
|
405
|
-
cwd: hookInspection.cwd,
|
|
406
|
-
toolName: display.toolName,
|
|
407
|
-
...(requestDetail ? { requestDetail } : {}),
|
|
408
|
-
...(command?.command === undefined ? {} : { command: command.command }),
|
|
409
|
-
action: {
|
|
410
|
-
mode: "structured",
|
|
411
|
-
keys: [],
|
|
412
|
-
label: "Allow once",
|
|
413
|
-
requestId: hookInspection.requestId
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Tokenjuice's official Claude PreToolUse hook wraps Bash through a login shell before
|
|
420
|
-
* PermissionRequest runs. Recover only that exact, source-tagged shape so policy matching
|
|
421
|
-
* sees Claude's original command; every other wrapper remains visible and fails closed.
|
|
422
|
-
*/
|
|
423
|
-
export function normalizeClaudePermissionCommand(command, trustedTokenjuiceLaunchers = []) {
|
|
424
|
-
const tokens = tokenizeTrustedWrapperCommand(command);
|
|
425
|
-
let normalized = command;
|
|
426
|
-
const launcher = tokens?.[0];
|
|
427
|
-
const canonicalLauncher = launcher && path.isAbsolute(launcher)
|
|
428
|
-
? realpathOrUndefined(launcher)
|
|
429
|
-
: undefined;
|
|
430
|
-
const trustedLauncher = launcher
|
|
431
|
-
? trustedTokenjuiceLaunchers.find((candidate) => candidate.configuredPath === launcher)
|
|
432
|
-
: undefined;
|
|
433
|
-
if (tokens &&
|
|
434
|
-
canonicalLauncher &&
|
|
435
|
-
trustedLauncher?.canonicalPath === canonicalLauncher &&
|
|
436
|
-
tokens[1] === "wrap") {
|
|
437
|
-
let index = 2;
|
|
438
|
-
if (tokens[index] === "--source" && tokens[index + 1] === "claude-code") {
|
|
439
|
-
index += 2;
|
|
440
|
-
}
|
|
441
|
-
else if (tokens[index] === "--source=claude-code") {
|
|
442
|
-
index += 1;
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
return command;
|
|
446
|
-
}
|
|
447
|
-
const shell = tokens[index + 1];
|
|
448
|
-
const canonicalShell = shell && path.isAbsolute(shell)
|
|
449
|
-
? realpathOrUndefined(shell)
|
|
450
|
-
: undefined;
|
|
451
|
-
if (tokens[index] === "--" &&
|
|
452
|
-
shell !== undefined &&
|
|
453
|
-
TRUSTED_CLAUDE_SHELL_PATHS.has(shell) &&
|
|
454
|
-
canonicalShell !== undefined &&
|
|
455
|
-
TRUSTED_CLAUDE_SHELL_TARGETS.has(canonicalShell) &&
|
|
456
|
-
tokens[index + 2] === "-lc" &&
|
|
457
|
-
tokens.length === index + 4 &&
|
|
458
|
-
tokens[index + 3]) {
|
|
459
|
-
normalized = tokens[index + 3];
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return normalized;
|
|
463
|
-
}
|
|
464
|
-
export function claudePermissionCommandForApproval(command, trustedTokenjuiceLaunchers = []) {
|
|
465
|
-
const normalized = normalizeClaudePermissionCommand(command, trustedTokenjuiceLaunchers);
|
|
305
|
+
export function claudePermissionCommandForApproval(command) {
|
|
306
|
+
const normalized = command;
|
|
466
307
|
const redacted = redactString(normalized);
|
|
467
308
|
const display = singleLineClaudePermissionValue(redacted, CLAUDE_PERMISSION_DETAIL_LENGTH - 20);
|
|
468
309
|
const policySafe = normalized.length <= CLAUDE_AUTO_APPROVAL_COMMAND_LENGTH && redacted === normalized;
|
|
@@ -471,39 +312,6 @@ export function claudePermissionCommandForApproval(command, trustedTokenjuiceLau
|
|
|
471
312
|
display
|
|
472
313
|
};
|
|
473
314
|
}
|
|
474
|
-
function claudePermissionDisplay(toolName, toolInput) {
|
|
475
|
-
const safeToolName = singleLineClaudePermissionValue(toolName, 120) || "Unknown Claude tool";
|
|
476
|
-
if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
|
|
477
|
-
return { toolName: safeToolName };
|
|
478
|
-
}
|
|
479
|
-
const input = toolInput;
|
|
480
|
-
const fields = [
|
|
481
|
-
["file_path", "File"],
|
|
482
|
-
["path", "Path"],
|
|
483
|
-
["notebook_path", "Notebook"],
|
|
484
|
-
["url", "URL"],
|
|
485
|
-
["query", "Query"],
|
|
486
|
-
["pattern", "Pattern"],
|
|
487
|
-
["description", "Description"],
|
|
488
|
-
["domain", "Domain"]
|
|
489
|
-
];
|
|
490
|
-
const details = [];
|
|
491
|
-
for (const [key, label] of fields) {
|
|
492
|
-
const value = input[key];
|
|
493
|
-
if (typeof value !== "string") {
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
const safeValue = singleLineClaudePermissionValue(value, 300);
|
|
497
|
-
if (safeValue) {
|
|
498
|
-
details.push(`${label}: ${safeValue}`);
|
|
499
|
-
}
|
|
500
|
-
if (details.length === 3) {
|
|
501
|
-
break;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
const requestDetail = details.join("; ").slice(0, CLAUDE_PERMISSION_DETAIL_LENGTH);
|
|
505
|
-
return requestDetail ? { toolName: safeToolName, requestDetail } : { toolName: safeToolName };
|
|
506
|
-
}
|
|
507
315
|
function singleLineClaudePermissionValue(value, maxLength) {
|
|
508
316
|
return redactString(value)
|
|
509
317
|
.replace(/[\u0000-\u001F\u007F]+/gu, " ")
|
|
@@ -511,239 +319,6 @@ function singleLineClaudePermissionValue(value, maxLength) {
|
|
|
511
319
|
.trim()
|
|
512
320
|
.slice(0, maxLength);
|
|
513
321
|
}
|
|
514
|
-
function unverifiedHookApproval(screenInspection, reason) {
|
|
515
|
-
return {
|
|
516
|
-
...screenInspection,
|
|
517
|
-
activity: {
|
|
518
|
-
state: "awaiting_approval",
|
|
519
|
-
reason
|
|
520
|
-
},
|
|
521
|
-
approval: {
|
|
522
|
-
blocked: true,
|
|
523
|
-
approvable: false,
|
|
524
|
-
reason,
|
|
525
|
-
promptKind: screenInspection.approval.promptKind ?? "claude_permission",
|
|
526
|
-
...(screenInspection.approval.command === undefined
|
|
527
|
-
? {}
|
|
528
|
-
: { command: screenInspection.approval.command })
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
async function resolveClaudeHookApproval(hookStore, request) {
|
|
533
|
-
if (request.expectedFingerprint !== request.actualFingerprint) {
|
|
534
|
-
return {
|
|
535
|
-
resolved: false,
|
|
536
|
-
reason: "Claude approval fingerprint changed before the structured decision"
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
if (!request.inspection.approval.approvable ||
|
|
540
|
-
request.inspection.approval.action.mode !== "structured" ||
|
|
541
|
-
!request.inspection.approval.action.requestId) {
|
|
542
|
-
return {
|
|
543
|
-
resolved: false,
|
|
544
|
-
reason: "Claude approval is not a current structured hook request"
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
const runtime = request.runtime;
|
|
548
|
-
const identity = exactClaudeHookIdentity(runtime);
|
|
549
|
-
if (!identity || !runtime?.conversationId || !runtime.messageId) {
|
|
550
|
-
return {
|
|
551
|
-
resolved: false,
|
|
552
|
-
requestId: request.inspection.approval.action.requestId,
|
|
553
|
-
reason: "Claude structured approval requires exact session or pid plus conversation and message identity"
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
try {
|
|
557
|
-
const current = hookStore.inspectPermission(identity);
|
|
558
|
-
if (!current.blocked || !current.approvable) {
|
|
559
|
-
return {
|
|
560
|
-
resolved: false,
|
|
561
|
-
requestId: request.inspection.approval.action.requestId,
|
|
562
|
-
reason: current.reason
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
if (current.requestId !== request.inspection.approval.action.requestId) {
|
|
566
|
-
return {
|
|
567
|
-
resolved: false,
|
|
568
|
-
requestId: current.requestId,
|
|
569
|
-
reason: "Claude permission request changed before the structured decision"
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
if (current.conversationId !== runtime.conversationId || current.messageId !== runtime.messageId) {
|
|
573
|
-
return {
|
|
574
|
-
resolved: false,
|
|
575
|
-
requestId: current.requestId,
|
|
576
|
-
reason: "Claude permission request belongs to a different conversation message"
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
if (runtime.sessionId !== undefined && current.sessionId !== runtime.sessionId) {
|
|
580
|
-
return {
|
|
581
|
-
resolved: false,
|
|
582
|
-
requestId: current.requestId,
|
|
583
|
-
reason: "Claude permission request belongs to a different session"
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
const lease = hookStore.resolveLease(identity);
|
|
587
|
-
if (!lease?.authorizationEligible ||
|
|
588
|
-
lease.lease.conversationId !== runtime.conversationId ||
|
|
589
|
-
lease.lease.messageId !== runtime.messageId ||
|
|
590
|
-
(runtime.terminalTarget !== undefined && lease.lease.terminalTarget !== runtime.terminalTarget)) {
|
|
591
|
-
return {
|
|
592
|
-
resolved: false,
|
|
593
|
-
requestId: current.requestId,
|
|
594
|
-
reason: "Claude managed lease no longer matches the terminal conversation"
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
hookStore.decidePermission({
|
|
598
|
-
sessionId: current.sessionId,
|
|
599
|
-
requestId: current.requestId,
|
|
600
|
-
fingerprint: current.fingerprint,
|
|
601
|
-
conversationId: runtime.conversationId,
|
|
602
|
-
messageId: runtime.messageId,
|
|
603
|
-
decision: request.decision,
|
|
604
|
-
...(request.decision === "deny" && request.interrupt !== undefined
|
|
605
|
-
? { interrupt: request.interrupt }
|
|
606
|
-
: {})
|
|
607
|
-
});
|
|
608
|
-
return {
|
|
609
|
-
resolved: true,
|
|
610
|
-
requestId: current.requestId,
|
|
611
|
-
reason: request.decision === "allow"
|
|
612
|
-
? "Claude one-time permission was allowed through the structured hook"
|
|
613
|
-
: "Claude permission was denied through the structured hook"
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
catch (error) {
|
|
617
|
-
return {
|
|
618
|
-
resolved: false,
|
|
619
|
-
requestId: request.inspection.approval.action.requestId,
|
|
620
|
-
reason: `Claude structured permission could not be resolved: ${errorMessage(error)}`
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
async function detectClaudeHookCompletion(hookStore, request) {
|
|
625
|
-
if (!request.startedAt) {
|
|
626
|
-
return undefined;
|
|
627
|
-
}
|
|
628
|
-
const runtime = completionRuntime(request.context);
|
|
629
|
-
if (!runtime.conversationId || !runtime.messageId) {
|
|
630
|
-
return undefined;
|
|
631
|
-
}
|
|
632
|
-
const identity = {
|
|
633
|
-
...(runtime.sessionId ?? request.sessionId
|
|
634
|
-
? { sessionId: runtime.sessionId ?? request.sessionId }
|
|
635
|
-
: {}),
|
|
636
|
-
...(runtime.pid === undefined ? {} : { pid: runtime.pid }),
|
|
637
|
-
...(runtime.cwd ?? request.cwd ? { cwd: runtime.cwd ?? request.cwd } : {}),
|
|
638
|
-
requireUnique: true
|
|
639
|
-
};
|
|
640
|
-
if (!identity.sessionId && identity.pid === undefined && !identity.cwd) {
|
|
641
|
-
return undefined;
|
|
642
|
-
}
|
|
643
|
-
let completion;
|
|
644
|
-
try {
|
|
645
|
-
completion = hookStore.detectCompletion({
|
|
646
|
-
...identity,
|
|
647
|
-
startedAt: request.startedAt,
|
|
648
|
-
...(runtime.promptId === undefined ? {} : { promptId: runtime.promptId }),
|
|
649
|
-
conversationId: runtime.conversationId,
|
|
650
|
-
messageId: runtime.messageId
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
catch {
|
|
654
|
-
return undefined;
|
|
655
|
-
}
|
|
656
|
-
if (completion.status === "done") {
|
|
657
|
-
return {
|
|
658
|
-
source: "durable",
|
|
659
|
-
outcome: "success",
|
|
660
|
-
text: completion.text,
|
|
661
|
-
timestamp: completion.timestamp,
|
|
662
|
-
id: completion.eventId,
|
|
663
|
-
confidence: "high",
|
|
664
|
-
metadata: {
|
|
665
|
-
match: "claude_stop_hook",
|
|
666
|
-
session_id: completion.sessionId,
|
|
667
|
-
prompt_id: completion.promptId,
|
|
668
|
-
cwd: completion.cwd
|
|
669
|
-
}
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
if (completion.status === "failed") {
|
|
673
|
-
return {
|
|
674
|
-
source: "durable",
|
|
675
|
-
outcome: "failure",
|
|
676
|
-
text: claudeFailureText(completion),
|
|
677
|
-
timestamp: completion.failure.receivedAt,
|
|
678
|
-
id: completion.failure.eventId,
|
|
679
|
-
confidence: "high",
|
|
680
|
-
metadata: {
|
|
681
|
-
match: "claude_stop_failure_hook",
|
|
682
|
-
session_id: completion.sessionId,
|
|
683
|
-
prompt_id: completion.promptId,
|
|
684
|
-
error: completion.failure.code,
|
|
685
|
-
...(completion.failure.details === undefined
|
|
686
|
-
? {}
|
|
687
|
-
: { error_details: completion.failure.details })
|
|
688
|
-
}
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
// Pending background work and unknown/incomplete hook payloads are not completion evidence.
|
|
692
|
-
return undefined;
|
|
693
|
-
}
|
|
694
|
-
function exactClaudeHookIdentity(runtime) {
|
|
695
|
-
if (!runtime || (runtime.sessionId === undefined && runtime.pid === undefined)) {
|
|
696
|
-
return undefined;
|
|
697
|
-
}
|
|
698
|
-
return {
|
|
699
|
-
...(runtime.sessionId === undefined ? {} : { sessionId: runtime.sessionId }),
|
|
700
|
-
...(runtime.pid === undefined ? {} : { pid: runtime.pid }),
|
|
701
|
-
...(runtime.cwd === undefined ? {} : { cwd: runtime.cwd }),
|
|
702
|
-
requireUnique: true
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
function completionRuntime(context) {
|
|
706
|
-
const direct = recordValue(context);
|
|
707
|
-
const nativeTakeover = recordValue(direct?.nativeTakeover);
|
|
708
|
-
const conversation = recordValue(direct?.conversation);
|
|
709
|
-
return {
|
|
710
|
-
sessionId: nonEmptyString(nativeTakeover?.terminal_agent_session_id) ??
|
|
711
|
-
nonEmptyString(direct?.sessionId),
|
|
712
|
-
pid: positiveInteger(nativeTakeover?.terminal_agent_pid) ?? positiveInteger(direct?.pid),
|
|
713
|
-
cwd: nonEmptyString(nativeTakeover?.source_cwd) ?? nonEmptyString(direct?.cwd),
|
|
714
|
-
promptId: nonEmptyString(nativeTakeover?.terminal_agent_prompt_id) ??
|
|
715
|
-
nonEmptyString(direct?.promptId) ??
|
|
716
|
-
nonEmptyString(direct?.prompt_id),
|
|
717
|
-
conversationId: nonEmptyString(direct?.conversationId) ??
|
|
718
|
-
nonEmptyString(conversation?.conversation_id),
|
|
719
|
-
messageId: nonEmptyString(direct?.messageId) ??
|
|
720
|
-
nonEmptyString(nativeTakeover?.terminal_bridge_message_id)
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
function claudeFailureText(completion) {
|
|
724
|
-
const details = completion.failure.details === undefined
|
|
725
|
-
? undefined
|
|
726
|
-
: typeof completion.failure.details === "string"
|
|
727
|
-
? completion.failure.details
|
|
728
|
-
: JSON.stringify(completion.failure.details);
|
|
729
|
-
return [
|
|
730
|
-
`Claude Code turn failed (${completion.failure.code}).`,
|
|
731
|
-
completion.failure.message,
|
|
732
|
-
details === undefined ? undefined : `Details: ${details}`
|
|
733
|
-
].filter((part) => Boolean(part)).join(" ");
|
|
734
|
-
}
|
|
735
|
-
function recordValue(value) {
|
|
736
|
-
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
737
|
-
? value
|
|
738
|
-
: undefined;
|
|
739
|
-
}
|
|
740
|
-
function positiveInteger(value) {
|
|
741
|
-
const parsed = typeof value === "number" ? value : Number(value);
|
|
742
|
-
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
743
|
-
}
|
|
744
|
-
function errorMessage(error) {
|
|
745
|
-
return error instanceof Error ? error.message : String(error);
|
|
746
|
-
}
|
|
747
322
|
export function detectClaudeApprovalPrompt(screen) {
|
|
748
323
|
const lines = claudeDetectionTail(screen);
|
|
749
324
|
const markerIndexes = lines
|
|
@@ -1030,66 +605,6 @@ function shortOptionValue(args, option) {
|
|
|
1030
605
|
}
|
|
1031
606
|
return undefined;
|
|
1032
607
|
}
|
|
1033
|
-
function realpathOrUndefined(value) {
|
|
1034
|
-
try {
|
|
1035
|
-
return fs.realpathSync(value);
|
|
1036
|
-
}
|
|
1037
|
-
catch {
|
|
1038
|
-
return undefined;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
function tokenizeTrustedWrapperCommand(command) {
|
|
1042
|
-
const tokens = [];
|
|
1043
|
-
let token = "";
|
|
1044
|
-
let quote;
|
|
1045
|
-
let escaped = false;
|
|
1046
|
-
let started = false;
|
|
1047
|
-
for (const character of command.trim()) {
|
|
1048
|
-
if (escaped) {
|
|
1049
|
-
token += character;
|
|
1050
|
-
escaped = false;
|
|
1051
|
-
started = true;
|
|
1052
|
-
continue;
|
|
1053
|
-
}
|
|
1054
|
-
if (character === "\\" && quote !== "'") {
|
|
1055
|
-
escaped = true;
|
|
1056
|
-
started = true;
|
|
1057
|
-
continue;
|
|
1058
|
-
}
|
|
1059
|
-
if (quote) {
|
|
1060
|
-
if (character === quote) {
|
|
1061
|
-
quote = undefined;
|
|
1062
|
-
}
|
|
1063
|
-
else {
|
|
1064
|
-
token += character;
|
|
1065
|
-
}
|
|
1066
|
-
started = true;
|
|
1067
|
-
continue;
|
|
1068
|
-
}
|
|
1069
|
-
if (character === "'" || character === '"') {
|
|
1070
|
-
quote = character;
|
|
1071
|
-
started = true;
|
|
1072
|
-
continue;
|
|
1073
|
-
}
|
|
1074
|
-
if (/\s/u.test(character)) {
|
|
1075
|
-
if (started) {
|
|
1076
|
-
tokens.push(token);
|
|
1077
|
-
token = "";
|
|
1078
|
-
started = false;
|
|
1079
|
-
}
|
|
1080
|
-
continue;
|
|
1081
|
-
}
|
|
1082
|
-
token += character;
|
|
1083
|
-
started = true;
|
|
1084
|
-
}
|
|
1085
|
-
if (escaped || quote) {
|
|
1086
|
-
return undefined;
|
|
1087
|
-
}
|
|
1088
|
-
if (started) {
|
|
1089
|
-
tokens.push(token);
|
|
1090
|
-
}
|
|
1091
|
-
return tokens;
|
|
1092
|
-
}
|
|
1093
608
|
function tokenizeCommand(command) {
|
|
1094
609
|
const tokens = [];
|
|
1095
610
|
let token = "";
|