@hayasaka7/haya-pet 0.2.1 → 0.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/.github/workflows/ci.yml +75 -0
- package/CHANGELOG.md +18 -0
- package/README.md +3 -2
- package/apps/companion/src/renderer/task-talk-window.js +1 -1
- package/eslint.config.js +32 -0
- package/package.json +7 -1
- package/packages/cli-core/src/run-command.js +0 -1
- package/packages/session-core/src/bubble-view.js +10 -7
- package/packages/session-core/test/bubble-view.test.mjs +30 -5
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Run code quality checks and the test suite on every push that touches code.
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
paths:
|
|
7
|
+
- "**/*.js"
|
|
8
|
+
- "**/*.mjs"
|
|
9
|
+
- "**/*.cjs"
|
|
10
|
+
- "package.json"
|
|
11
|
+
- "package-lock.json"
|
|
12
|
+
- ".github/workflows/ci.yml"
|
|
13
|
+
pull_request:
|
|
14
|
+
paths:
|
|
15
|
+
- "**/*.js"
|
|
16
|
+
- "**/*.mjs"
|
|
17
|
+
- "**/*.cjs"
|
|
18
|
+
- "package.json"
|
|
19
|
+
- "package-lock.json"
|
|
20
|
+
- ".github/workflows/ci.yml"
|
|
21
|
+
|
|
22
|
+
concurrency:
|
|
23
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
24
|
+
cancel-in-progress: true
|
|
25
|
+
|
|
26
|
+
permissions:
|
|
27
|
+
contents: read
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
lint:
|
|
31
|
+
name: Code quality (ESLint)
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- name: Set up Node.js
|
|
37
|
+
uses: actions/setup-node@v4
|
|
38
|
+
with:
|
|
39
|
+
node-version: 22
|
|
40
|
+
cache: npm
|
|
41
|
+
|
|
42
|
+
- name: Install dependencies
|
|
43
|
+
# Electron's binary isn't needed for linting or tests; skip the ~150 MB
|
|
44
|
+
# download so CI is fast and isn't at the mercy of the Electron CDN.
|
|
45
|
+
env:
|
|
46
|
+
ELECTRON_SKIP_BINARY_DOWNLOAD: "1"
|
|
47
|
+
run: npm ci
|
|
48
|
+
|
|
49
|
+
- name: Run ESLint
|
|
50
|
+
run: npm run lint
|
|
51
|
+
|
|
52
|
+
test:
|
|
53
|
+
name: Tests (Node ${{ matrix.node }} on ${{ matrix.os }})
|
|
54
|
+
runs-on: ${{ matrix.os }}
|
|
55
|
+
strategy:
|
|
56
|
+
fail-fast: false
|
|
57
|
+
matrix:
|
|
58
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
59
|
+
node: [20, 22]
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/checkout@v4
|
|
62
|
+
|
|
63
|
+
- name: Set up Node.js
|
|
64
|
+
uses: actions/setup-node@v4
|
|
65
|
+
with:
|
|
66
|
+
node-version: ${{ matrix.node }}
|
|
67
|
+
cache: npm
|
|
68
|
+
|
|
69
|
+
- name: Install dependencies
|
|
70
|
+
env:
|
|
71
|
+
ELECTRON_SKIP_BINARY_DOWNLOAD: "1"
|
|
72
|
+
run: npm ci
|
|
73
|
+
|
|
74
|
+
- name: Run the test suite
|
|
75
|
+
run: npm test
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ All notable changes to Haya Pet are documented here. This project adheres to
|
|
|
7
7
|
> 0.2.0 npm publish; they are listed under 0.2.1, which is the first version that
|
|
8
8
|
> ships them.
|
|
9
9
|
|
|
10
|
+
## [0.2.2]
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Session bubbles no longer reshuffle while sessions run.** Bubbles used to be
|
|
14
|
+
sorted by state urgency and latest activity, so every status change could move
|
|
15
|
+
a bubble up or down the stack mid-progress. They now stack by the time each
|
|
16
|
+
session **connected to the pet** — newest on top, first one at the bottom —
|
|
17
|
+
and that order stays fixed for the session's whole life. Urgency still shows
|
|
18
|
+
through each bubble's status icon, the collapsed-folder summary dot, and the
|
|
19
|
+
pet animation.
|
|
20
|
+
|
|
21
|
+
### Internal
|
|
22
|
+
- **CI on every code push** — a new GitHub Actions workflow lints and runs the
|
|
23
|
+
test suite (Ubuntu + Windows + macOS, Node 20/22) for any push or PR touching
|
|
24
|
+
code.
|
|
25
|
+
- **ESLint adopted** (`npm run lint`, flat config); the few existing findings
|
|
26
|
+
were fixed with no behavior change.
|
|
27
|
+
|
|
10
28
|
## [0.2.1]
|
|
11
29
|
|
|
12
30
|
### Added
|
package/README.md
CHANGED
|
@@ -38,8 +38,9 @@ Haya Pet watches all of them and presents one ambient interface:
|
|
|
38
38
|
draggable, and position-persistent like a real desktop companion.
|
|
39
39
|
- **Session bubbles** — one compact bubble per active session showing client,
|
|
40
40
|
project, the latest activity, and a status icon (a spinning *working* circle, a
|
|
41
|
-
green *done* check, a yellow *needs you*, or a red *failed* cross).
|
|
42
|
-
|
|
41
|
+
green *done* check, a yellow *needs you*, or a red *failed* cross). Bubbles stack
|
|
42
|
+
by connect time — the newest session on top — so the stack never reshuffles while
|
|
43
|
+
work is in progress. A folder button beside the pet folds them away.
|
|
43
44
|
|
|
44
45
|
## Features
|
|
45
46
|
|
|
@@ -121,7 +121,7 @@ function renderComposer(bubble, replyMode, bridge) {
|
|
|
121
121
|
return composer;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
function renderControls(controlsPromise,
|
|
124
|
+
function renderControls(controlsPromise, _bubble, _bridge) {
|
|
125
125
|
const wrap = document.createElement("div");
|
|
126
126
|
wrap.className = "controls";
|
|
127
127
|
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
{
|
|
6
|
+
ignores: [
|
|
7
|
+
"node_modules/**",
|
|
8
|
+
"native/**",
|
|
9
|
+
"tmp/**",
|
|
10
|
+
"docs/**",
|
|
11
|
+
".gax/**",
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
js.configs.recommended,
|
|
15
|
+
{
|
|
16
|
+
files: ["**/*.js", "**/*.mjs", "**/*.cjs"],
|
|
17
|
+
languageOptions: {
|
|
18
|
+
ecmaVersion: "latest",
|
|
19
|
+
sourceType: "module",
|
|
20
|
+
globals: {
|
|
21
|
+
...globals.node,
|
|
22
|
+
...globals.browser,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
rules: {
|
|
26
|
+
"no-unused-vars": [
|
|
27
|
+
"error",
|
|
28
|
+
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hayasaka7/haya-pet",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Generic AI CLI pet runtime foundation.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"haya-pet": "apps/cli/src/haya-pet.js"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
+
"lint": "eslint .",
|
|
20
21
|
"test": "node test/run-tests.mjs"
|
|
21
22
|
},
|
|
22
23
|
"workspaces": [
|
|
@@ -31,5 +32,10 @@
|
|
|
31
32
|
},
|
|
32
33
|
"engines": {
|
|
33
34
|
"node": ">=16.20.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@eslint/js": "^10.0.1",
|
|
38
|
+
"eslint": "^10.4.1",
|
|
39
|
+
"globals": "^17.6.0"
|
|
34
40
|
}
|
|
35
41
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { mapAiStateToPetAction } from "../../pet-core/src/atlas.js";
|
|
2
|
-
import { getSessionPriorityRank } from "./priority.js";
|
|
3
2
|
import { buildSessionSummary, buildStatusLabel, formatElapsed } from "./summaries.js";
|
|
4
3
|
|
|
5
4
|
// Collapses the full AI-state vocabulary into the four progress kinds the
|
|
@@ -53,17 +52,21 @@ export function buildBubbleViews(sessions, now = Date.now(), options = {}) {
|
|
|
53
52
|
return sessions
|
|
54
53
|
.filter(Boolean)
|
|
55
54
|
.slice()
|
|
56
|
-
.sort(
|
|
55
|
+
.sort(compareByConnectTime)
|
|
57
56
|
.map((session) => buildBubbleView(session, now, options));
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
// Bubbles stack by connect time — the newest session on top, the first one at
|
|
60
|
+
// the bottom — and never reshuffle while sessions are in progress. State
|
|
61
|
+
// urgency only drives the collapsed-folder dot and the pet animation, not the
|
|
62
|
+
// list order.
|
|
63
|
+
function compareByConnectTime(left, right) {
|
|
64
|
+
const startedDelta = numeric(right.startedAt) - numeric(left.startedAt);
|
|
65
|
+
if (startedDelta !== 0) {
|
|
66
|
+
return startedDelta;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
return
|
|
69
|
+
return String(left.sessionId).localeCompare(String(right.sessionId));
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
function safePetAction(state) {
|
|
@@ -28,15 +28,40 @@ test("builds a bubble view model with label, summary, action, and elapsed", () =
|
|
|
28
28
|
assert.equal(view.elapsedLabel, "1m 4s");
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test("
|
|
31
|
+
test("stacks bubbles by connect time, newest on top, so they never reshuffle mid-session", () => {
|
|
32
32
|
const sessions = [
|
|
33
|
-
{ ...baseSession, sessionId: "
|
|
34
|
-
{ ...baseSession, sessionId: "
|
|
35
|
-
{ ...baseSession, sessionId: "
|
|
33
|
+
{ ...baseSession, sessionId: "sess_third", state: "waiting_approval", startedAt: 3_000, updatedAt: 4_000 },
|
|
34
|
+
{ ...baseSession, sessionId: "sess_first", state: "idle", startedAt: 1_000, updatedAt: 9_000 },
|
|
35
|
+
{ ...baseSession, sessionId: "sess_second", state: "running_tool", startedAt: 2_000, updatedAt: 8_000 }
|
|
36
36
|
];
|
|
37
37
|
|
|
38
38
|
const views = buildBubbleViews(sessions, 10_000);
|
|
39
|
-
assert.deepEqual(views.map((view) => view.sessionId), ["
|
|
39
|
+
assert.deepEqual(views.map((view) => view.sessionId), ["sess_third", "sess_second", "sess_first"]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("keeps bubble order stable when states and activity change", () => {
|
|
43
|
+
const before = [
|
|
44
|
+
{ ...baseSession, sessionId: "sess_first", state: "running_tool", startedAt: 1_000, updatedAt: 2_000 },
|
|
45
|
+
{ ...baseSession, sessionId: "sess_second", state: "idle", startedAt: 2_000, updatedAt: 2_500 }
|
|
46
|
+
];
|
|
47
|
+
// Later, the second session becomes urgent and more recently active.
|
|
48
|
+
const after = [
|
|
49
|
+
{ ...baseSession, sessionId: "sess_first", state: "idle", startedAt: 1_000, updatedAt: 3_000 },
|
|
50
|
+
{ ...baseSession, sessionId: "sess_second", state: "waiting_approval", startedAt: 2_000, updatedAt: 9_000 }
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const order = (sessions) => buildBubbleViews(sessions, 10_000).map((view) => view.sessionId);
|
|
54
|
+
assert.deepEqual(order(before), order(after));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("breaks connect-time ties by session id for a deterministic order", () => {
|
|
58
|
+
const sessions = [
|
|
59
|
+
{ ...baseSession, sessionId: "sess_b", startedAt: 1_000 },
|
|
60
|
+
{ ...baseSession, sessionId: "sess_a", startedAt: 1_000 }
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const views = buildBubbleViews(sessions, 10_000);
|
|
64
|
+
assert.deepEqual(views.map((view) => view.sessionId), ["sess_a", "sess_b"]);
|
|
40
65
|
});
|
|
41
66
|
|
|
42
67
|
test("marks the selected/pinned session", () => {
|