@tekmidian/pai 0.6.6 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +50 -0
- package/PLUGIN-ARCHITECTURE.md +365 -0
- package/dist/cli/index.mjs +1 -1
- package/dist/daemon/index.mjs +1 -1
- package/dist/{daemon-D3hYb5_C.mjs → daemon-DJoesjez.mjs} +847 -4
- package/dist/daemon-DJoesjez.mjs.map +1 -0
- package/dist/hooks/context-compression-hook.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +4 -23
- package/dist/hooks/load-project-context.mjs.map +2 -2
- package/dist/hooks/stop-hook.mjs +206 -125
- package/dist/hooks/stop-hook.mjs.map +3 -3
- package/dist/hooks/sync-todo-to-md.mjs.map +1 -1
- package/gemini-extension.json +26 -0
- package/package.json +8 -2
- package/pai-plugin.json +212 -0
- package/plugins/context-preservation/hooks/hooks.json +23 -0
- package/plugins/context-preservation/plugin.json +10 -0
- package/plugins/core/hooks/hooks.json +37 -0
- package/plugins/core/plugin.json +10 -0
- package/plugins/creative/plugin.json +10 -0
- package/plugins/observability/hooks/hooks.json +75 -0
- package/plugins/observability/plugin.json +11 -0
- package/plugins/productivity/hooks/hooks.json +17 -0
- package/plugins/productivity/plugin.json +11 -0
- package/plugins/semantic-search/plugin.json +21 -0
- package/plugins/ui/hooks/hooks.json +17 -0
- package/plugins/ui/plugin.json +11 -0
- package/plugins/zettelkasten/plugin.json +19 -0
- package/src/hooks/ts/lib/project-utils/session-notes.ts +24 -5
- package/src/hooks/ts/session-start/load-project-context.ts +9 -25
- package/src/hooks/ts/stop/stop-hook.ts +259 -199
- package/user-extensions/README.md +87 -0
- package/user-extensions/hooks/.gitkeep +0 -0
- package/user-extensions/skills/.gitkeep +0 -0
- 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,16 @@ 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.startsWith("/")) return true;
|
|
275
|
+
if (t.startsWith("#!")) return true;
|
|
276
|
+
if (t.includes("[object Object]")) return true;
|
|
277
|
+
if (/^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]+)?$/.test(t)) return true;
|
|
278
|
+
if (/^\d{1,2}:\d{2}(:\d{2})?(\s*(AM|PM))?$/i.test(t)) return true;
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
269
281
|
function extractMeaningfulName(noteContent, summary) {
|
|
270
282
|
const workDoneMatch = noteContent.match(/## Work Done\n\n([\s\S]*?)(?=\n---|\n## Next)/);
|
|
271
283
|
if (workDoneMatch) {
|
|
@@ -273,23 +285,27 @@ function extractMeaningfulName(noteContent, summary) {
|
|
|
273
285
|
const subheadings = workDoneSection.match(/### ([^\n]+)/g);
|
|
274
286
|
if (subheadings && subheadings.length > 0) {
|
|
275
287
|
const firstHeading = subheadings[0].replace("### ", "").trim();
|
|
276
|
-
if (firstHeading.length > 5 && firstHeading.length < 60) {
|
|
288
|
+
if (!isMeaninglessCandidate(firstHeading) && firstHeading.length > 5 && firstHeading.length < 60) {
|
|
277
289
|
return sanitizeForFilename(firstHeading);
|
|
278
290
|
}
|
|
279
291
|
}
|
|
280
292
|
const boldMatches = workDoneSection.match(/\*\*([^*]+)\*\*/g);
|
|
281
293
|
if (boldMatches && boldMatches.length > 0) {
|
|
282
294
|
const firstBold = boldMatches[0].replace(/\*\*/g, "").trim();
|
|
283
|
-
if (firstBold.length > 3 && firstBold.length < 50) {
|
|
295
|
+
if (!isMeaninglessCandidate(firstBold) && firstBold.length > 3 && firstBold.length < 50) {
|
|
284
296
|
return sanitizeForFilename(firstBold);
|
|
285
297
|
}
|
|
286
298
|
}
|
|
287
299
|
const numberedItems = workDoneSection.match(/^\d+\.\s+\*\*([^*]+)\*\*/m);
|
|
288
|
-
if (numberedItems
|
|
300
|
+
if (numberedItems && !isMeaninglessCandidate(numberedItems[1])) {
|
|
301
|
+
return sanitizeForFilename(numberedItems[1]);
|
|
302
|
+
}
|
|
289
303
|
}
|
|
290
|
-
if (summary && summary.length > 5 && summary !== "Session completed.") {
|
|
304
|
+
if (summary && summary.length > 5 && summary !== "Session completed." && !isMeaninglessCandidate(summary)) {
|
|
291
305
|
const cleanSummary = summary.replace(/[^\w\s-]/g, " ").trim().split(/\s+/).slice(0, 5).join(" ");
|
|
292
|
-
if (cleanSummary.length > 3
|
|
306
|
+
if (cleanSummary.length > 3 && !isMeaninglessCandidate(cleanSummary)) {
|
|
307
|
+
return sanitizeForFilename(cleanSummary);
|
|
308
|
+
}
|
|
293
309
|
}
|
|
294
310
|
return "";
|
|
295
311
|
}
|
|
@@ -418,6 +434,105 @@ ${stateLines}
|
|
|
418
434
|
}
|
|
419
435
|
|
|
420
436
|
// src/hooks/ts/stop/stop-hook.ts
|
|
437
|
+
var DAEMON_SOCKET = process.env.PAI_SOCKET ?? "/tmp/pai.sock";
|
|
438
|
+
var DAEMON_TIMEOUT_MS = 3e3;
|
|
439
|
+
function contentToText(content) {
|
|
440
|
+
if (typeof content === "string") return content;
|
|
441
|
+
if (Array.isArray(content)) {
|
|
442
|
+
return content.map((c) => {
|
|
443
|
+
if (typeof c === "string") return c;
|
|
444
|
+
if (c?.text) return c.text;
|
|
445
|
+
if (c?.content) return String(c.content);
|
|
446
|
+
return "";
|
|
447
|
+
}).join(" ").trim();
|
|
448
|
+
}
|
|
449
|
+
return "";
|
|
450
|
+
}
|
|
451
|
+
function extractCompletedMessage(lines) {
|
|
452
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
453
|
+
try {
|
|
454
|
+
const entry = JSON.parse(lines[i]);
|
|
455
|
+
if (entry.type === "assistant" && entry.message?.content) {
|
|
456
|
+
const content = contentToText(entry.message.content);
|
|
457
|
+
const m = content.match(/COMPLETED:\s*(.+?)(?:\n|$)/i);
|
|
458
|
+
if (m) {
|
|
459
|
+
return m[1].trim().replace(/\*+/g, "").replace(/\[.*?\]/g, "").trim();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return "";
|
|
466
|
+
}
|
|
467
|
+
function enqueueWithDaemon(payload) {
|
|
468
|
+
return new Promise((resolve2) => {
|
|
469
|
+
let done = false;
|
|
470
|
+
let buffer = "";
|
|
471
|
+
let timer = null;
|
|
472
|
+
function finish(ok) {
|
|
473
|
+
if (done) return;
|
|
474
|
+
done = true;
|
|
475
|
+
if (timer !== null) {
|
|
476
|
+
clearTimeout(timer);
|
|
477
|
+
timer = null;
|
|
478
|
+
}
|
|
479
|
+
try {
|
|
480
|
+
client.destroy();
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
resolve2(ok);
|
|
484
|
+
}
|
|
485
|
+
const client = connect(DAEMON_SOCKET, () => {
|
|
486
|
+
const msg = JSON.stringify({
|
|
487
|
+
id: randomUUID(),
|
|
488
|
+
method: "work_queue_enqueue",
|
|
489
|
+
params: {
|
|
490
|
+
type: "session-end",
|
|
491
|
+
priority: 2,
|
|
492
|
+
payload: {
|
|
493
|
+
transcriptPath: payload.transcriptPath,
|
|
494
|
+
cwd: payload.cwd,
|
|
495
|
+
message: payload.message
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}) + "\n";
|
|
499
|
+
client.write(msg);
|
|
500
|
+
});
|
|
501
|
+
client.on("data", (chunk) => {
|
|
502
|
+
buffer += chunk.toString();
|
|
503
|
+
const nl = buffer.indexOf("\n");
|
|
504
|
+
if (nl === -1) return;
|
|
505
|
+
const line = buffer.slice(0, nl);
|
|
506
|
+
try {
|
|
507
|
+
const response = JSON.parse(line);
|
|
508
|
+
if (response.ok) {
|
|
509
|
+
console.error(`STOP-HOOK: Work enqueued with daemon (id=${response.result?.id}).`);
|
|
510
|
+
finish(true);
|
|
511
|
+
} else {
|
|
512
|
+
console.error(`STOP-HOOK: Daemon rejected enqueue: ${response.error}`);
|
|
513
|
+
finish(false);
|
|
514
|
+
}
|
|
515
|
+
} catch {
|
|
516
|
+
finish(false);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
client.on("error", (e) => {
|
|
520
|
+
if (e.code === "ENOENT" || e.code === "ECONNREFUSED") {
|
|
521
|
+
console.error("STOP-HOOK: Daemon not running \u2014 falling back to direct execution.");
|
|
522
|
+
} else {
|
|
523
|
+
console.error(`STOP-HOOK: Daemon socket error: ${e.message}`);
|
|
524
|
+
}
|
|
525
|
+
finish(false);
|
|
526
|
+
});
|
|
527
|
+
client.on("end", () => {
|
|
528
|
+
if (!done) finish(false);
|
|
529
|
+
});
|
|
530
|
+
timer = setTimeout(() => {
|
|
531
|
+
console.error(`STOP-HOOK: Daemon timeout after ${DAEMON_TIMEOUT_MS}ms \u2014 falling back.`);
|
|
532
|
+
finish(false);
|
|
533
|
+
}, DAEMON_TIMEOUT_MS);
|
|
534
|
+
});
|
|
535
|
+
}
|
|
421
536
|
function extractWorkFromTranscript(lines) {
|
|
422
537
|
const workItems = [];
|
|
423
538
|
const seenSummaries = /* @__PURE__ */ new Set();
|
|
@@ -446,13 +561,10 @@ function extractWorkFromTranscript(lines) {
|
|
|
446
561
|
}
|
|
447
562
|
const completedMatch = content.match(/COMPLETED:\s*(.+?)(?:\n|$)/i);
|
|
448
563
|
if (completedMatch && workItems.length === 0) {
|
|
449
|
-
const completed = completedMatch[1].trim().replace(/\*+/g, "");
|
|
564
|
+
const completed = completedMatch[1].trim().replace(/\*+/g, "").replace(/\[.*?\]/g, "");
|
|
450
565
|
if (completed && !seenSummaries.has(completed) && completed.length > 5) {
|
|
451
566
|
seenSummaries.add(completed);
|
|
452
|
-
workItems.push({
|
|
453
|
-
title: completed,
|
|
454
|
-
completed: true
|
|
455
|
-
});
|
|
567
|
+
workItems.push({ title: completed, completed: true });
|
|
456
568
|
}
|
|
457
569
|
}
|
|
458
570
|
}
|
|
@@ -467,9 +579,7 @@ function generateTabTitle(prompt, completedLine) {
|
|
|
467
579
|
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
580
|
if (completedWords.length >= 2) {
|
|
469
581
|
const summary = completedWords.slice(0, 4);
|
|
470
|
-
while (summary.length < 4)
|
|
471
|
-
summary.push("Done");
|
|
472
|
-
}
|
|
582
|
+
while (summary.length < 4) summary.push("Done");
|
|
473
583
|
return summary.slice(0, 4).join(" ");
|
|
474
584
|
}
|
|
475
585
|
}
|
|
@@ -494,37 +604,80 @@ function generateTabTitle(prompt, completedLine) {
|
|
|
494
604
|
}
|
|
495
605
|
const remainingWords = words.filter((word) => !actionVerbs.includes(word.toLowerCase())).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
|
|
496
606
|
for (const word of remainingWords) {
|
|
497
|
-
if (titleWords.length < 4)
|
|
498
|
-
|
|
499
|
-
} else {
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
607
|
+
if (titleWords.length < 4) titleWords.push(word);
|
|
608
|
+
else break;
|
|
502
609
|
}
|
|
503
|
-
if (titleWords.length === 0)
|
|
504
|
-
|
|
610
|
+
if (titleWords.length === 0) titleWords.push("Completed");
|
|
611
|
+
if (titleWords.length === 1) titleWords.push("Task");
|
|
612
|
+
if (titleWords.length === 2) titleWords.push("Successfully");
|
|
613
|
+
if (titleWords.length === 3) titleWords.push("Done");
|
|
614
|
+
return titleWords.slice(0, 4).join(" ");
|
|
615
|
+
}
|
|
616
|
+
async function executeDirectly(lines, transcriptPath, cwd, message, lastUserQuery) {
|
|
617
|
+
let tabTitle = message || "";
|
|
618
|
+
if (!tabTitle && lastUserQuery) {
|
|
619
|
+
tabTitle = generateTabTitle(lastUserQuery, "");
|
|
505
620
|
}
|
|
506
|
-
if (
|
|
507
|
-
|
|
621
|
+
if (tabTitle) {
|
|
622
|
+
try {
|
|
623
|
+
const escapedTitle = tabTitle.replace(/'/g, "'\\''");
|
|
624
|
+
const { execSync } = await import("child_process");
|
|
625
|
+
execSync(`printf '\\033]0;${escapedTitle}\\007' >&2`);
|
|
626
|
+
execSync(`printf '\\033]2;${escapedTitle}\\007' >&2`);
|
|
627
|
+
execSync(`printf '\\033]30;${escapedTitle}\\007' >&2`);
|
|
628
|
+
console.error(`Tab title set to: "${tabTitle}"`);
|
|
629
|
+
} catch (e) {
|
|
630
|
+
console.error(`Failed to set tab title: ${e}`);
|
|
631
|
+
}
|
|
508
632
|
}
|
|
509
|
-
if (
|
|
510
|
-
|
|
633
|
+
if (message) {
|
|
634
|
+
const finalTabTitle = message.slice(0, 50);
|
|
635
|
+
process.stderr.write(`\x1B]2;${finalTabTitle}\x07`);
|
|
511
636
|
}
|
|
512
|
-
|
|
513
|
-
|
|
637
|
+
try {
|
|
638
|
+
const notesInfo = findNotesDir(cwd);
|
|
639
|
+
const currentNotePath = getCurrentNotePath(notesInfo.path);
|
|
640
|
+
if (currentNotePath) {
|
|
641
|
+
const workItems = extractWorkFromTranscript(lines);
|
|
642
|
+
if (workItems.length > 0) {
|
|
643
|
+
addWorkToSessionNote(currentNotePath, workItems);
|
|
644
|
+
console.error(`Added ${workItems.length} work item(s) to session note`);
|
|
645
|
+
} else if (message) {
|
|
646
|
+
addWorkToSessionNote(currentNotePath, [{ title: message, completed: true }]);
|
|
647
|
+
console.error(`Added completion message to session note`);
|
|
648
|
+
}
|
|
649
|
+
const summary = message || "Session completed.";
|
|
650
|
+
finalizeSessionNote(currentNotePath, summary);
|
|
651
|
+
console.error(`Session note finalized: ${basename3(currentNotePath)}`);
|
|
652
|
+
try {
|
|
653
|
+
const stateLines = [];
|
|
654
|
+
stateLines.push(`Working directory: ${cwd}`);
|
|
655
|
+
if (workItems.length > 0) {
|
|
656
|
+
stateLines.push("", "Work completed:");
|
|
657
|
+
for (const item of workItems.slice(0, 5)) {
|
|
658
|
+
stateLines.push(`- ${item.title}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (message) {
|
|
662
|
+
stateLines.push("", `Last completed: ${message}`);
|
|
663
|
+
}
|
|
664
|
+
updateTodoContinue(cwd, basename3(currentNotePath), stateLines.join("\n"), "session-end");
|
|
665
|
+
} catch (todoError) {
|
|
666
|
+
console.error(`Could not update TODO.md: ${todoError}`);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
} catch (noteError) {
|
|
670
|
+
console.error(`Could not finalize session note: ${noteError}`);
|
|
514
671
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (c?.content) return String(c.content);
|
|
524
|
-
return "";
|
|
525
|
-
}).join(" ").trim();
|
|
672
|
+
try {
|
|
673
|
+
const transcriptDir = dirname(transcriptPath);
|
|
674
|
+
const movedCount = moveSessionFilesToSessionsDir(transcriptDir);
|
|
675
|
+
if (movedCount > 0) {
|
|
676
|
+
console.error(`Moved ${movedCount} session file(s) to sessions/`);
|
|
677
|
+
}
|
|
678
|
+
} catch (moveError) {
|
|
679
|
+
console.error(`Could not move session files: ${moveError}`);
|
|
526
680
|
}
|
|
527
|
-
return "";
|
|
528
681
|
}
|
|
529
682
|
async function main() {
|
|
530
683
|
if (isProbeSession()) {
|
|
@@ -590,45 +743,20 @@ STOP-HOOK TRIGGERED AT ${timestamp}`);
|
|
|
590
743
|
}
|
|
591
744
|
if (lastUserQuery) break;
|
|
592
745
|
}
|
|
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
|
-
}
|
|
746
|
+
} catch {
|
|
607
747
|
}
|
|
608
|
-
} catch (e) {
|
|
609
|
-
console.error("Error parsing assistant response:", e);
|
|
610
748
|
}
|
|
749
|
+
const message = extractCompletedMessage(lines);
|
|
750
|
+
console.error(`User query: ${lastUserQuery || "No query found"}`);
|
|
751
|
+
console.error(`Message: ${message || "No completion message"}`);
|
|
611
752
|
let tabTitle = message || "";
|
|
612
753
|
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
|
-
}
|
|
754
|
+
tabTitle = generateTabTitle(lastUserQuery, "");
|
|
627
755
|
}
|
|
628
756
|
if (tabTitle) {
|
|
629
757
|
try {
|
|
630
|
-
const escapedTitle = tabTitle.replace(/'/g, "'\\''");
|
|
631
758
|
const { execSync } = await import("child_process");
|
|
759
|
+
const escapedTitle = tabTitle.replace(/'/g, "'\\''");
|
|
632
760
|
execSync(`printf '\\033]0;${escapedTitle}\\007' >&2`);
|
|
633
761
|
execSync(`printf '\\033]2;${escapedTitle}\\007' >&2`);
|
|
634
762
|
execSync(`printf '\\033]30;${escapedTitle}\\007' >&2`);
|
|
@@ -637,69 +765,22 @@ STOP-HOOK TRIGGERED AT ${timestamp}`);
|
|
|
637
765
|
console.error(`Failed to set tab title: ${e}`);
|
|
638
766
|
}
|
|
639
767
|
}
|
|
640
|
-
console.error(`User query: ${lastUserQuery || "No query found"}`);
|
|
641
|
-
console.error(`Message: ${message || "No completion message"}`);
|
|
642
768
|
if (message) {
|
|
643
|
-
|
|
644
|
-
process.stderr.write(`\x1B]2;${finalTabTitle}\x07`);
|
|
769
|
+
process.stderr.write(`\x1B]2;${message.slice(0, 50)}\x07`);
|
|
645
770
|
}
|
|
646
771
|
if (message) {
|
|
647
772
|
await sendNtfyNotification(message);
|
|
648
773
|
} else {
|
|
649
774
|
await sendNtfyNotification("Session ended");
|
|
650
775
|
}
|
|
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}`);
|
|
776
|
+
const relayed = await enqueueWithDaemon({
|
|
777
|
+
transcriptPath,
|
|
778
|
+
cwd,
|
|
779
|
+
message
|
|
780
|
+
});
|
|
781
|
+
if (!relayed) {
|
|
782
|
+
console.error("STOP-HOOK: Using direct execution fallback.");
|
|
783
|
+
await executeDirectly(lines, transcriptPath, cwd, message, lastUserQuery);
|
|
703
784
|
}
|
|
704
785
|
console.error(`STOP-HOOK COMPLETED SUCCESSFULLY at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
705
786
|
`);
|