@hayasaka7/haya-pet 0.3.6 → 0.3.8

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.
@@ -3,7 +3,8 @@ import { mkdtempSync, readFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { test } from "../../../test/harness.mjs";
6
- import { parseStateArgs, runStateCommand } from "../src/run-state.js";
6
+ import { extractTranscriptPath, parseStateArgs, runStateCommand } from "../src/run-state.js";
7
+ import { readSessionTranscriptLink } from "../src/session-transcript-link.js";
7
8
 
8
9
  test("runStateCommand appends a debug line when HAYA_PET_HOOK_DEBUG is set", async () => {
9
10
  const logPath = join(mkdtempSync(join(tmpdir(), "haya-dbg-")), "hooks.jsonl");
@@ -21,6 +22,51 @@ test("runStateCommand appends a debug line when HAYA_PET_HOOK_DEBUG is set", asy
21
22
  assert.deepEqual(line, { ts: 7, state: "waiting_approval", sessionId: "s1", summary: "approval" });
22
23
  });
23
24
 
25
+ test("extractTranscriptPath pulls transcript_path out of a Claude hook payload", () => {
26
+ assert.equal(
27
+ extractTranscriptPath(JSON.stringify({ session_id: "x", transcript_path: "/p/a.jsonl", cwd: "/p" })),
28
+ "/p/a.jsonl"
29
+ );
30
+ // Defensive: junk, missing field, wrong type, and empty all yield undefined.
31
+ assert.equal(extractTranscriptPath("{not json"), undefined);
32
+ assert.equal(extractTranscriptPath(JSON.stringify({ session_id: "x" })), undefined);
33
+ assert.equal(extractTranscriptPath(JSON.stringify({ transcript_path: 42 })), undefined);
34
+ assert.equal(extractTranscriptPath(""), undefined);
35
+ assert.equal(extractTranscriptPath(undefined), undefined);
36
+ });
37
+
38
+ test("runStateCommand records the session->transcript link when given a transcript path", async () => {
39
+ const sessionDir = mkdtempSync(join(tmpdir(), "sess-"));
40
+ await runStateCommand(
41
+ { command: "state", state: "thinking", summary: undefined, session: "sess_link" },
42
+ {
43
+ now: () => 1,
44
+ sessionDir,
45
+ transcriptPath: "/p/.claude/projects/D--p/abc.jsonl",
46
+ createIpcClient: async () => ({ send: async () => {}, close: async () => {} })
47
+ }
48
+ );
49
+
50
+ assert.equal(
51
+ readSessionTranscriptLink({ sessionDir, sessionId: "sess_link" }),
52
+ "/p/.claude/projects/D--p/abc.jsonl"
53
+ );
54
+ });
55
+
56
+ test("runStateCommand writes no link when no transcript path is supplied", async () => {
57
+ const sessionDir = mkdtempSync(join(tmpdir(), "sess-"));
58
+ await runStateCommand(
59
+ { command: "state", state: "thinking", summary: undefined, session: "sess_nolink" },
60
+ {
61
+ now: () => 1,
62
+ sessionDir,
63
+ createIpcClient: async () => ({ send: async () => {}, close: async () => {} })
64
+ }
65
+ );
66
+
67
+ assert.equal(readSessionTranscriptLink({ sessionDir, sessionId: "sess_nolink" }), undefined);
68
+ });
69
+
24
70
  test("parseStateArgs reads state, summary, and session", () => {
25
71
  assert.deepEqual(parseStateArgs(["thinking"]), {
26
72
  command: "state",
@@ -0,0 +1,67 @@
1
+ import assert from "node:assert/strict";
2
+ import { existsSync, mkdtempSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { test } from "../../../test/harness.mjs";
6
+ import {
7
+ readSessionTranscriptLink,
8
+ removeSessionTranscriptLink,
9
+ sessionLinkPath,
10
+ writeSessionTranscriptLink
11
+ } from "../src/session-transcript-link.js";
12
+
13
+ test("write then read round-trips a session's transcript path", () => {
14
+ const sessionDir = mkdtempSync(join(tmpdir(), "sess-"));
15
+ const transcriptPath = "D:\\proj\\.claude\\projects\\D--proj\\abc.jsonl";
16
+
17
+ const wrote = writeSessionTranscriptLink({ sessionDir, sessionId: "sess_a", transcriptPath });
18
+ assert.equal(wrote, true);
19
+ assert.equal(readSessionTranscriptLink({ sessionDir, sessionId: "sess_a" }), transcriptPath);
20
+ });
21
+
22
+ test("two sessions in the same dir keep separate, independent links", () => {
23
+ const sessionDir = mkdtempSync(join(tmpdir(), "sess-"));
24
+ writeSessionTranscriptLink({ sessionDir, sessionId: "sess_a", transcriptPath: "/p/a.jsonl" });
25
+ writeSessionTranscriptLink({ sessionDir, sessionId: "sess_b", transcriptPath: "/p/b.jsonl" });
26
+
27
+ // The core of the bug fix: each session resolves ONLY its own transcript.
28
+ assert.equal(readSessionTranscriptLink({ sessionDir, sessionId: "sess_a" }), "/p/a.jsonl");
29
+ assert.equal(readSessionTranscriptLink({ sessionDir, sessionId: "sess_b" }), "/p/b.jsonl");
30
+ });
31
+
32
+ test("reading a session with no link returns undefined (no guessing)", () => {
33
+ const sessionDir = mkdtempSync(join(tmpdir(), "sess-"));
34
+ assert.equal(readSessionTranscriptLink({ sessionDir, sessionId: "missing" }), undefined);
35
+ });
36
+
37
+ test("write is a no-op without the required fields", () => {
38
+ const sessionDir = mkdtempSync(join(tmpdir(), "sess-"));
39
+ assert.equal(writeSessionTranscriptLink({ sessionDir, sessionId: "s" }), false);
40
+ assert.equal(writeSessionTranscriptLink({ sessionDir, transcriptPath: "/p/a.jsonl" }), false);
41
+ assert.equal(writeSessionTranscriptLink({ sessionId: "s", transcriptPath: "/p/a.jsonl" }), false);
42
+ });
43
+
44
+ test("remove deletes the link and is safe when it is already gone", () => {
45
+ const sessionDir = mkdtempSync(join(tmpdir(), "sess-"));
46
+ writeSessionTranscriptLink({ sessionDir, sessionId: "sess_a", transcriptPath: "/p/a.jsonl" });
47
+ const path = sessionLinkPath(sessionDir, "sess_a");
48
+ assert.equal(existsSync(path), true);
49
+
50
+ removeSessionTranscriptLink({ sessionDir, sessionId: "sess_a" });
51
+ assert.equal(existsSync(path), false);
52
+ // Second removal must not throw.
53
+ removeSessionTranscriptLink({ sessionDir, sessionId: "sess_a" });
54
+ });
55
+
56
+ test("a corrupt link file reads as undefined rather than throwing", () => {
57
+ const sessionDir = mkdtempSync(join(tmpdir(), "sess-"));
58
+ writeSessionTranscriptLink(
59
+ { sessionDir, sessionId: "sess_a", transcriptPath: "/p/a.jsonl" },
60
+ { writeFileSync: () => {} } // pretend write; nothing on disk
61
+ );
62
+ // Inject a reader returning junk to simulate a partially-written file.
63
+ assert.equal(
64
+ readSessionTranscriptLink({ sessionDir, sessionId: "sess_a" }, { readFileSync: () => "{not json" }),
65
+ undefined
66
+ );
67
+ });
@@ -30,6 +30,7 @@ function getWindowsPaths(env, homeDir) {
30
30
  statePath: joinWindows(localAppData, "haya-pet", "state.json"),
31
31
  configPath: joinWindows(appData, "haya-pet", "config.json"),
32
32
  logDir: joinWindows(localAppData, "haya-pet", "logs"),
33
+ sessionDir: joinWindows(localAppData, "haya-pet", "sessions"),
33
34
  petSearchPaths: [
34
35
  joinWindows(homeDir, ".codex", "pets"),
35
36
  joinWindows(localAppData, "haya-pet", "pets")
@@ -44,6 +45,7 @@ function getUnixPaths(homeDir) {
44
45
  statePath: joinUnix(homeDir, ".haya-pet", "state.json"),
45
46
  configPath: joinUnix(homeDir, ".haya-pet", "config.json"),
46
47
  logDir: joinUnix(homeDir, ".haya-pet", "logs"),
48
+ sessionDir: joinUnix(homeDir, ".haya-pet", "sessions"),
47
49
  petSearchPaths: [
48
50
  joinUnix(homeDir, ".codex", "pets"),
49
51
  joinUnix(homeDir, ".haya-pet", "pets")
@@ -58,6 +60,7 @@ function getUnsupportedPaths(homeDir) {
58
60
  statePath: joinUnix(homeDir, ".haya-pet", "state.json"),
59
61
  configPath: joinUnix(homeDir, ".haya-pet", "config.json"),
60
62
  logDir: joinUnix(homeDir, ".haya-pet", "logs"),
63
+ sessionDir: joinUnix(homeDir, ".haya-pet", "sessions"),
61
64
  petSearchPaths: [
62
65
  joinUnix(homeDir, ".codex", "pets"),
63
66
  joinUnix(homeDir, ".haya-pet", "pets")