pi-onlyne 0.3.1 → 0.3.3
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/README.md +8 -10
- package/SPEC.md +2 -2
- package/dist/index.js +3 -4
- package/dist/onlyne.d.ts +4 -5
- package/dist/onlyne.js +5 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,25 +46,23 @@ For a one-off run without installing:
|
|
|
46
46
|
pi -e npm:pi-onlyne
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
You also need
|
|
49
|
+
You also need an initialized Onlyne workspace and a running workspace-local daemon:
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
52
|
onlyne init
|
|
53
|
-
|
|
53
|
+
onlyne run
|
|
54
|
+
# Optional, in another shell: refresh the workspace-local agent skill
|
|
54
55
|
onlyne export-skill
|
|
55
56
|
```
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
export ONLYNE_BIN=/path/to/onlyne
|
|
61
|
-
```
|
|
58
|
+
`pi-onlyne` does not install launchd/systemd jobs and does not spawn a global daemon. If you want background supervision, wrap `onlyne --workspace /path/to/project run` yourself per workspace.
|
|
62
59
|
|
|
63
60
|
## Typical workflow
|
|
64
61
|
|
|
65
62
|
1. Initialize/configure Onlyne in your project.
|
|
66
|
-
2.
|
|
67
|
-
3.
|
|
63
|
+
2. Start that workspace's daemon with `onlyne run`.
|
|
64
|
+
3. Install this Pi extension.
|
|
65
|
+
4. Start watching from pi:
|
|
68
66
|
|
|
69
67
|
```text
|
|
70
68
|
/onlyne watch on
|
|
@@ -118,7 +116,7 @@ onlyne_broadcast({
|
|
|
118
116
|
{ channelId: "telegram" },
|
|
119
117
|
{ channelId: "feishu" }
|
|
120
118
|
],
|
|
121
|
-
text: "# Release shipped\n\nVersion 0.3.
|
|
119
|
+
text: "# Release shipped\n\nVersion 0.3.2 is live."
|
|
122
120
|
})
|
|
123
121
|
```
|
|
124
122
|
|
package/SPEC.md
CHANGED
|
@@ -7,8 +7,8 @@ Pi extension for Onlyne. Onlyne remains a workspace-local IM broker; this extens
|
|
|
7
7
|
## v1 Decisions
|
|
8
8
|
|
|
9
9
|
- Watch is configurable; default manual.
|
|
10
|
-
- `watch on`
|
|
11
|
-
-
|
|
10
|
+
- `watch on` connects only to the workspace-local `.onlyne/run/onlyne.sock`; if unavailable, it tells the user to start `onlyne --workspace <root> run`.
|
|
11
|
+
- pi-onlyne never owns or launches the daemon. Users handle launchd/systemd/background scripts outside the extension, per workspace.
|
|
12
12
|
- Inbound events come from Onlyne `subscribe_events`; no polling.
|
|
13
13
|
- Inbound mode is rule-based: `auto-handle`, `queue-only`, or `muted`.
|
|
14
14
|
- Outbound defaults to `guarded-explicit`: prefer tool reply, fallback to final text, else send configured error text.
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { broadcast,
|
|
3
|
+
import { broadcast, connectDaemon, loopback, markConsumed, sendWithRetry, stopProcess, subscribe } from "./onlyne.js";
|
|
4
4
|
import { inboundModeFor, loadConfig, saveConfig } from "./config.js";
|
|
5
5
|
import { findWorkspace } from "./workspace.js";
|
|
6
6
|
const state = { cwd: process.cwd(), workspace: null, watching: false, owner: "stopped" };
|
|
@@ -10,7 +10,7 @@ function inboundText(data) { const msg = data?.data?.data ?? data?.data ?? data;
|
|
|
10
10
|
function consumeIfNotified(inbound) { if (state.workspace && inbound.messageId)
|
|
11
11
|
void markConsumed(state.workspace.socketPath, inbound.messageId).catch(() => { }); }
|
|
12
12
|
async function startWatch(pi) { state.workspace = findWorkspace(state.cwd); if (!state.workspace)
|
|
13
|
-
throw new Error("current workspace has no .onlyne configuration"); const conn = await
|
|
13
|
+
throw new Error("current workspace has no .onlyne configuration"); const conn = await connectDaemon(state.workspace); state.owner = conn.owner; state.child = conn.process; state.socket = subscribe(state.workspace.socketPath, (line) => { if (!line?.event || line.type !== "inbound_message")
|
|
14
14
|
return; const inbound = inboundText(line); if (!inbound)
|
|
15
15
|
return; const mode = inboundModeFor(currentConfig(), inbound.channelId, inbound.conversationId); if (mode === "muted")
|
|
16
16
|
return; if (inbound.channelId === "loopback") {
|
|
@@ -25,8 +25,7 @@ async function startWatch(pi) { state.workspace = findWorkspace(state.cwd); if (
|
|
|
25
25
|
pi.sendUserMessage(`Onlyne inbound message from ${inbound.channelId}/${inbound.conversationId}:\n\n${inbound.text}\n\nReply with onlyne_reply, or call onlyne_mark_no_reply if no reply is needed.`, { deliverAs: "followUp" });
|
|
26
26
|
consumeIfNotified(inbound);
|
|
27
27
|
} }); state.watching = true; return `watching ${state.workspace.root} (${state.owner})`; }
|
|
28
|
-
function stopWatch() { state.socket?.destroy(); state.socket = undefined;
|
|
29
|
-
stopProcess(state.child); state.child = undefined; state.watching = false; state.owner = "stopped"; return "watch stopped"; }
|
|
28
|
+
function stopWatch() { state.socket?.destroy(); state.socket = undefined; stopProcess(state.child); state.child = undefined; state.watching = false; state.owner = "stopped"; return "watch stopped"; }
|
|
30
29
|
async function reply(text) { if (!state.workspace)
|
|
31
30
|
throw new Error("onlyne workspace not found"); const inbound = state.currentInbound; if (!inbound)
|
|
32
31
|
throw new Error("no active inbound message"); const res = await sendWithRetry(state.workspace.socketPath, { channelId: inbound.channelId }, text, currentConfig().outbound.retry.attempts); if (res.ok)
|
package/dist/onlyne.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ChildProcess } from "node:child_process";
|
|
2
2
|
import { type Socket } from "node:net";
|
|
3
3
|
import type { Workspace } from "./workspace.js";
|
|
4
4
|
export interface OnlyneRequest {
|
|
@@ -20,13 +20,12 @@ export interface SendResult extends SendTarget {
|
|
|
20
20
|
}
|
|
21
21
|
export declare function request(socketPath: string, req: OnlyneRequest): Promise<any>;
|
|
22
22
|
export declare function subscribe(socketPath: string, onLine: (line: any) => void): Socket;
|
|
23
|
-
export declare function spawnDaemon(ws: Workspace, onlyneBin?: string): ChildProcess;
|
|
24
23
|
export declare function waitForSocket(socketPath: string, timeoutMs?: number): Promise<void>;
|
|
25
|
-
export declare function
|
|
26
|
-
owner: "external"
|
|
24
|
+
export declare function connectDaemon(ws: Workspace): Promise<{
|
|
25
|
+
owner: "external";
|
|
27
26
|
process?: ChildProcess;
|
|
28
27
|
}>;
|
|
29
|
-
export declare function stopProcess(
|
|
28
|
+
export declare function stopProcess(_child?: ChildProcess): void;
|
|
30
29
|
export declare function loopback(socketPath: string, text: string, rawText?: boolean): Promise<any>;
|
|
31
30
|
export declare function markConsumed(socketPath: string, messageId: string): Promise<any>;
|
|
32
31
|
export declare function sendWithRetry(socketPath: string, target: SendTarget, text: string, attempts: number, rawText?: boolean): Promise<SendResult>;
|
package/dist/onlyne.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { createConnection } from "node:net";
|
|
3
2
|
export function request(socketPath, req) {
|
|
4
3
|
return new Promise((resolve, reject) => {
|
|
@@ -38,20 +37,6 @@ export function subscribe(socketPath, onLine) {
|
|
|
38
37
|
} });
|
|
39
38
|
return socket;
|
|
40
39
|
}
|
|
41
|
-
export function spawnDaemon(ws, onlyneBin = process.env.ONLYNE_BIN ?? "onlyne") {
|
|
42
|
-
const script = `
|
|
43
|
-
set -eu
|
|
44
|
-
parent="$1"
|
|
45
|
-
shift
|
|
46
|
-
"$@" &
|
|
47
|
-
child=$!
|
|
48
|
-
cleanup() { kill "$child" 2>/dev/null || true; wait "$child" 2>/dev/null || true; }
|
|
49
|
-
trap cleanup INT TERM HUP EXIT
|
|
50
|
-
while kill -0 "$parent" 2>/dev/null && kill -0 "$child" 2>/dev/null; do sleep 1; done
|
|
51
|
-
cleanup
|
|
52
|
-
`;
|
|
53
|
-
return spawn("sh", ["-c", script, "onlyne-supervisor", String(process.pid), onlyneBin, "--workspace", ws.root, "run"], { cwd: ws.root, stdio: "ignore" });
|
|
54
|
-
}
|
|
55
40
|
export async function waitForSocket(socketPath, timeoutMs = 5000) {
|
|
56
41
|
const deadline = Date.now() + timeoutMs;
|
|
57
42
|
let last;
|
|
@@ -67,19 +52,16 @@ export async function waitForSocket(socketPath, timeoutMs = 5000) {
|
|
|
67
52
|
}
|
|
68
53
|
throw last instanceof Error ? last : new Error("onlyne socket not ready");
|
|
69
54
|
}
|
|
70
|
-
export async function
|
|
55
|
+
export async function connectDaemon(ws) {
|
|
71
56
|
try {
|
|
72
57
|
await request(ws.socketPath, { id: "ping", op: "ping" });
|
|
73
58
|
return { owner: "external" };
|
|
74
59
|
}
|
|
75
|
-
catch {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return { owner: "extension", process };
|
|
60
|
+
catch (e) {
|
|
61
|
+
throw new Error(`onlyne daemon is not running for ${ws.root}; start it with: onlyne --workspace ${ws.root} run`, { cause: e });
|
|
62
|
+
}
|
|
79
63
|
}
|
|
80
|
-
export function stopProcess(
|
|
81
|
-
return; child.kill("SIGTERM"); setTimeout(() => { if (!child.killed)
|
|
82
|
-
child.kill("SIGKILL"); }, 1500).unref(); }
|
|
64
|
+
export function stopProcess(_child) { }
|
|
83
65
|
export async function loopback(socketPath, text, rawText = true) {
|
|
84
66
|
return request(socketPath, { id: `loopback-${Date.now()}`, op: "loopback", text, raw_text: rawText });
|
|
85
67
|
}
|