pi-open-deskos 0.2.1 → 0.4.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/README.md +88 -19
- package/README.zh-CN.md +76 -19
- package/fixtures/control-v2.json +2 -2
- package/package.json +7 -6
- package/session-control +38 -0
- package/src/config.ts +135 -15
- package/src/console-client.ts +47 -21
- package/src/console-extension.ts +84 -43
- package/src/control-transport.ts +3 -3
- package/src/desk-set.ts +69 -0
- package/src/events.ts +77 -19
- package/src/index.ts +172 -29
- package/src/processes.ts +117 -13
- package/src/reporter.ts +117 -39
- package/src/session-host-client.mjs +274 -0
- package/src/session-host.ts +632 -0
- package/src/session-replay.ts +38 -0
- package/src/transport.ts +34 -11
- package/src/types.ts +18 -9
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Report this machine's Pi sessions and their operating events to Open DeskOS, and
|
|
|
4
4
|
|
|
5
5
|
**English** | [简体中文](README.zh-CN.md)
|
|
6
6
|
|
|
7
|
-
Open DeskOS normally learns about Pi sessions by scanning a machine locally or over SSH. That direction is not always available, and a scan cannot see what a session is actually doing. This package inverts it: the machine that owns the sessions opens one **
|
|
7
|
+
Open DeskOS normally learns about Pi sessions by scanning a machine locally or over SSH. That direction is not always available, and a scan cannot see what a session is actually doing. This package inverts it: the machine that owns the sessions opens one **Open DeskOS Link** out to Open DeskOS and reports them itself.
|
|
8
8
|
|
|
9
9
|
A machine may also hold a **Control Credential** and become a **Console**: it opens a separate control connection to the same listener, lists the **Hosted Pi** sessions the desk hosts, launches new ones, attaches to them, steers a running turn, cancels it, ends the session, and reads its complete history on demand.
|
|
10
10
|
|
|
@@ -24,36 +24,58 @@ The command records an absolute package path, so the checkout can live anywhere,
|
|
|
24
24
|
|
|
25
25
|
## Configure
|
|
26
26
|
|
|
27
|
-
A machine reports nothing until an address **and** a token are both set. A partially configured link stays silent. Control stays off until a separate control credential is also set. Run `/
|
|
27
|
+
A machine reports nothing until an address **and** a token are both set. A partially configured link stays silent. Control stays off until a separate control credential is also set. Run `/odk` for configuration guidance.
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
export
|
|
31
|
-
export
|
|
32
|
-
export
|
|
33
|
-
export
|
|
30
|
+
export ODK_LINK_ADDRESS="192.168.88.161:8765" # Open DeskOS Link Service
|
|
31
|
+
export ODK_LINK_TOKEN="<per-link token>"
|
|
32
|
+
export ODK_LINK_MACHINE="desk-mac" # optional, defaults to the hostname
|
|
33
|
+
export ODK_LINK_CONTROL_TOKEN="<control credential>" # optional: makes this machine a Console
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Put them in the environment Pi runs with (for a desktop session, the same place that starts `pi`). The control credential is never transmitted: the desk challenges with a one-time nonce and this machine answers with a proof over it.
|
|
37
37
|
|
|
38
|
+
### Report to more than one desk
|
|
39
|
+
|
|
40
|
+
Each desk has its own token, so a machine that feeds several desks states them in one file: `~/.config/odk/desks.json`, or the file named by `ODK_LINK_DESKS_FILE`.
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"desks": [
|
|
45
|
+
{ "address": "cm5.example.ts.net:8765", "token": "<per-link token>", "controlToken": "<control credential>" },
|
|
46
|
+
{ "address": "handheld.example.ts.net:8765", "token": "<per-link token>" }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Discovery runs once. Every desk receives the same sessions, statuses, events, and replayed history.
|
|
52
|
+
- One desk being unreachable never stops the others. The status row names every desk with its own link state, and a set that is only half up is never stated as connected.
|
|
53
|
+
- When this file exists it is the one source of truth: the single-desk environment variables are not read beside it. With no such file the environment form above still works exactly as before.
|
|
54
|
+
- An entry with no token, with an address that is not `host:port`, or listed twice is refused with a reason the `/odk` menu shows, and the other desks still report. A file the operator named but that cannot be read is refused with the same visibility instead of looking like a link that never connects.
|
|
55
|
+
- The Console drives one desk: the first desk with a control credential, or the desk named by `ODK_LINK_CONSOLE_DESK` (`host:port` or a bare host). A named desk without a credential reports that a credential is needed rather than quietly driving a different desk.
|
|
56
|
+
- `machine` may be stated per desk; it defaults to `ODK_LINK_MACHINE`, then to the hostname.
|
|
57
|
+
|
|
38
58
|
## What is reported
|
|
39
59
|
|
|
40
|
-
- The current session's live identity, goal, activity, and events, plus other sessions registered on this machine in `directory-sessions` metadata (written by `pi-utils` and `pi-keyboard`). UUID and timestamp-prefixed UUID aliases merge into one session.
|
|
41
|
-
- State: `running` requires working metadata, a live Pi process, and timestamps compatible with that process's lifetime; live idle/settled records are `settled`. Dead/invalid/reused PIDs, unverifiable processes, and explicitly exited records are `exited`. One bounded process-table read (1 MiB
|
|
60
|
+
- The current session's live identity, goal, activity, and events, plus other sessions registered on this machine in `directory-sessions` metadata (written by `pi-utils` and `pi-keyboard`). On resumed, reloaded, or forked reporter start, the current session replays its bounded durable JSONL tail before forwarding later live messages, so a resumed Pi does not appear as running with an empty Open DeskOS Link stream. UUID and timestamp-prefixed UUID aliases merge into one session.
|
|
61
|
+
- State: `running` requires working metadata, a live Pi process, and timestamps compatible with that process's lifetime; live idle/settled records are `settled`. Dead/invalid/reused PIDs, unverifiable processes, and explicitly exited records are `exited`. One bounded process-table read (1 MiB; a 2-second deadline on Unix, 5 seconds for Windows CIM) checks all PIDs; only the newest unambiguous session registered to a PID can be live. Quoted Windows launcher paths retain spaces. A failed or stalled query cannot leave discovery waiting indefinitely. For the current session, Pi's own idle/agent events take precedence.
|
|
42
62
|
- Original metadata start/update times, optional session name, goal, and activity/recap. Scanning does not invent a new activity timestamp or read session histories.
|
|
43
|
-
- Bounded session events: the newest contiguous tail of at most 300 events and 1,048,576 UTF-8 bytes per session, counting text and
|
|
44
|
-
- Tool results preserve their complete multiline Markdown, including tables, code fences, and whitespace, up to 65,536 UTF-8 bytes per result. All text parts are joined with two newlines; images and arbitrary result metadata are not sent. Any body shortened at its kind's byte limit carries `truncated: true` and never splits a Unicode code point. A result's optional `toolName` is a separate 200-character summary, not a prefix inserted into the Markdown. Session activity remains a short first-line summary.
|
|
63
|
+
- Bounded session events: the newest contiguous tail of at most 300 events and 1,048,576 UTF-8 bytes per session, counting text, tool names, completion identities, and stop reasons. Event bodies preserve line structure with per-kind limits: user 8 KiB, thinking 4 KiB, tool call 4 KiB, assistant 16 KiB, and tool result 64 KiB.
|
|
64
|
+
- Tool results preserve their complete multiline Markdown, including tables, code fences, and whitespace, up to 65,536 UTF-8 bytes per result. All text parts are joined with two newlines; images and arbitrary result metadata are not sent. Any body shortened at its kind's byte limit carries `truncated: true` and never splits a Unicode code point. A result's optional `toolName` is a separate 200-character summary, not a prefix inserted into the Markdown. A tool call and the result that answers it both carry the optional `toolCallId` Pi wrote for that call, and a result Pi recorded as an error carries `isError: true`, so a consumer pairs a result with its own call and reads the outcome from Pi's record instead of from event order. A tool call event's text names what the call was doing, not merely where it looked: `bash: <command>`, `search: <pattern>` for a content or filename search, and `<tool>: <path|query|subject>` otherwise. Session activity remains a short first-line summary.
|
|
65
|
+
|
|
66
|
+
Completed assistant events carry the actual persisted message entry ID as `eventId` and Pi's bounded `stopReason`. Live completion metadata is sent only after `agent_settled`, when the new entry exists on the current branch. `message_end` remains provisional because Pi persists the message after that callback. Replay uses the same entry ID. Aborted runs, repeated settlement, and nonterminal messages do not create completion identities. A reported completion does not establish independent task verification.
|
|
45
67
|
|
|
46
68
|
The inventory refreshes on session start and every 5 seconds after each scan, including while the link is offline. A refresh replaces only discovered entries: the current session's identity and events cannot be removed or overwritten by stale metadata. Shutdown/reload cancels refresh scheduling and invalidates pending results and reconnects.
|
|
47
69
|
|
|
48
70
|
At most 64 sessions are reported. Only the current session is pinned; other entries favor running, then settled, then exited state, with recency breaking ties. Newer metadata can refresh a formerly observed session resumed elsewhere without dropping its retained events. The cap can evict old history and its events—it is not an archive.
|
|
49
71
|
|
|
50
|
-
Each scan visits at most 256 workspaces, 2,048 JSON files, and 8,192 directory entries, reading at most 16 KiB per file. Malformed, oversized, linked, and non-regular files are skipped. Name, goal, and activity are limited to 200 characters each and may shorten further on the wire toward the 65,536-byte snapshot budget. Session IDs, exact working directories, workspace names, and timestamps are never shortened or collapsed. If exact identity fields still exceed that budget, the reporter omits whole lowest-priority entries from that snapshot and `/
|
|
72
|
+
Each scan visits at most 256 workspaces, 2,048 JSON files, and 8,192 directory entries, reading at most 16 KiB per file. Malformed, oversized, linked, and non-regular files are skipped. Name, goal, and activity are limited to 200 characters each and may shorten further on the wire toward the 65,536-byte snapshot budget. Session IDs, exact working directories, workspace names, and timestamps are never shortened or collapsed. If exact identity fields still exceed that budget, the reporter omits whole lowest-priority entries from that snapshot and `/odk` reports the omitted count. The current session is preserved. A single current identity larger than the frame budget cannot be represented without changing that identity; this remains a pathological limit.
|
|
51
73
|
|
|
52
74
|
The default registry is `~/.pi/agent/directory-sessions`. `PI_DIRECTORY_SESSIONS_DIR` selects an explicit registry; otherwise `PI_CODING_AGENT_DIR` (or legacy `PI_AGENT_DIR`) overrides the agent directory. Metadata writers must use the same registry. Discovery never edits or prunes registry files. Sessions without registry metadata are not invented, and removed metadata disappears on the next refresh unless this reporter observed that session directly.
|
|
53
75
|
|
|
54
76
|
## Driving a Hosted Pi
|
|
55
77
|
|
|
56
|
-
`/
|
|
78
|
+
`/odk` opens a menu, and its console row lists the Hosted Pi sessions the desk hosts, each with its state, goal, project, and age, and lets you launch a new one, attach to an existing one, send it a further instruction (including while a turn is running, which steers that turn rather than starting a second one), cancel the running turn, end the session, and read its complete history.
|
|
57
79
|
|
|
58
80
|
- **One Console drives one Hosted Pi.** Attaching replaces rather than shares any previous Console. Attaching is idempotent and repeatable by session identity, so a resumed Pi session can attach again.
|
|
59
81
|
- **Nothing is repeated and nothing is skipped.** An event's position is the physical position of the corresponding complete entry in the Hosted Pi's own session log, so positions are strictly increasing but need not be consecutive when non-message entries intervene. A fresh attach sends `after: null` and begins at the desk's current boundary; older content is fetched explicitly through history. A resumed attach sends the last applied position and receives ordered catch-up through a `caught_up` boundary before live events. There is no replay window and no resync record to interpret.
|
|
@@ -62,20 +84,65 @@ The default registry is `~/.pi/agent/directory-sessions`. `PI_DIRECTORY_SESSIONS
|
|
|
62
84
|
- **The desk stays in charge of itself.** While this machine drives a Hosted Pi, the desk names it in the Pi Sessions overview header, and local touch and keyboard keep working.
|
|
63
85
|
- **Tools.** The assistant can call `desk_*` tools to list, start, attach, prompt, cancel, end, and read history. The console surface owns its own keyboard input and never intercepts global terminal input.
|
|
64
86
|
|
|
87
|
+
## Letting a desk drive this session
|
|
88
|
+
|
|
89
|
+
A reported session is read-only: a desk can see what this session is doing, and nothing more. This package can also make *this* session drivable, from inside this session, with no process installed and no second credential.
|
|
90
|
+
|
|
91
|
+
Declare where this machine's session endpoints live, and every Pi session on this machine publishes its own endpoint from `session_start` and withdraws it on `session_shutdown`:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
export ODK_SESSION_HOST_SOCKET="$HOME/.local/run/odk/sessions"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
An existing Pi process keeps the environment from its launch. If it has no endpoint, exit Pi when idle.
|
|
98
|
+
Set the variable in its launching shell, then resume the existing session file:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
pi --session /absolute/path/to/existing-session.jsonl
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use the original file, and keep only one live process for that session. Resuming preserves its session identity and history.
|
|
105
|
+
|
|
106
|
+
Each session then serves the task protocol a desk's voice agent already speaks — `list`, `status`, `prompt`, `cancel` — on `<directory>/<sessionId>.sock`, with a `0600` descriptor beside it naming its socket, project and state. `start`, `launch`, `end` and `history` are refused with one fixed reason: this process is one session, not a session host.
|
|
107
|
+
|
|
108
|
+
This endpoint works independently of reporting credentials on Unix. On Windows it is explicitly unavailable until private IPC is supported; `/odk status` reports the refusal. Windows Open DeskOS Link reporting and remote Hosted Pi control remain supported. The packaged `session-control` launcher selects the requested task identity when several sessions share a project.
|
|
109
|
+
|
|
110
|
+
- **It is a declared capability, never a default.** A machine that declares no endpoint has none to reach. An endpoint that lets something else hand a session work is a decision, not something a package turns on.
|
|
111
|
+
- **Ownership is the only gate.** The socket lives in a `0700` directory with a `0600` socket. There is no token to present, and nothing listens on the network.
|
|
112
|
+
- **A desk reaches it over your own SSH session.** Run the `session-control` launcher on the far side of it; it finds a node runtime itself, because a login session's PATH is not the one an interactive shell has. With no socket named it resolves the session from the request's own project, and a project no session serves is answered with the protocol's own refusal rather than with some other session's reply.
|
|
113
|
+
- **Every session answers at its own endpoint.** A root-directory list collects live sessions in that directory and its descendants. Status, prompt, and cancel require the exact project and session ID returned by that list.
|
|
114
|
+
- **A missing reply does not mean no sessions.** List results retain healthy endpoints and report `incomplete` and `unavailable` when an endpoint fails. A truncated or incomplete list cannot establish that a session is absent.
|
|
115
|
+
|
|
116
|
+
For a dedicated SSH identity, use a fixed relay command with approved project roots:
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
session-control --find --allow-root /private/tmp/odk-session-demo --allow-root /Users/FradSer/Developer/FradSer
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Repeat `--allow-root` for up to 16 existing absolute directories. The relay resolves each root and requested project before socket contact.
|
|
123
|
+
It accepts only version 1 list, status, prompt, and cancel requests inside an approved root.
|
|
124
|
+
It refuses outside projects, broad ancestors, symlink escapes, and invalid requests. It preserves the original project string for session identity.
|
|
125
|
+
The fixed command ignores `SSH_ORIGINAL_COMMAND`. Without `--allow-root`, the existing CLI behavior stays unchanged.
|
|
126
|
+
The root check restricts session selection. Pi keeps its original tools and permissions. This check does not sandbox the selected session.
|
|
127
|
+
- **Input follows the original session.** A prompt uses normal Pi Enter behavior: steer during a running turn. An explicit `followUp` waits for the next turn. Commands and prompt templates expand as they do in Pi.
|
|
128
|
+
- **Submission and execution have separate evidence.** Pi's public extension API does not acknowledge an individual message. Native prompt receipts therefore report `delivery: unknown`, retain their `mutationId`, and prohibit automatic replay. Status reports the session's observed prompt, response, and activity; matching text cannot certify a particular mutation.
|
|
129
|
+
- **Retries cannot silently repeat a mutation.** Within one SessionHost lifetime, the same mutation ID and payload reuse their original receipt. Conflicting payloads are refused. The host retains at most 256 mutation receipts, then refuses new mutations rather than forgetting old ones. Receipts do not survive a process restart or extension reload.
|
|
130
|
+
- **The endpoint never keeps Pi alive.** It is unref'd, so reaching this session is not a reason this session cannot be closed.
|
|
131
|
+
|
|
65
132
|
## What is not
|
|
66
133
|
|
|
67
|
-
- **Reporting is never management.** A machine holding only the reporting token can report and nothing else: the package never sends a prompt, blocks a turn, or mutates a message on a reported session, and visibility through a
|
|
134
|
+
- **Reporting is never management.** A machine holding only the reporting token can report and nothing else: the package never sends a prompt, blocks a turn, or mutates a message on a reported session, and visibility through a Open DeskOS Link never makes a session controllable.
|
|
68
135
|
- **Control is a separate capability.** It needs the Control Credential, it never travels on the reporting connection, and a machine without it cannot become a Console.
|
|
69
136
|
- **No management of reported sessions.** Even as a Console, the package does not manage the sessions it reports. Control applies to Hosted Pi sessions the desk hosts, which are a different thing.
|
|
70
137
|
- **No answering a Hosted Pi mid-turn.** The desk's Pi host loads no extensions, and the SDK exposes no permission, approval, or ask-user surface, so a Hosted Pi cannot raise a request for you to answer. This is a limit of the host's configuration, not a missing feature.
|
|
71
138
|
- **No network discovery or pairing.** Local session metadata is discovered, but a machine that does not already know the address and token cannot register, and a Console must already know the control credential.
|
|
72
|
-
- **No history or credential scan.**
|
|
73
|
-
- **No encryption.** The
|
|
139
|
+
- **No other-session history or credential scan.** On resumed, reloaded, or forked start, the current Pi reads only the bounded tail of its own `sessionManager.getSessionFile()` JSONL file through the same event bounds; it never reads other sessions' histories, auth files, or arbitrary extra metadata. Other sessions contribute registry metadata only, not synthetic events. Current-session tool-result text **is sent** within the bounds above; it may include file contents or sensitive output returned by a tool. Configure the link only when that sharing is intended.
|
|
140
|
+
- **No encryption.** The Open DeskOS Link is a plain connection to a LAN-only listener. The control credential is kept off the wire so a capture yields no reusable execution token, but content itself is not encrypted. Do not expose the Open DeskOS Link Service beyond the local network.
|
|
74
141
|
|
|
75
142
|
## Check the link from inside Pi
|
|
76
143
|
|
|
77
144
|
```
|
|
78
|
-
/
|
|
145
|
+
/odk
|
|
79
146
|
```
|
|
80
147
|
|
|
81
148
|
The menu is the same in every configuration, and a row that is unavailable names the reason instead of disappearing. Its status row shows the link state, the machine identity, the endpoint, the reported session count, the event count, the control state, and the latest connection error when present; its console row opens the Hosted Pi list. Arguments are the fast path: `console`, `status`, `attach`, `launch`, `cancel`, and `history`. Diagnostics are command-only in every configuration: Open DeskOS adds no footer status line, custom footer, or working-directory suffix, even when offline or unconfigured.
|
|
@@ -84,14 +151,16 @@ The menu is the same in every configuration, and a row that is unavailable names
|
|
|
84
151
|
|
|
85
152
|
The reporter reconnects with a growing wait (1s, 2s, 4s … capped at 30s) and keeps exactly one link. Both retained history and pending events obey the per-session 300-event / 1,048,576-byte bounds. Each new connection sends the current session state and replays the complete retained event tail, even when there were no new offline events, because the service can forget machine state after its last link closes. Later flushes on that connection send only new events.
|
|
86
153
|
|
|
154
|
+
The link remains `connecting` until the service accepts `hello` with a valid v1 ACK. A refused or silent handshake becomes `offline` and retries; connecting the TCP socket alone does not establish authenticated reporting. Records retain UTF-8 characters across packet boundaries. Session shutdown invalidates pending replay, attachment, and startup work, so a late callback cannot recreate an endpoint or affect a replacement session.
|
|
155
|
+
|
|
87
156
|
The control connection is separate and does not reproduce that behavior: list, launch, and history requests use a connection that closes after the answer, and a connection is held only while this machine is attached to a Hosted Pi. Losing it does not affect reporting, and it does not end the session on the desk.
|
|
88
157
|
|
|
89
158
|
## Wire contract
|
|
90
159
|
|
|
91
|
-
Newline-delimited JSON over one TCP connection, version 1 for reporting. Inventory sessions carry `discovered: true`; direct current-session observations do not. The service can therefore prefer active direct observations over later-written metadata from another reporter. The reporter writes `hello`, `sessions`, `events`, and `bye`; the service may reply `ack` or `error`. The `prompt` reply reserved in version 1 is still unimplemented, and control does not use it. Frames split on LF only and tolerate a trailing CR. Result events carry raw Markdown in `text`, plus optional `toolName` and `truncated: true
|
|
160
|
+
Newline-delimited JSON over one TCP connection, version 1 for reporting. Inventory sessions carry `discovered: true`; direct current-session observations do not. The service can therefore prefer active direct observations over later-written metadata from another reporter. The reporter writes `hello`, `sessions`, `events`, and `bye`; the service may reply `ack` or `error`. The `prompt` reply reserved in version 1 is still unimplemented, and control does not use it. Frames split on LF only and tolerate a trailing CR. Result events carry raw Markdown in `text`, plus optional `toolName`, `toolCallId`, `isError: true`, and `truncated: true`; a tool call event carries the same optional `toolCallId` as the result that answers it. Append-only event records are split between whole events into frames of at most 1 MiB, including JSON escaping and the final LF; bodies are never split or flattened to fit a frame. Replacing `sessions` snapshots retain their separate 65,536-byte producer budget. An event whose exact identity envelope alone makes it impossible to fit is not sent, and the command reports the frame-limit error.
|
|
92
161
|
|
|
93
|
-
Control uses version 2 on its own connection to the same listener. The `control-hello` record carries the reporting token, protocol version, machine name, and this Pi session's identity, so two Pi sessions on one machine are distinguishable Consoles. The desk replies with a one-time `hmac-sha256` challenge; the proof binds `
|
|
162
|
+
Control uses version 2 on its own connection to the same listener. The `control-hello` record carries the reporting token, protocol version, machine name, and this Pi session's identity, so two Pi sessions on one machine are distinguishable Consoles. The desk replies with a one-time `hmac-sha256` challenge; the proof binds `odk-control-v2`, version `2`, nonce, machine, and Console session ID as newline-separated UTF-8 text. The separate Control Credential is the HMAC key and is never sent. Every request has a `requestId`; mutations also have a retry-stable `mutationId`; held attach traffic has an `attachmentId`; session-log events have a physical `position`. List, launch, and history are one-shot, while attach holds its connection. A version the desk does not accept is refused explicitly before credential failure. The canonical fixture is bundled at `fixtures/control-v2.json`. There is no detach record, no replay window, and no resync record.
|
|
94
163
|
|
|
95
164
|
## License
|
|
96
165
|
|
|
97
|
-
MIT
|
|
166
|
+
MIT
|
package/README.zh-CN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**English** | [简体中文](README.zh-CN.md)
|
|
6
6
|
|
|
7
|
-
Open DeskOS 通常靠扫描来发现 Pi 会话:本地扫,或经 SSH 扫。这个方向不一定可用,而且扫描看不到会话到底在做什么。本 package 把它反过来:拥有会话的那台机器主动打开一条 **
|
|
7
|
+
Open DeskOS 通常靠扫描来发现 Pi 会话:本地扫,或经 SSH 扫。这个方向不一定可用,而且扫描看不到会话到底在做什么。本 package 把它反过来:拥有会话的那台机器主动打开一条 **Open DeskOS Link**,自己上报。
|
|
8
8
|
|
|
9
9
|
机器还可以另外持有 **Control Credential** 而成为 **Console**:向同一个监听面另开一条控制连接,列出 desk 上托管的 **Hosted Pi**、启动新的、进入某个、改向正在执行的 turn、取消、结束,并按需读取完整历史。
|
|
10
10
|
|
|
@@ -24,36 +24,58 @@ pi install "$(pwd -P)/packages/open-deskos"
|
|
|
24
24
|
|
|
25
25
|
## 配置
|
|
26
26
|
|
|
27
|
-
地址与 token 必须同时提供,机器才会开始上报;只配一半会保持静默。控制能力还需要单独的控制凭据,未配置时完全关闭。运行 `/
|
|
27
|
+
地址与 token 必须同时提供,机器才会开始上报;只配一半会保持静默。控制能力还需要单独的控制凭据,未配置时完全关闭。运行 `/odk` 查看配置提示。
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
export
|
|
31
|
-
export
|
|
32
|
-
export
|
|
33
|
-
export
|
|
30
|
+
export ODK_LINK_ADDRESS="192.168.88.161:8765" # Open DeskOS Link Service
|
|
31
|
+
export ODK_LINK_TOKEN="<per-link token>"
|
|
32
|
+
export ODK_LINK_MACHINE="desk-mac" # 可选,默认为主机名
|
|
33
|
+
export ODK_LINK_CONTROL_TOKEN="<control credential>" # 可选:使本机成为 Console
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
请把它们放进 Pi 运行时的环境(桌面会话中即启动 `pi` 的那处)。控制凭据**不会上线**:desk 下发一次性 nonce,本机对 nonce 做出证明。
|
|
37
37
|
|
|
38
|
+
### 同时向多台 desk 上报
|
|
39
|
+
|
|
40
|
+
每台 desk 有自己的 token,所以需要喂多台 desk 的机器用一个文件声明:`~/.config/odk/desks.json`,或由 `ODK_LINK_DESKS_FILE` 指定的文件。
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"desks": [
|
|
45
|
+
{ "address": "cm5.example.ts.net:8765", "token": "<per-link token>", "controlToken": "<control credential>" },
|
|
46
|
+
{ "address": "handheld.example.ts.net:8765", "token": "<per-link token>" }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- 会话扫描只跑一次,**每台 desk 收到同一份**会话、状态、事件与历史回放。
|
|
52
|
+
- 某台 desk 不可达**不会影响其他 desk**;状态行会逐个列出每台 desk 各自的链路状态,“只有一半连通”绝不会被说成已连接。
|
|
53
|
+
- 该文件存在时它就是唯一来源:单台的环境变量形式不再同时读取(没有该文件时,环境变量形式与以前完全一致)。
|
|
54
|
+
- 缺 token、地址不是 `host:port`、或重复列出的条目会被**拒绝并把原因显示在 `/odk` 菜单里**,其余 desk 照常上报。运维指定了文件却读不到时同样会显式拒绝,而不是看起来像“永远连不上”。
|
|
55
|
+
- Console 只驱动一台 desk:默认是带有控制凭据的那台,也可用 `ODK_LINK_CONSOLE_DESK`(`host:port` 或裸主机名)指定。被指定但没有凭据的 desk 会如实提示需要凭据,而不是悄悄去驱动另一台。
|
|
56
|
+
- `machine` 可按 desk 单独给出,默认取 `ODK_LINK_MACHINE`,再退回主机名。
|
|
57
|
+
|
|
38
58
|
## 上报内容
|
|
39
59
|
|
|
40
|
-
- 当前会话的实时身份、目标、activity 与事件,以及本机注册在 `directory-sessions` 元数据中的其他会话(由 `pi-utils` 与 `pi-keyboard` 写入)。UUID 与带时间戳前缀的 UUID 别名会合并为同一会话。
|
|
41
|
-
- 会话状态:元数据表示工作中、Pi 进程存活、时间戳匹配该进程生命周期时才为 `running`;存活的 idle/settled 会话为 `settled`。已结束、无效、被复用、无法核实的 PID 或明确退出的记录为 `exited`。每次扫描只读取一次有界进程表(1 MiB
|
|
60
|
+
- 当前会话的实时身份、目标、activity 与事件,以及本机注册在 `directory-sessions` 元数据中的其他会话(由 `pi-utils` 与 `pi-keyboard` 写入)。resumed、reload 或 fork 后上报端启动时会先回放当前会话自己的有界 durable JSONL 尾部,再转发后续实时消息,避免 resumed Pi 被 Open DeskOS Link 显示为运行中却没有事件。UUID 与带时间戳前缀的 UUID 别名会合并为同一会话。
|
|
61
|
+
- 会话状态:元数据表示工作中、Pi 进程存活、时间戳匹配该进程生命周期时才为 `running`;存活的 idle/settled 会话为 `settled`。已结束、无效、被复用、无法核实的 PID 或明确退出的记录为 `exited`。每次扫描只读取一次有界进程表(1 MiB;Unix 截止时间为 2 秒,Windows CIM 为 5 秒),同一 PID 只有最新且无歧义的会话可视为存活。Windows 启动路径中的空格按引号规则保留;查询失败或系统命令挂起不会让发现流程无限等待。当前会话以 Pi 自身的 idle/agent 事件为准。
|
|
42
62
|
- 原始元数据的 start/update 时间、可选会话名、目标与 activity/recap。扫描不会凭空发明新的 activity 时间戳,也不读取会话历史。
|
|
43
|
-
- 有界会话事件:每会话最多保留最新连续的 300 条事件与 1,048,576 UTF-8
|
|
44
|
-
- 工具结果保留完整的多行 Markdown,包括表格、代码围栏和空白,每条正文最多 65,536 UTF-8 字节。所有文本块以两个换行连接;图片与任意结果元数据不上报。任一类型的正文超过自身字节上限时会带 `truncated: true`,且不会截断 Unicode 码点。可选 `toolName` 为独立的 200 字符摘要,不再作为前缀插入 Markdown
|
|
63
|
+
- 有界会话事件:每会话最多保留最新连续的 300 条事件与 1,048,576 UTF-8 字节(计入正文、工具名、完成事件身份与停止原因)。正文保留换行并按类型限长:user 8 KiB、thinking 4 KiB、tool-call 4 KiB、assistant 16 KiB、tool result 64 KiB。
|
|
64
|
+
- 工具结果保留完整的多行 Markdown,包括表格、代码围栏和空白,每条正文最多 65,536 UTF-8 字节。所有文本块以两个换行连接;图片与任意结果元数据不上报。任一类型的正文超过自身字节上限时会带 `truncated: true`,且不会截断 Unicode 码点。可选 `toolName` 为独立的 200 字符摘要,不再作为前缀插入 Markdown 正文。工具调用与回应它的结果都携带 Pi 为该次调用写下的可选 `toolCallId`,被 Pi 记为错误的结果携带 `isError: true`,因此消费端能把结果与它自己的调用配对,并从 Pi 的记录而非事件顺序读出结果状态。工具调用事件的文本说明该调用在做什么,而不只是它在哪里查找:`bash: <command>`、内容或文件名搜索的 `search: <pattern>`,其他工具为 `<tool>: <path|query|subject>`。会话 activity 仍为简短的首行摘要。
|
|
65
|
+
|
|
66
|
+
已完成的 assistant 事件使用实际持久化消息条目的 ID 作为 `eventId`,并保留 Pi 的有界 `stopReason`。实时完成元数据只在 `agent_settled` 后发送,此时当前分支上已存在新条目。`message_end` 仍为临时事件,因为 Pi 在该回调之后才持久化消息。重放使用同一条目 ID。中止、重复 settled 与非终态消息不会产生完成身份。上报完成不代表任务已经独立验证。
|
|
45
67
|
|
|
46
68
|
会话开始时以及每次扫描后每 5 秒刷新一次清单,链路离线期间同样刷新。刷新只替换被发现的条目:当前会话的身份与事件不会被陈旧元数据删除或覆盖。关闭/重载会取消刷新调度,并使待处理结果与重连失效。
|
|
47
69
|
|
|
48
70
|
最多上报 64 个会话。只有当前会话固定保留;其余按 running、settled、exited 顺序优先,再按时间排序。曾直接观察的会话被其他进程恢复时,可由更新的元数据刷新状态,同时保留已有事件。容量上限允许淘汰旧历史及其事件,并非归档。
|
|
49
71
|
|
|
50
|
-
每次扫描最多访问 256 个 workspace、2,048 个 JSON 文件、8,192 个目录项,每个文件最多读取 16 KiB。格式错误、超限、符号链接与非普通文件一律跳过。名称、目标与 activity 各限 200 字符,并可能在传输中进一步压缩以符合 65,536 字节的快照预算。会话 ID、精确工作目录、workspace 名称与时间戳从不缩短或折叠。若精确身份字段仍超出该预算,上报端会整条省略优先级最低的条目,并由 `/
|
|
72
|
+
每次扫描最多访问 256 个 workspace、2,048 个 JSON 文件、8,192 个目录项,每个文件最多读取 16 KiB。格式错误、超限、符号链接与非普通文件一律跳过。名称、目标与 activity 各限 200 字符,并可能在传输中进一步压缩以符合 65,536 字节的快照预算。会话 ID、精确工作目录、workspace 名称与时间戳从不缩短或折叠。若精确身份字段仍超出该预算,上报端会整条省略优先级最低的条目,并由 `/odk` 报告省略数量。当前会话始终保留。单个当前身份大于帧预算时无法在不改变该身份的前提下表示;这是病态上限。
|
|
51
73
|
|
|
52
74
|
默认读取 `~/.pi/agent/directory-sessions`。可用 `PI_DIRECTORY_SESSIONS_DIR` 指定注册表;否则 `PI_CODING_AGENT_DIR`(或旧的 `PI_AGENT_DIR`)覆盖 agent 目录。元数据写入端必须使用同一目录。发现过程不修改、清理注册表文件。不凭空创建没有元数据的会话;被删除的元数据会在下次刷新消失,除非本上报端曾直接观察该会话。
|
|
53
75
|
|
|
54
76
|
## 驱动 Hosted Pi
|
|
55
77
|
|
|
56
|
-
`/
|
|
78
|
+
`/odk` 会打开菜单,其控制台一行列出 desk 托管的 Hosted Pi(含状态、目标、项目与时长),并可启动新的会话、进入已有会话、追加指令(**运行中也可**,此时是改向当前 turn 而不是另起一个)、取消当前 turn、结束会话、按需读取完整历史。
|
|
57
79
|
|
|
58
80
|
- **一个 Console 同时驱动一个 Hosted Pi。** attach 是替换而非共享,可重复且按会话身份幂等,因此重启后的 Pi 会话可以重新 attach。
|
|
59
81
|
- **不重复、不遗漏。** 事件位置是其对应完整条目在 Hosted Pi 会话日志中的物理位置;若中间有非消息条目,位置只保证严格递增,不保证连续。首次 attach 发送 `after: null`,从 desk 当前边界开始;旧内容需显式通过 history 获取。恢复 attach 会发送上次已应用位置,先按序接收直到 `caught_up` 边界的补流,再进入实时事件。没有重放窗口,也没有需要解释的 resync。
|
|
@@ -62,20 +84,53 @@ export ODK_DESK_LINK_CONTROL_TOKEN="<control credential>" # 可选:使本机
|
|
|
62
84
|
- **desk 自己说了算。** 本机驱动期间,desk 会在 Pi Sessions 总览标题上标明驱动方,本地触控与键盘始终可用。
|
|
63
85
|
- **工具面。** 助手可调用 `desk_*` 工具完成 list / start / attach / prompt / cancel / end / history。控制台面板自己接管键盘输入,不拦截全局终端输入。
|
|
64
86
|
|
|
87
|
+
## 让 desk 指挥这个会话
|
|
88
|
+
|
|
89
|
+
上报的会话是只读的:desk 能看到它在做什么,仅此而已。这个包也可以让**这个**会话从内部变得可被指挥,不装任何进程,也不要第二把凭据。
|
|
90
|
+
|
|
91
|
+
声明这台机器的会话端点放在哪里,之后这台机器上每个 Pi session 都会在 `session_start` 发布自己的端点、在 `session_shutdown` 收掉它:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
export ODK_SESSION_HOST_SOCKET="$HOME/.local/run/odk/sessions"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
已经运行的 Pi 会保留启动时的环境。如果它没有会话端点,先在空闲时退出 Pi。
|
|
98
|
+
在启动它的 shell 中设置上面的变量,再恢复原有会话文件:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
pi --session /absolute/path/to/existing-session.jsonl
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
使用原文件,同一会话只运行一个进程。恢复会保留原会话身份和历史。
|
|
105
|
+
|
|
106
|
+
Unix 上这个端点独立于上报凭据工作。Windows 当前会话端点在支持私有 IPC 前明确不可用,拒绝原因可通过 `/odk status` 查看;Windows 的 Open DeskOS Link 上报及远程 Hosted Pi 控制仍受支持。发布包包含 `session-control` 启动器;同一项目有多个会话时,它会按请求的任务身份选择会话。
|
|
107
|
+
|
|
108
|
+
于是每个 session 在 `<目录>/<sessionId>.sock` 上提供 desk 语音 agent 已经在说的那个协议 —— `list`、`status`、`prompt`、`cancel` —— 旁边还有一个 `0600` 的描述文件,写明自己的 socket、project 和状态。`start`、`launch`、`end`、`history` 用同一个固定理由拒绝:这个进程是一个会话,不是会话宿主。
|
|
109
|
+
|
|
110
|
+
- **它是声明出来的能力,不是默认。** 没声明端点的机器就没有端点可被到达;让别人给这个会话派活的端点是一个决定,不是某个包顺手打开的开关。
|
|
111
|
+
- **属主是唯一的门。** socket 在 `0700` 目录里、自己是 `0600`;没有令牌要出示,也没有任何东西监听网络。
|
|
112
|
+
- **desk 通过你自己的 SSH 会话到达它。** 在那一侧运行 `session-control` 启动器即可,它自己找 node 运行时 —— 登录会话的 PATH 和交互式 shell 的不是一回事。命令里不带 socket 路径时,它按请求自己的 project 找会话;没有任何 session 服务那个 project 时,返回的是协议自己的拒绝,而不是别的会话的回答。
|
|
113
|
+
- **每个 session 都用自己的端点。** 按根目录列出会话时,聚合该目录及子目录中的活动会话;status、prompt、cancel 必须使用列表返回的准确 project 和 session ID。
|
|
114
|
+
- **没有回复不等于没有会话。** 列表保留健康端点的会话,失败端点通过 `incomplete` 和 `unavailable` 明示;截断或不完整的列表不能证明会话不存在。
|
|
115
|
+
- **输入继续原会话。** 默认采用 Pi 普通 Enter 的行为:运行时使用 steer;显式 followUp 留到下一轮。命令和提示模板按 Pi 的规则展开。
|
|
116
|
+
- **提交与执行分别核对。** Pi 的公开扩展 API 不确认单条消息,因此原生 prompt 回执报告 `delivery: unknown`,保留 `mutationId`,禁止自动重放。状态中的 prompt、response、activity 是观察到的会话事实;相同文本不能证明某一次请求已执行。
|
|
117
|
+
- **重试不会静默重复变更。** 同一 SessionHost 生命周期内,相同 mutation ID 和内容复用原回执,不同内容被拒绝。最多保留 256 条变更回执;满后拒绝新变更,不淘汰旧记录。进程重启或扩展 reload 后不保留这些回执。
|
|
118
|
+
- **这个端点不会让 Pi 活不下去。** 它被 unref,所以"这个 session 能被别人到达"不构成"这个 session 不能关闭"的理由。
|
|
119
|
+
|
|
65
120
|
## 不上报什么
|
|
66
121
|
|
|
67
|
-
- **上报永远不等于管理。** 只持有上报 token 的机器只能上报:package 从不向被上报的会话发送 prompt、不阻塞回合、不修改消息;通过
|
|
122
|
+
- **上报永远不等于管理。** 只持有上报 token 的机器只能上报:package 从不向被上报的会话发送 prompt、不阻塞回合、不修改消息;通过 Open DeskOS Link 可见**不等于**可管理。
|
|
68
123
|
- **控制是独立能力。** 它需要 Control Credential,且从不走上报连接;没有该凭据的机器不可能成为 Console。
|
|
69
124
|
- **不管理被上报的会话。** 即使作为 Console,package 也不管理自己上报的那些会话;控制只作用于 desk 托管的 Hosted Pi,那是另一回事。
|
|
70
125
|
- **不能回答 Hosted Pi 的中途请求。** desk 的 Pi host 不加载扩展,SDK 也不提供权限、审批或提问面,因此 Hosted Pi 无法产生需要你回答的请求。这是 host 配置的限制,不是缺失的功能。
|
|
71
126
|
- **无网络发现与配对。** 发现的是本地会话元数据;不知道地址与 token 的机器无法注册,Console 也必须事先知道控制凭据。
|
|
72
|
-
-
|
|
73
|
-
- **不加密。**
|
|
127
|
+
- **不扫描其他会话历史与凭据。** resumed、reload 或 fork 后当前 Pi 启动时只读取 `sessionManager.getSessionFile()` 指向的当前 JSONL 文件有界尾部,并按同一事件上限回放;不读取其他会话的历史、认证文件或任意额外元数据(其他会话只贡献注册表元数据,不合成事件)。当前会话的工具结果正文**会**在上述上限内上报,其中可能包含文件内容或工具返回的敏感输出;只有在你确实愿意共享时才配置该链路。
|
|
128
|
+
- **不加密。** Open DeskOS Link 是面向仅局域网监听的明文连接。控制凭据不上线,因此抓包拿不到可复用的执行凭据,但内容本身并未加密。请勿把 Open DeskOS Link Service 暴露到局域网之外。
|
|
74
129
|
|
|
75
130
|
## 在 Pi 内查看链路
|
|
76
131
|
|
|
77
132
|
```
|
|
78
|
-
/
|
|
133
|
+
/odk
|
|
79
134
|
```
|
|
80
135
|
|
|
81
136
|
菜单在任何配置下形状一致,不可用的一行会说明原因而不是消失。其中状态一行显示链路状态、机器标识、连接地址、上报会话数、事件数、控制状态,以及最近的连接错误(如有);控制台一行打开 Hosted Pi 列表。带参是快捷路径:`console`、`status`、`attach`、`launch`、`cancel`、`history`。诊断信息在任何配置下都仅通过命令查看:Open DeskOS 不添加页脚状态行、自定义页脚或工作目录后缀,离线或未配置时也一样。
|
|
@@ -84,14 +139,16 @@ export ODK_DESK_LINK_CONTROL_TOKEN="<control credential>" # 可选:使本机
|
|
|
84
139
|
|
|
85
140
|
上报端以递增等待重连(1s、2s、4s……上限 30s),并始终只保持一条链路。历史与待发事件均受每会话 300 条 / 1,048,576 字节上限约束。每次新连接都会发送当前会话状态,重放完整的已保留事件尾部,即使离线期间没有新事件也一样,因为服务端可能在最后一条链路关闭后清除机器状态。此连接后续刷新只发送新事件。
|
|
86
141
|
|
|
142
|
+
只有服务端用合法 v1 ACK 接受 `hello` 后,链路才从 `connecting` 变为 `connected`。认证被拒绝或超时会变为 `offline` 并重试;TCP 接通本身不代表上报认证成功。跨网络分片的 UTF-8 字符保持完整。会话关闭会使待完成的回放、挂接与启动失效,迟到回调无法重建端点或操作替换后的会话。
|
|
143
|
+
|
|
87
144
|
控制连接是分开的,不复制这套行为:list / launch / history 用一枪式请求连接,仅在本机 attach 到某个 Hosted Pi 期间才保持一条连接。丢失它不影响上报,也不会结束 desk 上的会话。
|
|
88
145
|
|
|
89
146
|
## 线上契约
|
|
90
147
|
|
|
91
|
-
一条 TCP 连接上的换行分隔 JSON,上报为版本 1。清单发现的会话带 `discovered: true`,当前会话直接观察的记录不带该标记,使服务端优先采用活动中的直接观察,而非其他上报端读取的更晚元数据。上报端写 `hello`、`sessions`、`events`、`bye`;服务端可回 `ack`、`error`。版本 1 预留的 `prompt` 回复仍未实现,且控制不使用它。分帧只按 LF,并容忍尾部 CR。结果事件的 `text` 为原始 Markdown,可带 `toolName` 和 `truncated: true`。追加式 `events` 记录按完整事件拆成每帧最多 1 MiB 的批次,上限包括 JSON 转义和末尾 LF;不会为适应帧大小而拆分、压平正文。替换式 `sessions` 快照仍使用独立的 65,536 字节生产端预算。若完整身份字段导致单条事件无法装入一帧,该事件不会发送,命令会显示帧上限错误。
|
|
148
|
+
一条 TCP 连接上的换行分隔 JSON,上报为版本 1。清单发现的会话带 `discovered: true`,当前会话直接观察的记录不带该标记,使服务端优先采用活动中的直接观察,而非其他上报端读取的更晚元数据。上报端写 `hello`、`sessions`、`events`、`bye`;服务端可回 `ack`、`error`。版本 1 预留的 `prompt` 回复仍未实现,且控制不使用它。分帧只按 LF,并容忍尾部 CR。结果事件的 `text` 为原始 Markdown,可带 `toolName`、`toolCallId`、`isError: true` 和 `truncated: true`;工具调用事件携带与回应它的结果相同的可选 `toolCallId`。追加式 `events` 记录按完整事件拆成每帧最多 1 MiB 的批次,上限包括 JSON 转义和末尾 LF;不会为适应帧大小而拆分、压平正文。替换式 `sessions` 快照仍使用独立的 65,536 字节生产端预算。若完整身份字段导致单条事件无法装入一帧,该事件不会发送,命令会显示帧上限错误。
|
|
92
149
|
|
|
93
|
-
控制使用版本 2,走同一监听面上的独立连接。`control-hello` 携带上报 token、协议版本、机器名与当前 Pi 会话身份,因此同一机器上的两个 Pi 会话是两个可区分的 Console。desk 返回一次性 `hmac-sha256` challenge;证明内容把 `
|
|
150
|
+
控制使用版本 2,走同一监听面上的独立连接。`control-hello` 携带上报 token、协议版本、机器名与当前 Pi 会话身份,因此同一机器上的两个 Pi 会话是两个可区分的 Console。desk 返回一次性 `hmac-sha256` challenge;证明内容把 `odk-control-v2`、版本 `2`、nonce、机器名与 Console 会话 ID 以换行分隔的 UTF-8 文本绑定起来。独立 Control Credential 只作为 HMAC 密钥,绝不上线。每个请求都有 `requestId`,变更请求另有可用于重试对账的稳定 `mutationId`,保持连接的 attach 流量带 `attachmentId`,会话日志事件带物理 `position`。list / launch / history 为一枪式连接,attach 才保持连接。desk 不接受的版本会先明确报错,不会伪装成凭据错误。规范 fixture 随包位于 `fixtures/control-v2.json`。没有 detach 记录、没有重放窗口、也没有 resync 记录。
|
|
94
151
|
|
|
95
152
|
## License
|
|
96
153
|
|
|
97
|
-
MIT
|
|
154
|
+
MIT
|
package/fixtures/control-v2.json
CHANGED
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
"nonce": "nonce-fixture",
|
|
15
15
|
"algorithm": "hmac-sha256"
|
|
16
16
|
},
|
|
17
|
-
"transcript": "
|
|
17
|
+
"transcript": "odk-control-v2\n2\nnonce-fixture\ndesk-mac\nconsole-session",
|
|
18
18
|
"credential": "control-secret",
|
|
19
19
|
"proof": {
|
|
20
20
|
"v": 2,
|
|
21
21
|
"type": "control-proof",
|
|
22
|
-
"proof": "
|
|
22
|
+
"proof": "9d6021124c1d0ad40f28080d202921b696845fec6c09286758f69d52d7b025dd"
|
|
23
23
|
},
|
|
24
24
|
"accepted": {
|
|
25
25
|
"v": 2,
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-open-deskos",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Report this machine's Pi sessions and their operating events to Open DeskOS over a package-initiated
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Report this machine's Pi sessions and their operating events to Open DeskOS over a package-initiated Open DeskOS Link, and optionally drive a Pi session Open DeskOS hosts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"odk",
|
|
9
|
+
"odk-link",
|
|
10
10
|
"reporting",
|
|
11
11
|
"observability",
|
|
12
12
|
"sessions",
|
|
@@ -26,11 +26,12 @@
|
|
|
26
26
|
"index.ts",
|
|
27
27
|
"src",
|
|
28
28
|
"fixtures",
|
|
29
|
+
"session-control",
|
|
29
30
|
"README.md",
|
|
30
31
|
"README.zh-CN.md"
|
|
31
32
|
],
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@fradser/pi-kit": "0.
|
|
34
|
+
"@fradser/pi-kit": "0.6.0"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
37
|
"@earendil-works/pi-coding-agent": "*",
|
|
@@ -42,4 +43,4 @@
|
|
|
42
43
|
"./index.ts"
|
|
43
44
|
]
|
|
44
45
|
}
|
|
45
|
-
}
|
|
46
|
+
}
|
package/session-control
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# What a desk runs on the far side of its own SSH session to reach a session on this
|
|
3
|
+
# machine. It exists because that session's PATH is whatever the login shell happens
|
|
4
|
+
# to carry: on a machine where node is a version manager's install, node is not on
|
|
5
|
+
# the PATH a non-interactive SSH session gets, and a client that assumed it would
|
|
6
|
+
# answer nothing at all.
|
|
7
|
+
#
|
|
8
|
+
# The frame arrives on standard input and the answer leaves on standard output, so
|
|
9
|
+
# this launcher adds no behaviour of its own: it finds a node runtime and execs the
|
|
10
|
+
# relay beside itself.
|
|
11
|
+
set -eu
|
|
12
|
+
|
|
13
|
+
here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
14
|
+
client="$here/src/session-host-client.mjs"
|
|
15
|
+
|
|
16
|
+
if [ ! -f "$client" ]; then
|
|
17
|
+
echo "session-control: the session relay is missing at $client" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# An operator who knows which runtime this machine uses can say so; otherwise the
|
|
22
|
+
# first one that exists wins, and a stable version-manager alias is preferred over a
|
|
23
|
+
# versioned path that the next upgrade replaces.
|
|
24
|
+
for node in \
|
|
25
|
+
"${ODK_NODE:-}" \
|
|
26
|
+
"$(command -v node 2>/dev/null || true)" \
|
|
27
|
+
"$HOME/.local/share/fnm/aliases/default/bin/node" \
|
|
28
|
+
"$HOME/.local/share/fnm/node-versions"/*/installation/bin/node \
|
|
29
|
+
/opt/homebrew/bin/node \
|
|
30
|
+
/usr/local/bin/node \
|
|
31
|
+
/usr/bin/node; do
|
|
32
|
+
if [ -n "$node" ] && [ -x "$node" ]; then
|
|
33
|
+
exec "$node" "$client" "$@"
|
|
34
|
+
fi
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
echo "session-control: no node runtime was found on this host; set ODK_NODE to one" >&2
|
|
38
|
+
exit 1
|