@adhdev/daemon-core 0.9.82-rc.185 → 0.9.82-rc.187
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/commands/mesh-coordinator.d.ts +13 -0
- package/dist/config/chat-history.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +21088 -20535
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6601 -6047
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/beads-db.d.ts +1 -0
- package/dist/mesh/mesh-events.d.ts +46 -1
- package/dist/mesh/mesh-work-queue.d.ts +1 -0
- package/dist/providers/contracts.d.ts +1 -1
- package/dist/providers/native-history/dispatcher.d.ts +2 -0
- package/dist/providers/spec/cli-adapter.d.ts +1 -0
- package/dist/providers/spec/native-history-executor.d.ts +2 -0
- package/package.json +1 -1
- package/src/chat/subscription-updates.ts +6 -0
- package/src/commands/chat-commands.ts +182 -18
- package/src/commands/cli-manager.ts +4 -0
- package/src/commands/mesh-coordinator.ts +110 -5
- package/src/commands/router.ts +111 -17
- package/src/config/chat-history.ts +4 -0
- package/src/index.ts +1 -1
- package/src/mesh/beads-db.ts +4 -0
- package/src/mesh/mesh-events.ts +264 -4
- package/src/mesh/mesh-work-queue.ts +4 -0
- package/src/providers/cli-provider-instance.ts +12 -4
- package/src/providers/contracts.ts +1 -1
- package/src/providers/native-history/dispatcher.ts +126 -17
- package/src/providers/provider-loader.ts +4 -7
- package/src/providers/spec/cli-adapter.ts +32 -5
- package/src/providers/spec/evaluator.ts +11 -1
- package/src/providers/spec/native-history-executor.ts +93 -27
- package/src/providers/types/interactive-prompt.ts +13 -1
|
@@ -36,6 +36,14 @@ import {
|
|
|
36
36
|
type InteractivePromptResponse,
|
|
37
37
|
} from '../types/interactive-prompt.js';
|
|
38
38
|
|
|
39
|
+
function stripAnsi(text: string): string {
|
|
40
|
+
// eslint-disable-next-line no-control-regex
|
|
41
|
+
return String(text || '')
|
|
42
|
+
.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '')
|
|
43
|
+
.replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, '')
|
|
44
|
+
.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
export class SpecCliAdapter implements CliAdapter {
|
|
40
48
|
readonly cliType: string;
|
|
41
49
|
readonly cliName: string;
|
|
@@ -129,15 +137,16 @@ export class SpecCliAdapter implements CliAdapter {
|
|
|
129
137
|
}
|
|
130
138
|
|
|
131
139
|
getStatus(): CliAdapterStatus {
|
|
132
|
-
|
|
133
|
-
if (
|
|
140
|
+
const sessionFields = this.providerSessionId ? { providerSessionId: this.providerSessionId } : {};
|
|
141
|
+
if (this.exited) return { status: 'stopped', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
142
|
+
if (!this.spawned) return { status: 'starting', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
134
143
|
|
|
135
144
|
// Refresh native history lazily — the watch_path is cheap to stat,
|
|
136
145
|
// but parsing a full session.jsonl every call would be wasteful.
|
|
137
146
|
this.maybeRefreshNativeHistory();
|
|
138
147
|
|
|
139
148
|
const state = this.latestState;
|
|
140
|
-
if (!state) return { status: 'starting', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt };
|
|
149
|
+
if (!state) return { status: 'starting', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
141
150
|
|
|
142
151
|
const modal = this.latestModal;
|
|
143
152
|
const lc = state.id.toLowerCase();
|
|
@@ -150,12 +159,13 @@ export class SpecCliAdapter implements CliAdapter {
|
|
|
150
159
|
buttons: modal.buttons.map(b => b.label),
|
|
151
160
|
},
|
|
152
161
|
activeInteractivePrompt: this.activeInteractivePrompt,
|
|
162
|
+
...sessionFields,
|
|
153
163
|
};
|
|
154
164
|
}
|
|
155
165
|
if (lc === 'busy' || lc === 'generating') {
|
|
156
|
-
return { status: 'generating', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt };
|
|
166
|
+
return { status: 'generating', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
157
167
|
}
|
|
158
|
-
return { status: 'idle', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt };
|
|
168
|
+
return { status: 'idle', messages: [], activeModal: null, activeInteractivePrompt: this.activeInteractivePrompt, ...sessionFields };
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
private maybeRefreshNativeHistory(): void {
|
|
@@ -165,10 +175,13 @@ export class SpecCliAdapter implements CliAdapter {
|
|
|
165
175
|
}
|
|
166
176
|
|
|
167
177
|
getScriptParsedStatus(): unknown {
|
|
178
|
+
const providerSessionId = this.extractProviderSessionIdFromScreen();
|
|
179
|
+
if (providerSessionId) this.providerSessionId = providerSessionId;
|
|
168
180
|
const status = this.getStatus();
|
|
169
181
|
return {
|
|
170
182
|
...status,
|
|
171
183
|
messages: this.readClaudeScreenAssistantMessages(),
|
|
184
|
+
...(this.providerSessionId ? { providerSessionId: this.providerSessionId } : {}),
|
|
172
185
|
};
|
|
173
186
|
}
|
|
174
187
|
|
|
@@ -345,6 +358,7 @@ export class SpecCliAdapter implements CliAdapter {
|
|
|
345
358
|
displayName: this.spec.name,
|
|
346
359
|
spawnedAtMs: this.spawnedAtMs,
|
|
347
360
|
spawnedEnv: this.spawnedEnv,
|
|
361
|
+
...(this.providerSessionId ? { providerSessionId: this.providerSessionId } : {}),
|
|
348
362
|
};
|
|
349
363
|
}
|
|
350
364
|
updateRuntimeMeta(meta?: Record<string, unknown>): void {
|
|
@@ -419,6 +433,19 @@ export class SpecCliAdapter implements CliAdapter {
|
|
|
419
433
|
}
|
|
420
434
|
}
|
|
421
435
|
|
|
436
|
+
private extractProviderSessionIdFromScreen(): string | undefined {
|
|
437
|
+
if (this.cliType !== 'codex-cli') return this.providerSessionId;
|
|
438
|
+
let screenText = '';
|
|
439
|
+
try {
|
|
440
|
+
screenText = this.driver.snapshot();
|
|
441
|
+
} catch {
|
|
442
|
+
return this.providerSessionId;
|
|
443
|
+
}
|
|
444
|
+
const clean = stripAnsi(screenText);
|
|
445
|
+
const match = clean.match(/(?:gpt-|o\d|codex-)[^\n·]*·[^\n·]*·\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
446
|
+
return match?.[1] || this.providerSessionId;
|
|
447
|
+
}
|
|
448
|
+
|
|
422
449
|
private readClaudeScreenAssistantMessages(): ChatMessage[] {
|
|
423
450
|
if (this.cliType !== 'claude-cli') return [];
|
|
424
451
|
let screenText = '';
|
|
@@ -231,8 +231,18 @@ export function evaluate(spec: CliSpec, screenText: string): SpecEvaluation {
|
|
|
231
231
|
for (const st of spec.states) {
|
|
232
232
|
const { matched, title } = matchState(st, sections, screenText, trace);
|
|
233
233
|
if (!matched) continue;
|
|
234
|
+
const extractedModal = extractModal(st, sections, screenText, title, trace);
|
|
235
|
+
// If the state declares modal_buttons but extraction failed (button
|
|
236
|
+
// count below min_count, or text-was-mistaken-for-modal), do not
|
|
237
|
+
// promote the state. Otherwise we would surface a phantom approval
|
|
238
|
+
// built from arbitrary screen text — see claude-cli numbered-list
|
|
239
|
+
// false-positive on 2026-06-07.
|
|
240
|
+
if (st.modal_buttons && !extractedModal) {
|
|
241
|
+
trace.push({ kind: 'state_skip', text: `state[${st.id}] matched but modal_buttons extraction failed — not promoting` });
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
234
244
|
activeState = { id: st.id, label: st.label, title };
|
|
235
|
-
modal =
|
|
245
|
+
modal = extractedModal;
|
|
236
246
|
break;
|
|
237
247
|
}
|
|
238
248
|
|
|
@@ -58,6 +58,7 @@ export interface NativeHistoryMessage {
|
|
|
58
58
|
content: string;
|
|
59
59
|
receivedAt: number;
|
|
60
60
|
kind?: string;
|
|
61
|
+
workspace?: string;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
export interface NativeHistoryResult {
|
|
@@ -66,6 +67,7 @@ export interface NativeHistoryResult {
|
|
|
66
67
|
sourcePath: string;
|
|
67
68
|
sourceMtimeMs: number;
|
|
68
69
|
nativeHistoryCoverage?: 'full' | 'partial' | 'best-effort';
|
|
70
|
+
workspace?: string;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
const UUID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
@@ -87,6 +89,7 @@ function executeJsonl(src: NativeHistoryJsonlSource, input: NativeHistoryInput):
|
|
|
87
89
|
|
|
88
90
|
const windowMs = typeof src.recent_window_ms === 'number' ? src.recent_window_ms : 5 * 60_000;
|
|
89
91
|
const filePat = src.file_pattern ? globToRegex(src.file_pattern) : /.*\.jsonl$/;
|
|
92
|
+
const requestedSessionId = readRequestedSessionId(input);
|
|
90
93
|
|
|
91
94
|
// path can be:
|
|
92
95
|
// - a concrete file → used as-is
|
|
@@ -112,7 +115,8 @@ function executeJsonl(src: NativeHistoryJsonlSource, input: NativeHistoryInput):
|
|
|
112
115
|
const workspaceHint = typeof input.workspace === 'string' && input.workspace.trim() ? input.workspace.trim() : '';
|
|
113
116
|
let sourcePath: string | null = null;
|
|
114
117
|
if (resolved.includes('*')) {
|
|
115
|
-
sourcePath =
|
|
118
|
+
sourcePath = pickExactSessionFileAcrossGlob(resolved, filePat, requestedSessionId)
|
|
119
|
+
|| pickSessionBoundFileAcrossGlob(resolved, filePat, windowMs, sessionFloor, workspaceHint)
|
|
116
120
|
|| newestRecentFileAcrossGlob(resolved, filePat, windowMs, sessionFloor);
|
|
117
121
|
} else {
|
|
118
122
|
let stat: fs.Stats | null = null;
|
|
@@ -120,18 +124,18 @@ function executeJsonl(src: NativeHistoryJsonlSource, input: NativeHistoryInput):
|
|
|
120
124
|
if (stat && stat.isFile()) {
|
|
121
125
|
sourcePath = resolved;
|
|
122
126
|
} else if (stat && stat.isDirectory()) {
|
|
123
|
-
sourcePath =
|
|
124
|
-
||
|
|
127
|
+
sourcePath = pickExactSessionFile(resolved, filePat, requestedSessionId)
|
|
128
|
+
|| (requestedSessionId ? null : pickSessionBoundFile(resolved, filePat, windowMs, sessionFloor, workspaceHint))
|
|
129
|
+
|| (requestedSessionId ? null : newestRecentFile(resolved, filePat, windowMs, sessionFloor));
|
|
125
130
|
}
|
|
126
131
|
// Date-templated directories (e.g. ~/.codex/sessions/{yyyy}/{mm}/{dd})
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
// or doesn't exist yet, walk the last 3 UTC days at the same depth
|
|
131
|
-
// and pick the newest matching rollout across all of them.
|
|
132
|
+
// can drift from the provider's chosen calendar day because CLIs
|
|
133
|
+
// disagree on local-vs-UTC date buckets. Search nearby date dirs
|
|
134
|
+
// before falling back to non-exact matching.
|
|
132
135
|
if (!sourcePath && hasDateTemplateSegment(src.path)) {
|
|
133
|
-
sourcePath =
|
|
134
|
-
||
|
|
136
|
+
sourcePath = pickExactSessionFileAcrossDateWindow(src.path, input, filePat, requestedSessionId)
|
|
137
|
+
|| (requestedSessionId ? null : pickSessionBoundFileAcrossDateWindow(src.path, input, filePat, windowMs, sessionFloor, workspaceHint))
|
|
138
|
+
|| (requestedSessionId ? null : newestRecentFileAcrossDateWindow(src.path, input, filePat, windowMs, sessionFloor));
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
if (!sourcePath) return null;
|
|
@@ -139,6 +143,7 @@ function executeJsonl(src: NativeHistoryJsonlSource, input: NativeHistoryInput):
|
|
|
139
143
|
const mtime = safeMtimeMs(sourcePath);
|
|
140
144
|
const lines = readJsonlLines(sourcePath);
|
|
141
145
|
if (lines.length === 0) return null;
|
|
146
|
+
const transcriptWorkspace = readSessionMetaWorkspace(lines);
|
|
142
147
|
|
|
143
148
|
// session id: filename uuid or extracted from first record
|
|
144
149
|
let providerSessionId: string | undefined;
|
|
@@ -150,7 +155,7 @@ function executeJsonl(src: NativeHistoryJsonlSource, input: NativeHistoryInput):
|
|
|
150
155
|
if (m) providerSessionId = m[1];
|
|
151
156
|
}
|
|
152
157
|
|
|
153
|
-
const requested =
|
|
158
|
+
const requested = requestedSessionId || '';
|
|
154
159
|
if (requested && providerSessionId && providerSessionId !== requested) return null;
|
|
155
160
|
|
|
156
161
|
const filter = src.message_filter ? compileWhere(src.message_filter.where) : null;
|
|
@@ -159,7 +164,10 @@ function executeJsonl(src: NativeHistoryJsonlSource, input: NativeHistoryInput):
|
|
|
159
164
|
const rec = lines[i];
|
|
160
165
|
if (filter && !filter(rec)) continue;
|
|
161
166
|
const msg = projectMessage(rec, src.message_map, i, lines.length, mtime);
|
|
162
|
-
if (msg)
|
|
167
|
+
if (msg) {
|
|
168
|
+
if (transcriptWorkspace) msg.workspace = transcriptWorkspace;
|
|
169
|
+
messages.push(msg);
|
|
170
|
+
}
|
|
163
171
|
}
|
|
164
172
|
if (messages.length === 0) return null;
|
|
165
173
|
|
|
@@ -169,9 +177,19 @@ function executeJsonl(src: NativeHistoryJsonlSource, input: NativeHistoryInput):
|
|
|
169
177
|
sourcePath,
|
|
170
178
|
sourceMtimeMs: mtime,
|
|
171
179
|
nativeHistoryCoverage: 'full',
|
|
180
|
+
workspace: transcriptWorkspace,
|
|
172
181
|
};
|
|
173
182
|
}
|
|
174
183
|
|
|
184
|
+
function readSessionMetaWorkspace(lines: any[]): string | undefined {
|
|
185
|
+
for (const record of lines.slice(0, 5)) {
|
|
186
|
+
if (String(record?.type ?? '') !== 'session_meta') continue;
|
|
187
|
+
const cwd = typeof record?.payload?.cwd === 'string' ? record.payload.cwd.trim() : '';
|
|
188
|
+
if (cwd) return cwd;
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
175
193
|
function readJsonlLines(p: string): any[] {
|
|
176
194
|
let text: string;
|
|
177
195
|
try { text = fs.readFileSync(p, 'utf8'); } catch { return []; }
|
|
@@ -292,9 +310,9 @@ function expandPath(template: string, input: NativeHistoryInput): string | null
|
|
|
292
310
|
cwd: workspaceResolved,
|
|
293
311
|
cwd_dashed: workspaceResolved.replace(/\//g, '-'),
|
|
294
312
|
session_id: input.providerSessionId || input.sessionId || input.historySessionId || '',
|
|
295
|
-
yyyy: String(now.
|
|
296
|
-
mm: String(now.
|
|
297
|
-
dd: String(now.
|
|
313
|
+
yyyy: String(now.getFullYear()),
|
|
314
|
+
mm: String(now.getMonth() + 1).padStart(2, '0'),
|
|
315
|
+
dd: String(now.getDate()).padStart(2, '0'),
|
|
298
316
|
};
|
|
299
317
|
// Replace {var}. If a referenced variable is empty (e.g. session_id
|
|
300
318
|
// before the agent has allocated one), return null so the caller
|
|
@@ -393,12 +411,10 @@ function hasDateTemplateSegment(template: string): boolean {
|
|
|
393
411
|
}
|
|
394
412
|
|
|
395
413
|
/**
|
|
396
|
-
* Walk
|
|
414
|
+
* Walk nearby local calendar days of a date-templated path (e.g.
|
|
397
415
|
* `~/.codex/sessions/{yyyy}/{mm}/{dd}`) and return the newest matching
|
|
398
|
-
* file across all of them.
|
|
399
|
-
*
|
|
400
|
-
* date rolls over, so today's expanded dir alone misses the live
|
|
401
|
-
* transcript.
|
|
416
|
+
* file across all of them. Providers differ on local-vs-UTC date buckets,
|
|
417
|
+
* so today's expanded dir alone can miss a live transcript.
|
|
402
418
|
*/
|
|
403
419
|
function newestRecentFileAcrossDateWindow(
|
|
404
420
|
template: string,
|
|
@@ -409,8 +425,8 @@ function newestRecentFileAcrossDateWindow(
|
|
|
409
425
|
): string | null {
|
|
410
426
|
const cutoff = Math.max(Date.now() - windowMs, sessionFloorMs);
|
|
411
427
|
let best: { p: string; mtime: number } | null = null;
|
|
412
|
-
for (
|
|
413
|
-
const dayMs = Date.now()
|
|
428
|
+
for (const dayOffset of [0, -1, 1, -2, 2]) {
|
|
429
|
+
const dayMs = Date.now() + dayOffset * 24 * 60 * 60 * 1000;
|
|
414
430
|
const dayInput: NativeHistoryInput = { ...input, sessionStartedAtMs: sessionFloorMs };
|
|
415
431
|
const resolved = expandPathForDate(template, dayInput, new Date(dayMs));
|
|
416
432
|
if (!resolved) continue;
|
|
@@ -448,9 +464,9 @@ function expandPathForDate(template: string, input: NativeHistoryInput, day: Dat
|
|
|
448
464
|
cwd: workspaceResolved,
|
|
449
465
|
cwd_dashed: workspaceResolved.replace(/\//g, '-'),
|
|
450
466
|
session_id: input.providerSessionId || input.sessionId || input.historySessionId || '',
|
|
451
|
-
yyyy: String(day.
|
|
452
|
-
mm: String(day.
|
|
453
|
-
dd: String(day.
|
|
467
|
+
yyyy: String(day.getFullYear()),
|
|
468
|
+
mm: String(day.getMonth() + 1).padStart(2, '0'),
|
|
469
|
+
dd: String(day.getDate()).padStart(2, '0'),
|
|
454
470
|
};
|
|
455
471
|
let missing = false;
|
|
456
472
|
out = out.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_m, name) => {
|
|
@@ -481,6 +497,56 @@ function safeMtimeMs(p: string): number {
|
|
|
481
497
|
try { return Math.floor(fs.statSync(p).mtimeMs); } catch { return 0; }
|
|
482
498
|
}
|
|
483
499
|
|
|
500
|
+
function readRequestedSessionId(input: NativeHistoryInput): string {
|
|
501
|
+
const raw = input.providerSessionId || input.sessionId || input.historySessionId || '';
|
|
502
|
+
const value = typeof raw === 'string' ? raw.trim() : '';
|
|
503
|
+
return UUID_RE.test(value) ? value : '';
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function filenameUuid(filePath: string): string {
|
|
507
|
+
const match = path.basename(filePath).match(UUID_RE);
|
|
508
|
+
return match?.[1] || '';
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function pickExactSessionFile(dir: string, pattern: RegExp, requestedSessionId: string): string | null {
|
|
512
|
+
if (!requestedSessionId) return null;
|
|
513
|
+
const files = listMatchingFiles(dir, pattern)
|
|
514
|
+
.filter(p => filenameUuid(p).toLowerCase() === requestedSessionId.toLowerCase())
|
|
515
|
+
.sort((a, b) => safeMtimeMs(b) - safeMtimeMs(a));
|
|
516
|
+
return files[0] || null;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function pickExactSessionFileAcrossGlob(template: string, pattern: RegExp, requestedSessionId: string): string | null {
|
|
520
|
+
if (!requestedSessionId) return null;
|
|
521
|
+
const dirs = expandDirGlob(template);
|
|
522
|
+
const matches: string[] = [];
|
|
523
|
+
for (const d of dirs) {
|
|
524
|
+
const found = pickExactSessionFile(d, pattern, requestedSessionId);
|
|
525
|
+
if (found) matches.push(found);
|
|
526
|
+
}
|
|
527
|
+
matches.sort((a, b) => safeMtimeMs(b) - safeMtimeMs(a));
|
|
528
|
+
return matches[0] || null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function pickExactSessionFileAcrossDateWindow(
|
|
532
|
+
template: string,
|
|
533
|
+
input: NativeHistoryInput,
|
|
534
|
+
pattern: RegExp,
|
|
535
|
+
requestedSessionId: string,
|
|
536
|
+
): string | null {
|
|
537
|
+
if (!requestedSessionId) return null;
|
|
538
|
+
const matches: string[] = [];
|
|
539
|
+
for (const dayOffset of [0, -1, 1, -2, 2]) {
|
|
540
|
+
const dayMs = Date.now() + dayOffset * 24 * 60 * 60 * 1000;
|
|
541
|
+
const resolved = expandPathForDate(template, input, new Date(dayMs));
|
|
542
|
+
if (!resolved) continue;
|
|
543
|
+
const found = pickExactSessionFile(resolved, pattern, requestedSessionId);
|
|
544
|
+
if (found) matches.push(found);
|
|
545
|
+
}
|
|
546
|
+
matches.sort((a, b) => safeMtimeMs(b) - safeMtimeMs(a));
|
|
547
|
+
return matches[0] || null;
|
|
548
|
+
}
|
|
549
|
+
|
|
484
550
|
// ────────────────────────────────────────────────────────────────────────────
|
|
485
551
|
// Per-session rollout binding
|
|
486
552
|
//
|
|
@@ -624,8 +690,8 @@ function pickSessionBoundFileAcrossDateWindow(
|
|
|
624
690
|
if (!sessionFloorMs || !workspaceHint) return null;
|
|
625
691
|
const cutoff = Math.max(Date.now() - windowMs, sessionFloorMs - SPAWN_BIND_GRACE_MS);
|
|
626
692
|
const files: string[] = [];
|
|
627
|
-
for (
|
|
628
|
-
const dayMs = sessionFloorMs
|
|
693
|
+
for (const dayOffset of [0, -1, 1, -2, 2]) {
|
|
694
|
+
const dayMs = sessionFloorMs + dayOffset * 24 * 60 * 60 * 1000;
|
|
629
695
|
const dayInput: NativeHistoryInput = { ...input, sessionStartedAtMs: sessionFloorMs };
|
|
630
696
|
const resolved = expandPathForDate(template, dayInput, new Date(dayMs));
|
|
631
697
|
if (!resolved) continue;
|
|
@@ -217,7 +217,19 @@ function parseClaudeHeaderlessInteractiveTuiQuestion(page: ClaudeInteractiveTuiP
|
|
|
217
217
|
let question = '';
|
|
218
218
|
for (let i = firstOptionIndex - 1; i >= 0; i -= 1) {
|
|
219
219
|
const candidate = lines[i].trim();
|
|
220
|
-
if (!candidate || /^─+$/.test(candidate)
|
|
220
|
+
if (!candidate || /^─+$/.test(candidate)) continue;
|
|
221
|
+
// Standalone ☐/☒ markers (decorative section dividers in the headered
|
|
222
|
+
// variant) are not the question — keep skipping them.
|
|
223
|
+
if (/^[☐☒]\s*$/.test(candidate)) continue;
|
|
224
|
+
// The headerless variant introduced in claude-cli >=2.1 prefixes the
|
|
225
|
+
// actual question with `☐ ` (e.g. "☐ RPS R1 1라운드 — …"). Previously
|
|
226
|
+
// we skipped any ☐/☒ line and returned null, never opening the picker.
|
|
227
|
+
// Strip the marker so the dashboard label matches the on-screen text.
|
|
228
|
+
const markerMatch = candidate.match(/^[☐☒]\s+(.+)$/);
|
|
229
|
+
if (markerMatch) {
|
|
230
|
+
question = markerMatch[1].trim();
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
221
233
|
question = candidate;
|
|
222
234
|
break;
|
|
223
235
|
}
|