@code-yeongyu/senpi 2026.10.4 → 2026.10.5
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/CHANGELOG.md +20 -0
- package/dist/bundle/chunks/{anthropic-messages-GE4OCV55.js → anthropic-messages-G5236QNK.js} +1 -1
- package/dist/bundle/chunks/{app-server-command-I2AKSC6I.js → app-server-command-3Q5G4CAF.js} +1 -1
- package/dist/bundle/chunks/{chunk-2CS2GZBE.js → chunk-2WV426OJ.js} +2 -2
- package/dist/bundle/chunks/{chunk-INGR4HFB.js → chunk-ECDLITEY.js} +2 -2
- package/dist/bundle/chunks/{chunk-6O26XO2C.js → chunk-EEOKVSNF.js} +220 -252
- package/dist/bundle/chunks/{chunk-MNLBL232.js → chunk-JVSOW4QU.js} +1 -1
- package/dist/bundle/chunks/chunk-K3BXFPWZ.js +2 -0
- package/dist/bundle/chunks/{chunk-QOT4HXY2.js → chunk-UHRXXTFZ.js} +3 -3
- package/dist/bundle/chunks/chunk-UUAKEDVS.js +14 -0
- package/dist/bundle/chunks/{chunk-ZKLQIAG5.js → chunk-UXIQK5NQ.js} +1 -1
- package/dist/bundle/chunks/chunk-WTJU5B2C.js +51 -0
- package/dist/bundle/chunks/chunk-YSPHACIN.js +2 -0
- package/dist/bundle/chunks/{cli-main-BLXUYHLQ.js → cli-main-VZWSUHFB.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-TQWU6DPG.js → help-fast-path-SHZJ3QWQ.js} +1 -1
- package/dist/bundle/chunks/{host-command-JXNOYPAC.js → host-command-7JIGGOWR.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-CSVSUAS7.js → interactive-mode-JA7AHYYL.js} +1 -1
- package/dist/bundle/chunks/multi-session-host-34WOINP4.js +23 -0
- package/dist/bundle/chunks/{openai-completions-2DUKDEVN.js → openai-completions-VSH22FCN.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-WCB4HIPI.js → openai-responses-WLBHCFYL.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-Q5EQZL7I.js → package-manager-cli-2QLVGQSC.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-VZLJIEP5.js → rotation-stream-JUCEXHWU.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-6VMXY46C.js → rpc-mode-QEBSN2OH.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-K3RWX652.js → session-control-endpoint-UGQIP6ZN.js} +1 -1
- package/dist/bundle/chunks/{session-picker-IPG3MKRC.js → session-picker-HX4LOJZ7.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +251 -242
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session-runtime.d.ts +5 -0
- package/dist/core/agent-session-runtime.js +5 -0
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session.d.ts +14 -0
- package/dist/core/agent-session.js +31 -0
- package/dist/core/browser-engine.d.ts +7 -0
- package/dist/core/browser-engine.js +7 -0
- package/dist/core/continue-from-leaf.d.ts +18 -0
- package/dist/core/continue-from-leaf.js +30 -0
- package/dist/core/extensions/builtin/gpt-apply-patch/line-endings.d.ts +28 -0
- package/dist/core/extensions/builtin/gpt-apply-patch/line-endings.js +76 -0
- package/dist/core/extensions/builtin/gpt-apply-patch/parser.js +21 -23
- package/dist/core/extensions/builtin/gpt-apply-patch/patch-replace.js +5 -16
- package/dist/core/extensions/builtin/gpt-apply-patch/streaming-parser.js +3 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/text.d.ts +8 -0
- package/dist/core/extensions/builtin/gpt-apply-patch/text.js +26 -3
- package/dist/core/extensions/builtin/gpt-apply-patch/types.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +1 -0
- package/dist/core/extensions/builtin/terminal/tools/bash.js +8 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.js +7 -0
- package/dist/core/extensions/types.d.ts +4 -0
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/sdk.d.ts +3 -0
- package/dist/core/sdk.js +1 -0
- package/dist/core/tools/bash.js +3 -0
- package/dist/main.js +1 -0
- package/dist/modes/interactive/components/session-selector.d.ts +11 -0
- package/dist/modes/interactive/components/session-selector.js +30 -5
- package/dist/modes/rpc/connection-handler.js +18 -1
- package/dist/modes/rpc/custom-capability.d.ts +9 -0
- package/dist/modes/rpc/custom-capability.js +9 -0
- package/dist/modes/rpc/host-daemon-env.js +2 -0
- package/dist/modes/rpc/host-idle-handover.js +1 -0
- package/dist/modes/rpc/media-placeholders.d.ts +23 -1
- package/dist/modes/rpc/media-placeholders.js +16 -13
- package/dist/modes/rpc/rpc-client.d.ts +8 -0
- package/dist/modes/rpc/rpc-client.js +8 -0
- package/dist/modes/rpc/rpc-input-validation.d.ts +6 -0
- package/dist/modes/rpc/rpc-input-validation.js +11 -0
- package/dist/modes/rpc/rpc-types.d.ts +19 -0
- package/dist/modes/rpc/session-command-router.js +11 -2
- package/dist/modes/rpc/session-event-writer.d.ts +4 -0
- package/dist/modes/rpc/session-event-writer.js +12 -1
- package/dist/modes/rpc/session-registry-attach.js +4 -0
- package/dist/modes/rpc/session-worker-client.d.ts +3 -0
- package/dist/modes/rpc/session-worker-client.js +6 -0
- package/dist/modes/rpc/session-worker-protocol.d.ts +5 -0
- package/dist/modes/rpc/session-worker.js +6 -0
- package/dist/modes/rpc/tool-media-store.d.ts +59 -0
- package/dist/modes/rpc/tool-media-store.js +190 -0
- package/dist/modes/rpc/worker-session-registry.js +8 -3
- package/docs/environment-variables.md +1 -0
- package/docs/rpc.md +62 -3
- package/package.json +7 -6
- package/dist/bundle/chunks/chunk-KMMECBM2.js +0 -15
- package/dist/bundle/chunks/chunk-LHYG7TK7.js +0 -2
- package/dist/bundle/chunks/chunk-LJWC5IVX.js +0 -2
- package/dist/bundle/chunks/chunk-PC3E2YS6.js +0 -9
- package/dist/bundle/chunks/multi-session-host-5AFJKSSM.js +0 -23
package/docs/rpc.md
CHANGED
|
@@ -87,6 +87,23 @@ snapshot replay) with an `image_ref` placeholder:
|
|
|
87
87
|
[`get_media`](#get_media) using the `ref`. User-authored images (`prompt`/`steer`/`follow_up`
|
|
88
88
|
`images`) are never replaced.
|
|
89
89
|
|
|
90
|
+
A multi-session host also keeps each tool-result image on disk, so a client can render it from a
|
|
91
|
+
path without a round trip. The bytes are written atomically BEFORE the placeholder is emitted, and the
|
|
92
|
+
placeholder gains one of two fields:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{"type": "image_ref", "mimeType": "image/png", "byteLength": 553000,
|
|
96
|
+
"ref": {"toolCallId": "call_abc123", "contentIndex": 1},
|
|
97
|
+
"path": "/home/me/.senpi/agent/sessions/--proj--/media/<durableSessionId>/<sha256(toolCallId)>/1-<sha256(bytes)>.png"}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`unavailableReason` replaces `path` when the image was not kept: `image_too_large` (over 20 MiB),
|
|
101
|
+
`session_limit` (the durable session already holds 256 MiB of images; new images are refused and
|
|
102
|
+
stored ones are never evicted) or `storage_error` (the write failed, the format is not PNG, JPEG,
|
|
103
|
+
GIF or WebP, or the bytes are not the format the tool claimed). A session with no session file has neither field. Files are immutable and private, live next to the session files and never under the
|
|
104
|
+
project, outlive disconnects, idle shutdown and generation handover, and are deleted with the session
|
|
105
|
+
(from the interactive session selector). `get_media` keeps working for every placeholder.
|
|
106
|
+
|
|
90
107
|
A connection that does not advertise the capability receives byte-identical output to before. The
|
|
91
108
|
capability is advertised by the host in `get_protocol_info.capabilities` in both classic and
|
|
92
109
|
multi-session mode, but the transform itself is applied only by the multi-session host: classic
|
|
@@ -850,6 +867,24 @@ surface, and `new_session` / `switch_session` / `fork` inside the session keep i
|
|
|
850
867
|
`chat`: a host without it refuses `chat` with `invalid_launch_profile`, so a gateway falls back to `app`. Any value other
|
|
851
868
|
than `terminal`, `app` or `chat` is refused with `invalid_launch_profile`.
|
|
852
869
|
|
|
870
|
+
#### Browser engine per session (`browser_engine`)
|
|
871
|
+
|
|
872
|
+
`open_session.browserEngine: "connected" | "builtin" | "none"` names the browser THIS session's skills drive: the
|
|
873
|
+
user's own browser, the app's in-app browser, or none. It is session-scoped, never process-wide: the session's tool
|
|
874
|
+
subprocesses (the bash tool and everything it spawns) and its eval kernels, including their child processes, see
|
|
875
|
+
`OMO_BROWSER_ENGINE=<value>`, and no other session on the host does. A session opened without the field sees no such
|
|
876
|
+
variable, even when the host process itself exports one (a daemon never inherits it from the process that ensured it).
|
|
877
|
+
`BSK_HOME` and `BSK_BIN` are per install, not per session: they pass from the host environment to every session
|
|
878
|
+
unchanged. A later `open_session` that attaches to a live session with another `browserEngine` moves that session to
|
|
879
|
+
it, and an attach without the field keeps the current engine; `new_session` / `switch_session` / `fork` keep it too.
|
|
880
|
+
Probe `browser_engine` in `get_protocol_info` before sending the field; the capability is advertised only because the
|
|
881
|
+
value reaches every consumer above. Any other value is refused with `invalid_launch_profile`.
|
|
882
|
+
|
|
883
|
+
A skill that must tell the client what the browser is doing publishes it as that session's own event: an extension
|
|
884
|
+
registers a tool that calls `pi.rpc.emit(name, data)`, an eval cell reaches it as `tool.<name>(...)`, and the host adds
|
|
885
|
+
the owning `sessionId` to the `extension_event` record. Only clients that advertised `extension_events` receive it.
|
|
886
|
+
|
|
887
|
+
|
|
853
888
|
### Session auto-titling
|
|
854
889
|
|
|
855
890
|
Auto-generated session titles are on by default only for interactive launches. A multi-session host decides titling per
|
|
@@ -1397,7 +1432,7 @@ Explicit permission rules and remembered approvals retain their existing precede
|
|
|
1397
1432
|
| Command | Params | Success data | Notes |
|
|
1398
1433
|
| --- | --- | --- | --- |
|
|
1399
1434
|
| `get_protocol_info` | - | `{ protocolVersion: 1, serverVersion: string, capabilities: string[], mode: "classic"\|"multi", instanceId: string, generation: number, engineVersion: string, engineOrdinal: [y, m, d, n, epoch], launch_profile: { profile_id, core }, memory_pressure?: boolean }` | Answered in BOTH modes; side-effect-free; the capability probe. Multi-session hosts include `multi_session`, `retain_on_disconnect`, `session_kind`, `session_context`, `auto_title_per_session`, `durable_session_id`, `prompt_surface` and `prompt_surface_chat` plus the negotiated launch capabilities. Those are HOST capabilities (a client never sends them) and are advertised only in multi-session mode, where the host owns the attachment refcount and the per-session launch profile. The identity fields are described under "Host identity" above; compatibility is decided from `protocolVersion`, `capabilities` and `engineOrdinal`, NEVER from `serverVersion`. |
|
|
1400
|
-
| `open_session` | `sessionPath?`, `cwd?`, `provider?`, `modelId?`, `thinkingLevel?`, `permissionPreset?`, `retain_on_disconnect?`, `kind?`, `context?`, `auto_title?`, `durableSessionId?`, `promptSurface?` (all optional; paths MUST be absolute) | `{ sessionId, state: RpcSessionState, attached?: true }` | `sessionPath` = today's `--session` semantics (open-if-exists else create persisting there, `session-manager.ts:926-940`); `provider`/`modelId` applied only on create (resume restores the session's model — mirrors `SenpiSessionRuntime.ts:198-200`); params form the immutable launch profile (D8). When the path is already held by a fully-open session, the open ATTACHES to it: same routing handle, `attached: true`, one more attachment counted; the runtime is torn down only when the last attachment closes. Idle sessions past the eviction window are closed by the host itself. `retain_on_disconnect: true` (default false) makes a dropped connection DETACH from this session instead of closing it — see "Retained sessions" below. `kind` (default `interactive`) and the opaque `context` map are described under "Session kind and context" above; both are stored frozen for the session's life and never influence auth, model or resource resolution. |
|
|
1435
|
+
| `open_session` | `sessionPath?`, `cwd?`, `provider?`, `modelId?`, `thinkingLevel?`, `permissionPreset?`, `retain_on_disconnect?`, `kind?`, `context?`, `auto_title?`, `durableSessionId?`, `promptSurface?`, `browserEngine?` (all optional; paths MUST be absolute) | `{ sessionId, state: RpcSessionState, attached?: true }` | `sessionPath` = today's `--session` semantics (open-if-exists else create persisting there, `session-manager.ts:926-940`); `provider`/`modelId` applied only on create (resume restores the session's model — mirrors `SenpiSessionRuntime.ts:198-200`); params form the immutable launch profile (D8). When the path is already held by a fully-open session, the open ATTACHES to it: same routing handle, `attached: true`, one more attachment counted; the runtime is torn down only when the last attachment closes. Idle sessions past the eviction window are closed by the host itself. `retain_on_disconnect: true` (default false) makes a dropped connection DETACH from this session instead of closing it — see "Retained sessions" below. `kind` (default `interactive`) and the opaque `context` map are described under "Session kind and context" above; both are stored frozen for the session's life and never influence auth, model or resource resolution. |
|
|
1401
1436
|
| `close_session` | `sessionId` | `{}` | Refused with `unknown_session` when the requesting connection never attached to that handle (a close releases the CALLER's attachment, and `list_sessions` publishes every handle). Otherwise aborts active work, awaits agent idle + settled persistence for up to the host grace window (default 10s), then quarantines any worker that has not exited without releasing its path reservation; its response is the LAST record tagged with that handle for the first closer — no events after (test-pinned). An admitted concurrent close joins the same teardown and receives its own successful response; output saturation rejects admission with the bounded close-overflow/resync notice described above. |
|
|
1402
1437
|
| `list_sessions` | `include_workers?` (default false) | `{ sessions: [{ sessionId, durableSessionId, sessionPath, cwd, name, status, attachments, kind, context? }] }` | Includes `opening`/`closing` entries. Internally quarantined workers remain externally `closing` until exit. `attachments` is the session's live client attachment count; `0` on an `open` row is a retained session with no client attached. Every row carries `kind`. Rows with `kind: "worker"` are omitted unless `include_workers: true`, and `context` is published ONLY on that listing — a default listing carries no `context` at all. |
|
|
1403
1438
|
| `release_session` | `sessionId`, `reason: "takeover"`, `interrupt?`, `force?` | `{ released: true, session_path, attachments }` | Hands the session to a runtime outside this host. See "Handing a session over (`release_session`)" below. |
|
|
@@ -1430,7 +1465,7 @@ In the response `error` field, machine-matchable:
|
|
|
1430
1465
|
- `open_failed: <detail>`
|
|
1431
1466
|
- `invalid_session_context: <detail>` (`open_session.context` past a documented cap: more than 32 keys, a key that does not match `^[a-z][a-z0-9_]*$`, a non-string or >16 KiB value, or more than 32 KiB of JSON in total; the detail names the cap and its byte budget)
|
|
1432
1467
|
- `invalid_session_kind: <detail>` (`open_session.kind` other than `interactive` or `worker`)
|
|
1433
|
-
- `invalid_launch_profile: <detail>` (`open_session.auto_title` present but not a boolean,
|
|
1468
|
+
- `invalid_launch_profile: <detail>` (`open_session.auto_title` present but not a boolean, `open_session.promptSurface` other than `terminal`, `app` or `chat`, or `open_session.browserEngine` other than `connected`, `builtin` or `none`)
|
|
1434
1469
|
- `host_memory_pressure` (sent only by hosts released before #2207, which declined to CREATE a `kind: "worker"` session above `SENPI_RPC_HOST_RSS_REFUSE_MB`; `errorData { rssMb, retry_after_ms }` says when to ask again. Current hosts never refuse an open for memory; a client talking to an older generation waits and retries, it never starts a second host or a per-child process)
|
|
1435
1470
|
- `warm_failed: <detail>` (`warm` could not load its profile, for example a `cwd` that does not exist; not remembered, so a retry loads again)
|
|
1436
1471
|
- `media_not_found` (`get_media` for an unknown `toolCallId`, or a `contentIndex` that does not point at an image block)
|
|
@@ -1853,7 +1888,7 @@ List all configured models.
|
|
|
1853
1888
|
{"type": "get_available_models"}
|
|
1854
1889
|
```
|
|
1855
1890
|
|
|
1856
|
-
Response contains an array of full [Model](#model) objects with supported thinking levels
|
|
1891
|
+
Response contains an array of full [Model](#model) objects with supported thinking levels. Each row also carries `supportsAssistantPrefill`: whether the model accepts a request ending with an assistant message, given the session's current thinking level. It is `false` for every model today, so clients continue an edited answer with `continue_from_leaf`:
|
|
1857
1892
|
```json
|
|
1858
1893
|
{
|
|
1859
1894
|
"type": "response",
|
|
@@ -2450,6 +2485,30 @@ Failures carry a typed `errorCode`:
|
|
|
2450
2485
|
|
|
2451
2486
|
Message identity: RPC mode emits `entry_appended` right after every persisted `message_end`, carrying the full session entry (`entry.id`, `entry.parentId`, `entry.message`). Clients should record `entry.id` from that stream as the identity of each rendered message instead of inferring it by position, and pass it as `entryId` here.
|
|
2452
2487
|
|
|
2488
|
+
#### continue_from_leaf
|
|
2489
|
+
|
|
2490
|
+
Start a new turn from the session's current leaf without a new user prompt. A typical use is after `edit_assistant_message`: the agent carries on from the edited answer as if it were its own words. Default models do not accept a request that ends with an assistant message, so the turn is driven by a hidden custom message (`customType: "continue-from-leaf"`, `display: false`). It is persisted, but clients must never render it. Only hosts that advertise the `continue_from_leaf` capability in `get_protocol_info` accept this command.
|
|
2491
|
+
|
|
2492
|
+
```json
|
|
2493
|
+
{"type": "continue_from_leaf"}
|
|
2494
|
+
```
|
|
2495
|
+
|
|
2496
|
+
Response (the turn then streams like any other):
|
|
2497
|
+
|
|
2498
|
+
```json
|
|
2499
|
+
{"type": "response", "command": "continue_from_leaf", "success": true}
|
|
2500
|
+
```
|
|
2501
|
+
|
|
2502
|
+
Failures carry a typed `errorCode`:
|
|
2503
|
+
|
|
2504
|
+
| `errorCode` | Meaning |
|
|
2505
|
+
|-------------|---------|
|
|
2506
|
+
| `streaming` | A response is in flight; retry once the turn ends |
|
|
2507
|
+
| `nothing_to_continue` | The session has no messages yet |
|
|
2508
|
+
| `leaf_not_assistant` | The conversation ends on a user message (for example an edited prompt): there is no answer to continue. Send or retry it instead |
|
|
2509
|
+
|
|
2510
|
+
A provider error during the continued turn is reported through the usual turn events, as for a prompt.
|
|
2511
|
+
|
|
2453
2512
|
#### edit_user_message
|
|
2454
2513
|
|
|
2455
2514
|
Replace a user message with an edited copy, in place. This is the `/tree` flow for selecting a prompt (see [Selection Behavior](sessions.md#selection-behavior)) with the edited text written into the session instead of into an editor: the leaf moves to the target's parent and the edited copy is appended there as the new leaf. The original prompt and every reply after it stay in the file on an abandoned branch. Nothing is deleted.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.5",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"vendor"
|
|
43
43
|
],
|
|
44
44
|
"scripts": {
|
|
45
|
+
"check:provider-defaults": "vitest run test/model-resolver.test.ts -t \"default model selection\"",
|
|
45
46
|
"clean": "shx rm -rf dist",
|
|
46
47
|
"build": "tsgo -p tsconfig.build.json && npm run copy-assets && shx chmod +x dist/cli.js dist/rpc-entry.js && shx cp dist/cli.js dist/senpi && shx chmod +x dist/senpi && npm run build:bundle",
|
|
47
48
|
"build:bundle": "node ../../scripts/build-coding-agent-bundle.mjs",
|
|
@@ -56,9 +57,9 @@
|
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
59
|
"@earendil-works/chord": "0.99.1",
|
|
59
|
-
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.
|
|
60
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.
|
|
61
|
-
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.
|
|
60
|
+
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.5",
|
|
61
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.5",
|
|
62
|
+
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.5",
|
|
62
63
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
63
64
|
"chalk": "6.0.0",
|
|
64
65
|
"cross-spawn": "7.0.6",
|
|
@@ -77,8 +78,8 @@
|
|
|
77
78
|
"yaml": "2.9.1",
|
|
78
79
|
"@anthropic-ai/claude-agent-sdk": "0.3.288",
|
|
79
80
|
"@anthropic-ai/sdk": "0.127.0",
|
|
80
|
-
"@code-yeongyu/senpi-codemode": "2026.10.
|
|
81
|
-
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.
|
|
81
|
+
"@code-yeongyu/senpi-codemode": "2026.10.5",
|
|
82
|
+
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.5",
|
|
82
83
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
83
84
|
"@mozilla/readability": "0.6.0",
|
|
84
85
|
"linkedom": "0.18.13",
|