@tekmidian/pai 0.7.0 → 0.7.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/cli/index.mjs +1 -1
- package/dist/daemon/index.mjs +1 -1
- package/dist/{daemon-D3hYb5_C.mjs → daemon-DuGlDnV7.mjs} +861 -4
- package/dist/daemon-DuGlDnV7.mjs.map +1 -0
- package/dist/hooks/context-compression-hook.mjs +58 -22
- package/dist/hooks/context-compression-hook.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +78 -27
- package/dist/hooks/load-project-context.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs +220 -125
- package/dist/hooks/stop-hook.mjs.map +3 -3
- package/dist/hooks/sync-todo-to-md.mjs.map +1 -1
- package/dist/skills/Reconstruct/SKILL.md +232 -0
- package/package.json +1 -1
- package/plugins/productivity/plugin.json +1 -1
- package/plugins/productivity/skills/Reconstruct/SKILL.md +232 -0
- package/src/hooks/ts/lib/project-utils/index.ts +1 -0
- package/src/hooks/ts/lib/project-utils/session-notes.ts +46 -5
- package/src/hooks/ts/lib/project-utils.ts +1 -0
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +60 -37
- package/src/hooks/ts/session-start/load-project-context.ts +110 -28
- package/src/hooks/ts/stop/stop-hook.ts +259 -199
- package/dist/daemon-D3hYb5_C.mjs.map +0 -1
package/dist/hooks/stop-hook.mjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// src/hooks/ts/stop/stop-hook.ts
|
|
4
4
|
import { readFileSync as readFileSync5 } from "fs";
|
|
5
5
|
import { basename as basename3, dirname } from "path";
|
|
6
|
+
import { connect } from "net";
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
6
8
|
|
|
7
9
|
// src/hooks/ts/lib/project-utils/paths.ts
|
|
8
10
|
import { existsSync as existsSync2, mkdirSync, readdirSync, renameSync } from "fs";
|
|
@@ -266,6 +268,30 @@ function addWorkToSessionNote(notePath, workItems, sectionTitle) {
|
|
|
266
268
|
function sanitizeForFilename(str) {
|
|
267
269
|
return str.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 50);
|
|
268
270
|
}
|
|
271
|
+
function isMeaninglessCandidate(text) {
|
|
272
|
+
const t = text.trim();
|
|
273
|
+
if (!t) return true;
|
|
274
|
+
if (t.length < 5) return true;
|
|
275
|
+
if (t.startsWith("/") || t.startsWith("~")) return true;
|
|
276
|
+
if (t.startsWith("#!")) return true;
|
|
277
|
+
if (t.includes("[object Object]")) return true;
|
|
278
|
+
if (/^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]+)?$/.test(t)) return true;
|
|
279
|
+
if (/^\d{1,2}:\d{2}(:\d{2})?(\s*(AM|PM))?$/i.test(t)) return true;
|
|
280
|
+
if (/^<[a-z-]+[\s/>]/i.test(t)) return true;
|
|
281
|
+
if (/^[0-9a-f]{10,}$/i.test(t)) return true;
|
|
282
|
+
if (/^Exit code \d+/i.test(t)) return true;
|
|
283
|
+
if (/^Error:/i.test(t)) return true;
|
|
284
|
+
if (/^This session is being continued/i.test(t)) return true;
|
|
285
|
+
if (/^\(Bash completed/i.test(t)) return true;
|
|
286
|
+
if (/^Task Notification$/i.test(t)) return true;
|
|
287
|
+
if (/^New Session$/i.test(t)) return true;
|
|
288
|
+
if (/^Recovered Session$/i.test(t)) return true;
|
|
289
|
+
if (/^Continued Session$/i.test(t)) return true;
|
|
290
|
+
if (/^Untitled Session$/i.test(t)) return true;
|
|
291
|
+
if (/^Context Compression$/i.test(t)) return true;
|
|
292
|
+
if (/^[A-Fa-f0-9]{8,}\s+Output$/i.test(t)) return true;
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
269
295
|
function extractMeaningfulName(noteContent, summary) {
|
|
270
296
|
const workDoneMatch = noteContent.match(/## Work Done\n\n([\s\S]*?)(?=\n---|\n## Next)/);
|
|
271
297
|
if (workDoneMatch) {
|
|
@@ -273,23 +299,27 @@ function extractMeaningfulName(noteContent, summary) {
|
|
|
273
299
|
const subheadings = workDoneSection.match(/### ([^\n]+)/g);
|
|
274
300
|
if (subheadings && subheadings.length > 0) {
|
|
275
301
|
const firstHeading = subheadings[0].replace("### ", "").trim();
|
|
276
|
-
if (firstHeading.length > 5 && firstHeading.length < 60) {
|
|
302
|
+
if (!isMeaninglessCandidate(firstHeading) && firstHeading.length > 5 && firstHeading.length < 60) {
|
|
277
303
|
return sanitizeForFilename(firstHeading);
|
|
278
304
|
}
|
|
279
305
|
}
|
|
280
306
|
const boldMatches = workDoneSection.match(/\*\*([^*]+)\*\*/g);
|
|
281
307
|
if (boldMatches && boldMatches.length > 0) {
|
|
282
308
|
const firstBold = boldMatches[0].replace(/\*\*/g, "").trim();
|
|
283
|
-
if (firstBold.length > 3 && firstBold.length < 50) {
|
|
309
|
+
if (!isMeaninglessCandidate(firstBold) && firstBold.length > 3 && firstBold.length < 50) {
|
|
284
310
|
return sanitizeForFilename(firstBold);
|
|
285
311
|
}
|
|
286
312
|
}
|
|
287
313
|
const numberedItems = workDoneSection.match(/^\d+\.\s+\*\*([^*]+)\*\*/m);
|
|
288
|
-
if (numberedItems
|
|
314
|
+
if (numberedItems && !isMeaninglessCandidate(numberedItems[1])) {
|
|
315
|
+
return sanitizeForFilename(numberedItems[1]);
|
|
316
|
+
}
|
|
289
317
|
}
|
|
290
|
-
if (summary && summary.length > 5 && summary !== "Session completed.") {
|
|
318
|
+
if (summary && summary.length > 5 && summary !== "Session completed." && !isMeaninglessCandidate(summary)) {
|
|
291
319
|
const cleanSummary = summary.replace(/[^\w\s-]/g, " ").trim().split(/\s+/).slice(0, 5).join(" ");
|
|
292
|
-
if (cleanSummary.length > 3
|
|
320
|
+
if (cleanSummary.length > 3 && !isMeaninglessCandidate(cleanSummary)) {
|
|
321
|
+
return sanitizeForFilename(cleanSummary);
|
|
322
|
+
}
|
|
293
323
|
}
|
|
294
324
|
return "";
|
|
295
325
|
}
|
|
@@ -418,6 +448,105 @@ ${stateLines}
|
|
|
418
448
|
}
|
|
419
449
|
|
|
420
450
|
// src/hooks/ts/stop/stop-hook.ts
|
|
451
|
+
var DAEMON_SOCKET = process.env.PAI_SOCKET ?? "/tmp/pai.sock";
|
|
452
|
+
var DAEMON_TIMEOUT_MS = 3e3;
|
|
453
|
+
function contentToText(content) {
|
|
454
|
+
if (typeof content === "string") return content;
|
|
455
|
+
if (Array.isArray(content)) {
|
|
456
|
+
return content.map((c) => {
|
|
457
|
+
if (typeof c === "string") return c;
|
|
458
|
+
if (c?.text) return c.text;
|
|
459
|
+
if (c?.content) return String(c.content);
|
|
460
|
+
return "";
|
|
461
|
+
}).join(" ").trim();
|
|
462
|
+
}
|
|
463
|
+
return "";
|
|
464
|
+
}
|
|
465
|
+
function extractCompletedMessage(lines) {
|
|
466
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
467
|
+
try {
|
|
468
|
+
const entry = JSON.parse(lines[i]);
|
|
469
|
+
if (entry.type === "assistant" && entry.message?.content) {
|
|
470
|
+
const content = contentToText(entry.message.content);
|
|
471
|
+
const m = content.match(/COMPLETED:\s*(.+?)(?:\n|$)/i);
|
|
472
|
+
if (m) {
|
|
473
|
+
return m[1].trim().replace(/\*+/g, "").replace(/\[.*?\]/g, "").trim();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} catch {
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return "";
|
|
480
|
+
}
|
|
481
|
+
function enqueueWithDaemon(payload) {
|
|
482
|
+
return new Promise((resolve2) => {
|
|
483
|
+
let done = false;
|
|
484
|
+
let buffer = "";
|
|
485
|
+
let timer = null;
|
|
486
|
+
function finish(ok) {
|
|
487
|
+
if (done) return;
|
|
488
|
+
done = true;
|
|
489
|
+
if (timer !== null) {
|
|
490
|
+
clearTimeout(timer);
|
|
491
|
+
timer = null;
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
client.destroy();
|
|
495
|
+
} catch {
|
|
496
|
+
}
|
|
497
|
+
resolve2(ok);
|
|
498
|
+
}
|
|
499
|
+
const client = connect(DAEMON_SOCKET, () => {
|
|
500
|
+
const msg = JSON.stringify({
|
|
501
|
+
id: randomUUID(),
|
|
502
|
+
method: "work_queue_enqueue",
|
|
503
|
+
params: {
|
|
504
|
+
type: "session-end",
|
|
505
|
+
priority: 2,
|
|
506
|
+
payload: {
|
|
507
|
+
transcriptPath: payload.transcriptPath,
|
|
508
|
+
cwd: payload.cwd,
|
|
509
|
+
message: payload.message
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}) + "\n";
|
|
513
|
+
client.write(msg);
|
|
514
|
+
});
|
|
515
|
+
client.on("data", (chunk) => {
|
|
516
|
+
buffer += chunk.toString();
|
|
517
|
+
const nl = buffer.indexOf("\n");
|
|
518
|
+
if (nl === -1) return;
|
|
519
|
+
const line = buffer.slice(0, nl);
|
|
520
|
+
try {
|
|
521
|
+
const response = JSON.parse(line);
|
|
522
|
+
if (response.ok) {
|
|
523
|
+
console.error(`STOP-HOOK: Work enqueued with daemon (id=${response.result?.id}).`);
|
|
524
|
+
finish(true);
|
|
525
|
+
} else {
|
|
526
|
+
console.error(`STOP-HOOK: Daemon rejected enqueue: ${response.error}`);
|
|
527
|
+
finish(false);
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
finish(false);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
client.on("error", (e) => {
|
|
534
|
+
if (e.code === "ENOENT" || e.code === "ECONNREFUSED") {
|
|
535
|
+
console.error("STOP-HOOK: Daemon not running \u2014 falling back to direct execution.");
|
|
536
|
+
} else {
|
|
537
|
+
console.error(`STOP-HOOK: Daemon socket error: ${e.message}`);
|
|
538
|
+
}
|
|
539
|
+
finish(false);
|
|
540
|
+
});
|
|
541
|
+
client.on("end", () => {
|
|
542
|
+
if (!done) finish(false);
|
|
543
|
+
});
|
|
544
|
+
timer = setTimeout(() => {
|
|
545
|
+
console.error(`STOP-HOOK: Daemon timeout after ${DAEMON_TIMEOUT_MS}ms \u2014 falling back.`);
|
|
546
|
+
finish(false);
|
|
547
|
+
}, DAEMON_TIMEOUT_MS);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
421
550
|
function extractWorkFromTranscript(lines) {
|
|
422
551
|
const workItems = [];
|
|
423
552
|
const seenSummaries = /* @__PURE__ */ new Set();
|
|
@@ -446,13 +575,10 @@ function extractWorkFromTranscript(lines) {
|
|
|
446
575
|
}
|
|
447
576
|
const completedMatch = content.match(/COMPLETED:\s*(.+?)(?:\n|$)/i);
|
|
448
577
|
if (completedMatch && workItems.length === 0) {
|
|
449
|
-
const completed = completedMatch[1].trim().replace(/\*+/g, "");
|
|
578
|
+
const completed = completedMatch[1].trim().replace(/\*+/g, "").replace(/\[.*?\]/g, "");
|
|
450
579
|
if (completed && !seenSummaries.has(completed) && completed.length > 5) {
|
|
451
580
|
seenSummaries.add(completed);
|
|
452
|
-
workItems.push({
|
|
453
|
-
title: completed,
|
|
454
|
-
completed: true
|
|
455
|
-
});
|
|
581
|
+
workItems.push({ title: completed, completed: true });
|
|
456
582
|
}
|
|
457
583
|
}
|
|
458
584
|
}
|
|
@@ -467,9 +593,7 @@ function generateTabTitle(prompt, completedLine) {
|
|
|
467
593
|
const completedWords = cleanCompleted.split(/\s+/).filter((word) => word.length > 2 && !["the", "and", "but", "for", "are", "with", "his", "her", "this", "that", "you", "can", "will", "have", "been", "your", "from", "they", "were", "said", "what", "them", "just", "told", "how", "does", "into", "about", "completed"].includes(word.toLowerCase())).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
|
|
468
594
|
if (completedWords.length >= 2) {
|
|
469
595
|
const summary = completedWords.slice(0, 4);
|
|
470
|
-
while (summary.length < 4)
|
|
471
|
-
summary.push("Done");
|
|
472
|
-
}
|
|
596
|
+
while (summary.length < 4) summary.push("Done");
|
|
473
597
|
return summary.slice(0, 4).join(" ");
|
|
474
598
|
}
|
|
475
599
|
}
|
|
@@ -494,37 +618,80 @@ function generateTabTitle(prompt, completedLine) {
|
|
|
494
618
|
}
|
|
495
619
|
const remainingWords = words.filter((word) => !actionVerbs.includes(word.toLowerCase())).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
|
|
496
620
|
for (const word of remainingWords) {
|
|
497
|
-
if (titleWords.length < 4)
|
|
498
|
-
|
|
499
|
-
} else {
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
621
|
+
if (titleWords.length < 4) titleWords.push(word);
|
|
622
|
+
else break;
|
|
502
623
|
}
|
|
503
|
-
if (titleWords.length === 0)
|
|
504
|
-
|
|
624
|
+
if (titleWords.length === 0) titleWords.push("Completed");
|
|
625
|
+
if (titleWords.length === 1) titleWords.push("Task");
|
|
626
|
+
if (titleWords.length === 2) titleWords.push("Successfully");
|
|
627
|
+
if (titleWords.length === 3) titleWords.push("Done");
|
|
628
|
+
return titleWords.slice(0, 4).join(" ");
|
|
629
|
+
}
|
|
630
|
+
async function executeDirectly(lines, transcriptPath, cwd, message, lastUserQuery) {
|
|
631
|
+
let tabTitle = message || "";
|
|
632
|
+
if (!tabTitle && lastUserQuery) {
|
|
633
|
+
tabTitle = generateTabTitle(lastUserQuery, "");
|
|
505
634
|
}
|
|
506
|
-
if (
|
|
507
|
-
|
|
635
|
+
if (tabTitle) {
|
|
636
|
+
try {
|
|
637
|
+
const escapedTitle = tabTitle.replace(/'/g, "'\\''");
|
|
638
|
+
const { execSync } = await import("child_process");
|
|
639
|
+
execSync(`printf '\\033]0;${escapedTitle}\\007' >&2`);
|
|
640
|
+
execSync(`printf '\\033]2;${escapedTitle}\\007' >&2`);
|
|
641
|
+
execSync(`printf '\\033]30;${escapedTitle}\\007' >&2`);
|
|
642
|
+
console.error(`Tab title set to: "${tabTitle}"`);
|
|
643
|
+
} catch (e) {
|
|
644
|
+
console.error(`Failed to set tab title: ${e}`);
|
|
645
|
+
}
|
|
508
646
|
}
|
|
509
|
-
if (
|
|
510
|
-
|
|
647
|
+
if (message) {
|
|
648
|
+
const finalTabTitle = message.slice(0, 50);
|
|
649
|
+
process.stderr.write(`\x1B]2;${finalTabTitle}\x07`);
|
|
511
650
|
}
|
|
512
|
-
|
|
513
|
-
|
|
651
|
+
try {
|
|
652
|
+
const notesInfo = findNotesDir(cwd);
|
|
653
|
+
const currentNotePath = getCurrentNotePath(notesInfo.path);
|
|
654
|
+
if (currentNotePath) {
|
|
655
|
+
const workItems = extractWorkFromTranscript(lines);
|
|
656
|
+
if (workItems.length > 0) {
|
|
657
|
+
addWorkToSessionNote(currentNotePath, workItems);
|
|
658
|
+
console.error(`Added ${workItems.length} work item(s) to session note`);
|
|
659
|
+
} else if (message) {
|
|
660
|
+
addWorkToSessionNote(currentNotePath, [{ title: message, completed: true }]);
|
|
661
|
+
console.error(`Added completion message to session note`);
|
|
662
|
+
}
|
|
663
|
+
const summary = message || "Session completed.";
|
|
664
|
+
finalizeSessionNote(currentNotePath, summary);
|
|
665
|
+
console.error(`Session note finalized: ${basename3(currentNotePath)}`);
|
|
666
|
+
try {
|
|
667
|
+
const stateLines = [];
|
|
668
|
+
stateLines.push(`Working directory: ${cwd}`);
|
|
669
|
+
if (workItems.length > 0) {
|
|
670
|
+
stateLines.push("", "Work completed:");
|
|
671
|
+
for (const item of workItems.slice(0, 5)) {
|
|
672
|
+
stateLines.push(`- ${item.title}`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (message) {
|
|
676
|
+
stateLines.push("", `Last completed: ${message}`);
|
|
677
|
+
}
|
|
678
|
+
updateTodoContinue(cwd, basename3(currentNotePath), stateLines.join("\n"), "session-end");
|
|
679
|
+
} catch (todoError) {
|
|
680
|
+
console.error(`Could not update TODO.md: ${todoError}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
} catch (noteError) {
|
|
684
|
+
console.error(`Could not finalize session note: ${noteError}`);
|
|
514
685
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (c?.content) return String(c.content);
|
|
524
|
-
return "";
|
|
525
|
-
}).join(" ").trim();
|
|
686
|
+
try {
|
|
687
|
+
const transcriptDir = dirname(transcriptPath);
|
|
688
|
+
const movedCount = moveSessionFilesToSessionsDir(transcriptDir);
|
|
689
|
+
if (movedCount > 0) {
|
|
690
|
+
console.error(`Moved ${movedCount} session file(s) to sessions/`);
|
|
691
|
+
}
|
|
692
|
+
} catch (moveError) {
|
|
693
|
+
console.error(`Could not move session files: ${moveError}`);
|
|
526
694
|
}
|
|
527
|
-
return "";
|
|
528
695
|
}
|
|
529
696
|
async function main() {
|
|
530
697
|
if (isProbeSession()) {
|
|
@@ -590,45 +757,20 @@ STOP-HOOK TRIGGERED AT ${timestamp}`);
|
|
|
590
757
|
}
|
|
591
758
|
if (lastUserQuery) break;
|
|
592
759
|
}
|
|
593
|
-
} catch
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
let message = "";
|
|
597
|
-
const lastResponse = lines[lines.length - 1];
|
|
598
|
-
try {
|
|
599
|
-
const entry = JSON.parse(lastResponse);
|
|
600
|
-
if (entry.type === "assistant" && entry.message?.content) {
|
|
601
|
-
const content = contentToText(entry.message.content);
|
|
602
|
-
const completedMatch = content.match(/COMPLETED:\s*(.+?)(?:\n|$)/i);
|
|
603
|
-
if (completedMatch) {
|
|
604
|
-
message = completedMatch[1].trim().replace(/\*+/g, "").replace(/\[.*?\]/g, "").trim();
|
|
605
|
-
console.error(`COMPLETION: ${message}`);
|
|
606
|
-
}
|
|
760
|
+
} catch {
|
|
607
761
|
}
|
|
608
|
-
} catch (e) {
|
|
609
|
-
console.error("Error parsing assistant response:", e);
|
|
610
762
|
}
|
|
763
|
+
const message = extractCompletedMessage(lines);
|
|
764
|
+
console.error(`User query: ${lastUserQuery || "No query found"}`);
|
|
765
|
+
console.error(`Message: ${message || "No completion message"}`);
|
|
611
766
|
let tabTitle = message || "";
|
|
612
767
|
if (!tabTitle && lastUserQuery) {
|
|
613
|
-
|
|
614
|
-
const entry = JSON.parse(lastResponse);
|
|
615
|
-
if (entry.type === "assistant" && entry.message?.content) {
|
|
616
|
-
const content = contentToText(entry.message.content);
|
|
617
|
-
const completedMatch = content.match(/COMPLETED:\s*(.+?)(?:\n|$)/im);
|
|
618
|
-
if (completedMatch) {
|
|
619
|
-
tabTitle = completedMatch[1].trim().replace(/\*+/g, "").replace(/\[.*?\]/g, "").trim();
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
} catch (e) {
|
|
623
|
-
}
|
|
624
|
-
if (!tabTitle) {
|
|
625
|
-
tabTitle = generateTabTitle(lastUserQuery, "");
|
|
626
|
-
}
|
|
768
|
+
tabTitle = generateTabTitle(lastUserQuery, "");
|
|
627
769
|
}
|
|
628
770
|
if (tabTitle) {
|
|
629
771
|
try {
|
|
630
|
-
const escapedTitle = tabTitle.replace(/'/g, "'\\''");
|
|
631
772
|
const { execSync } = await import("child_process");
|
|
773
|
+
const escapedTitle = tabTitle.replace(/'/g, "'\\''");
|
|
632
774
|
execSync(`printf '\\033]0;${escapedTitle}\\007' >&2`);
|
|
633
775
|
execSync(`printf '\\033]2;${escapedTitle}\\007' >&2`);
|
|
634
776
|
execSync(`printf '\\033]30;${escapedTitle}\\007' >&2`);
|
|
@@ -637,69 +779,22 @@ STOP-HOOK TRIGGERED AT ${timestamp}`);
|
|
|
637
779
|
console.error(`Failed to set tab title: ${e}`);
|
|
638
780
|
}
|
|
639
781
|
}
|
|
640
|
-
console.error(`User query: ${lastUserQuery || "No query found"}`);
|
|
641
|
-
console.error(`Message: ${message || "No completion message"}`);
|
|
642
782
|
if (message) {
|
|
643
|
-
|
|
644
|
-
process.stderr.write(`\x1B]2;${finalTabTitle}\x07`);
|
|
783
|
+
process.stderr.write(`\x1B]2;${message.slice(0, 50)}\x07`);
|
|
645
784
|
}
|
|
646
785
|
if (message) {
|
|
647
786
|
await sendNtfyNotification(message);
|
|
648
787
|
} else {
|
|
649
788
|
await sendNtfyNotification("Session ended");
|
|
650
789
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
console.error(`Added ${workItems.length} work item(s) to session note`);
|
|
660
|
-
} else {
|
|
661
|
-
if (message) {
|
|
662
|
-
addWorkToSessionNote(currentNotePath, [{
|
|
663
|
-
title: message,
|
|
664
|
-
completed: true
|
|
665
|
-
}]);
|
|
666
|
-
console.error(`Added completion message to session note`);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
const summary = message || "Session completed.";
|
|
670
|
-
finalizeSessionNote(currentNotePath, summary);
|
|
671
|
-
console.error(`Session note finalized: ${basename3(currentNotePath)}`);
|
|
672
|
-
try {
|
|
673
|
-
const stateLines = [];
|
|
674
|
-
stateLines.push(`Working directory: ${cwd}`);
|
|
675
|
-
if (workItems.length > 0) {
|
|
676
|
-
stateLines.push("");
|
|
677
|
-
stateLines.push("Work completed:");
|
|
678
|
-
for (const item of workItems.slice(0, 5)) {
|
|
679
|
-
stateLines.push(`- ${item.title}`);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
if (message) {
|
|
683
|
-
stateLines.push("");
|
|
684
|
-
stateLines.push(`Last completed: ${message}`);
|
|
685
|
-
}
|
|
686
|
-
const state = stateLines.join("\n");
|
|
687
|
-
updateTodoContinue(cwd, basename3(currentNotePath), state, "session-end");
|
|
688
|
-
} catch (todoError) {
|
|
689
|
-
console.error(`Could not update TODO.md: ${todoError}`);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
} catch (noteError) {
|
|
693
|
-
console.error(`Could not finalize session note: ${noteError}`);
|
|
694
|
-
}
|
|
695
|
-
try {
|
|
696
|
-
const transcriptDir = dirname(transcriptPath);
|
|
697
|
-
const movedCount = moveSessionFilesToSessionsDir(transcriptDir);
|
|
698
|
-
if (movedCount > 0) {
|
|
699
|
-
console.error(`Moved ${movedCount} session file(s) to sessions/`);
|
|
700
|
-
}
|
|
701
|
-
} catch (moveError) {
|
|
702
|
-
console.error(`Could not move session files: ${moveError}`);
|
|
790
|
+
const relayed = await enqueueWithDaemon({
|
|
791
|
+
transcriptPath,
|
|
792
|
+
cwd,
|
|
793
|
+
message
|
|
794
|
+
});
|
|
795
|
+
if (!relayed) {
|
|
796
|
+
console.error("STOP-HOOK: Using direct execution fallback.");
|
|
797
|
+
await executeDirectly(lines, transcriptPath, cwd, message, lastUserQuery);
|
|
703
798
|
}
|
|
704
799
|
console.error(`STOP-HOOK COMPLETED SUCCESSFULLY at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
705
800
|
`);
|