@co0ontty/wand 1.2.0 → 1.2.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/dist/claude-pty-bridge.js +3 -6
- package/dist/process-manager.js +19 -192
- package/dist/pty-text-utils.d.ts +2 -0
- package/dist/pty-text-utils.js +20 -0
- package/dist/resume-policy.d.ts +80 -0
- package/dist/resume-policy.js +178 -0
- package/dist/server-session-routes.d.ts +6 -0
- package/dist/server-session-routes.js +359 -0
- package/dist/server.js +5 -328
- package/dist/web-ui/content/scripts.js +91 -45
- package/dist/web-ui/content/styles.css +27 -18
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 2. Structured messages for chat view (parsed)
|
|
8
8
|
*/
|
|
9
9
|
import { EventEmitter } from "node:events";
|
|
10
|
-
import { stripAnsi, isNoiseLine, appendWindow, normalizePromptText } from "./pty-text-utils.js";
|
|
10
|
+
import { stripAnsi, isNoiseLine, appendWindow, normalizePromptText, hasExplicitConfirmSyntax, hasPermissionActionContext } from "./pty-text-utils.js";
|
|
11
11
|
// ── Constants ──
|
|
12
12
|
const OUTPUT_MAX_SIZE = 120000;
|
|
13
13
|
const SESSION_ID_WINDOW_SIZE = 16384;
|
|
@@ -431,11 +431,8 @@ export class ClaudePtyBridge extends EventEmitter {
|
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
433
|
isPermissionPromptDetected(normalized) {
|
|
434
|
-
return (
|
|
435
|
-
/\
|
|
436
|
-
/\bhaven't granted\b/i.test(normalized) ||
|
|
437
|
-
/\benter to confirm\b/i.test(normalized) ||
|
|
438
|
-
/\bwould you like to proceed\b/i.test(normalized));
|
|
434
|
+
return hasPermissionActionContext(normalized)
|
|
435
|
+
&& (hasExplicitConfirmSyntax(normalized) || /\bdo you want to\b/i.test(normalized) || /\bwould you like to\b/i.test(normalized));
|
|
439
436
|
}
|
|
440
437
|
extractPromptText(normalized) {
|
|
441
438
|
// Return a snippet around the permission prompt
|
package/dist/process-manager.js
CHANGED
|
@@ -8,7 +8,8 @@ import pty from "node-pty";
|
|
|
8
8
|
import { SessionLogger } from "./session-logger.js";
|
|
9
9
|
import { SessionLifecycleManager } from "./session-lifecycle.js";
|
|
10
10
|
import { ClaudePtyBridge } from "./claude-pty-bridge.js";
|
|
11
|
-
import { appendWindow, normalizePromptText } from "./pty-text-utils.js";
|
|
11
|
+
import { appendWindow, hasExplicitConfirmSyntax, hasPermissionActionContext, normalizePromptText } from "./pty-text-utils.js";
|
|
12
|
+
import { getResumeCommandSessionId, hasLiveProjectConversation, hasRealConversationMessages, hasStoredProjectConversation, shouldAllowResume, shouldDisplayResumeAction, } from "./resume-policy.js";
|
|
12
13
|
/** Check if the current process is running as root (UID 0). */
|
|
13
14
|
function isRunningAsRoot() {
|
|
14
15
|
return process.getuid?.() === 0 || process.geteuid?.() === 0;
|
|
@@ -33,34 +34,15 @@ const PROMPT_PATTERNS = [
|
|
|
33
34
|
/\bcontinue\?\s*(?:\((?:y|yes)\s*\/\s*(?:n|no)\))?/i,
|
|
34
35
|
/\bare you sure\??/i,
|
|
35
36
|
/\bdo you want to continue\??/i,
|
|
36
|
-
/\bdo you want to (?:create|write|delete|modify|execute)/i,
|
|
37
|
+
/\bdo you want to (?:create|write|delete|modify|execute)\b/i,
|
|
37
38
|
/\bconfirm(?:\s+execution|\s+changes|\s+action)?\??/i,
|
|
38
|
-
/\bproceed
|
|
39
|
+
/\bproceed\?\s*(?:\((?:y|yes)\s*\/\s*(?:n|no)\))?/i,
|
|
39
40
|
/\benter to confirm\b/i,
|
|
40
|
-
/\
|
|
41
|
-
/\bshall i\b/i,
|
|
42
|
-
/\bcan i\b/i,
|
|
43
|
-
/\bgrant\b.*\bpermission\b/i
|
|
41
|
+
/\bgrant\b.*\bpermission\b/i,
|
|
44
42
|
];
|
|
45
43
|
const REAL_CONVERSATION_MIN_LINES = 2;
|
|
46
|
-
const REAL_CONVERSATION_MIN_MESSAGES = 2;
|
|
47
44
|
const DISCOVERY_RECENT_WINDOW_MS = 10 * 60 * 1000;
|
|
48
45
|
const START_TIME_SKEW_MS = 30 * 1000;
|
|
49
|
-
const RESUME_COMMAND_ID_PATTERN = /(?:^|\s)--resume\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\s|$)/i;
|
|
50
|
-
function hasRealConversationMessages(messages) {
|
|
51
|
-
if (!messages || messages.length < REAL_CONVERSATION_MIN_MESSAGES) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
const hasUser = messages.some((turn) => turn.role === "user"
|
|
55
|
-
&& turn.content.some((block) => block.type === "text" && block.text.trim().length > 0));
|
|
56
|
-
const hasAssistant = messages.some((turn) => turn.role === "assistant"
|
|
57
|
-
&& turn.content.some((block) => block.type === "text" && block.text.trim().length > 0));
|
|
58
|
-
return hasUser && hasAssistant;
|
|
59
|
-
}
|
|
60
|
-
function getResumeCommandSessionId(command) {
|
|
61
|
-
const match = RESUME_COMMAND_ID_PATTERN.exec(command);
|
|
62
|
-
return match?.[1] ?? null;
|
|
63
|
-
}
|
|
64
46
|
function readClaudeProjectSessionDetails(filePath, id) {
|
|
65
47
|
try {
|
|
66
48
|
const stats = statSync(filePath);
|
|
@@ -107,173 +89,11 @@ function readClaudeProjectSessionDetails(filePath, id) {
|
|
|
107
89
|
return null;
|
|
108
90
|
}
|
|
109
91
|
}
|
|
110
|
-
function hasRuntimeConversationSignal(messages) {
|
|
111
|
-
if (!messages || messages.length === 0) {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
const hasUser = messages.some((turn) => turn.role === "user"
|
|
115
|
-
&& turn.content.some((block) => block.type === "text" && block.text.trim().length > 0));
|
|
116
|
-
const hasAssistant = messages.some((turn) => turn.role === "assistant");
|
|
117
|
-
return hasUser && hasAssistant;
|
|
118
|
-
}
|
|
119
|
-
function hasStoredConversationHistory(messages) {
|
|
120
|
-
return hasRealConversationMessages(messages);
|
|
121
|
-
}
|
|
122
|
-
function shouldBindClaudeSessionId(record) {
|
|
123
|
-
return hasRuntimeConversationSignal(record.messages);
|
|
124
|
-
}
|
|
125
|
-
function shouldAllowResume(record) {
|
|
126
|
-
return Boolean(record.claudeSessionId) && hasStoredConversationHistory(record.messages);
|
|
127
|
-
}
|
|
128
|
-
function shouldBackfillFromStoredHistory(record) {
|
|
129
|
-
return hasStoredConversationHistory(record.messages);
|
|
130
|
-
}
|
|
131
|
-
function shouldDisplayResumeAction(messages) {
|
|
132
|
-
return hasStoredConversationHistory(messages);
|
|
133
|
-
}
|
|
134
|
-
function shouldAutoResumeMessages(messages) {
|
|
135
|
-
return hasStoredConversationHistory(messages);
|
|
136
|
-
}
|
|
137
|
-
function shouldBackfillMessages(messages) {
|
|
138
|
-
return hasStoredConversationHistory(messages);
|
|
139
|
-
}
|
|
140
|
-
function shouldPromoteProjectSessionId(record) {
|
|
141
|
-
return shouldBindClaudeSessionId(record);
|
|
142
|
-
}
|
|
143
|
-
function shouldPromoteStoredSessionId(record) {
|
|
144
|
-
return shouldBackfillMessages(record.messages);
|
|
145
|
-
}
|
|
146
|
-
function shouldPromoteUiSessionId(messages) {
|
|
147
|
-
return shouldDisplayResumeAction(messages);
|
|
148
|
-
}
|
|
149
|
-
function shouldPromoteResumeSessionId(messages) {
|
|
150
|
-
return shouldAutoResumeMessages(messages);
|
|
151
|
-
}
|
|
152
|
-
function hasBindableConversation(messages) {
|
|
153
|
-
return shouldBindFromRuntimeMessages({ messages: messages ?? [] });
|
|
154
|
-
}
|
|
155
|
-
function hasBackfillableConversation(messages) {
|
|
156
|
-
return shouldBackfillMessages(messages);
|
|
157
|
-
}
|
|
158
|
-
function hasUiConversation(messages) {
|
|
159
|
-
return shouldPromoteUiSessionId(messages);
|
|
160
|
-
}
|
|
161
|
-
function hasResumeConversation(messages) {
|
|
162
|
-
return shouldPromoteResumeSessionId(messages);
|
|
163
|
-
}
|
|
164
|
-
function isRuntimeConversationReady(messages) {
|
|
165
|
-
return hasBindableConversation(messages);
|
|
166
|
-
}
|
|
167
|
-
function isStoredConversationReady(messages) {
|
|
168
|
-
return hasBackfillableConversation(messages);
|
|
169
|
-
}
|
|
170
|
-
function isResumeConversationReady(messages) {
|
|
171
|
-
return hasResumeConversation(messages);
|
|
172
|
-
}
|
|
173
|
-
function shouldBindFromRuntimeMessages(record) {
|
|
174
|
-
return isRuntimeConversationReady(record.messages);
|
|
175
|
-
}
|
|
176
|
-
function shouldAllowUiResume(messages) {
|
|
177
|
-
return hasUiConversation(messages);
|
|
178
|
-
}
|
|
179
|
-
function shouldPromoteResumeAction(record) {
|
|
180
|
-
return shouldAllowResume(record);
|
|
181
|
-
}
|
|
182
|
-
function shouldBackfillClaudeSessionIdFromDisk(record) {
|
|
183
|
-
return isStoredConversationReady(record.messages);
|
|
184
|
-
}
|
|
185
|
-
function shouldUseProjectCandidate(record) {
|
|
186
|
-
return shouldBindFromRuntimeMessages(record);
|
|
187
|
-
}
|
|
188
|
-
function shouldResumeProjectCandidate(record) {
|
|
189
|
-
return shouldPromoteResumeAction(record);
|
|
190
|
-
}
|
|
191
|
-
function shouldBackfillProjectCandidate(record) {
|
|
192
|
-
return shouldBackfillClaudeSessionIdFromDisk(record);
|
|
193
|
-
}
|
|
194
|
-
function hasMinimumRuntimeConversation(messages) {
|
|
195
|
-
return shouldBindFromRuntimeMessages({ messages: messages ?? [] });
|
|
196
|
-
}
|
|
197
|
-
function hasMinimumStoredConversation(messages) {
|
|
198
|
-
return shouldAllowUiResume(messages);
|
|
199
|
-
}
|
|
200
|
-
function hasMinimumResumeConversation(messages) {
|
|
201
|
-
return isResumeConversationReady(messages);
|
|
202
|
-
}
|
|
203
|
-
function hasMinimumBackfillConversation(messages) {
|
|
204
|
-
return isStoredConversationReady(messages);
|
|
205
|
-
}
|
|
206
|
-
function hasProjectConversationSignal(messages) {
|
|
207
|
-
return hasMinimumRuntimeConversation(messages);
|
|
208
|
-
}
|
|
209
|
-
function hasStoredProjectConversationSignal(messages) {
|
|
210
|
-
return hasMinimumBackfillConversation(messages);
|
|
211
|
-
}
|
|
212
|
-
function hasUiProjectConversationSignal(messages) {
|
|
213
|
-
return hasMinimumStoredConversation(messages);
|
|
214
|
-
}
|
|
215
|
-
function hasResumeProjectConversationSignal(messages) {
|
|
216
|
-
return hasMinimumResumeConversation(messages);
|
|
217
|
-
}
|
|
218
|
-
function canBindFromProjectConversation(messages) {
|
|
219
|
-
return hasProjectConversationSignal(messages);
|
|
220
|
-
}
|
|
221
|
-
function canBackfillFromProjectConversation(messages) {
|
|
222
|
-
return hasStoredProjectConversationSignal(messages);
|
|
223
|
-
}
|
|
224
|
-
function canShowUiProjectConversation(messages) {
|
|
225
|
-
return hasUiProjectConversationSignal(messages);
|
|
226
|
-
}
|
|
227
|
-
function canResumeProjectConversation(messages) {
|
|
228
|
-
return hasResumeProjectConversationSignal(messages);
|
|
229
|
-
}
|
|
230
|
-
function shouldUseRuntimeProjectConversation(messages) {
|
|
231
|
-
return canBindFromProjectConversation(messages);
|
|
232
|
-
}
|
|
233
|
-
function shouldUseStoredProjectConversation(messages) {
|
|
234
|
-
return canBackfillFromProjectConversation(messages);
|
|
235
|
-
}
|
|
236
|
-
function shouldUseUiProjectConversation(messages) {
|
|
237
|
-
return canShowUiProjectConversation(messages);
|
|
238
|
-
}
|
|
239
|
-
function shouldUseResumeProjectConversation(messages) {
|
|
240
|
-
return canResumeProjectConversation(messages);
|
|
241
|
-
}
|
|
242
|
-
function hasProjectConversationForBinding(messages) {
|
|
243
|
-
return shouldUseRuntimeProjectConversation(messages);
|
|
244
|
-
}
|
|
245
|
-
function hasProjectConversationForBackfill(messages) {
|
|
246
|
-
return shouldUseStoredProjectConversation(messages);
|
|
247
|
-
}
|
|
248
|
-
function hasProjectConversationForUi(messages) {
|
|
249
|
-
return shouldUseUiProjectConversation(messages);
|
|
250
|
-
}
|
|
251
|
-
function hasProjectConversationForResume(messages) {
|
|
252
|
-
return shouldUseResumeProjectConversation(messages);
|
|
253
|
-
}
|
|
254
|
-
function isBindableProjectConversation(messages) {
|
|
255
|
-
return hasProjectConversationForBinding(messages);
|
|
256
|
-
}
|
|
257
|
-
function isBackfillableProjectConversation(messages) {
|
|
258
|
-
return hasProjectConversationForBackfill(messages);
|
|
259
|
-
}
|
|
260
|
-
function isUiProjectConversation(messages) {
|
|
261
|
-
return hasProjectConversationForUi(messages);
|
|
262
|
-
}
|
|
263
|
-
function isResumeProjectConversation(messages) {
|
|
264
|
-
return hasProjectConversationForResume(messages);
|
|
265
|
-
}
|
|
266
|
-
function hasLiveProjectConversation(messages) {
|
|
267
|
-
return isBindableProjectConversation(messages);
|
|
268
|
-
}
|
|
269
|
-
function hasStoredProjectConversation(messages) {
|
|
270
|
-
return isBackfillableProjectConversation(messages);
|
|
271
|
-
}
|
|
272
92
|
function hasVisibleProjectConversation(messages) {
|
|
273
|
-
return
|
|
93
|
+
return shouldDisplayResumeAction(messages);
|
|
274
94
|
}
|
|
275
95
|
function hasRecoverableProjectConversation(messages) {
|
|
276
|
-
return
|
|
96
|
+
return shouldAllowResume({ claudeSessionId: "resume-candidate", messages });
|
|
277
97
|
}
|
|
278
98
|
function shouldBindLiveProjectSessionId(messages) {
|
|
279
99
|
return hasLiveProjectConversation(messages);
|
|
@@ -1625,15 +1445,22 @@ export class ProcessManager extends EventEmitter {
|
|
|
1625
1445
|
const trustFolderPrompt = /\byes,\s*i\s*trust\s*this\s*folder\b/i.test(normalized) &&
|
|
1626
1446
|
/\benter to confirm\b/i.test(normalized);
|
|
1627
1447
|
const claudeConfirmPrompt = /\bdo you want to\b/i.test(normalized) &&
|
|
1628
|
-
|
|
1448
|
+
hasExplicitConfirmSyntax(normalized) &&
|
|
1449
|
+
hasPermissionActionContext(normalized);
|
|
1629
1450
|
// Check for Claude's tool permission prompt patterns
|
|
1630
1451
|
const toolPermissionPrompt = /\bdo you want to\b/i.test(normalized) &&
|
|
1631
|
-
/\(yes\b/i.test(normalized)
|
|
1452
|
+
/\(yes\b/i.test(normalized) &&
|
|
1453
|
+
hasPermissionActionContext(normalized);
|
|
1632
1454
|
// Reduced cooldown for faster response
|
|
1633
1455
|
if (now - record.lastAutoConfirmAt < 500) {
|
|
1634
1456
|
return;
|
|
1635
1457
|
}
|
|
1636
|
-
const shouldConfirm = trustFolderPrompt
|
|
1458
|
+
const shouldConfirm = trustFolderPrompt
|
|
1459
|
+
|| claudeConfirmPrompt
|
|
1460
|
+
|| toolPermissionPrompt
|
|
1461
|
+
|| (hasExplicitConfirmSyntax(normalized)
|
|
1462
|
+
&& hasPermissionActionContext(normalized)
|
|
1463
|
+
&& PROMPT_PATTERNS.some((pattern) => pattern.test(normalized)));
|
|
1637
1464
|
if (shouldConfirm) {
|
|
1638
1465
|
record.lastAutoConfirmAt = now;
|
|
1639
1466
|
process.stderr.write(`[wand] Auto-confirming prompt for ${record.mode} mode\n`);
|
|
@@ -1756,7 +1583,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
1756
1583
|
return ["-lc", command];
|
|
1757
1584
|
}
|
|
1758
1585
|
shouldAutoApprovePermissions(command, mode) {
|
|
1759
|
-
if (!/^claude(?:\s|$)/.test(command)) {
|
|
1586
|
+
if (!/^(?:claude|npx\s+claude|[^\s]+\/claude)(?:\s|$)/.test(command)) {
|
|
1760
1587
|
return false;
|
|
1761
1588
|
}
|
|
1762
1589
|
// Root mode: always auto-approve (Claude CLI refuses --permission-mode bypassPermissions under root)
|
|
@@ -1774,7 +1601,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
1774
1601
|
processCommandForMode(command, mode) {
|
|
1775
1602
|
// In managed mode, append a system prompt instructing Claude to act autonomously
|
|
1776
1603
|
// without asking the user for confirmation, since the user may not be monitoring.
|
|
1777
|
-
if (mode === "managed" && /^claude(?:\s|$)/.test(command)) {
|
|
1604
|
+
if (mode === "managed" && /^(?:claude|npx\s+claude|[^\s]+\/claude)(?:\s|$)/.test(command)) {
|
|
1778
1605
|
const autonomousPrompt = "You are running in a fully managed, autonomous mode. The user may not be available to respond to questions or confirmations in a timely manner. You MUST make all decisions independently — choose the best approach yourself instead of asking the user for preferences, confirmations, or clarifications. If multiple approaches are viable, pick the one you judge most appropriate and proceed. Never block on user input unless the task is fundamentally ambiguous and cannot be reasonably inferred. Be decisive and self-directed.";
|
|
1779
1606
|
// Escape single quotes for shell safety
|
|
1780
1607
|
const escaped = autonomousPrompt.replace(/'/g, "'\\''");
|
package/dist/pty-text-utils.d.ts
CHANGED
|
@@ -9,5 +9,7 @@ export declare function stripAnsi(text: string): string;
|
|
|
9
9
|
export declare function isNoiseLine(line: string): boolean;
|
|
10
10
|
/** Append text to a windowed buffer, trimming from start if over max size. */
|
|
11
11
|
export declare function appendWindow(buffer: string, chunk: string, maxSize: number): string;
|
|
12
|
+
export declare function hasExplicitConfirmSyntax(normalized: string): boolean;
|
|
13
|
+
export declare function hasPermissionActionContext(normalized: string): boolean;
|
|
12
14
|
/** Normalize prompt text for permission detection (strip ANSI, collapse whitespace). */
|
|
13
15
|
export declare function normalizePromptText(value: string): string;
|
package/dist/pty-text-utils.js
CHANGED
|
@@ -72,6 +72,26 @@ export function appendWindow(buffer, chunk, maxSize) {
|
|
|
72
72
|
const next = buffer + chunk;
|
|
73
73
|
return next.length > maxSize ? next.slice(-maxSize) : next;
|
|
74
74
|
}
|
|
75
|
+
const EXPLICIT_CONFIRM_PATTERNS = [
|
|
76
|
+
/(?:^|\b)(?:press\s+)?(?:y|yes)\s*(?:\/|\bor\b)\s*(?:n|no)(?:\b|$)/i,
|
|
77
|
+
/\[(?:y|yes)\s*\/\s*(?:n|no)\]/i,
|
|
78
|
+
/\((?:y|yes)\s*\/\s*(?:n|no)\)/i,
|
|
79
|
+
/\((?:y|yes)\s*\/\s*(?:n|no)\s*\/\s*always\)/i,
|
|
80
|
+
/\byes\b.*\bno\b/i,
|
|
81
|
+
/\benter to confirm\b/i,
|
|
82
|
+
];
|
|
83
|
+
const PERMISSION_ACTION_PATTERNS = [
|
|
84
|
+
/\bgrant\b.*\bpermission\b/i,
|
|
85
|
+
/\bhaven't granted\b/i,
|
|
86
|
+
/\byes,\s*i\s*trust\s*this\s*folder\b/i,
|
|
87
|
+
/\b(?:write|modify|delete|create|execute|run|bash|command|network|web|fetch|permission|allow)\b/i,
|
|
88
|
+
];
|
|
89
|
+
export function hasExplicitConfirmSyntax(normalized) {
|
|
90
|
+
return EXPLICIT_CONFIRM_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
91
|
+
}
|
|
92
|
+
export function hasPermissionActionContext(normalized) {
|
|
93
|
+
return PERMISSION_ACTION_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
94
|
+
}
|
|
75
95
|
/** Normalize prompt text for permission detection (strip ANSI, collapse whitespace). */
|
|
76
96
|
export function normalizePromptText(value) {
|
|
77
97
|
return value
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ConversationTurn } from "./types.js";
|
|
2
|
+
export declare function hasRealConversationMessages(messages: ConversationTurn[] | undefined): boolean;
|
|
3
|
+
export declare function hasRuntimeConversationSignal(messages: ConversationTurn[] | undefined): boolean;
|
|
4
|
+
export declare function hasStoredConversationHistory(messages: ConversationTurn[] | undefined): boolean;
|
|
5
|
+
export declare function shouldBindClaudeSessionId(record: {
|
|
6
|
+
messages: ConversationTurn[] | undefined;
|
|
7
|
+
}): boolean;
|
|
8
|
+
export declare function shouldAllowResume(record: {
|
|
9
|
+
claudeSessionId: string | null | undefined;
|
|
10
|
+
messages: ConversationTurn[] | undefined;
|
|
11
|
+
}): boolean;
|
|
12
|
+
export declare function shouldBackfillFromStoredHistory(record: {
|
|
13
|
+
messages: ConversationTurn[] | undefined;
|
|
14
|
+
}): boolean;
|
|
15
|
+
export declare function shouldDisplayResumeAction(messages: ConversationTurn[] | undefined): boolean;
|
|
16
|
+
export declare function shouldAutoResumeMessages(messages: ConversationTurn[] | undefined): boolean;
|
|
17
|
+
export declare function shouldBackfillMessages(messages: ConversationTurn[] | undefined): boolean;
|
|
18
|
+
export declare function shouldPromoteProjectSessionId(record: {
|
|
19
|
+
messages: ConversationTurn[] | undefined;
|
|
20
|
+
}): boolean;
|
|
21
|
+
export declare function shouldPromoteStoredSessionId(record: {
|
|
22
|
+
messages: ConversationTurn[] | undefined;
|
|
23
|
+
}): boolean;
|
|
24
|
+
export declare function shouldPromoteUiSessionId(messages: ConversationTurn[] | undefined): boolean;
|
|
25
|
+
export declare function shouldPromoteResumeSessionId(messages: ConversationTurn[] | undefined): boolean;
|
|
26
|
+
export declare function hasBindableConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
27
|
+
export declare function hasBackfillableConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
28
|
+
export declare function hasUiConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
29
|
+
export declare function hasResumeConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
30
|
+
export declare function isRuntimeConversationReady(messages: ConversationTurn[] | undefined): boolean;
|
|
31
|
+
export declare function isStoredConversationReady(messages: ConversationTurn[] | undefined): boolean;
|
|
32
|
+
export declare function isResumeConversationReady(messages: ConversationTurn[] | undefined): boolean;
|
|
33
|
+
export declare function shouldBindFromRuntimeMessages(record: {
|
|
34
|
+
messages: ConversationTurn[] | undefined;
|
|
35
|
+
}): boolean;
|
|
36
|
+
export declare function shouldAllowUiResume(messages: ConversationTurn[] | undefined): boolean;
|
|
37
|
+
export declare function shouldPromoteResumeAction(record: {
|
|
38
|
+
claudeSessionId: string | null | undefined;
|
|
39
|
+
messages: ConversationTurn[] | undefined;
|
|
40
|
+
}): boolean;
|
|
41
|
+
export declare function shouldBackfillClaudeSessionIdFromDisk(record: {
|
|
42
|
+
messages: ConversationTurn[] | undefined;
|
|
43
|
+
}): boolean;
|
|
44
|
+
export declare function shouldUseProjectCandidate(record: {
|
|
45
|
+
messages: ConversationTurn[] | undefined;
|
|
46
|
+
}): boolean;
|
|
47
|
+
export declare function shouldResumeProjectCandidate(record: {
|
|
48
|
+
claudeSessionId: string | null | undefined;
|
|
49
|
+
messages: ConversationTurn[] | undefined;
|
|
50
|
+
}): boolean;
|
|
51
|
+
export declare function shouldBackfillProjectCandidate(record: {
|
|
52
|
+
messages: ConversationTurn[] | undefined;
|
|
53
|
+
}): boolean;
|
|
54
|
+
export declare function hasMinimumRuntimeConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
55
|
+
export declare function hasMinimumStoredConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
56
|
+
export declare function hasMinimumResumeConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
57
|
+
export declare function hasMinimumBackfillConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
58
|
+
export declare function hasProjectConversationSignal(messages: ConversationTurn[] | undefined): boolean;
|
|
59
|
+
export declare function hasStoredProjectConversationSignal(messages: ConversationTurn[] | undefined): boolean;
|
|
60
|
+
export declare function hasUiProjectConversationSignal(messages: ConversationTurn[] | undefined): boolean;
|
|
61
|
+
export declare function hasResumeProjectConversationSignal(messages: ConversationTurn[] | undefined): boolean;
|
|
62
|
+
export declare function canBindFromProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
63
|
+
export declare function canBackfillFromProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
64
|
+
export declare function canShowUiProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
65
|
+
export declare function canResumeProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
66
|
+
export declare function shouldUseRuntimeProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
67
|
+
export declare function shouldUseStoredProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
68
|
+
export declare function shouldUseUiProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
69
|
+
export declare function shouldUseResumeProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
70
|
+
export declare function hasProjectConversationForBinding(messages: ConversationTurn[] | undefined): boolean;
|
|
71
|
+
export declare function hasProjectConversationForBackfill(messages: ConversationTurn[] | undefined): boolean;
|
|
72
|
+
export declare function hasProjectConversationForUi(messages: ConversationTurn[] | undefined): boolean;
|
|
73
|
+
export declare function hasProjectConversationForResume(messages: ConversationTurn[] | undefined): boolean;
|
|
74
|
+
export declare function isBindableProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
75
|
+
export declare function isBackfillableProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
76
|
+
export declare function isUiProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
77
|
+
export declare function isResumeProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
78
|
+
export declare function hasLiveProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
79
|
+
export declare function hasStoredProjectConversation(messages: ConversationTurn[] | undefined): boolean;
|
|
80
|
+
export declare function getResumeCommandSessionId(command: string): string | null;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
const REAL_CONVERSATION_MIN_MESSAGES = 2;
|
|
2
|
+
const RESUME_COMMAND_ID_PATTERN = /(?:^|\s)--resume\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\s|$)/i;
|
|
3
|
+
export function hasRealConversationMessages(messages) {
|
|
4
|
+
if (!messages || messages.length < REAL_CONVERSATION_MIN_MESSAGES) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
const hasUser = messages.some((turn) => turn.role === "user"
|
|
8
|
+
&& turn.content.some((block) => block.type === "text" && block.text.trim().length > 0));
|
|
9
|
+
const hasAssistant = messages.some((turn) => turn.role === "assistant"
|
|
10
|
+
&& turn.content.some((block) => block.type === "text" && block.text.trim().length > 0));
|
|
11
|
+
return hasUser && hasAssistant;
|
|
12
|
+
}
|
|
13
|
+
export function hasRuntimeConversationSignal(messages) {
|
|
14
|
+
if (!messages || messages.length === 0) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const hasUser = messages.some((turn) => turn.role === "user"
|
|
18
|
+
&& turn.content.some((block) => block.type === "text" && block.text.trim().length > 0));
|
|
19
|
+
const hasAssistant = messages.some((turn) => turn.role === "assistant");
|
|
20
|
+
return hasUser && hasAssistant;
|
|
21
|
+
}
|
|
22
|
+
export function hasStoredConversationHistory(messages) {
|
|
23
|
+
return hasRealConversationMessages(messages);
|
|
24
|
+
}
|
|
25
|
+
export function shouldBindClaudeSessionId(record) {
|
|
26
|
+
return hasRuntimeConversationSignal(record.messages);
|
|
27
|
+
}
|
|
28
|
+
export function shouldAllowResume(record) {
|
|
29
|
+
return Boolean(record.claudeSessionId) && hasStoredConversationHistory(record.messages);
|
|
30
|
+
}
|
|
31
|
+
export function shouldBackfillFromStoredHistory(record) {
|
|
32
|
+
return hasStoredConversationHistory(record.messages);
|
|
33
|
+
}
|
|
34
|
+
export function shouldDisplayResumeAction(messages) {
|
|
35
|
+
return hasStoredConversationHistory(messages);
|
|
36
|
+
}
|
|
37
|
+
export function shouldAutoResumeMessages(messages) {
|
|
38
|
+
return hasStoredConversationHistory(messages);
|
|
39
|
+
}
|
|
40
|
+
export function shouldBackfillMessages(messages) {
|
|
41
|
+
return hasStoredConversationHistory(messages);
|
|
42
|
+
}
|
|
43
|
+
export function shouldPromoteProjectSessionId(record) {
|
|
44
|
+
return shouldBindClaudeSessionId(record);
|
|
45
|
+
}
|
|
46
|
+
export function shouldPromoteStoredSessionId(record) {
|
|
47
|
+
return shouldBackfillMessages(record.messages);
|
|
48
|
+
}
|
|
49
|
+
export function shouldPromoteUiSessionId(messages) {
|
|
50
|
+
return shouldDisplayResumeAction(messages);
|
|
51
|
+
}
|
|
52
|
+
export function shouldPromoteResumeSessionId(messages) {
|
|
53
|
+
return shouldAutoResumeMessages(messages);
|
|
54
|
+
}
|
|
55
|
+
export function hasBindableConversation(messages) {
|
|
56
|
+
return shouldBindFromRuntimeMessages({ messages: messages ?? [] });
|
|
57
|
+
}
|
|
58
|
+
export function hasBackfillableConversation(messages) {
|
|
59
|
+
return shouldBackfillMessages(messages);
|
|
60
|
+
}
|
|
61
|
+
export function hasUiConversation(messages) {
|
|
62
|
+
return shouldPromoteUiSessionId(messages);
|
|
63
|
+
}
|
|
64
|
+
export function hasResumeConversation(messages) {
|
|
65
|
+
return shouldPromoteResumeSessionId(messages);
|
|
66
|
+
}
|
|
67
|
+
export function isRuntimeConversationReady(messages) {
|
|
68
|
+
return hasBindableConversation(messages);
|
|
69
|
+
}
|
|
70
|
+
export function isStoredConversationReady(messages) {
|
|
71
|
+
return hasBackfillableConversation(messages);
|
|
72
|
+
}
|
|
73
|
+
export function isResumeConversationReady(messages) {
|
|
74
|
+
return hasResumeConversation(messages);
|
|
75
|
+
}
|
|
76
|
+
export function shouldBindFromRuntimeMessages(record) {
|
|
77
|
+
return isRuntimeConversationReady(record.messages);
|
|
78
|
+
}
|
|
79
|
+
export function shouldAllowUiResume(messages) {
|
|
80
|
+
return hasUiConversation(messages);
|
|
81
|
+
}
|
|
82
|
+
export function shouldPromoteResumeAction(record) {
|
|
83
|
+
return shouldAllowResume(record);
|
|
84
|
+
}
|
|
85
|
+
export function shouldBackfillClaudeSessionIdFromDisk(record) {
|
|
86
|
+
return isStoredConversationReady(record.messages);
|
|
87
|
+
}
|
|
88
|
+
export function shouldUseProjectCandidate(record) {
|
|
89
|
+
return shouldBindFromRuntimeMessages(record);
|
|
90
|
+
}
|
|
91
|
+
export function shouldResumeProjectCandidate(record) {
|
|
92
|
+
return shouldPromoteResumeAction(record);
|
|
93
|
+
}
|
|
94
|
+
export function shouldBackfillProjectCandidate(record) {
|
|
95
|
+
return shouldBackfillClaudeSessionIdFromDisk(record);
|
|
96
|
+
}
|
|
97
|
+
export function hasMinimumRuntimeConversation(messages) {
|
|
98
|
+
return shouldBindFromRuntimeMessages({ messages: messages ?? [] });
|
|
99
|
+
}
|
|
100
|
+
export function hasMinimumStoredConversation(messages) {
|
|
101
|
+
return shouldAllowUiResume(messages);
|
|
102
|
+
}
|
|
103
|
+
export function hasMinimumResumeConversation(messages) {
|
|
104
|
+
return isResumeConversationReady(messages);
|
|
105
|
+
}
|
|
106
|
+
export function hasMinimumBackfillConversation(messages) {
|
|
107
|
+
return isStoredConversationReady(messages);
|
|
108
|
+
}
|
|
109
|
+
export function hasProjectConversationSignal(messages) {
|
|
110
|
+
return hasMinimumRuntimeConversation(messages);
|
|
111
|
+
}
|
|
112
|
+
export function hasStoredProjectConversationSignal(messages) {
|
|
113
|
+
return hasMinimumBackfillConversation(messages);
|
|
114
|
+
}
|
|
115
|
+
export function hasUiProjectConversationSignal(messages) {
|
|
116
|
+
return hasMinimumStoredConversation(messages);
|
|
117
|
+
}
|
|
118
|
+
export function hasResumeProjectConversationSignal(messages) {
|
|
119
|
+
return hasMinimumResumeConversation(messages);
|
|
120
|
+
}
|
|
121
|
+
export function canBindFromProjectConversation(messages) {
|
|
122
|
+
return hasProjectConversationSignal(messages);
|
|
123
|
+
}
|
|
124
|
+
export function canBackfillFromProjectConversation(messages) {
|
|
125
|
+
return hasStoredProjectConversationSignal(messages);
|
|
126
|
+
}
|
|
127
|
+
export function canShowUiProjectConversation(messages) {
|
|
128
|
+
return hasUiProjectConversationSignal(messages);
|
|
129
|
+
}
|
|
130
|
+
export function canResumeProjectConversation(messages) {
|
|
131
|
+
return hasResumeProjectConversationSignal(messages);
|
|
132
|
+
}
|
|
133
|
+
export function shouldUseRuntimeProjectConversation(messages) {
|
|
134
|
+
return canBindFromProjectConversation(messages);
|
|
135
|
+
}
|
|
136
|
+
export function shouldUseStoredProjectConversation(messages) {
|
|
137
|
+
return canBackfillFromProjectConversation(messages);
|
|
138
|
+
}
|
|
139
|
+
export function shouldUseUiProjectConversation(messages) {
|
|
140
|
+
return canShowUiProjectConversation(messages);
|
|
141
|
+
}
|
|
142
|
+
export function shouldUseResumeProjectConversation(messages) {
|
|
143
|
+
return canResumeProjectConversation(messages);
|
|
144
|
+
}
|
|
145
|
+
export function hasProjectConversationForBinding(messages) {
|
|
146
|
+
return shouldUseRuntimeProjectConversation(messages);
|
|
147
|
+
}
|
|
148
|
+
export function hasProjectConversationForBackfill(messages) {
|
|
149
|
+
return shouldUseStoredProjectConversation(messages);
|
|
150
|
+
}
|
|
151
|
+
export function hasProjectConversationForUi(messages) {
|
|
152
|
+
return shouldUseUiProjectConversation(messages);
|
|
153
|
+
}
|
|
154
|
+
export function hasProjectConversationForResume(messages) {
|
|
155
|
+
return shouldUseResumeProjectConversation(messages);
|
|
156
|
+
}
|
|
157
|
+
export function isBindableProjectConversation(messages) {
|
|
158
|
+
return hasProjectConversationForBinding(messages);
|
|
159
|
+
}
|
|
160
|
+
export function isBackfillableProjectConversation(messages) {
|
|
161
|
+
return hasProjectConversationForBackfill(messages);
|
|
162
|
+
}
|
|
163
|
+
export function isUiProjectConversation(messages) {
|
|
164
|
+
return hasProjectConversationForUi(messages);
|
|
165
|
+
}
|
|
166
|
+
export function isResumeProjectConversation(messages) {
|
|
167
|
+
return hasProjectConversationForResume(messages);
|
|
168
|
+
}
|
|
169
|
+
export function hasLiveProjectConversation(messages) {
|
|
170
|
+
return isBindableProjectConversation(messages);
|
|
171
|
+
}
|
|
172
|
+
export function hasStoredProjectConversation(messages) {
|
|
173
|
+
return isBackfillableProjectConversation(messages);
|
|
174
|
+
}
|
|
175
|
+
export function getResumeCommandSessionId(command) {
|
|
176
|
+
const match = RESUME_COMMAND_ID_PATTERN.exec(command);
|
|
177
|
+
return match?.[1] ?? null;
|
|
178
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Express } from "express";
|
|
2
|
+
import { ProcessManager } from "./process-manager.js";
|
|
3
|
+
import { WandStorage } from "./storage.js";
|
|
4
|
+
import { ExecutionMode } from "./types.js";
|
|
5
|
+
export declare function registerSessionRoutes(app: Express, processes: ProcessManager, storage: WandStorage, defaultMode: ExecutionMode): void;
|
|
6
|
+
export declare function registerClaudeHistoryRoutes(app: Express, processes: ProcessManager, storage: WandStorage): void;
|