@tryinget/pi-activity-strip 0.2.0
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/LICENSE +78 -0
- package/README.md +203 -0
- package/bin/pi-activity-strip.mjs +226 -0
- package/examples/simulate-sessions.mjs +64 -0
- package/extensions/activity-strip.js +202 -0
- package/extensions/activity-strip.ts +1 -0
- package/package.json +94 -0
- package/policy/security-policy.json +10 -0
- package/prompts/pi-activity-strip-implementation-planning.md +21 -0
- package/prompts/pi-activity-strip-security-review.md +21 -0
- package/src/broker/server.mjs +165 -0
- package/src/broker/session-store.mjs +49 -0
- package/src/client/broker-client.mjs +115 -0
- package/src/client/launcher.mjs +116 -0
- package/src/client/session-telemetry.mjs +214 -0
- package/src/common/compatibility.mjs +153 -0
- package/src/common/constants.mjs +35 -0
- package/src/common/contracts.d.ts +142 -0
- package/src/common/contracts.ts +142 -0
- package/src/common/electron.mjs +51 -0
- package/src/common/format.mjs +75 -0
- package/src/common/protocol.mjs +71 -0
- package/src/common/status-report.mjs +58 -0
- package/src/common/telemetry.mjs +180 -0
- package/src/electron/main.mjs +319 -0
- package/src/electron/preload.cjs +10 -0
- package/src/ui/strip-html.mjs +375 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
MIT License (with OpenAI/Anthropic/xAI/PRC Frontier Labs Rider)
|
|
2
|
+
|
|
3
|
+
Copyright (c) contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
ADDITIONAL RIDER / RESTRICTION (OpenAI / Anthropic / xAI / PRC Frontier Labs):
|
|
13
|
+
|
|
14
|
+
This rider is part of the "conditions" of this License. In the event of any
|
|
15
|
+
conflict between this rider and any other portion of this License, this rider
|
|
16
|
+
controls.
|
|
17
|
+
|
|
18
|
+
"Restricted Parties" means OpenAI, L.L.C.; Anthropic, PBC; xAI Corp.; and the
|
|
19
|
+
following PRC frontier AI labs and related entities: Baidu (including ERNIE-
|
|
20
|
+
related entities), Alibaba and Alibaba Cloud (including Qwen-related entities),
|
|
21
|
+
Tencent (including Hunyuan-related entities), ByteDance (including Doubao/Seed-
|
|
22
|
+
related entities), DeepSeek, Zhipu AI, Moonshot AI, MiniMax, 01.AI
|
|
23
|
+
(Lingyiwanwu), and iFlyTek. "Restricted Parties" also includes any of their
|
|
24
|
+
respective Affiliates and any person or entity acting directly or indirectly
|
|
25
|
+
on behalf of, for the benefit of, or under the direction of any of the
|
|
26
|
+
foregoing (including any officer, director, employee, contractor, agent,
|
|
27
|
+
consultant, service provider, or representative).
|
|
28
|
+
|
|
29
|
+
Notwithstanding any other provision of this License, no rights are granted to
|
|
30
|
+
any Restricted Party. Any purported license, sublicense, assignment, transfer,
|
|
31
|
+
or other permission to any Restricted Party is null and void absent the
|
|
32
|
+
express prior written permission of the copyright holders.
|
|
33
|
+
|
|
34
|
+
You may not provide, disclose, distribute, sublicense, sell, lease, lend,
|
|
35
|
+
host, make available, or otherwise permit access to the Software or any
|
|
36
|
+
derivative work of the Software (as defined in applicable copyright law)
|
|
37
|
+
(collectively, "Derivative Works") to or for any Restricted Party.
|
|
38
|
+
|
|
39
|
+
For purposes of this rider, "use" includes, without limitation: copying,
|
|
40
|
+
modifying, merging, publishing, distributing, sublicensing, selling,
|
|
41
|
+
transferring, making available, hosting, deploying, executing, benchmarking,
|
|
42
|
+
testing, analyzing, indexing, or incorporating the Software or any Derivative
|
|
43
|
+
Works into any dataset, training corpus, evaluation harness, or pipeline for
|
|
44
|
+
machine learning or other automated systems.
|
|
45
|
+
|
|
46
|
+
This rider applies to the Software and all Derivative Works. As a condition of
|
|
47
|
+
use, you agree that this rider is a precondition to exercising any rights
|
|
48
|
+
under this License, and you agree that any distribution of the Software or any
|
|
49
|
+
Derivative Works must include this rider provision unmodified.
|
|
50
|
+
|
|
51
|
+
Any breach of this rider automatically and immediately terminates the
|
|
52
|
+
permissions granted by this License. Upon termination, you must immediately
|
|
53
|
+
cease all use and distribution of the Software and any Derivative Works and
|
|
54
|
+
destroy all copies under your control.
|
|
55
|
+
|
|
56
|
+
You agree that a breach of this rider would cause irreparable harm and that
|
|
57
|
+
the copyright holders may seek injunctive or other equitable relief to enforce
|
|
58
|
+
this rider, in addition to any other remedies available at law. To the maximum
|
|
59
|
+
extent permitted by applicable law, the prevailing party in any action to
|
|
60
|
+
enforce this rider shall be entitled to recover reasonable attorneys' fees and
|
|
61
|
+
costs.
|
|
62
|
+
|
|
63
|
+
For purposes of this rider, "Affiliate" means any entity that directly or
|
|
64
|
+
indirectly controls, is controlled by, or is under common control with the
|
|
65
|
+
specified party. "Control" means ownership of more than 50% of the voting
|
|
66
|
+
securities or other ownership interest, or the power to direct management or
|
|
67
|
+
policies by contract or otherwise.
|
|
68
|
+
|
|
69
|
+
The above copyright notice and this permission notice shall be included in all
|
|
70
|
+
copies or substantial portions of the Software.
|
|
71
|
+
|
|
72
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
73
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
74
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
75
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
76
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
77
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
78
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Top-row live activity strip for local Pi sessions running in Ghostty or other terminals."
|
|
3
|
+
read_when:
|
|
4
|
+
- "Starting work in this package workspace."
|
|
5
|
+
- "Installing or verifying the activity strip in Pi."
|
|
6
|
+
system4d:
|
|
7
|
+
container: "Monorepo package for a local broker + Electron overlay + Pi extension telemetry seam."
|
|
8
|
+
compass: "Make Pi session activity visible at a glance without changing the operator's normal terminal workflow."
|
|
9
|
+
engine: "Pi extension emits session telemetry -> local broker aggregates -> top-row Electron strip renders live state."
|
|
10
|
+
fog: "Main risks are runtime drift across Pi host versions, Electron availability, and stale-session behavior under real long-running work."
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# @tryinget/pi-activity-strip
|
|
14
|
+
|
|
15
|
+
A Pi extension package that gives you a **screen-top activity strip** showing what your live Pi sessions are doing.
|
|
16
|
+
|
|
17
|
+
This package is designed for the exact workflow you asked for:
|
|
18
|
+
- multiple Ghostty tabs
|
|
19
|
+
- multiple Pi sessions
|
|
20
|
+
- a persistent top-row ribbon
|
|
21
|
+
- fine-grained live detail without changing how you normally run Pi
|
|
22
|
+
|
|
23
|
+
## What it does
|
|
24
|
+
|
|
25
|
+
- auto-starts a local top-row overlay when Pi starts in a TUI session
|
|
26
|
+
- tracks each active Pi session independently
|
|
27
|
+
- shows one card per live session
|
|
28
|
+
- surfaces:
|
|
29
|
+
- repo/session label
|
|
30
|
+
- current phase
|
|
31
|
+
- current tool or target
|
|
32
|
+
- fine-grained detail text
|
|
33
|
+
- elapsed time plus last-seen freshness
|
|
34
|
+
- state color (`thinking`, `tool`, `waiting`, `done`, `error`)
|
|
35
|
+
- keeps a local broker so multiple Pi processes can report into one strip
|
|
36
|
+
|
|
37
|
+
## Architecture
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
Pi session
|
|
41
|
+
-> activity-strip extension
|
|
42
|
+
-> local unix-socket broker
|
|
43
|
+
-> Electron top-row overlay
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This is intentionally **local-first**.
|
|
47
|
+
It does not require moving your workflow onto `pi-server` first.
|
|
48
|
+
|
|
49
|
+
## Current scope
|
|
50
|
+
|
|
51
|
+
Implemented now:
|
|
52
|
+
- local per-host broker
|
|
53
|
+
- primary-display top-row strip
|
|
54
|
+
- one card per active Pi session
|
|
55
|
+
- headless-safe telemetry publishing
|
|
56
|
+
- explicit open/status/doctor/snapshot/fix-top/stop commands
|
|
57
|
+
- local visual capture helpers so the agent can inspect the strip directly
|
|
58
|
+
|
|
59
|
+
Not implemented yet:
|
|
60
|
+
- multi-monitor strip replication
|
|
61
|
+
- historical timeline / expand-on-hover detail
|
|
62
|
+
- remote observers via `pi-server`
|
|
63
|
+
|
|
64
|
+
## Installation in Pi
|
|
65
|
+
|
|
66
|
+
From this package directory:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cd ~/ai-society/softwareco/owned/pi-extensions/packages/pi-activity-strip
|
|
70
|
+
pi install "$PWD"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Then for **existing Pi tabs**:
|
|
74
|
+
- run `/reload` in each tab you want tracked
|
|
75
|
+
|
|
76
|
+
For **new Pi tabs**:
|
|
77
|
+
- the package will load automatically from your Pi settings
|
|
78
|
+
|
|
79
|
+
## Operator commands
|
|
80
|
+
|
|
81
|
+
### Package-local CLI
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm run strip:open
|
|
85
|
+
npm run strip:status
|
|
86
|
+
npm run strip:doctor
|
|
87
|
+
npm run strip:snapshot
|
|
88
|
+
npm run strip:fix-top
|
|
89
|
+
npm run strip:stop
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
or directly:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
node ./bin/pi-activity-strip.mjs open
|
|
96
|
+
node ./bin/pi-activity-strip.mjs status
|
|
97
|
+
node ./bin/pi-activity-strip.mjs doctor
|
|
98
|
+
node ./bin/pi-activity-strip.mjs snapshot
|
|
99
|
+
node ./bin/pi-activity-strip.mjs fix-top
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Pi slash commands
|
|
103
|
+
|
|
104
|
+
Inside Pi:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
/activity-strip
|
|
108
|
+
/activity-strip status
|
|
109
|
+
/activity-strip doctor
|
|
110
|
+
/activity-strip fix-top
|
|
111
|
+
/activity-strip stop
|
|
112
|
+
/activity-strip-stop
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
In Pi with UI support:
|
|
116
|
+
- `/activity-strip status` opens a detailed runtime status report when an editor surface is available
|
|
117
|
+
- `/activity-strip doctor` opens the host-compatibility report
|
|
118
|
+
|
|
119
|
+
## Verification commands
|
|
120
|
+
|
|
121
|
+
### Package checks
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npm install
|
|
125
|
+
npm run check
|
|
126
|
+
npm run release:check:quick
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Run the strip locally
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm run strip:open
|
|
133
|
+
npm run strip:status
|
|
134
|
+
npm run strip:doctor
|
|
135
|
+
npm run strip:snapshot
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Capture what the agent should inspect
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npm run capture:strip # just the Pi activity strip window
|
|
142
|
+
npm run capture:top # top band of the focused output, including the strip + upper window area
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
These are specifically useful so the agent can inspect the current visual state without you manually posting screenshots.
|
|
146
|
+
|
|
147
|
+
### Simulate multiple sessions
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm run demo:simulate
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Real Pi smoke on the live broker
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm run smoke:headless-live
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This smoke:
|
|
160
|
+
- opens the strip
|
|
161
|
+
- runs a real headless Pi session with this extension loaded
|
|
162
|
+
- exercises a real tool call
|
|
163
|
+
- verifies that the broker observed the session while it was active
|
|
164
|
+
|
|
165
|
+
### Compatibility diagnostics
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
npm run strip:doctor
|
|
169
|
+
node ./bin/pi-activity-strip.mjs doctor --json
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Use `doctor` before opening the strip when the host/display assumptions are uncertain. It reports:
|
|
173
|
+
- whether a graphical session is present
|
|
174
|
+
- whether Electron can be resolved
|
|
175
|
+
- whether Niri-specific top-edge repair is available
|
|
176
|
+
- whether the current setup is multi-display even though the strip remains primary-display-only
|
|
177
|
+
|
|
178
|
+
## Environment controls
|
|
179
|
+
|
|
180
|
+
- `PI_ACTIVITY_STRIP_AUTO_START=0`
|
|
181
|
+
- disable automatic strip opening on Pi session start
|
|
182
|
+
- `PI_ACTIVITY_STRIP_CLICK_THROUGH=0`
|
|
183
|
+
- make the overlay clickable instead of mouse-transparent
|
|
184
|
+
- `PI_ACTIVITY_STRIP_ELECTRON_BIN=/path/to/electron`
|
|
185
|
+
- override Electron binary discovery
|
|
186
|
+
- `GLIMPSE_ELECTRON_BIN=/path/to/electron`
|
|
187
|
+
- shared Electron override also respected
|
|
188
|
+
|
|
189
|
+
## Practical usage for your Ghostty tabs
|
|
190
|
+
|
|
191
|
+
If you want this for all current tabs:
|
|
192
|
+
|
|
193
|
+
1. install the package once with `pi install`
|
|
194
|
+
2. run `/reload` inside each already-open Pi tab
|
|
195
|
+
3. open the strip once with `/activity-strip` or `npm run strip:open`
|
|
196
|
+
4. from then on, every loaded Pi session should report into the same top-row ribbon
|
|
197
|
+
|
|
198
|
+
## References
|
|
199
|
+
|
|
200
|
+
- [Project vision](docs/project/vision.md)
|
|
201
|
+
- [Project resources](docs/project/resources.md)
|
|
202
|
+
- [Verification notes](docs/project/verification.md)
|
|
203
|
+
- [Next session prompt](next_session_prompt.md)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile, spawn } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import {
|
|
8
|
+
getBrokerStatus,
|
|
9
|
+
isBrokerAlive,
|
|
10
|
+
requestBrokerShutdown,
|
|
11
|
+
sendBrokerMessage,
|
|
12
|
+
} from "../src/client/broker-client.mjs";
|
|
13
|
+
import {
|
|
14
|
+
assessActivityStripCompatibility,
|
|
15
|
+
formatCompatibilityReport,
|
|
16
|
+
} from "../src/common/compatibility.mjs";
|
|
17
|
+
import { ACTIVITY_STRIP_START_TIMEOUT_MS } from "../src/common/constants.mjs";
|
|
18
|
+
import { locateElectron } from "../src/common/electron.mjs";
|
|
19
|
+
import { makeMessage } from "../src/common/protocol.mjs";
|
|
20
|
+
import { formatBrokerRuntimeStatus } from "../src/common/status-report.mjs";
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = path.dirname(__filename);
|
|
24
|
+
const electronEntry = path.resolve(__dirname, "..", "src", "electron", "main.mjs");
|
|
25
|
+
const execFileAsync = promisify(execFile);
|
|
26
|
+
|
|
27
|
+
function usage() {
|
|
28
|
+
console.log(
|
|
29
|
+
`Usage: pi-activity-strip <open|status|doctor|snapshot|fix-top|stop|serve> [--json]\n\nCommands:\n open Start the top-row activity strip if it is not already running\n status Check broker + overlay readiness and surface runtime warnings\n doctor Inspect host compatibility assumptions before opening the strip\n snapshot Print the current broker snapshot as JSON\n fix-top Move the strip window flush to the top edge in Niri\n stop Ask the running strip to shut down\n serve Internal helper; starts the Electron shell in the foreground\n`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function moveStripToTop() {
|
|
34
|
+
const { stdout } = await execFileAsync("niri", ["msg", "-j", "windows"], {
|
|
35
|
+
env: process.env,
|
|
36
|
+
});
|
|
37
|
+
const windows = JSON.parse(stdout);
|
|
38
|
+
if (!Array.isArray(windows)) {
|
|
39
|
+
throw new Error("Unexpected niri windows payload");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const stripWindow = windows.find((window) => window?.title === "Pi Activity Strip");
|
|
43
|
+
if (!stripWindow?.id) {
|
|
44
|
+
throw new Error("Could not find Pi Activity Strip window in niri");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const currentY = Number(stripWindow.layout?.tile_pos_in_workspace_view?.[1] ?? 0);
|
|
48
|
+
if (Math.abs(currentY) < 1) {
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await execFileAsync(
|
|
53
|
+
"niri",
|
|
54
|
+
[
|
|
55
|
+
"msg",
|
|
56
|
+
"action",
|
|
57
|
+
"move-floating-window",
|
|
58
|
+
"--id",
|
|
59
|
+
String(stripWindow.id),
|
|
60
|
+
"-y",
|
|
61
|
+
String(-Math.round(currentY)),
|
|
62
|
+
],
|
|
63
|
+
{ env: process.env },
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
console.log("Moved activity strip to the top edge.");
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function waitForBrokerReady(timeoutMs = ACTIVITY_STRIP_START_TIMEOUT_MS) {
|
|
71
|
+
const timeoutAt = Date.now() + timeoutMs;
|
|
72
|
+
/** @type {import("../src/common/contracts.ts").BrokerResponse | null} */
|
|
73
|
+
let latestStatus = null;
|
|
74
|
+
|
|
75
|
+
while (Date.now() < timeoutAt) {
|
|
76
|
+
try {
|
|
77
|
+
latestStatus = await getBrokerStatus();
|
|
78
|
+
} catch {
|
|
79
|
+
latestStatus = null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
latestStatus?.ok &&
|
|
84
|
+
(!latestStatus.runtimeStatus || latestStatus.runtimeStatus.state === "ready")
|
|
85
|
+
) {
|
|
86
|
+
return { ok: true };
|
|
87
|
+
}
|
|
88
|
+
if (latestStatus?.runtimeStatus?.state === "error") {
|
|
89
|
+
return { ok: false, error: formatBrokerRuntimeStatus(latestStatus) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await delay(125);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
error: `${formatBrokerRuntimeStatus(latestStatus || { ok: false })}\nTimeout: ${timeoutMs}ms`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** @param {{ detached?: boolean }} [options] @returns {Promise<number>} */
|
|
102
|
+
async function openStrip({ detached = true } = {}) {
|
|
103
|
+
if (await isBrokerAlive()) {
|
|
104
|
+
console.log("Activity strip is already running.");
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const compatibility = await assessActivityStripCompatibility();
|
|
109
|
+
if (!compatibility.ok) {
|
|
110
|
+
console.error(formatCompatibilityReport(compatibility));
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const electron = await locateElectron();
|
|
115
|
+
const child = spawn(electron, [electronEntry], {
|
|
116
|
+
detached,
|
|
117
|
+
stdio: detached ? "ignore" : "inherit",
|
|
118
|
+
env: process.env,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (detached) {
|
|
122
|
+
child.unref();
|
|
123
|
+
const ready = await waitForBrokerReady();
|
|
124
|
+
if (!ready.ok) {
|
|
125
|
+
console.error(ready.error || "Activity strip did not become ready.");
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
console.log("Started activity strip.");
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return await new Promise((resolve) => {
|
|
133
|
+
child.on("exit", (code) => resolve(typeof code === "number" ? code : 0));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function main() {
|
|
138
|
+
const command = process.argv[2] ?? "open";
|
|
139
|
+
const jsonOutput = process.argv.includes("--json");
|
|
140
|
+
|
|
141
|
+
switch (command) {
|
|
142
|
+
case "open":
|
|
143
|
+
process.exitCode = await openStrip({ detached: true });
|
|
144
|
+
return;
|
|
145
|
+
case "serve":
|
|
146
|
+
process.exitCode = await openStrip({ detached: false });
|
|
147
|
+
return;
|
|
148
|
+
case "status": {
|
|
149
|
+
try {
|
|
150
|
+
const result = await getBrokerStatus({ expectReply: true });
|
|
151
|
+
if (jsonOutput) {
|
|
152
|
+
console.log(JSON.stringify(result, null, 2));
|
|
153
|
+
} else {
|
|
154
|
+
console.log(formatBrokerRuntimeStatus(result));
|
|
155
|
+
}
|
|
156
|
+
process.exitCode =
|
|
157
|
+
result?.ok && (!result.runtimeStatus || result.runtimeStatus.state === "ready") ? 0 : 1;
|
|
158
|
+
} catch {
|
|
159
|
+
console.log(
|
|
160
|
+
jsonOutput ? JSON.stringify({ ok: false, state: "stopped" }, null, 2) : "stopped",
|
|
161
|
+
);
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
case "doctor": {
|
|
167
|
+
const report = await assessActivityStripCompatibility();
|
|
168
|
+
if (jsonOutput) {
|
|
169
|
+
console.log(JSON.stringify(report, null, 2));
|
|
170
|
+
} else {
|
|
171
|
+
console.log(formatCompatibilityReport(report));
|
|
172
|
+
}
|
|
173
|
+
process.exitCode = report.ok ? 0 : 1;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
case "snapshot": {
|
|
177
|
+
try {
|
|
178
|
+
const result = await sendBrokerMessage(makeMessage("ping"), { expectReply: true });
|
|
179
|
+
console.log(JSON.stringify(result?.snapshot ?? { sessions: [] }, null, 2));
|
|
180
|
+
process.exitCode = result?.ok ? 0 : 1;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
183
|
+
process.exitCode = 1;
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
case "fix-top": {
|
|
188
|
+
try {
|
|
189
|
+
process.exitCode = await moveStripToTop();
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
192
|
+
process.exitCode = 1;
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
case "stop": {
|
|
197
|
+
try {
|
|
198
|
+
const result = await requestBrokerShutdown();
|
|
199
|
+
console.log(result?.ok ? "stopping" : "not-running");
|
|
200
|
+
process.exitCode = result?.ok ? 0 : 1;
|
|
201
|
+
} catch {
|
|
202
|
+
console.log("not-running");
|
|
203
|
+
process.exitCode = 1;
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
case "-h":
|
|
208
|
+
case "--help":
|
|
209
|
+
usage();
|
|
210
|
+
process.exitCode = 0;
|
|
211
|
+
return;
|
|
212
|
+
default:
|
|
213
|
+
usage();
|
|
214
|
+
console.error(`Unknown command: ${command}`);
|
|
215
|
+
process.exitCode = 1;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
main().catch((error) => {
|
|
220
|
+
if (error instanceof Error) {
|
|
221
|
+
console.error(error.stack ?? error.message);
|
|
222
|
+
} else {
|
|
223
|
+
console.error(String(error));
|
|
224
|
+
}
|
|
225
|
+
process.exit(1);
|
|
226
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { publishSessionSnapshot, removeSession } from "../src/client/broker-client.mjs";
|
|
3
|
+
import { createInitialSnapshot } from "../src/common/telemetry.mjs";
|
|
4
|
+
|
|
5
|
+
function makeSession(label, cwd, state, phase, detail, offset = 0) {
|
|
6
|
+
const snapshot = createInitialSnapshot({ cwd, sessionName: label });
|
|
7
|
+
snapshot.state = state;
|
|
8
|
+
snapshot.phase = phase;
|
|
9
|
+
snapshot.detail = detail;
|
|
10
|
+
snapshot.agentActive = state === "tool" || state === "thinking" || state === "waiting";
|
|
11
|
+
snapshot.agentStartedAt = snapshot.startedAt - offset;
|
|
12
|
+
snapshot.updatedAt = Date.now();
|
|
13
|
+
return snapshot;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sessions = [
|
|
17
|
+
makeSession(
|
|
18
|
+
"api refactor",
|
|
19
|
+
"~/ai-society/softwareco/owned/pi-server",
|
|
20
|
+
"tool",
|
|
21
|
+
"Editing file",
|
|
22
|
+
"src/routes/session.ts",
|
|
23
|
+
18000,
|
|
24
|
+
),
|
|
25
|
+
makeSession(
|
|
26
|
+
"vault replay",
|
|
27
|
+
"~/ai-society/softwareco/owned/pi-extensions/packages/pi-vault-client",
|
|
28
|
+
"thinking",
|
|
29
|
+
"Thinking",
|
|
30
|
+
"Replaying execution receipt and comparing current render path",
|
|
31
|
+
9000,
|
|
32
|
+
),
|
|
33
|
+
makeSession(
|
|
34
|
+
"frontend polish",
|
|
35
|
+
"~/ai-society/softwareco/owned/pi-web",
|
|
36
|
+
"waiting",
|
|
37
|
+
"Waiting for input",
|
|
38
|
+
"interactive form is open",
|
|
39
|
+
22000,
|
|
40
|
+
),
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const session of sessions) {
|
|
44
|
+
await publishSessionSnapshot(session);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await delay(1500);
|
|
48
|
+
sessions[0].detail = "tests/session-contract.test.mjs";
|
|
49
|
+
sessions[0].updatedAt = Date.now();
|
|
50
|
+
await publishSessionSnapshot(sessions[0]);
|
|
51
|
+
|
|
52
|
+
await delay(1500);
|
|
53
|
+
sessions[1].state = "success";
|
|
54
|
+
sessions[1].phase = "Done";
|
|
55
|
+
sessions[1].detail = "Replay matches stored receipt";
|
|
56
|
+
sessions[1].agentActive = false;
|
|
57
|
+
sessions[1].agentStartedAt = null;
|
|
58
|
+
sessions[1].updatedAt = Date.now();
|
|
59
|
+
await publishSessionSnapshot(sessions[1]);
|
|
60
|
+
|
|
61
|
+
await delay(2000);
|
|
62
|
+
for (const session of sessions) {
|
|
63
|
+
await removeSession(session.sessionId);
|
|
64
|
+
}
|